File Coverage

blib/lib/Schedule/Cron.pm
Criterion Covered Total %
statement 432 611 70.7
branch 197 346 56.9
condition 98 178 55.0
subroutine 36 42 85.7
pod 12 14 85.7
total 775 1191 65.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             =head1 NAME
4              
5             Cron - cron-like scheduler for Perl subroutines
6              
7             =head1 SYNOPSIS
8              
9             use Schedule::Cron;
10              
11             # Subroutines to be called
12             sub dispatcher {
13             print "ID: ",shift,"\n";
14             print "Args: ","@_","\n";
15             }
16              
17             sub check_links {
18             # do something...
19             }
20              
21             # Create new object with default dispatcher
22             my $cron = new Schedule::Cron(\&dispatcher);
23              
24             # Load a crontab file
25             $cron->load_crontab("/var/spool/cron/perl");
26              
27             # Add dynamically crontab entries
28             $cron->add_entry("3 4 * * *",ROTATE => "apache","sendmail");
29             $cron->add_entry("0 11 * * Mon-Fri",\&check_links);
30              
31             # Run scheduler
32             $cron->run(detach=>1);
33            
34              
35             =head1 DESCRIPTION
36              
37             This module provides a simple but complete cron like scheduler. I.e this
38             module can be used for periodically executing Perl subroutines. The dates and
39             parameters for the subroutines to be called are specified with a format known
40             as crontab entry (see L<"METHODS">, C and L)
41              
42             The philosophy behind C is to call subroutines periodically
43             from within one single Perl program instead of letting C trigger several
44             (possibly different) Perl scripts. Everything under one roof. Furthermore,
45             C provides mechanism to create crontab entries dynamically,
46             which isn't that easy with C.
47              
48             C knows about all extensions (well, at least all extensions I'm
49             aware of, i.e those of the so called "Vixie" cron) for crontab entries like
50             ranges including 'steps', specification of month and days of the week by name,
51             or coexistence of lists and ranges in the same field. It even supports a bit
52             more (like lists and ranges with symbolic names).
53              
54             =head1 METHODS
55              
56             =over 4
57              
58             =cut
59              
60             #'
61              
62             package Schedule::Cron;
63              
64 17     17   629636 use Time::ParseDate;
  17         157041  
  17         963  
65 17     17   9642 use Data::Dumper;
  17         98532  
  17         956  
66              
67 17     17   107 use strict;
  17         34  
  17         359  
68 17     17   70 use vars qw($VERSION $DEBUG);
  17         27  
  17         820  
69 17     17   15348 use subs qw(dbg);
  17         396  
  17         74  
