1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2000-2011 Free Software Foundation Europe e.V.
5    Copyright (C) 2011-2012 Planets Communications B.V.
6    Copyright (C) 2013-2018 Bareos GmbH & Co. KG
7 
8    This program is Free Software; you can redistribute it and/or
9    modify it under the terms of version three of the GNU Affero General Public
10    License as published by the Free Software Foundation and included
11    in the file LICENSE.
12 
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16    Affero General Public License for more details.
17 
18    You should have received a copy of the GNU Affero General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21    02110-1301, USA.
22 */
23 /*
24  * Kern Sibbald, May MM, major revision December MMIII
25  */
26 /**
27  * @file
28  * BAREOS scheduler
29  *
30  * It looks at what jobs are to be run and when
31  * and waits around until it is time to
32  * fire them up.
33  */
34 
35 #include "include/bareos.h"
36 #include "dird.h"
37 #include "dird/dird_globals.h"
38 #include "dird/job.h"
39 #include "dird/storage.h"
40 
41 #if 0
42 #define SCHED_DEBUG
43 #define DBGLVL 0
44 #else
45 #undef SCHED_DEBUG
46 #define DBGLVL 200
47 #endif
48 
49 namespace directordaemon {
50 
51 const int debuglevel = DBGLVL;
52 
53 /* Local variables */
54 struct job_item {
55    RunResource *run;
56    JobResource *job;
57    time_t runtime;
58    int Priority;
59    dlink link;                        /* link for list */
60 };
61 
62 /* List of jobs to be run. They were scheduled in this hour or the next */
63 static dlist *jobs_to_run;               /* list of jobs to be run */
64 
65 /* Time interval in secs to sleep if nothing to be run */
66 static int const next_check_secs = 60;
67 
68 /* Forward referenced subroutines */
69 static void find_runs();
70 static void add_job(JobResource *job, RunResource *run, time_t now, time_t runtime);
71 static void dump_job(job_item *ji, const char *msg);
72 
73 /* Imported subroutines */
74 
75 /* Imported variables */
76 
77 /**
78  * called by reload_config to tell us that the schedules
79  * we may have based our next jobs to run queues have been
80  * invalidated.  In fact the schedules may not have changed
81  * but the run object that we have recorded the last_run time
82  * on are new and no longer have a valid last_run time which
83  * causes us to double run schedules that get put into the list
84  * because run_nh = 1.
85  */
86 static bool schedules_invalidated = false;
InvalidateSchedules(void)87 void InvalidateSchedules(void) {
88     schedules_invalidated = true;
89 }
90 
91 /**
92  *
93  *         Main Bareos Scheduler
94  *
95  */
wait_for_next_job(char * one_shot_job_to_run)96 JobControlRecord *wait_for_next_job(char *one_shot_job_to_run)
97 {
98    JobControlRecord *jcr;
99    JobResource *job;
100    RunResource *run;
101    time_t now, prev;
102    static bool first = true;
103    job_item *next_job = NULL;
104 
105    Dmsg0(debuglevel, "Enter wait_for_next_job\n");
106    if (first) {
107       first = false;
108       /* Create scheduled jobs list */
109       jobs_to_run = New(dlist(next_job, &next_job->link));
110       if (one_shot_job_to_run) {            /* one shot */
111          job = (JobResource *)my_config->GetResWithName(R_JOB, one_shot_job_to_run);
112          if (!job) {
113             Emsg1(M_ABORT, 0, _("Job %s not found\n"), one_shot_job_to_run);
114          }
115          Dmsg1(5, "Found one_shot_job_to_run %s\n", one_shot_job_to_run);
116          jcr = new_jcr(sizeof(JobControlRecord), DirdFreeJcr);
117          SetJcrDefaults(jcr, job);
118          return jcr;
119       }
120    }
121 
122    /* Wait until we have something in the
123     * next hour or so.
124     */
125 again:
126    while (jobs_to_run->empty()) {
127       find_runs();
128       if (!jobs_to_run->empty()) {
129          break;
130       }
131       Bmicrosleep(next_check_secs, 0); /* recheck once per minute */
132    }
133 
134 #ifdef  list_chain
135    job_item *je;
136    foreach_dlist(je, jobs_to_run) {
137       dump_job(je, _("Walk queue"));
138    }
139 #endif
140    /*
141     * Pull the first job to run (already sorted by runtime and
142     *  Priority, then wait around until it is time to run it.
143     */
144    next_job = (job_item *)jobs_to_run->first();
145    jobs_to_run->remove(next_job);
146 
147    dump_job(next_job, _("Dequeued job"));
148 
149    if (!next_job) {                /* we really should have something now */
150       Emsg0(M_ABORT, 0, _("Scheduler logic error\n"));
151    }
152 
153    /* Now wait for the time to run the job */
154    for (;;) {
155       time_t twait;
156       /* discard scheduled queue and rebuild with new schedule objects. */
157       LockJobs();
158       if (schedules_invalidated) {
159           dump_job(next_job, "Invalidated job");
160           free(next_job);
161           while (!jobs_to_run->empty()) {
162               next_job = (job_item *)jobs_to_run->first();
163               jobs_to_run->remove(next_job);
164               dump_job(next_job, "Invalidated job");
165               free(next_job);
166           }
167           schedules_invalidated = false;
168           UnlockJobs();
169           goto again;
170       }
171       UnlockJobs();
172       prev = now = time(NULL);
173       twait = next_job->runtime - now;
174       if (twait <= 0) {               /* time to run it */
175          break;
176       }
177       /* Recheck at least once per minute */
178       Bmicrosleep((next_check_secs < twait)?next_check_secs:twait, 0);
179       /* Attempt to handle clock shift (but not daylight savings time changes)
180        * we allow a skew of 10 seconds before invalidating everything.
181        */
182       now = time(NULL);
183       if (now < prev-10 || now > (prev+next_check_secs+10)) {
184          schedules_invalidated = true;
185       }
186    }
187 
188    jcr = new_jcr(sizeof(JobControlRecord), DirdFreeJcr);
189    run = next_job->run;               /* pick up needed values */
190    job = next_job->job;
191 
192    if (job->enabled && (!job->client || job->client->enabled)) {
193       dump_job(next_job, _("Run job"));
194    }
195 
196    free(next_job);
197 
198    if (!job->enabled ||
199        (job->schedule && !job->schedule->enabled) ||
200        (job->client && !job->client->enabled)) {
201       FreeJcr(jcr);
202       goto again;                     /* ignore this job */
203    }
204 
205    run->last_run = now;               /* mark as run now */
206 
207    ASSERT(job);
208    SetJcrDefaults(jcr, job);
209    if (run->level) {
210       jcr->setJobLevel(run->level);  /* override run level */
211    }
212 
213    if (run->pool) {
214       jcr->res.pool = run->pool; /* override pool */
215       jcr->res.run_pool_override = true;
216    }
217 
218    if (run->full_pool) {
219       jcr->res.full_pool = run->full_pool; /* override full pool */
220       jcr->res.run_full_pool_override = true;
221    }
222 
223    if (run->vfull_pool) {
224       jcr->res.vfull_pool = run->vfull_pool; /* override virtual full pool */
225       jcr->res.run_vfull_pool_override = true;
226    }
227 
228    if (run->inc_pool) {
229       jcr->res.inc_pool = run->inc_pool; /* override inc pool */
230       jcr->res.run_inc_pool_override = true;
231    }
232 
233    if (run->diff_pool) {
234       jcr->res.diff_pool = run->diff_pool; /* override diff pool */
235       jcr->res.run_diff_pool_override = true;
236    }
237 
238    if (run->next_pool) {
239       jcr->res.next_pool = run->next_pool; /* override next pool */
240       jcr->res.run_next_pool_override = true;
241    }
242 
243    if (run->storage) {
244       UnifiedStorageResource store;
245       store.store = run->storage;
246       PmStrcpy(store.store_source, _("run override"));
247       SetRwstorage(jcr, &store); /* override storage */
248    }
249 
250    if (run->msgs) {
251       jcr->res.messages = run->msgs; /* override messages */
252    }
253 
254    if (run->Priority) {
255       jcr->JobPriority = run->Priority;
256    }
257 
258    if (run->spool_data_set) {
259       jcr->spool_data = run->spool_data;
260    }
261 
262    if (run->accurate_set) {
263       jcr->accurate = run->accurate; /* overwrite accurate mode */
264    }
265 
266    if (run->MaxRunSchedTime_set) {
267       jcr->MaxRunSchedTime = run->MaxRunSchedTime;
268    }
269 
270    Dmsg0(debuglevel, "Leave wait_for_next_job()\n");
271    return jcr;
272 }
273 
274 /**
275  * Shutdown the scheduler
276  */
TermScheduler()277 void TermScheduler()
278 {
279    if (jobs_to_run) {
280       delete jobs_to_run;
281    }
282 }
283 
284 /**
285  * check if given day of year is in last week of the month in the current year
286  * depending if the year is leap year or not, the doy of the last day of the month
287  * is varying one day.
288  */
IsDoyInLastWeek(int year,int doy)289 bool IsDoyInLastWeek(int year, int doy)
290 {
291    int i;
292    int *last_dom;
293    int last_day_of_month[] = { 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
294    int last_day_of_month_leap[] = { 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 };
295 
296    /*
297     * Determine if this is a leap year.
298     */
299    if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) {
300       last_dom = last_day_of_month_leap;
301    } else {
302       last_dom = last_day_of_month;
303    }
304 
305    for (i = 0; i < 12; i++) {
306       /* doy is zero-based */
307       if (doy > ((last_dom[i] - 1) - 7) && doy <= (last_dom[i] - 1)) {
308          return true;
309       }
310    }
311 
312    return false;
313 }
314 
315 /**
316  * Find all jobs to be run this hour and the next hour.
317  */
find_runs()318 static void find_runs()
319 {
320    time_t now, next_hour, runtime;
321    RunResource *run;
322    JobResource *job;
323    ScheduleResource *sched;
324    struct tm tm;
325    bool is_last_week = false;         /* are we in the last week of a month? */
326    bool nh_is_last_week = false;      /* are we in the last week of a month? */
327    int hour, mday, wday, month, wom, woy, yday;
328    /* Items corresponding to above at the next hour */
329    int nh_hour, nh_mday, nh_wday, nh_month, nh_wom, nh_woy, nh_yday;
330 
331    Dmsg0(debuglevel, "enter find_runs()\n");
332 
333    /*
334     * Compute values for time now
335     */
336    now = time(NULL);
337    Blocaltime(&now, &tm);
338    hour = tm.tm_hour;
339    mday = tm.tm_mday - 1;
340    wday = tm.tm_wday;
341    month = tm.tm_mon;
342    wom = mday / 7;
343    woy = TmWoy(now);                 /* get week of year */
344    yday = tm.tm_yday;                 /* get day of year */
345 
346    Dmsg8(debuglevel, "now = %x: h=%d m=%d md=%d wd=%d wom=%d woy=%d yday=%d\n",
347          now, hour, month, mday, wday, wom, woy, yday);
348 
349    is_last_week = IsDoyInLastWeek(tm.tm_year + 1900 , yday);
350 
351    /*
352     * Compute values for next hour from now.
353     * We do this to be sure we don't miss a job while
354     * sleeping.
355     */
356    next_hour = now + 3600;
357    Blocaltime(&next_hour, &tm);
358    nh_hour = tm.tm_hour;
359    nh_mday = tm.tm_mday - 1;
360    nh_wday = tm.tm_wday;
361    nh_month = tm.tm_mon;
362    nh_wom = nh_mday / 7;
363    nh_woy = TmWoy(next_hour);        /* get week of year */
364    nh_yday = tm.tm_yday;              /* get day of year */
365 
366    Dmsg8(debuglevel, "nh = %x: h=%d m=%d md=%d wd=%d wom=%d woy=%d yday=%d\n",
367          next_hour, nh_hour, nh_month, nh_mday, nh_wday, nh_wom, nh_woy, nh_yday);
368 
369    nh_is_last_week = IsDoyInLastWeek(tm.tm_year + 1900 , nh_yday);
370 
371    /*
372     * Loop through all jobs
373     */
374    LockRes(my_config);
375    foreach_res(job, R_JOB) {
376       sched = job->schedule;
377       if (sched == NULL ||
378           !sched->enabled ||
379           !job->enabled ||
380           (job->client && !job->client->enabled)) { /* scheduled? or enabled? */
381          continue;                    /* no, skip this job */
382       }
383 
384       Dmsg1(debuglevel, "Got job: %s\n", job->hdr.name);
385       for (run = sched->run; run; run = run->next) {
386          bool run_now, run_nh;
387          /*
388           * Find runs scheduled between now and the next hour.
389           */
390 #ifdef xxxx
391          Dmsg0(000, "\n");
392          Dmsg7(000, "run h=%d m=%d md=%d wd=%d wom=%d woy=%d yday=%d\n",
393             hour, month, mday, wday, wom, woy, yday);
394          Dmsg6(000, "bitset bsh=%d bsm=%d bsmd=%d bswd=%d bswom=%d bswoy=%d\n",
395                BitIsSet(hour, run->hour),
396                BitIsSet(month, run->month),
397                BitIsSet(mday, run->mday),
398                BitIsSet(wday, run->wday),
399                BitIsSet(wom, run->wom),
400                BitIsSet(woy, run->woy));
401 
402          Dmsg7(000, "nh_run h=%d m=%d md=%d wd=%d wom=%d woy=%d yday=%d\n",
403                nh_hour, nh_month, nh_mday, nh_wday, nh_wom, nh_woy, nh_yday);
404          Dmsg6(000, "nh_bitset bsh=%d bsm=%d bsmd=%d bswd=%d bswom=%d bswoy=%d\n",
405                BitIsSet(nh_hour, run->hour),
406                BitIsSet(nh_month, run->month),
407                BitIsSet(nh_mday, run->mday),
408                BitIsSet(nh_wday, run->wday),
409                BitIsSet(nh_wom, run->wom),
410                BitIsSet(nh_woy, run->woy));
411          Dmsg2(000, "run->last_set:%d, is_last_week:%d\n", run->last_set, is_last_week);
412          Dmsg2(000, "run->last_set:%d, nh_is_last_week:%d\n", run->last_set, nh_is_last_week);
413 #endif
414 
415          run_now = BitIsSet(hour, run->hour) &&
416                    BitIsSet(mday, run->mday) &&
417                    BitIsSet(wday, run->wday) &&
418                    BitIsSet(month, run->month) &&
419                   (BitIsSet(wom, run->wom) || (run->last_set && is_last_week)) &&
420                    BitIsSet(woy, run->woy);
421 
422          run_nh = BitIsSet(nh_hour, run->hour) &&
423                   BitIsSet(nh_mday, run->mday) &&
424                   BitIsSet(nh_wday, run->wday) &&
425                   BitIsSet(nh_month, run->month) &&
426                  (BitIsSet(nh_wom, run->wom) || (run->last_set && nh_is_last_week)) &&
427                   BitIsSet(nh_woy, run->woy);
428 
429          Dmsg3(debuglevel, "run@%p: run_now=%d run_nh=%d\n", run, run_now, run_nh);
430 
431          if (run_now || run_nh) {
432            /*
433             * find time (time_t) job is to be run
434             */
435            Blocaltime(&now, &tm);      /* reset tm structure */
436            tm.tm_min = run->minute;     /* set run minute */
437            tm.tm_sec = 0;               /* zero secs */
438            runtime = mktime(&tm);
439            if (run_now) {
440              add_job(job, run, now, runtime);
441            }
442            /* If job is to be run in the next hour schedule it */
443            if (run_nh) {
444              add_job(job, run, now, runtime + 3600);
445            }
446          }
447       }
448    }
449    UnlockRes(my_config);
450    Dmsg0(debuglevel, "Leave find_runs()\n");
451 }
452 
add_job(JobResource * job,RunResource * run,time_t now,time_t runtime)453 static void add_job(JobResource *job, RunResource *run, time_t now, time_t runtime)
454 {
455    job_item *ji;
456    bool inserted = false;
457    /*
458     * Don't run any job that ran less than a minute ago, but
459     *  do run any job scheduled less than a minute ago.
460     */
461    if (((runtime - run->last_run) < 61) || ((runtime+59) < now)) {
462 #ifdef SCHED_DEBUG
463       Dmsg4(000, "Drop: Job=\"%s\" run=%lld. last_run=%lld. now=%lld\n", job->hdr.name,
464             (utime_t)runtime, (utime_t)run->last_run, (utime_t)now);
465       fflush(stdout);
466 #endif
467       return;
468    }
469 #ifdef SCHED_DEBUG
470    Dmsg4(000, "Add: Job=\"%s\" run=%lld last_run=%lld now=%lld\n", job->hdr.name,
471             (utime_t)runtime, (utime_t)run->last_run, (utime_t)now);
472 #endif
473    /* accept to run this job */
474    job_item *je = (job_item *)malloc(sizeof(job_item));
475    je->run = run;
476    je->job = job;
477    je->runtime = runtime;
478    if (run->Priority) {
479       je->Priority = run->Priority;
480    } else {
481       je->Priority = job->Priority;
482    }
483 
484    /* Add this job to the wait queue in runtime, priority sorted order */
485    foreach_dlist(ji, jobs_to_run) {
486       if (ji->runtime > je->runtime ||
487           (ji->runtime == je->runtime && ji->Priority > je->Priority)) {
488          jobs_to_run->InsertBefore(je, ji);
489          dump_job(je, _("Inserted job"));
490          inserted = true;
491          break;
492       }
493    }
494    /* If place not found in queue, append it */
495    if (!inserted) {
496       jobs_to_run->append(je);
497       dump_job(je, _("Appended job"));
498    }
499 #ifdef SCHED_DEBUG
500    foreach_dlist(ji, jobs_to_run) {
501       dump_job(ji, _("Run queue"));
502    }
503    Dmsg0(000, "End run queue\n");
504 #endif
505 }
506 
dump_job(job_item * ji,const char * msg)507 static void dump_job(job_item *ji, const char *msg)
508 {
509 #ifdef SCHED_DEBUG
510    char dt[MAX_TIME_LENGTH];
511    int save_debug = debug_level;
512    if (debug_level < debuglevel) {
513       return;
514    }
515    bstrftime_nc(dt, sizeof(dt), ji->runtime);
516    Dmsg4(debuglevel, "%s: Job=%s priority=%d run %s\n", msg, ji->job->hdr.name,
517       ji->Priority, dt);
518    fflush(stdout);
519    debug_level = save_debug;
520 #endif
521 }
522 } /* namespace directordaemon */
523