70              
71             my $HAS_POSIX;
72              
73             BEGIN {
74 17     17   2588 eval {
75 17         7186 require POSIX;
76 17         91275 import POSIX ":sys_wait_h";
77             };
78 17 50       25095 $HAS_POSIX = $@ ? 0 : 1;
79             }
80              
81              
82             $VERSION = "1.03";
83              
84             our $DEBUG = 0;
85             my %STARTEDCHILD = ();
86              
87             my @WDAYS = qw(
88             Sunday
89             Monday
90             Tuesday
91             Wednesday
92             Thursday
93             Friday
94             Saturday
95             Sunday
96             );
97              
98             my @ALPHACONV = (
99             { },
100             { },
101             { },
102             { qw(jan 1 feb 2 mar 3 apr 4 may 5 jun 6 jul 7 aug 8
103             sep 9 oct 10 nov 11 dec 12) },
104             { qw(sun 0 mon 1 tue 2 wed 3 thu 4 fri 5 sat 6)},
105             { }
106             );
107             my @RANGES = (
108             [ 0,59 ],
109             [ 0,23 ],
110             [ 0,31 ],
111             [ 0,12 ],
112             [ 0,7 ],
113             [ 0,59 ]
114             );
115              
116             my @LOWMAP = (
117             {},
118             {},
119             { 0 => 1},
120             { 0 => 1},
121             { 7 => 0},
122             {},
123             );
124              
125              
126             # Currently, there are two ways for reaping. One, which only waits explicitly
127             # on PIDs it forked on its own, and one which waits on all PIDs (even on those
128             # it doesn't forked itself). The later has been proved to work on Win32 with
129             # the 64 threads limit (RT #56926), but not when one creates forks on ones
130             # own. The specific reaper works for RT #55741.
131              
132             # It tend to use the specific one, if it also resolves RT #56926. Both are left
133             # here for reference until a decision has been done for 1.01
134              
135             sub REAPER {
136 5     5 0 52 &_reaper_all();
137             }
138              
139             # Specific reaper
140             sub _reaper_specific {
141 17     17   7848 local ($!,%!,$?);
  17     0   19146  
  17         153  
  0         0  
142 0 0       0 if ($HAS_POSIX)
143             {
144 0         0 foreach my $pid (keys %STARTEDCHILD) {
145 0 0       0 if ($STARTEDCHILD{$pid}) {
146 0 0       0 my $res = $HAS_POSIX ? waitpid($pid, WNOHANG) : waitpid($pid,0);
147 0 0       0 if ($res > 0) {
148             # We reaped a truly running process
149 0         0 $STARTEDCHILD{$pid} = 0;
150 0 0       0 dbg "Reaped child $res" if $DEBUG;
151             }
152             }
153             }
154             }
155             else
156             {
157 0         0 my $waitedpid = 0;
158 0         0 while($waitedpid != -1) {
159 0         0 $waitedpid = wait;
160             }
161             }
162             }
163              
164             # Catch all reaper
165             sub _reaper_all {
166             #local ($!,%!,$?,${^CHILD_ERROR_NATIVE});
167              
168             # Localizing ${^CHILD_ERROR_NATIVE} breaks signalhander.t which checks that
169             # chained SIGCHLD handlers are called. I don't know why, though, hence I
170             # leave it out for now. See #69916 for some discussion why this handler
171             # might be needed.
172 5     5   296 local ($!,%!,$?);
173 5         25 my $kid;
174             do
175 5   66     22 {
176             # Only on POSIX systems the wait will return immediately
177             # if there are no finished child processes. Simple 'wait'
178             # waits blocking on childs.
179 6 50       106 $kid = $HAS_POSIX ? waitpid(-1, WNOHANG) : wait;
180 6 50       28 dbg "Kid: $kid" if $DEBUG;
181 6 50 66     96 if ($kid != 0 && $kid != -1 && defined $STARTEDCHILD{$kid})
      66        
182             {
183             # We don't delete the hash entry here to avoid an issue
184             # when modifying global hash from multiple threads
185 1         9 $STARTEDCHILD{$kid} = 0;
186 1 50       18 dbg "Reaped child $kid" if $DEBUG;
187             }
188             } while ($kid != 0 && $kid != -1);
189              
190             # Note to myself: Is the %STARTEDCHILD hash really necessary if we use -1
191             # for waiting (i.e. for waiting on any child ?). In the current
192             # implementation, %STARTEDCHILD is not used at all. It would be only
193             # needed if we iterate over it to wait on pids specifically.
194             }
195              
196             # Cleaning is done in extra method called from the main
197             # process in order to avoid event handlers modifying this
198             # global hash which can lead to memory errors.
199             # See RT #55741 for more details on this.
200             # This method is called in strategic places.
201             sub _cleanup_process_list
202             {
203 14     14   91 my ($self, $cfg) = @_;
204            
205             # Cleanup processes even on those systems, where the SIGCHLD is not
206             # propagated. Only do this for POSIX, otherwise this call would block
207             # until all child processes would have been finished.
208             # See RT #56926 for more details.
209              
210             # Do not cleanup if nofork because jobs that fork will do their own reaping.
211 14 100 66     228 &REAPER() if $HAS_POSIX && !$cfg->{nofork};
212              
213             # Delete entries from this global hash only from within the main
214             # thread/process. Hence, this method must not be called from within
215             # a signalhandler
216 14         80 for my $k (keys %STARTEDCHILD)
217             {
218 5 50       22 delete $STARTEDCHILD{$k} unless $STARTEDCHILD{$k};
219             }
220             }
221              
222             =item $cron = new Schedule::Cron($dispatcher,[extra args])
223              
224             Creates a new C object. C<$dispatcher> is a reference to a subroutine,
225             which will be called by default. C<$dispatcher> will be invoked with the
226             arguments parameter provided in the crontab entry if no other subroutine is
227             specified. This can be either a single argument containing the argument
228             parameter literally has string (default behavior) or a list of arguments when
229             using the C option described below.
230              
231             The date specifications must be either provided via a crontab like file or
232             added explicitly with C (L<"add_entry">).
233              
234             I can be a hash or hash reference for additional arguments. The
235             following parameters are recognized:
236              
237             =over
238              
239             =item file =>
240              
241              
242             Load the crontab entries from
243              
244             =item eval => 1
245              
246             Eval the argument parameter in a crontab entry before calling the subroutine
247             (instead of literally calling the dispatcher with the argument parameter as
248             string)
249              
250             =item nofork => 1
251              
252             Don't fork when starting the scheduler. Instead, the jobs are executed within
253             current process. In your executed jobs, you have full access to the global
254             variables of your script and hence might influence other jobs running at a
255             different time. This behaviour is fundamentally different to the 'fork' mode,
256             where each jobs gets its own process and hence a B of the process space,
257             independent of each other job and the main process. This is due to the nature
258             of the C system call.
259              
260             =item nostatus => 1
261              
262             Do not update status in $0. Set this if you don't want ps to reveal the internals
263             of your application, including job argument lists. Default is 0 (update status).
264              
265             =item skip => 1
266              
267             Skip any pending jobs whose time has passed. This option is only useful in
268             combination with C where a job might block the execution of the
269             following jobs for quite some time. By default, any pending job is executed
270             even if its scheduled execution time has already passed. With this option set
271             to true all pending which would have been started in the meantime are skipped.
272              
273             =item catch => 1
274              
275             Catch any exception raised by a job. This is especially useful in combination with
276             the C option to avoid stopping the main process when a job raises an
277             exception (dies).
278              
279             =item after_job => \&after_sub
280              
281             Call a subroutine after a job has been run. The first argument is the return
282             value of the dispatched job, the reminding arguments are the arguments with
283             which the dispatched job has been called.
284              
285             Example:
286              
287             my $cron = new Schedule::Cron(..., after_job => sub {
288             my ($ret,@args) = @_;
289             print "Return value: ",$ret," - job arguments: (",join ":",@args,")\n";
290             });
291              
292             =item log => \&log_sub
293              
294             Install a logging subroutine. The given subroutine is called for several events
295             during the lifetime of a job. This method is called with two arguments: A log
296             level of 0 (info),1 (warning) or 2 (error) depending on the importance of the
297             message and the message itself.
298              
299             For example, you could use I (L) for logging
300             purposes for example like in the following code snippet:
301              
302             use Log::Log4perl;
303             use Log::Log4perl::Level;
304              
305             my $log_method = sub {
306             my ($level,$msg) = @_;
307             my $DBG_MAP = { 0 => $INFO, 1 => $WARN, 2 => $ERROR };
308              
309             my $logger = Log::Log4perl->get_logger("My::Package");
310             $logger->log($DBG_MAP->{$level},$msg);
311             }
312            
313             my $cron = new Schedule::Cron(.... , log => $log_method);
314              
315             =item loglevel => <-1,0,1,2>
316              
317             Restricts logging to the specified severity level or below. Use 0 to have all
318             messages generated, 1 for only warnings and errors and 2 for errors only.
319             Default is 0 (all messages). A loglevel of -1 (debug) will include job
320             argument lists (also in $0) in the job start message logged with a level of 0
321             or above. You may have security concerns with this. Unless you are debugging,
322             use 0 or higher. A value larger than 2 will disable logging completely.
323              
324             Although you can filter in your log routine, generating the messages can be
325             expensive, for example if you pass arguments pointing to large hashes. Specifying
326             a loglevel avoids formatting data that your routine would discard.
327              
328             =item processprefix =>
329              
330             Cron::Schedule sets the process' name (i.e. C<$0>) to contain some informative
331             messages like when the next job executes or with which arguments a job is
332             called. By default, the prefix for this labels is C. With this
333             option you can set it to something different. You can e.g. use C<$0> to include
334             the original process name. You can inhibit this with the C option, and
335             prevent the argument display by setting C to zero or higher.
336              
337             =item processname =>
338              
339             Set the process name (i.e. C<$0>) to a literal string. Using this setting
340             overrides C and C.
341              
342             =item sleep => \&hook
343              
344             If specified, &hook will be called instead of sleep(), with the time to sleep
345             in seconds as first argument and the Schedule::Cron object as second. This hook
346             allows you to use select() instead of sleep, so that you can handle IO, for
347             example job requests from a network connection.
348              
349             e.g.
350              
351             $cron->run( { sleep => \&sleep_hook, nofork => 1 } );
352              
353             sub sleep_hook {
354             my ($time, $cron) = @_;
355              
356             my ($rin, $win, $ein) = ('','','');
357             my ($rout, $wout, $eout);
358             vec($rin, fileno(STDIN), 1) = 1;
359             my ($nfound, $ttg) = select($rout=$rin, $wout=$win, $eout=$ein, $time);
360             if ($nfound) {
361             handle_io($rout, $wout, $eout);
362             }
363             return;
364             }
365              
366             =back
367              
368             =cut
369              
370             sub new
371             {
372 27     27 1 30795 my $class = shift;
373 27   50     238 my $dispatcher = shift || die "No dispatching sub provided";
374 27 50       114 die "Dispatcher not a ref to a subroutine" unless ref($dispatcher) eq "CODE";
375 27 100       118 my $cfg = ref($_[0]) eq "HASH" ? $_[0] : { @_ };
376 27 100       104 $cfg->{processprefix} = "Schedule::Cron" unless $cfg->{processprefix};
377 27   100     124 my $timeshift = $cfg->{timeshift} || 0;
378 27         137 my $self = {
379             cfg => $cfg,
380             dispatcher => $dispatcher,
381             timeshift => $timeshift,
382             queue => [ ],
383             map => { }
384             };
385 27   33     110 bless $self,(ref($class) || $class);
386            
387 27 100       86 $self->load_crontab if $cfg->{file};
388 27         228 $self;
389             }
390              
391             =item $cron->load_crontab($file)
392              
393             =item $cron->load_crontab(file=>$file,[eval=>1])
394              
395             Loads and parses the crontab file C<$file>. The entries found in this file will
396             be B to the current time table with C<$cron-Eadd_entry>.
397              
398             The format of the file consists of cron commands containing of lines with at
399             least 5 columns, whereas the first 5 columns specify the date. The rest of the
400             line (i.e columns 6 and greater) contains the argument with which the
401             dispatcher subroutine will be called. By default, the dispatcher will be
402             called with one single string argument containing the rest of the line
403             literally. Alternatively, if you call this method with the optional argument
404             C1> (you must then use the second format shown above), the rest of
405             the line will be evaled before used as argument for the dispatcher.
406              
407             For the format of the first 5 columns, please see L<"add_entry">.
408              
409             Blank lines and lines starting with a C<#> will be ignored.
410              
411             There's no way to specify another subroutine within the crontab file. All
412             calls will be made to the dispatcher provided at construction time.
413              
414             If you want to start up fresh, you should call
415             C<$cron-Eclean_timetable()> before.
416              
417             Example of a crontab fiqw(le:)
418              
419             # The following line runs on every Monday at 2:34 am
420             34 2 * * Mon "make_stats"
421             # The next line should be best read in with an eval=>1 argument
422             * * 1 1 * { NEW_YEAR => '1',HEADACHE => 'on' }
423              
424             =cut
425              
426             #'
427              
428             sub load_crontab
429             {
430 5     5 1 18 my $self = shift;
431 5         6 my $cfg = shift;
432              
433 5 100       11 if ($cfg)
434             {
435 4 100       13 if (@_)
    100          
436             {
437 1 50       6 $cfg = ref($cfg) eq "HASH" ? $cfg : { $cfg,@_ };
438             }
439             elsif (!ref($cfg))
440             {
441 2         4 my $new_cfg = { };
442 2         3 $new_cfg->{file} = $cfg;
443 2         5 $cfg = $new_cfg;
444             }
445             }
446            
447 5   50     15 my $file = $cfg->{file} || $self->{cfg}->{file} || die "No filename provided";
448 5   100     14 my $eval = $cfg->{eval} || $self->{cfg}->{eval};
449            
450 5 50       169 open(F,$file) || die "Cannot open schedule $file : $!";
451 5         17 my $line = 0;
452 5         125 while ()
453             {
454 345         391 $line++;
455             # Strip off trailing comments and ignore empty
456             # or pure comments lines:
457 345         518 s/#.*$//;
458 345 100       932 next if /^\s*$/;
459 185 50       277 next if /^\s*#/;
460 185         225 chomp;
461 185         628 s/\s*(.*)\s*$/$1/;
462 185         876 my ($min,$hour,$dmon,$month,$dweek,$rest) = split (/\s+/,$_,6);
463            
464 185         395 my $time = [ $min,$hour,$dmon,$month,$dweek ];
465              
466             # Try to check, whether an optional 6th column specifying seconds
467             # exists:
468 185         192 my $args;
469 185 50       262 if ($rest)
470             {
471 185         431 my ($col6,$more_args) = split(/\s+/,$rest,2);
472 185 100       389 if ($col6 =~ /^[\d\-\*\,\/]+$/)
473             {
474 15         24 push @$time,$col6;
475 15         37 dbg "M: $more_args";
476 15         20 $args = $more_args;
477             }
478             else
479             {
480 170         209 $args = $rest;
481             }
482             }
483 185         424 $self->add_entry($time,{ 'args' => $args, 'eval' => $eval});
484             }
485 5         64 close F;
486             }
487              
488             =item $cron->add_entry($timespec,[arguments])
489              
490             Adds a new entry to the list of scheduled cron jobs.
491              
492             B
493              
494             C<$timespec> is the specification of the scheduled time in crontab format
495             (L) which contains five mandatory time and date fields and an
496             optional 6th column. C<$timespec> can be either a plain string, which contains
497             a whitespace separated time and date specification. Alternatively,
498             C<$timespec> can be a reference to an array containing the five elements for
499             the date fields.
500              
501             The time and date fields are (taken mostly from L, "Vixie" cron):
502              
503             field values
504             ===== ======
505             minute 0-59
506             hour 0-23
507             day of month 1-31
508             month 1-12 (or as names)
509             day of week 0-7 (0 or 7 is Sunday, or as names)
510             seconds 0-59 (optional)
511              
512             A field may be an asterisk (*), which always stands for
513             ``first-last''.
514              
515             Ranges of numbers are allowed. Ranges are two numbers
516             separated with a hyphen. The specified range is
517             inclusive. For example, 8-11 for an ``hours'' entry
518             specifies execution at hours 8, 9, 10 and 11.
519              
520             Lists are allowed. A list is a set of numbers (or
521             ranges) separated by commas. Examples: ``1,2,5,9'',
522             ``0-4,8-12''.
523              
524             Step values can be used in conjunction with ranges.
525             Following a range with ``/'' specifies skips of
526             the numbers value through the range. For example,
527             ``0-23/2'' can be used in the hours field to specify
528             command execution every other hour (the alternative in
529             the V7 standard is ``0,2,4,6,8,10,12,14,16,18,20,22'').
530             Steps are also permitted after an asterisk, so if you
531             want to say ``every two hours'', just use ``*/2''.
532              
533             Names can also be used for the ``month'' and ``day of
534             week'' fields. Use the first three letters of the
535             particular day or month (case doesn't matter).
536              
537             Note: The day of a command's execution can be specified
538             by two fields -- day of month, and day of week.
539             If both fields are restricted (ie, aren't *), the
540             command will be run when either field matches the
541             current time. For example, ``30 4 1,15 * 5''
542             would cause a command to be run at 4:30 am on the
543             1st and 15th of each month, plus every Friday
544              
545             Examples:
546              
547             "8 0 * * *" ==> 8 minutes after midnight, every day
548             "5 11 * * Sat,Sun" ==> at 11:05 on each Saturday and Sunday
549             "0-59/5 * * * *" ==> every five minutes
550             "42 12 3 Feb Sat" ==> at 12:42 on 3rd of February and on
551             each Saturday in February
552             "32 11 * * * 0-30/2" ==> 11:32:00, 11:32:02, ... 11:32:30 every
553             day
554              
555             In addition, ranges or lists of names are allowed.
556              
557             An optional sixth column can be used to specify the seconds within the
558             minute. If not present, it is implicitly set to "0".
559              
560             B
561              
562             The subroutine to be executed when the C<$timespec> matches can be
563             specified in several ways.
564              
565             First, if the optional C are lacking, the default dispatching
566             subroutine provided at construction time will be called without arguments.
567              
568             If the second parameter to this method is a reference to a subroutine, this
569             subroutine will be used instead of the dispatcher.
570              
571             Any additional parameters will be given as arguments to the subroutine to be
572             executed. You can also specify a reference to an array instead of a list of
573             parameters.
574              
575             You can also use a named parameter list provided as an hashref. The named
576             parameters recognized are:
577              
578             =over
579              
580             =item subroutine
581              
582             =item sub
583              
584             Reference to subroutine to be executed
585              
586             =item arguments
587              
588             =item args
589              
590             Reference to array containing arguments to be use when calling the subroutine
591              
592             =item eval
593              
594             If true, use the evaled string provided with the C parameter. The
595             evaluation will take place immediately (not when the subroutine is going to be
596             called)
597              
598             =back
599              
600             Examples:
601              
602             $cron->add_entry("* * * * *");
603             $cron->add_entry("* * * * *","doit");
604             $cron->add_entry("* * * * *",\&dispatch,"first",2,"third");
605             $cron->add_entry("* * * * *",{'subroutine' => \&dispatch,
606             'arguments' => [ "first",2,"third" ]});
607             $cron->add_entry("* * * * *",{'subroutine' => \&dispatch,
608             'arguments' => '[ "first",2,"third" ]',
609             'eval' => 1});
610              
611             =cut
612              
613             sub add_entry
614             {
615 219     219 1 1218 my $self = shift;
616 219         241 my $time = shift;
617 219   100     391 my $args = shift || [];
618 219         240 my $dispatch;
619            
620             # dbg "Args: ",Dumper($time,$args);
621            
622 219 100       427 if (ref($args) eq "HASH")
    100          
623             {
624 193         209 my $cfg = $args;
625 193         205 $args = undef;
626 193   66     452 $dispatch = $cfg->{subroutine} || $cfg->{sub};
627 193   100     463 $args = $cfg->{arguments} || $cfg->{args} || [];
628 193 100 66     433 if ($cfg->{eval} && $cfg)
629             {
630 112 100       167 die "You have to provide a simple scalar if using eval" if (ref($args));
631 111         111 my $orig_args = $args;
632 111 50       143 dbg "Evaled args ",Dumper($args) if $DEBUG;
633 111         3802 $args = [ eval $args ];
634 111 50       328 die "Cannot evaluate args (\"$orig_args\")"
635             if $@;
636             }
637             }
638             elsif (ref($args) eq "CODE")
639             {
640 4         8 $dispatch = $args;
641 4   50     19 $args = shift || [];
642             }
643 218 100       404 if (ref($args) ne "ARRAY")
644             {
645 81         467 $args = [ $args,@_ ];
646             }
647              
648 218   66     724 $dispatch ||= $self->{dispatcher};
649              
650              
651 218 100       529 my $time_array = ref($time) ? $time : [ split(/\s+/,$time) ];
652 218 100 100     501 die "Invalid number of columns in time entry (5 or 6)\n"
653             if ($#$time_array != 4 && $#$time_array !=5);
654 217         471 $time = join ' ',@$time_array;
655              
656             # dbg "Adding ",Dumper($time);
657 217         252 push @{$self->{time_table}},
  217         652  
658             {
659             time => $time,
660             dispatcher => $dispatch,
661             args => $args
662             };
663            
664 217         321 $self->{entries_changed} = 1;
665             # dbg "Added Args ",Dumper($self->{args});
666            
667 217         227 my $index = $#{$self->{time_table}};
  217         291  
668 217         285 my $id = $args->[0];
669 217 100       413 $self->{map}->{$id} = $index if $id;
670            
671 217         222 return $#{$self->{time_table}};
  217         892  
672             }
673              
674             =item @entries = $cron->list_entries()
675              
676             Return a list of cron entries. Each entry is a hash reference of the following
677             form:
678              
679             $entry = {
680             time => $timespec,
681             dispatch => $dispatcher,
682             args => $args_ref
683             }
684              
685             Here C<$timespec> is the specified time in crontab format as provided to
686             C, C<$dispatcher> is a reference to the dispatcher for this entry
687             and C<$args_ref> is a reference to an array holding additional arguments (which
688             can be an empty array reference). For further explanation of this arguments
689             refer to the documentation of the method C.
690              
691             The order index of each entry can be used within C, C
692             and C. But be aware, when you are deleting an entry, that you
693             have to refetch the list, since the order will have changed.
694              
695             Note that these entries are returned by value and were obtained from the
696             internal list by a deep copy. I.e. you are free to modify it, but this won't
697             influence the original entries. Instead use C if you need to
698             modify an existing crontab entry.
699              
700             =cut
701              
702             sub list_entries
703             {
704 5     5 1 464 my ($self) = shift;
705            
706 5         9 my @ret;
707 5         6 foreach my $entry (@{$self->{time_table}})
  5         10  
708             {
709             # Deep copy $entry
710 7         15 push @ret,$self->_deep_copy_entry($entry);
711             }
712 5         24 return @ret;
713             }
714              
715              
716             =item $entry = $cron->get_entry($idx)
717              
718             Get a single entry. C<$entry> is either a hashref with the possible keys
719             C
720             with the given index C<$idx> exists.
721              
722             =cut
723              
724             sub get_entry
725             {
726 92     92 1 1121 my ($self,$idx) = @_;
727              
728 92         195 my $entry = $self->{time_table}->[$idx];
729 92 100       220 if ($entry)
730             {
731 91         289 return $self->_deep_copy_entry($entry);
732             }
733             else
734             {
735 1         5 return undef;
736             }
737             }
738              
739             =item $cron->delete_entry($idx)
740              
741             Delete the entry at index C<$idx>. Returns the deleted entry on success,
742             C otherwise.
743              
744             =cut
745              
746             sub delete_entry
747             {
748 3     3 1 984 my ($self,$idx) = @_;
749              
750 3 50       5 if ($idx <= $#{$self->{time_table}})
  3         11  
751             {
752 3         5 $self->{entries_changed} = 1;
753              
754             # Remove entry from $self->{map} which
755             # remembers the index in the timetable by name (==id)
756             # and update all larger indexes appropriately
757             # Fix for #54692
758 3         5 my $map = $self->{map};
759 3         5 foreach my $key (keys %{$map}) {
  3         8  
760 6 100       19 if ($map->{$key} > $idx) {
    100          
761 2         4 $map->{$key}--;
762             } elsif ($map->{$key} == $idx) {
763 2         4 delete $map->{$key};
764             }
765             }
766 3         4 return splice @{$self->{time_table}},$idx,1;
  3         12  
767             }
768             else
769             {
770 0         0 return undef;
771             }
772             }
773              
774             =item $cron->update_entry($idx,$entry)
775              
776             Updates the entry with index C<$idx>. C<$entry> is a hash ref as described in
777             C and must contain at least a value C<$entry-E{time}>. If no
778             C<$entry-E{dispatcher}> is given, then the default dispatcher is used. This
779             method returns the old entry on success, C otherwise.
780              
781             =cut
782              
783             sub update_entry
784             {
785 1     1 1 2 my ($self,$idx,$entry) = @_;
786              
787 1 50       3 die "No update entry given" unless $entry;
788 1 50       3 die "No time specification given" unless $entry->{time};
789            
790 1 50       3 if ($idx <= $#{$self->{time_table}})
  1         4  
791             {
792 1         2 my $new_entry = $self->_deep_copy_entry($entry);
793             $new_entry->{dispatcher} = $self->{dispatcher}
794 1 50       3 unless $new_entry->{dispatcher};
795             $new_entry->{args} = []
796 1 50       13 unless $new_entry->{args};
797 1         2 return splice @{$self->{time_table}},$idx,1,$new_entry;
  1         6  
798             }
799             else
800             {
801 0         0 return undef;
802             }
803             }
804              
805             =item $cron->run([options])
806              
807             This method starts the scheduler.
808              
809             When called without options, this method will never return and executes the
810             scheduled subroutine calls as needed.
811              
812             Alternatively, you can detach the main scheduler loop from the current process
813             (daemon mode). In this case, the pid of the forked scheduler process will be
814             returned.
815              
816             The C parameter specifies the running mode of C. It
817             can be either a plain list which will be interpreted as a hash or it can be a
818             reference to a hash. The following named parameters (keys of the provided hash)
819             are recognized:
820              
821             =over
822              
823             =item detach
824              
825             If set to a true value the scheduler process is detached from the current
826             process (UNIX only).
827              
828             =item pid_file
829              
830             If running in daemon mode, name the optional file, in which the process id of
831             the scheduler process should be written. By default, no PID File will be
832             created.
833              
834             =item nofork, skip, catch, log, loglevel, nostatus, sleep
835              
836             See C for a description of these configuration parameters, which can be
837             provided here as well. Note, that the options given here overrides those of the
838             constructor.
839              
840             =back
841              
842              
843             Examples:
844              
845             # Start scheduler, detach from current process and
846             # write the PID of the forked scheduler to the
847             # specified file
848             $cron->run(detach=>1,pid_file=>"/var/run/scheduler.pid");
849              
850             # Start scheduler and wait forever.
851             $cron->run();
852              
853             =cut
854              
855             sub run
856             {
857 18     18 1 144 my $self = shift;
858 18 100       69 my $cfg = ref($_[0]) eq "HASH" ? $_[0] : { @_ };
859 18         26 $cfg = { %{$self->{cfg}}, %$cfg }; # Merge in global config;
  18         129  
860              
861 18         48 my $log = $cfg->{log};
862 18         31 my $loglevel = $cfg->{loglevel};
863 18 50       77 $loglevel = 0 unless defined $loglevel;
864 18         30 my $sleeper = $cfg->{sleep};
865              
866 18         94 $self->_rebuild_queue;
867 18         39 delete $self->{entries_changed};
868 18 50       23 die "Nothing in schedule queue" unless @{$self->{queue}};
  18         90  
869            
870             # Install reaper now.
871 18 100       50 unless ($cfg->{nofork}) {
872 5         27 my $old_child_handler = $SIG{'CHLD'};
873             $SIG{'CHLD'} = sub {
874 1 50   1   26 dbg "Calling reaper" if $DEBUG;
875 1         14 &REAPER();
876 1 50 33     33 if ($old_child_handler && ref $old_child_handler eq 'CODE')
877             {
878 1 50       8 dbg "Calling old child handler" if $DEBUG;
879             #use B::Deparse ();
880             #my $deparse = B::Deparse->new;
881             #print 'sub ', $deparse->coderef2text($old_child_handler), "\n";
882 1         9 &$old_child_handler();
883             }
884 5         75 };
885             }
886            
887 18 100       56 if (my $name = $cfg->{processname}) {
888 2         15 $0 = $name
889             }
890              
891             my $mainloop = sub {
892             MAIN:
893 18     18   48 while (42)
894             {
895 31 50       47 unless (@{$self->{queue}}) # Queue length
  31         100  
896             {
897             # Last job deleted itself, or we were run with no entries.
898             # We can't return, so throw an exception - perhaps someone will catch.
899 0         0 die "No more jobs to run\n";
900             }
901 31         168 my ($indexes,$time) = $self->_get_next_jobs();
902 31 50       77 dbg "Jobs for $time : ",join(",",@$indexes) if $DEBUG;
903 31         67 my $now = $self->_now();
904 31         48 my $sleep = 0;
905 31 50       72 if ($time < $now)
906             {
907 0 0       0 if ($cfg->{skip})
908             {
909 0         0 for my $index (@$indexes) {
910 0 0 0     0 $log->(0,"Schedule::Cron - Skipping job $index")
911             if $log && $loglevel <= 0;
912 0         0 $self->_update_queue($index);
913             }
914 0         0 next;
915             }
916             # At least a safety airbag
917 0         0 $sleep = 1;
918             }
919             else
920             {
921 31         55 $sleep = $time - $now;
922             }
923              
924 31 100 100     181 unless ($cfg->{processname} || $cfg->{nostatus}) {
925 27         87 $0 = $self->_get_process_prefix()." MainLoop - next: ".scalar(localtime($time));
926             }
927              
928 31 50       133 if (!$time) {
929 0 0       0 die "Internal: No time found, self: ",$self->{queue},"\n" unless $time;
930             }
931              
932 31 50       95 dbg "R: sleep = $sleep | ",scalar(localtime($time))," (",scalar(localtime($now)),")" if $DEBUG;
933              
934 31         78 while ($sleep > 0)
935             {
936 31 50       68 if ($sleeper)
937             {
938 0         0 $sleeper->($sleep,$self);
939 0 0       0 if ($self->{entries_changed})
940             {
941 0         0 $self->_rebuild_queue;
942 0         0 delete $self->{entries_changed};
943 0         0 redo MAIN;
944             }
945             } else {
946 31         37530261 sleep($sleep);
947             }
948 31         758 $sleep = $time - $self->_now();
949             }
950              
951 29         145 for my $index (@$indexes) {
952 36         191 $self->_execute($index,$cfg);
953             # If "skip" is set and the job takes longer than a second, then
954             # the remaining jobs are skipped.
955 21 100 66     175 last if $cfg->{skip} && $time < $self->_now();
956             }
957 14         188 $self->_cleanup_process_list($cfg);
958              
959 14 100       49 if ($self->{entries_changed}) {
960 3 50       11 dbg "rebuilding queue" if $DEBUG;
961 3         12 $self->_rebuild_queue;
962 3         11 delete $self->{entries_changed};
963             } else {
964 11         57 for my $index (@$indexes) {
965 16         144 $self->_update_queue($index);
966             }
967             }
968             }
969 18         115 };
970              
971 18 50       50 if ($cfg->{detach})
972             {
973 0 0       0 defined(my $pid = fork) or die "Can't fork: $!";
974 0 0       0 if ($pid)
975             {
976             # Parent:
977 0 0       0 if ($cfg->{pid_file})
978             {
979 0 0       0 if (open(P,">".$cfg->{pid_file}))
980             {
981 0         0 print P $pid,"\n";
982 0         0 close P;
983             }
984             else
985             {
986 0         0 warn "Warning: Cannot open ",$cfg->{pid_file}," : $!\n";
987             }
988            
989             }
990 0         0 return $pid;
991             }
992             else
993             {
994             # Child:
995             # Try to detach from terminal:
996 0         0 chdir '/';
997 0 0       0 open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
998 0 0       0 open STDOUT, '>/dev/null' or die "Can't write to /dev/null: $!";
999            
1000 0         0 eval { require POSIX; };
  0         0  
1001 0 0       0 if ($@)
1002             {
1003             # if (1) {
1004 0 0       0 if (open(T,"/dev/tty"))
1005             {
1006 0         0 dbg "No setsid found, trying ioctl() (Error: $@)";
1007 0         0 eval { require 'ioctl.ph'; };
  0         0  
1008 0 0       0 if ($@)
1009             {
1010 0         0 eval { require 'sys/ioctl.ph'; };
  0         0  
1011 0 0       0 if ($@)
1012             {
1013 0         0 die "No 'ioctl.ph'. Probably you have to run h2ph (Error: $@)";
1014             }
1015             }
1016 0         0 my $notty = &TIOCNOTTY;
1017 0 0 0     0 die "No TIOCNOTTY !" if $@ || !$notty;
1018 0 0       0 ioctl(T,$notty,0) || die "Cannot issue ioctl(..,TIOCNOTTY) : $!";
1019 0         0 close(T);
1020             };
1021             }
1022             else
1023             {
1024 0 0       0 &POSIX::setsid() || die "Can't start a new session: $!";
1025             }
1026 0 0       0 open STDERR, '>&STDOUT' or die "Can't dup stdout: $!";
1027            
1028 0 0 0     0 unless ($cfg->{processname} || $cfg->{nostatus}) {
1029 0         0 $0 = $self->_get_process_prefix()." MainLoop";
1030             }
1031              
1032 0         0 &$mainloop();
1033             }
1034             }
1035             else
1036             {
1037 18         44 &$mainloop();
1038             }
1039             }
1040              
1041              
1042             =item $cron->clean_timetable()
1043              
1044             Remove all scheduled entries
1045              
1046             =cut
1047              
1048             sub clean_timetable
1049             {
1050 2     2 1 68 my $self = shift;
1051 2         10 $self->{entries_changed} = 1;
1052 2         10 $self->{time_table} = [];
1053             }
1054              
1055              
1056             =item $cron->check_entry($id)
1057              
1058             Check, whether the given ID is already registered in the timetable.
1059             A ID is the first argument in the argument parameter of the
1060             a crontab entry.
1061              
1062             Returns (one of) the index in the timetable (can be 0, too) if the ID
1063             could be found or C otherwise.
1064              
1065             Example:
1066              
1067             $cron->add_entry("* * * * *","ROTATE");
1068             .
1069             .
1070             defined($cron->check_entry("ROTATE")) || die "No ROTATE entry !"
1071              
1072             =cut
1073              
1074             sub check_entry
1075             {
1076 4     4 1 1194 my $self = shift;
1077 4         7 my $id = shift;
1078 4         9 return $self->{map}->{$id};
1079             }
1080              
1081              
1082             =item $cron->get_next_execution_time($cron_entry,[$ref_time])
1083              
1084             Well, this is mostly an internal method, but it might be useful on
1085             its own.
1086              
1087             The purpose of this method is to calculate the next execution time
1088             from a specified crontab entry
1089              
1090             Parameters:
1091              
1092             =over
1093              
1094             =item $cron_entry
1095              
1096             The crontab entry as specified in L<"add_entry">
1097              
1098             =item $ref_time
1099              
1100             The reference time for which the next time should be searched which matches
1101             C<$cron_entry>. By default, take the current time
1102              
1103             =back
1104              
1105             This method returns the number of epoch-seconds of the next matched
1106             date for C<$cron_entry>.
1107              
1108             Since I suspect, that this calculation of the next execution time might
1109             fail in some circumstances (bugs are lurking everywhere ;-) an
1110             additional interactive method C is provided for checking
1111             crontab entries against your expected output. Refer to the
1112             top-level README for additional usage information for this method.
1113              
1114             =cut
1115              
1116             sub get_next_execution_time
1117             {
1118 102     102 1 18586 my $self = shift;
1119 102         141 my $cron_entry = shift;
1120 102         220 my $time = shift;
1121            
1122 102 100       734 $cron_entry = [ split /\s+/,$cron_entry ] unless ref($cron_entry);
1123              
1124             # Expand and check entry:
1125             # =======================
1126 102 50 66     541 die "Exactly 5 or 6 columns has to be specified for a crontab entry ! (not ",
1127             scalar(@$cron_entry),")"
1128             if ($#$cron_entry != 4 && $#$cron_entry != 5);
1129            
1130 101         174 my @expanded;
1131             my $w;
1132            
1133 101         281 for my $i (0..$#$cron_entry)
1134             {
1135 559         1107 my @e = split /,/,$cron_entry->[$i];
1136 559         646 my @res;
1137             my $t;
1138 559         928 while (defined($t = shift @e)) {
1139             # Subst "*/5" -> "0-59/5"
1140 1392         1714 $t =~ s|^\*(/.+)$|$RANGES[$i][0]."-".$RANGES[$i][1].$1|e;
  6         32  
1141            
1142 1392 100       1875 if ($t =~ m|^([^-]+)-([^-/]+)(/(.*))?$|)
1143             {
1144 34         168 my ($low,$high,$step) = ($1,$2,$4);
1145 34 100       80 $step = 1 unless $step;
1146 34 100       115 if ($low !~ /^(\d+)/)
1147             {
1148 7         17 $low = $ALPHACONV[$i]{lc $low};
1149             }
1150 34 100       89 if ($high !~ /^(\d+)/)
1151             {
1152 7         13 $high = $ALPHACONV[$i]{lc $high};
1153             }
1154 34 50 33     290 if (! defined($low) || !defined($high) || $low > $high || $step !~ /^\d+$/)
      33        
      33        
1155             {
1156 0         0 die "Invalid cronentry '",$cron_entry->[$i],"'";
1157             }
1158 34         53 my $j;
1159 34         80 for ($j = $low; $j <= $high; $j += $step)
1160             {
1161 783         1134 push @e,$j;
1162             }
1163             }
1164             else
1165             {
1166 1358 100       2994 $t = $ALPHACONV[$i]{lc $t} if $t !~ /^(\d+|\*)$/;
1167 1358 100       2002 $t = $LOWMAP[$i]{$t} if exists($LOWMAP[$i]{$t});
1168            
1169 1358 50 33     4442 die "Invalid cronentry '",$cron_entry->[$i],"'"
      66        
      33        
1170             if (!defined($t) || ($t ne '*' && ($t < $RANGES[$i][0] || $t > $RANGES[$i][1])));
1171 1358         2623 push @res,$t;
1172             }
1173             }
1174 559 100 100     2042 push @expanded, ($#res == 0 && $res[0] eq '*') ? [ "*" ] : [ sort {$a <=> $b} @res];
  979         1206  
1175             }
1176            
1177             # Check for strange bug
1178 101         455 $self->_verify_expanded_cron_entry($cron_entry,\@expanded);
1179              
1180             # Calculating time:
1181             # =================
1182 101   66     318 my $now = $time || time;
1183              
1184 101 100 100     275 if ($expanded[2]->[0] ne '*' && $expanded[4]->[0] ne '*')
1185             {
1186             # Special check for which time is lower (Month-day or Week-day spec):
1187 4         7 my @bak = @{$expanded[4]};
  4         8  
1188 4         9 $expanded[4] = [ '*' ];
1189 4         9 my $t1 = $self->_calc_time($now,\@expanded);
1190 4         9 $expanded[4] = \@bak;
1191 4         10 $expanded[2] = [ '*' ];
1192 4         9 my $t2 = $self->_calc_time($now,\@expanded);
1193 4 50       12 dbg "MDay : ",scalar(localtime($t1))," -- WDay : ",scalar(localtime($t2)) if $DEBUG;
1194 4 100       21 return $t1 < $t2 ? $t1 : $t2;
1195             }
1196             else
1197             {
1198             # No conflicts possible:
1199 97         301 return $self->_calc_time($now,\@expanded);
1200             }
1201             }
1202              
1203             =item $cron->set_timeshift($ts)
1204              
1205             Modify global time shift for all timetable. The timeshift is subbed from localtime
1206             to calculate next execution time for all scheduled jobs.
1207              
1208             ts parameter must be in seconds. Default value is 0. Negative values are allowed to
1209             shift time in the past.
1210              
1211             Returns actual timeshift in seconds.
1212              
1213             Example:
1214              
1215             $cron->set_timeshift(120);
1216              
1217             Will delay all jobs 2 minutes in the future.
1218              
1219             =cut
1220              
1221             sub set_timeshift
1222             {
1223 0     0 1 0 my $self = shift;
1224 0   0     0 my $value = shift || 0;
1225              
1226 0         0 $self->{timeshift} = $value;
1227 0         0 return $self->{timeshift};
1228             }
1229              
1230             # ==================================================
1231             # PRIVATE METHODS:
1232             # ==================================================
1233              
1234             # Build up executing queue and delete any
1235             # existing entries
1236             sub _rebuild_queue
1237             {
1238 21     21   41 my $self = shift;
1239 21         46 $self->{queue} = [ ];
1240             #dbg "TT: ",$#{$self->{time_table}};
1241 21         37 for my $id (0..$#{$self->{time_table}})
  21         85  
1242             {
1243 33         94 $self->_update_queue($id);
1244             }
1245             }
1246              
1247             # deeply copy an entry in the time table
1248             sub _deep_copy_entry
1249             {
1250 99     99   176 my ($self,$entry) = @_;
1251              
1252 99         136 my $args = [ @{$entry->{args}} ];
  99         253  
1253 99         511 my $copied_entry = { %$entry };
1254 99         205 $copied_entry->{args} = $args;
1255 99         583 return $copied_entry;
1256             }
1257              
1258             # Return an array with an arrayref of entry index and the time which should be
1259             # executed now
1260             sub _get_next_jobs {
1261 31     31   51 my $self = shift;
1262 31         52 my ($index,$time) = @{shift @{$self->{queue}}};
  31         35  
  31         238  
1263 31         71 my $indexes = [ $index ];
1264 31   100     46 while (@{$self->{queue}} && $self->{queue}->[0]->[1] == $time) {
  40         156  
1265 9         15 my $index = @{shift @{$self->{queue}}}[0];
  9         9  
  9         22  
1266 9         19 push @$indexes,$index;
1267             }
1268 31         134 return $indexes,$time;
1269             }
1270              
1271             # Execute a subroutine whose time has come
1272             sub _execute
1273             {
1274 36     36   78 my $self = shift;
1275 36         72 my $index = shift;
1276 36   33     124 my $cfg = shift || $self->{cfg};
1277 36   50     160 my $entry = $self->get_entry($index)
1278             || die "Internal: No entry with index $index found in ",Dumper([$self->list_entries()]);
1279              
1280 36         75 my $pid;
1281              
1282              
1283 36         105 my $log = $cfg->{log};
1284 36   50     235 my $loglevel = $cfg->{loglevel} || 0;
1285              
1286 36 100       129 unless ($cfg->{nofork})
1287             {
1288 7 100       9943 if ($pid = fork)
1289             {
1290             # Parent
1291 4 50 33     105 $log->(0,"Schedule::Cron - Forking child PID $pid") if $log && $loglevel <= 0;
1292             # Register PID
1293 4         172 $STARTEDCHILD{$pid} = 1;
1294 4         305 return;
1295             }
1296             }
1297            
1298             # Child
1299 32         243 my $dispatch = $entry->{dispatcher};
1300 32 50       231 die "No subroutine provided with $dispatch"
1301             unless ref($dispatch) eq "CODE";
1302 32         104 my $args = $entry->{args};
1303            
1304 32         95 my @args = ();
1305 32 100 66     288 if (defined($args) && defined($args->[0]))
1306             {
1307 3         25 push @args,@$args;
1308             }
1309              
1310              
1311 32 100 66     367 if ($log && $loglevel <= 0 || !$cfg->{nofork} && !$cfg->{processname} && !$cfg->{nostatus}) {
      33        
      100        
1312 7 50 66     111 my $args_label = (@args && $loglevel <= -1) ? " with (".join(",",$self->_format_args(@args)).")" : "";
1313             $0 = $self->_get_process_prefix()." Dispatched job $index$args_label"
1314 7 50 66     242 unless $cfg->{nofork} || $cfg->{processname} || $cfg->{nostatus};
      33        
1315 7 100 66     105 $log->(0,"Schedule::Cron - Starting job $index$args_label")
1316             if $log && $loglevel <= 0;
1317             }
1318 32         1274 my $dispatch_result;
1319 32 100       105 if ($cfg->{catch})
1320             {
1321             # Evaluate dispatcher
1322             eval
1323 2         9 {
1324 2         8 $dispatch_result = &$dispatch(@args);
1325             };
1326 2 50       31 if ($@)
1327             {
1328 2 50 33     17 $log->(2,"Schedule::Cron - Error within job $index: $@")
1329             if $log && $loglevel <= 2;
1330             }
1331             }
1332             else
1333             {
1334             # Let dispatcher die if needed.
1335 30         271 $dispatch_result = &$dispatch(@args);
1336             }
1337            
1338 20 100       8002350 if($cfg->{after_job}) {
1339 1         2 my $job = $cfg->{after_job};
1340 1 50       3 if (ref($job) eq "CODE") {
1341             eval
1342 1         2 {
1343 1         3 &$job($dispatch_result,@args);
1344             };
1345 1 50       1101 if ($@)
1346             {
1347 0 0 0     0 $log->(2,"Schedule::Cron - Error while calling after_job callback with retval = $dispatch_result: $@")
1348             if $log && $loglevel <= 2;
1349             }
1350             } else {
1351 0 0 0     0 $log->(2,"Schedule::Cron - Invalid after_job callback, it's not a code ref (but ",$job,")")
1352             if $log && $loglevel <= 2;
1353             }
1354             }
1355              
1356 20 100 66     111 $log->(0,"Schedule::Cron - Finished job $index") if $log && $loglevel <= 0;
1357 20 100       2134 exit unless $cfg->{nofork};
1358             }
1359              
1360             # Udate the scheduler queue with a new entry
1361             sub _update_queue
1362             {
1363 49     49   101 my $self = shift;
1364 49         80 my $index = shift;
1365 49         135 my $entry = $self->get_entry($index);
1366            
1367 49         240 my $new_time = $self->get_next_execution_time($entry->{time});
1368             # Check, whether next execution time is *smaller* than the current time.
1369             # This can happen during DST backflip:
1370 48         148 my $now = $self->_now();
1371 48 50       123 if ($new_time <= $now) {
1372 0 0       0 dbg "Adjusting time calculation because of DST back flip (new_time - now = ",$new_time - $now,")" if $DEBUG;
1373             # We are adding hours as long as our target time is in the future
1374 0         0 while ($new_time <= $now) {
1375 0         0 $new_time += 3600;
1376             }
1377             }
1378              
1379 48 50       113 dbg "Updating Queue: ",scalar(localtime($new_time)) if $DEBUG;
1380 48         85 $self->{queue} = [ sort { $a->[1] <=> $b->[1] } @{$self->{queue}},[$index,$new_time] ];
  50         146  
  48         300  
1381             #dbg "Queue now: ",Dumper($self->{queue});
1382             }
1383              
1384              
1385             # Out "now" which can be shifted if as argument
1386             sub _now {
1387 109     109   269 my $self = shift;
1388 109         528 return time + $self->{timeshift};
1389             }
1390              
1391             # The heart of the module.
1392             # calculate the next concrete date
1393             # for execution from a crontab entry
1394             sub _calc_time
1395             {
1396 105     105   140 my $self = shift;
1397 105         132 my $now = shift;
1398 105         129 my $expanded = shift;
1399              
1400 105 100       253 my $offset = ($expanded->[5] ? 1 : 60) + $self->{timeshift};
1401 105         2913 my ($now_sec,$now_min,$now_hour,$now_mday,$now_mon,$now_wday,$now_year) =
1402             (localtime($now+$offset))[0,1,2,3,4,6,5];
1403 105         292 $now_mon++;
1404 105         172 $now_year += 1900;
1405              
1406             # Notes on variables set:
1407             # $now_... : the current date, fixed at call time
1408             # $dest_...: date used for backtracking. At the end, it contains
1409             # the desired lowest matching date
1410              
1411 105         214 my ($dest_mon,$dest_mday,$dest_wday,$dest_hour,$dest_min,$dest_sec,$dest_year) =
1412             ($now_mon,$now_mday,$now_wday,$now_hour,$now_min,$now_sec,$now_year);
1413              
1414             # dbg Dumper($expanded);
1415              
1416             # Airbag...
1417 105         296 while ($dest_year <= $now_year + 1)
1418             {
1419 131 50       269 dbg "Parsing $dest_hour:$dest_min:$dest_sec $dest_year/$dest_mon/$dest_mday" if $DEBUG;
1420            
1421             # Check month:
1422 131 100       305 if ($expanded->[3]->[0] ne '*')
1423             {
1424 21 100       51 unless (defined ($dest_mon = $self->_get_nearest($dest_mon,$expanded->[3])))
1425             {
1426 8         12 $dest_mon = $expanded->[3]->[0];
1427 8         12 $dest_year++;
1428             }
1429             }
1430            
1431             # Check for day of month:
1432 131 100       261 if ($expanded->[2]->[0] ne '*')
1433             {
1434 28 100       44 if ($dest_mon != $now_mon)
1435             {
1436 12         17 $dest_mday = $expanded->[2]->[0];
1437             }
1438             else
1439             {
1440 16 100       36 unless (defined ($dest_mday = $self->_get_nearest($dest_mday,$expanded->[2])))
1441             {
1442             # Next day matched is within the next month. ==> redo it
1443 5         9 $dest_mday = $expanded->[2]->[0];
1444 5         6 $dest_mon++;
1445 5 50       12 if ($dest_mon > 12)
1446             {
1447 5         6 $dest_mon = 1;
1448 5         5 $dest_year++;
1449             }
1450 5 50       9 dbg "Backtrack mday: $dest_mday/$dest_mon/$dest_year" if $DEBUG;
1451 5         36 next;
1452             }
1453             }
1454             }
1455             else
1456             {
1457 103 100       227 $dest_mday = ($dest_mon == $now_mon ? $dest_mday : 1);
1458             }
1459            
1460             # Check for day of week:
1461 126 100       547 if ($expanded->[4]->[0] ne '*')
1462             {
1463 17         37 $dest_wday = $self->_get_nearest($dest_wday,$expanded->[4]);
1464 17 100       36 $dest_wday = $expanded->[4]->[0] unless $dest_wday;
1465            
1466 17         18 my ($mon,$mday,$year);
1467             # dbg "M: $dest_mon MD: $dest_mday WD: $dest_wday Y:$dest_year";
1468 17 100       30 $dest_mday = 1 if $dest_mon != $now_mon;
1469 17         85 my $t = parsedate(sprintf("%4.4d/%2.2d/%2.2d",$dest_year,$dest_mon,$dest_mday));
1470 17         3325 ($mon,$mday,$year) =
1471             (localtime(parsedate("$WDAYS[$dest_wday]",PREFER_FUTURE=>1,NOW=>$t-1)))[4,3,5];
1472 17         4344 $mon++;
1473 17         27 $year += 1900;
1474            
1475 17 50       34 dbg "Calculated $mday/$mon/$year for weekday ",$WDAYS[$dest_wday] if $DEBUG;
1476 17 100 66     63 if ($mon != $dest_mon || $year != $dest_year) {
1477 3 50       6 dbg "backtracking" if $DEBUG;
1478 3         4 $dest_mon = $mon;
1479 3         3 $dest_year = $year;
1480 3         4 $dest_mday = 1;
1481 3         16 $dest_wday = (localtime(parsedate(sprintf("%4.4d/%2.2d/%2.2d",
1482             $dest_year,$dest_mon,$dest_mday))))[6];
1483 3         608 next;
1484             }
1485            
1486 14         21 $dest_mday = $mday;
1487             }
1488             else
1489             {
1490 109 50       220 unless ($dest_mday)
1491             {
1492 0 0       0 $dest_mday = ($dest_mon == $now_mon ? $dest_mday : 1);
1493             }
1494             }
1495              
1496            
1497             # Check for hour
1498 123 100       263 if ($expanded->[1]->[0] ne '*')
1499             {
1500 46 100 100     142 if ($dest_mday != $now_mday || $dest_mon != $now_mon || $dest_year != $now_year)
      66        
1501             {
1502 27         42 $dest_hour = $expanded->[1]->[0];
1503             }
1504             else
1505             {
1506             #dbg "Checking for next hour $dest_hour";
1507 19 100       39 unless (defined ($dest_hour = $self->_get_nearest($dest_hour,$expanded->[1])))
1508             {
1509             # Hour to match is at the next day ==> redo it
1510 8         11 $dest_hour = $expanded->[1]->[0];
1511 8         44 my $t = parsedate(sprintf("%2.2d:%2.2d:%2.2d %4.4d/%2.2d/%2.2d",
1512             $dest_hour,$dest_min,$dest_sec,$dest_year,$dest_mon,$dest_mday));
1513 8         1873 ($dest_mday,$dest_mon,$dest_year,$dest_wday) =
1514             (localtime(parsedate("+ 1 day",NOW=>$t)))[3,4,5,6];
1515 8         2050 $dest_mon++;
1516 8         12 $dest_year += 1900;
1517 8         24 next;
1518             }
1519             }
1520             }
1521             else
1522             {
1523 77 100       155 $dest_hour = ($dest_mday == $now_mday ? $dest_hour : 0);
1524             }
1525             # Check for minute
1526 115 100       251 if ($expanded->[0]->[0] ne '*')
1527             {
1528 40 100 100     132 if ($dest_hour != $now_hour || $dest_mday != $now_mday || $dest_mon != $dest_mon || $dest_year != $now_year)
      66        
      66        
1529             {
1530 30         41 $dest_min = $expanded->[0]->[0];
1531             }
1532             else
1533             {
1534 10 100       24 unless (defined ($dest_min = $self->_get_nearest($dest_min,$expanded->[0])))
1535             {
1536             # Minute to match is at the next hour ==> redo it
1537 2         5 $dest_min = $expanded->[0]->[0];
1538 2         15 my $t = parsedate(sprintf("%2.2d:%2.2d:%2.2d %4.4d/%2.2d/%2.2d",
1539             $dest_hour,$dest_min,$dest_sec,$dest_year,$dest_mon,$dest_mday));
1540 2         1474 ($dest_hour,$dest_mday,$dest_mon,$dest_year,$dest_wday) =
1541             (localtime(parsedate(" + 1 hour",NOW=>$t))) [2,3,4,5,6];
1542 2         864 $dest_mon++;
1543 2         5 $dest_year += 1900;
1544 2         7 next;
1545             }
1546             }
1547             }
1548             else
1549             {
1550 75 100 100     382 if ($dest_hour != $now_hour ||
      66        
1551             $dest_mday != $now_mday ||
1552             $dest_year != $now_year) {
1553 9         10 $dest_min = 0;
1554             }
1555             }
1556             # Check for seconds
1557 113 100       227 if ($expanded->[5])
1558             {
1559 61 100       128 if ($expanded->[5]->[0] ne '*')
1560             {
1561 45 100       89 if ($dest_min != $now_min)
1562             {
1563 7         13 $dest_sec = $expanded->[5]->[0];
1564             }
1565             else
1566             {
1567 38 100       121 unless (defined ($dest_sec = $self->_get_nearest($dest_sec,$expanded->[5])))
1568             {
1569             # Second to match is at the next minute ==> redo it
1570 7         12 $dest_sec = $expanded->[5]->[0];
1571 7         46 my $t = parsedate(sprintf("%2.2d:%2.2d:%2.2d %4.4d/%2.2d/%2.2d",
1572             $dest_hour,$dest_min,$dest_sec,
1573             $dest_year,$dest_mon,$dest_mday));
1574 7         1846 ($dest_min,$dest_hour,$dest_mday,$dest_mon,$dest_year,$dest_wday) =
1575             (localtime(parsedate(" + 1 minute",NOW=>$t))) [1,2,3,4,5,6];
1576 7         2648 $dest_mon++;
1577 7         13 $dest_year += 1900;
1578 7         23 next;
1579             }
1580             }
1581             }
1582             else
1583             {
1584 16 50       36 $dest_sec = ($dest_min == $now_min ? $dest_sec : 0);
1585             }
1586             }
1587             else
1588             {
1589 52         60 $dest_sec = 0;
1590             }
1591            
1592             # We did it !!
1593 106         576 my $date = sprintf("%2.2d:%2.2d:%2.2d %4.4d/%2.2d/%2.2d",
1594             $dest_hour,$dest_min,$dest_sec,$dest_year,$dest_mon,$dest_mday);
1595 106 50       213 dbg "Next execution time: $date ",$WDAYS[$dest_wday] if $DEBUG;
1596 106         453 my $result = parsedate($date, VALIDATE => 1);
1597             # Check for a valid date
1598 106 100       49957 if ($result)
1599             {
1600             # Valid date... return it!
1601 105         586 return $result;
1602             }
1603             else
1604             {
1605             # Invalid date i.e. (02/30/2008). Retry it with another, possibly
1606             # valid date
1607 1         4 my $t = parsedate($date); # print scalar(localtime($t)),"\n";
1608 1         219 ($dest_hour,$dest_mday,$dest_mon,$dest_year,$dest_wday) =
1609             (localtime(parsedate(" + 1 second",NOW=>$t))) [2,3,4,5,6];
1610 1         210 $dest_mon++;
1611 1         2 $dest_year += 1900;
1612 1         4 next;
1613             }
1614             }
1615              
1616             # Die with an error because we couldn't find a next execution entry
1617 0         0 my $dumper = new Data::Dumper($expanded);
1618 0         0 $dumper->Terse(1);
1619 0         0 $dumper->Indent(0);
1620              
1621 0         0 die "No suitable next execution time found for ",$dumper->Dump(),", now == ",scalar(localtime($now)),"\n";
1622             }
1623              
1624             # get next entry in list or
1625             # undef if is the highest entry found
1626             sub _get_nearest
1627             {
1628 121     121   172 my $self = shift;
1629 121         141 my $x = shift;
1630 121         129 my $to_check = shift;
1631 121         255 foreach my $i (0 .. $#$to_check)
1632             {
1633 469 100       701 if ($$to_check[$i] >= $x)
1634             {
1635 84         289 return $$to_check[$i] ;
1636             }
1637             }
1638 37         89 return undef;
1639             }
1640              
1641              
1642             # prepare a list of object for pretty printing e.g. in the process list
1643             sub _format_args {
1644 1     1   81 my $self = shift;
1645 1         9 my @args = @_;
1646 1         9 my $dumper = new Data::Dumper(\@args);
1647 1         35 $dumper->Terse(1);
1648 1         13 $dumper->Maxdepth(2);
1649 1         6 $dumper->Indent(0);
1650 1         14 return $dumper->Dump();
1651             }
1652              
1653             # get the prefix to use when setting $0
1654             sub _get_process_prefix {
1655 30     30   64 my $self = shift;
1656 30   50     137 my $prefix = $self->{cfg}->{processprefix} || "Schedule::Cron";
1657 30         1193 return $prefix;
1658             }
1659              
1660             # our very own debugging routine
1661             # ('guess everybody has its own style ;-)
1662             # Callers check $DEBUG on the critical path to save the computes
1663             # used to produce expensive arguments. Omitting those would be
1664             # functionally correct, but rather wasteful.
1665             sub dbg
1666             {
1667 15 50   15   27 if ($DEBUG)
1668             {
1669 0   0     0 my $args = join('',@_) || "";
1670 0         0 my $caller = (caller(1))[0];
1671 0         0 my $line = (caller(0))[2];
1672 0   0     0 $caller ||= $0;
1673 0 0       0 if (length $caller > 22)
1674             {
1675 0         0 $caller = substr($caller,0,10)."..".substr($caller,-10,10);
1676             }
1677 0         0 print STDERR sprintf ("%02d:%02d:%02d [%22.22s %4.4s] %s\n",
1678             (localtime)[2,1,0],$caller,$line,$args);
1679             }
1680             }
1681              
1682             # Helper method for reporting bugs concerning calculation
1683             # of execution bug:
1684             *bug = \&report_exectime_bug; # Shortcut
1685             sub report_exectime_bug
1686             {
1687 0     0 0 0 my $self = shift;
1688 0         0 my $endless = shift;
1689 0         0 my $time = time;
1690 0         0 my $inp;
1691 0         0 my $now = $self->_time_as_string($time);
1692 0         0 my $email;
1693              
1694             do
1695 0         0 {
1696 0         0 while (1)
1697             {
1698 0         0 $inp = $self->_get_input("Reference time\n(default: $now) : ");
1699 0 0       0 if ($inp)
1700             {
1701 0 0       0 parsedate($inp) || (print "Couldn't parse \"$inp\"\n",next);
1702 0         0 $now = $inp;
1703             }
1704 0         0 last;
1705             }
1706 0         0 my $now_time = parsedate($now);
1707            
1708 0         0 my ($next_time,$next);
1709 0         0 my @entries;
1710 0         0 while (1)
1711             {
1712 0         0 $inp = $self->_get_input("Crontab time (5 columns) : ");
1713 0         0 @entries = split (/\s+/,$inp);
1714 0 0       0 if (@entries != 5)
1715             {
1716 0         0 print "Invalid crontab entry \"$inp\"\n";
1717 0         0 next;
1718             }
1719             eval
1720 0         0 {
1721 0     0   0 local $SIG{ALRM} = sub { die "TIMEOUT" };
  0         0  
1722 0         0 alarm(60);
1723 0         0 $next_time = Schedule::Cron->get_next_execution_time(\@entries,$now_time);
1724 0         0 alarm(0);
1725             };
1726 0 0       0 if ($@)
1727             {
1728 0         0 alarm(0);
1729 0 0       0 if ($@ eq "TIMEOUT")
1730             {
1731 0         0 $next_time = -1;
1732             } else
1733             {
1734 0         0 print "Invalid crontab entry \"$inp\" ($@)\n";
1735 0         0 next;
1736             }
1737             }
1738            
1739 0 0       0 if ($next_time > 0)
1740             {
1741 0         0 $next = $self->_time_as_string($next_time);
1742             } else
1743             {
1744 0         0 $next = "Run into infinite loop !!";
1745             }
1746 0         0 last;
1747             }
1748            
1749 0         0 my ($expected,$expected_time);
1750 0         0 while (1)
1751             {
1752 0         0 $inp = $self->_get_input("Expected time : ");
1753 0 0       0 unless ($expected_time = parsedate($inp))
1754             {
1755 0         0 print "Couldn't parse \"$inp\"\n";
1756 0         0 next;
1757             }
1758 0         0 $expected = $self->_time_as_string($expected_time);
1759 0         0 last;
1760             }
1761            
1762             # Print out bug report:
1763 0 0       0 if ($expected eq $next)
1764             {
1765 0         0 print "\nHmm, seems that everything's ok, or ?\n\n";
1766 0         0 print "Calculated time: ",$next,"\n";
1767 0         0 print "Expected time : ",$expected,"\n";
1768             } else
1769             {
1770 0         0 print <
1771             Congratulation, you hit a bug.
1772              
1773             EOT
1774 0 0       0 $email = $self->_get_input("Your E-Mail Address (if available) : ")
1775             unless defined($email);
1776 0 0       0 $email = "" unless defined($email);
1777            
1778 0         0 print "\n","=" x 80,"\n";
1779 0         0 print <
1780             Please report the following lines
1781             to roland\@cpan.org
1782              
1783             EOT
1784 0         0 print "# ","-" x 78,"\n";
1785 0         0 print "Reftime: ",$now,"\n";
1786 0 0       0 print "# Reported by : ",$email,"\n" if $email;
1787 0         0 printf "%8s %8s %8s %8s %8s %s\n",@entries,$expected;
1788 0         0 print "# Calculated : \n";
1789 0         0 printf "# %8s %8s %8s %8s %8s %s\n",@entries,$next;
1790 0 0       0 unless ($endless)
1791             {
1792 0         0 require Config;
1793 0   0     0 my $vers = `uname -r 2>/dev/null` || $Config::Config{'osvers'} ;
1794 0         0 chomp $vers;
1795 0   0     0 my $osname = `uname -s 2>/dev/null` || $Config::Config{'osname'};
1796 0         0 chomp $osname;
1797 0         0 print "# OS: $osname ($vers)\n";
1798 0         0 print "# Perl-Version: $]\n";
1799 0         0 print "# Time::ParseDate-Version: ",$Time::ParseDate::VERSION,"\n";
1800             }
1801 0         0 print "# ","-" x 78,"\n";
1802             }
1803            
1804 0         0 print "\n","=" x 80,"\n";
1805             } while ($endless);
1806             }
1807              
1808             my ($input_initialized,$term);
1809             sub _get_input
1810             {
1811 0     0   0 my $self = shift;
1812 0         0 my $prompt = shift;
1813 17     17   93385 use vars qw($term);
  17         40  
  17         7688  
1814              
1815 0 0       0 unless (defined($input_initialized))
1816             {
1817 0         0 eval { require Term::ReadLine; };
  0         0  
1818            
1819 0 0       0 $input_initialized = $@ ? 0 : 1;
1820 0 0       0 if ($input_initialized)
1821             {
1822 0         0 $term = new Term::ReadLine;
1823 0         0 $term->ornaments(0);
1824             }
1825             }
1826            
1827 0 0       0 unless ($input_initialized)
1828             {
1829 0         0 print $prompt;
1830 0         0 my $inp = ;
1831 0         0 chomp $inp;
1832 0         0 return $inp;
1833             }
1834             else
1835             {
1836 0         0 chomp $prompt;
1837 0         0 my @prompt = split /\n/s,$prompt;
1838 0 0       0 if ($#prompt > 0)
1839             {
1840 0         0 print join "\n",@prompt[0..$#prompt-1],"\n";
1841             }
1842 0         0 my $inp = $term->readline($prompt[$#prompt]);
1843 0         0 return $inp;
1844             }
1845             }
1846              
1847             sub _time_as_string
1848             {
1849 0     0   0 my $self = shift;
1850 0         0 my $time = shift;
1851              
1852 0         0 my ($min,$hour,$mday,$month,$year,$wday) = (localtime($time))[1..6];
1853 0         0 $month++;
1854 0         0 $year += 1900;
1855 0         0 $wday = $WDAYS[$wday];
1856 0         0 return sprintf("%2.2d:%2.2d %2.2d/%2.2d/%4.4d %s",
1857             $hour,$min,$mday,$month,$year,$wday);
1858             }
1859              
1860              
1861             # As reported by RT Ticket #24712 sometimes,
1862             # the expanded version of the cron entry is flaky.
1863             # However, this occurs only very rarely and randomly.
1864             # So, we need to provide good diagnostics when this
1865             # happens
1866             sub _verify_expanded_cron_entry {
1867 101     101   139 my $self = shift;
1868 101         149 my $original = shift;
1869 101         124 my $entry = shift;
1870 101 50       251 die "Internal: Not an array ref. Orig: ",Dumper($original), ", expanded: ",Dumper($entry)," (self = ",Dumper($self),")"
1871             unless ref($entry) eq "ARRAY";
1872            
1873 101         130 for my $i (0 .. $#{$entry}) {
  101         248  
1874 559 50       956 die "Internal: Part $i of entry is not an array ref. Original: ",
1875             Dumper($original),", expanded: ",Dumper($entry)," (self=",Dumper($self),")",
1876             unless ref($entry->[$i]) eq "ARRAY";
1877             }
1878             }
1879              
1880             =back
1881              
1882             =head1 DST ISSUES
1883              
1884             Daylight saving occurs typically twice a year: In the first switch, one hour is
1885             skipped. Any job which triggers in this skipped hour will be fired in the
1886             next hour. So, when the DST switch goes from 2:00 to 3:00 a job which is
1887             scheduled for 2:43 will be executed at 3:43.
1888              
1889             For the reverse backwards switch later in the year, the behaviour is
1890             undefined. Two possible behaviours can occur: For jobs triggered in short
1891             intervals, where the next execution time would fire in the extra hour as well,
1892             the job could be executed again or skipped in this extra hour. Currently,
1893             running C in C would skip the extra job, in C it
1894             would execute a second time. The reason is the way how L
1895             calculates epoch times for dates given like C<02:50:00 2009/10/25>. Should it
1896             return the seconds since 1970 for this time happening 'first', or for this time
1897             in the extra hour ? As it turns out, L returns the epoch time
1898             of the first occurrence for C and for C it returns the second
1899             occurrence. Unfortunately, there is no way to specify I entry
1900             L should pick (until now). Of course, after all, this is
1901             obviously not L's fault, since a simple date specification
1902             within the DST backswitch period B ambiguous. However, it would be nice if
1903             the parsing behaviour of L would be consistent across time
1904             zones (a ticket has be raised for fixing this). Then L's
1905             behaviour within a DST backward switch would be consistent as well.
1906              
1907             Since changing the internal algorithm which worked now for over ten years would
1908             be too risky and I don't see any simple solution for this right now, it is
1909             likely that this I behaviour will exist for some time. Maybe some
1910             hero is coming along and will fix this, but this is probably not me ;-)
1911              
1912             Sorry for that.
1913              
1914             =head1 LICENSE
1915              
1916             Copyright 1999-2011 Roland Huss.
1917              
1918             This library is free software; you can redistribute it and/or
1919             modify it under the same terms as Perl itself.
1920              
1921             =head1 AUTHOR
1922              
1923             ... roland
1924              
1925             =head1 Contributors
1926              
1927             =over 4
1928              
1929             =item *
1930              
1931             Alexandr Ciornii
1932              
1933             =item *
1934              
1935             Nicholas Hubbard
1936              
1937             =back
1938              
1939             =cut
1940              
1941             1;
1942              
1943              
1944              
1945