3 * Bacula Director Job processing routines
5 * Kern Sibbald, October MM
10 Copyright (C) 2000-2005 Kern Sibbald
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU General Public License
14 version 2 as amended with additional clauses defined in the
15 file LICENSE in the main source directory.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 the file LICENSE for additional details.
27 /* Forward referenced subroutines */
28 static void *job_thread(void *arg);
29 static void job_monitor_watchdog(watchdog_t *self);
30 static void job_monitor_destructor(watchdog_t *self);
31 static bool job_check_maxwaittime(JCR *control_jcr, JCR *jcr);
32 static bool job_check_maxruntime(JCR *control_jcr, JCR *jcr);
34 /* Imported subroutines */
35 extern void term_scheduler();
36 extern void term_ua_server();
38 /* Imported variables */
39 extern time_t watchdog_time;
43 void init_job_server(int max_workers)
48 if ((stat = jobq_init(&job_queue, max_workers, job_thread)) != 0) {
50 Emsg1(M_ABORT, 0, _("Could not init job queue: ERR=%s\n"), be.strerror(stat));
53 wd->callback = job_monitor_watchdog;
54 wd->destructor = job_monitor_destructor;
57 wd->data = new_control_jcr("*JobMonitor*", JT_SYSTEM);
58 register_watchdog(wd);
61 void term_job_server()
63 jobq_destroy(&job_queue); /* ignore any errors */
67 * Run a job -- typically called by the scheduler, but may also
68 * be called by the UA (Console program).
70 * Returns: 0 on failure
74 JobId_t run_job(JCR *jcr)
80 sm_check(__FILE__, __LINE__, true);
81 init_msg(jcr, jcr->messages);
83 /* Initialize termination condition variable */
84 if ((errstat = pthread_cond_init(&jcr->term_wait, NULL)) != 0) {
86 Jmsg1(jcr, M_FATAL, 0, _("Unable to init job cond variable: ERR=%s\n"), be.strerror(errstat));
89 jcr->term_wait_inited = true;
95 Dmsg0(50, "Open database\n");
96 jcr->db=db_init_database(jcr, jcr->catalog->db_name, jcr->catalog->db_user,
97 jcr->catalog->db_password, jcr->catalog->db_address,
98 jcr->catalog->db_port, jcr->catalog->db_socket,
99 jcr->catalog->mult_db_connections);
100 if (!jcr->db || !db_open_database(jcr, jcr->db)) {
101 Jmsg(jcr, M_FATAL, 0, _("Could not open database \"%s\".\n"),
102 jcr->catalog->db_name);
104 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
108 Dmsg0(50, "DB opened\n");
113 create_unique_job_name(jcr, jcr->job->hdr.name);
114 set_jcr_job_status(jcr, JS_Created);
115 init_jcr_job_record(jcr);
116 if (!db_create_job_record(jcr, jcr->db, &jcr->jr)) {
117 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
120 JobId = jcr->JobId = jcr->jr.JobId;
121 Dmsg4(100, "Created job record JobId=%d Name=%s Type=%c Level=%c\n",
122 jcr->JobId, jcr->Job, jcr->jr.JobType, jcr->jr.JobLevel);
124 generate_daemon_event(jcr, "JobStart");
126 if (!get_or_create_client_record(jcr)) {
130 if (job_canceled(jcr)) {
134 Dmsg0(200, "Add jrc to work queue\n");
136 /* Queue the job to be run */
137 if ((stat = jobq_add(&job_queue, jcr)) != 0) {
139 Jmsg(jcr, M_FATAL, 0, _("Could not add job queue: ERR=%s\n"), be.strerror(stat));
143 Dmsg0(100, "Done run_job()\n");
150 free_memory(jcr->fname);
159 * This is the engine called by jobq.c:jobq_add() when we were pulled
160 * from the work queue.
161 * At this point, we are running in our own thread and all
162 * necessary resources are allocated -- see jobq.c
164 static void *job_thread(void *arg)
166 JCR *jcr = (JCR *)arg;
168 jcr->my_thread_id = pthread_self();
169 pthread_detach(jcr->my_thread_id);
170 sm_check(__FILE__, __LINE__, true);
172 Dmsg0(200, "=====Start Job=========\n");
173 jcr->start_time = time(NULL); /* set the real start time */
174 jcr->jr.StartTime = jcr->start_time;
176 if (jcr->job->MaxStartDelay != 0 && jcr->job->MaxStartDelay <
177 (utime_t)(jcr->start_time - jcr->sched_time)) {
178 Jmsg(jcr, M_FATAL, 0, _("Job canceled because max start delay time exceeded.\n"));
179 set_jcr_job_status(jcr, JS_Canceled);
183 * Note, we continue, even if the job is canceled above. This
184 * will permit proper setting of the job start record and
185 * the error (cancel) will be picked up below.
188 generate_job_event(jcr, "JobInit");
189 set_jcr_job_status(jcr, JS_Running); /* this will be set only if no error */
192 jcr->fname = get_pool_memory(PM_FNAME);
196 * Now, do pre-run stuff, like setting job level (Inc/diff, ...)
197 * this allows us to setup a proper job start record for restarting
198 * in case of later errors.
200 switch (jcr->JobType) {
202 if (!do_backup_init(jcr)) {
203 backup_cleanup(jcr, JS_ErrorTerminated);
207 if (!do_verify_init(jcr)) {
208 verify_cleanup(jcr, JS_ErrorTerminated);
212 if (!do_restore_init(jcr)) {
213 restore_cleanup(jcr, JS_ErrorTerminated);
217 if (!do_admin_init(jcr)) {
218 admin_cleanup(jcr, JS_ErrorTerminated);
224 if (!do_mac_init(jcr)) { /* migration, archive, copy */
225 mac_cleanup(jcr, JS_ErrorTerminated);
229 Pmsg1(0, "Unimplemented job type: %d\n", jcr->JobType);
230 set_jcr_job_status(jcr, JS_ErrorTerminated);
234 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
235 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
238 if (job_canceled(jcr)) {
239 update_job_end_record(jcr);
244 if (jcr->job->RunBeforeJob) {
245 POOLMEM *before = get_pool_memory(PM_FNAME);
248 char line[MAXSTRING];
250 before = edit_job_codes(jcr, before, jcr->job->RunBeforeJob, "");
251 bpipe = open_bpipe(before, 0, "r");
252 free_pool_memory(before);
253 while (fgets(line, sizeof(line), bpipe->rfd)) {
254 Jmsg(jcr, M_INFO, 0, _("RunBefore: %s"), line);
256 status = close_bpipe(bpipe);
259 Jmsg(jcr, M_FATAL, 0, _("RunBeforeJob error: ERR=%s\n"), be.strerror(status));
260 set_jcr_job_status(jcr, JS_FatalError);
261 update_job_end_record(jcr);
266 generate_job_event(jcr, "JobRun");
268 switch (jcr->JobType) {
270 if (do_backup(jcr)) {
273 backup_cleanup(jcr, JS_ErrorTerminated);
277 if (do_verify(jcr)) {
280 verify_cleanup(jcr, JS_ErrorTerminated);
284 if (do_restore(jcr)) {
287 restore_cleanup(jcr, JS_ErrorTerminated);
294 admin_cleanup(jcr, JS_ErrorTerminated);
300 if (do_mac(jcr)) { /* migration, archive, copy */
303 mac_cleanup(jcr, JS_ErrorTerminated);
307 Pmsg1(0, "Unimplemented job type: %d\n", jcr->JobType);
310 if ((jcr->job->RunAfterJob && jcr->JobStatus == JS_Terminated) ||
311 (jcr->job->RunAfterFailedJob && jcr->JobStatus != JS_Terminated)) {
312 POOLMEM *after = get_pool_memory(PM_FNAME);
315 char line[MAXSTRING];
317 if (jcr->JobStatus == JS_Terminated) {
318 after = edit_job_codes(jcr, after, jcr->job->RunAfterJob, "");
320 after = edit_job_codes(jcr, after, jcr->job->RunAfterFailedJob, "");
322 bpipe = open_bpipe(after, 0, "r");
323 free_pool_memory(after);
324 while (fgets(line, sizeof(line), bpipe->rfd)) {
325 Jmsg(jcr, M_INFO, 0, _("RunAfter: %s"), line);
327 status = close_bpipe(bpipe);
329 * Note, if we get an error here, do not mark the
330 * job in error, simply report the error condition.
334 if (jcr->JobStatus == JS_Terminated) {
335 Jmsg(jcr, M_WARNING, 0, _("RunAfterJob error: ERR=%s\n"), be.strerror(status));
337 Jmsg(jcr, M_FATAL, 0, _("RunAfterFailedJob error: ERR=%s\n"), be.strerror(status));
341 /* Send off any queued messages */
342 if (jcr->msg_queue->size() > 0) {
343 dequeue_messages(jcr);
348 generate_daemon_event(jcr, "JobEnd");
349 Dmsg1(50, "======== End Job stat=%c ==========\n", jcr->JobStatus);
350 sm_check(__FILE__, __LINE__, true);
356 * Cancel a job -- typically called by the UA (Console program), but may also
357 * be called by the job watchdog.
359 * Returns: 1 if cancel appears to be successful
360 * 0 on failure. Message sent to ua->jcr.
362 int cancel_job(UAContext *ua, JCR *jcr)
366 set_jcr_job_status(jcr, JS_Canceled);
368 switch (jcr->JobStatus) {
371 case JS_WaitClientRes:
372 case JS_WaitStoreRes:
373 case JS_WaitPriority:
375 case JS_WaitStartTime:
376 bsendmsg(ua, _("JobId %d, Job %s marked to be canceled.\n"),
377 jcr->JobId, jcr->Job);
378 jobq_remove(&job_queue, jcr); /* attempt to remove it from queue */
383 /* Cancel File daemon */
384 if (jcr->file_bsock) {
385 ua->jcr->client = jcr->client;
386 if (!connect_to_file_daemon(ua->jcr, 10, FDConnectTimeout, 1)) {
387 bsendmsg(ua, _("Failed to connect to File daemon.\n"));
390 Dmsg0(200, "Connected to file daemon\n");
391 fd = ua->jcr->file_bsock;
392 bnet_fsend(fd, "cancel Job=%s\n", jcr->Job);
393 while (bnet_recv(fd) >= 0) {
394 bsendmsg(ua, "%s", fd->msg);
396 bnet_sig(fd, BNET_TERMINATE);
398 ua->jcr->file_bsock = NULL;
401 /* Cancel Storage daemon */
402 if (jcr->store_bsock) {
403 if (!ua->jcr->storage) {
404 copy_storage(ua->jcr, jcr);
406 set_storage(ua->jcr, jcr->store);
408 if (!connect_to_storage_daemon(ua->jcr, 10, SDConnectTimeout, 1)) {
409 bsendmsg(ua, _("Failed to connect to Storage daemon.\n"));
412 Dmsg0(200, "Connected to storage daemon\n");
413 sd = ua->jcr->store_bsock;
414 bnet_fsend(sd, "cancel Job=%s\n", jcr->Job);
415 while (bnet_recv(sd) >= 0) {
416 bsendmsg(ua, "%s", sd->msg);
418 bnet_sig(sd, BNET_TERMINATE);
420 ua->jcr->store_bsock = NULL;
428 static void job_monitor_destructor(watchdog_t *self)
430 JCR *control_jcr = (JCR *) self->data;
432 free_jcr(control_jcr);
435 static void job_monitor_watchdog(watchdog_t *self)
437 JCR *control_jcr, *jcr;
439 control_jcr = (JCR *)self->data;
441 Dmsg1(800, "job_monitor_watchdog %p called\n", self);
446 if (jcr->JobId == 0) {
447 Dmsg2(800, "Skipping JCR %p (%s) with JobId 0\n",
449 /* Keep reference counts correct */
454 /* check MaxWaitTime */
455 cancel = job_check_maxwaittime(control_jcr, jcr);
457 /* check MaxRunTime */
458 cancel |= job_check_maxruntime(control_jcr, jcr);
461 Dmsg3(800, "Cancelling JCR %p jobid %d (%s)\n",
462 jcr, jcr->JobId, jcr->Job);
464 UAContext *ua = new_ua_context(jcr);
465 ua->jcr = control_jcr;
469 Dmsg1(800, "Have cancelled JCR %p\n", jcr);
472 /* Keep reference counts correct */
478 * Check if the maxwaittime has expired and it is possible
481 static bool job_check_maxwaittime(JCR *control_jcr, JCR *jcr)
484 bool ok_to_cancel = false;
487 if (job->MaxWaitTime == 0 && job->FullMaxWaitTime == 0 &&
488 job->IncMaxWaitTime == 0 && job->DiffMaxWaitTime == 0) {
491 if (jcr->JobLevel == L_FULL && job->FullMaxWaitTime != 0 &&
492 (watchdog_time - jcr->start_time) >= job->FullMaxWaitTime) {
494 } else if (jcr->JobLevel == L_DIFFERENTIAL && job->DiffMaxWaitTime != 0 &&
495 (watchdog_time - jcr->start_time) >= job->DiffMaxWaitTime) {
497 } else if (jcr->JobLevel == L_INCREMENTAL && job->IncMaxWaitTime != 0 &&
498 (watchdog_time - jcr->start_time) >= job->IncMaxWaitTime) {
500 } else if (job->MaxWaitTime != 0 &&
501 (watchdog_time - jcr->start_time) >= job->MaxWaitTime) {
507 Dmsg3(800, "Job %d (%s): MaxWaitTime of %d seconds exceeded, "
509 jcr->JobId, jcr->Job, job->MaxWaitTime);
510 switch (jcr->JobStatus) {
515 case JS_WaitStoreRes:
516 case JS_WaitClientRes:
518 case JS_WaitPriority:
520 case JS_WaitStartTime:
522 Dmsg0(200, "JCR blocked in #1\n");
525 Dmsg0(800, "JCR running, checking SD status\n");
526 switch (jcr->SDJobStatus) {
531 Dmsg0(800, "JCR blocked in #2\n");
534 Dmsg0(800, "JCR not blocked in #2\n");
539 case JS_ErrorTerminated:
542 Dmsg0(800, "JCR already dead in #3\n");
545 Jmsg1(jcr, M_ERROR, 0, _("Unhandled job status code %d\n"),
548 Dmsg3(800, "MaxWaitTime result: %scancel JCR %p (%s)\n",
549 cancel ? "" : "do not ", jcr, jcr->job);
555 * Check if maxruntime has expired and if the job can be
558 static bool job_check_maxruntime(JCR *control_jcr, JCR *jcr)
562 if (jcr->job->MaxRunTime == 0) {
565 if ((watchdog_time - jcr->start_time) < jcr->job->MaxRunTime) {
566 Dmsg3(200, "Job %p (%s) with MaxRunTime %d not expired\n",
567 jcr, jcr->Job, jcr->job->MaxRunTime);
571 switch (jcr->JobStatus) {
577 case JS_WaitStoreRes:
578 case JS_WaitClientRes:
580 case JS_WaitPriority:
582 case JS_WaitStartTime:
587 case JS_ErrorTerminated:
593 Jmsg1(jcr, M_ERROR, 0, _("Unhandled job status code %d\n"),
597 Dmsg3(200, "MaxRunTime result: %scancel JCR %p (%s)\n",
598 cancel ? "" : "do not ", jcr, jcr->job);
605 * Get or create a Client record for this Job
607 bool get_or_create_client_record(JCR *jcr)
611 memset(&cr, 0, sizeof(cr));
612 bstrncpy(cr.Name, jcr->client->hdr.name, sizeof(cr.Name));
613 cr.AutoPrune = jcr->client->AutoPrune;
614 cr.FileRetention = jcr->client->FileRetention;
615 cr.JobRetention = jcr->client->JobRetention;
616 if (!jcr->client_name) {
617 jcr->client_name = get_pool_memory(PM_NAME);
619 pm_strcpy(jcr->client_name, jcr->client->hdr.name);
620 if (!db_create_client_record(jcr, jcr->db, &cr)) {
621 Jmsg(jcr, M_FATAL, 0, _("Could not create Client record. ERR=%s\n"),
622 db_strerror(jcr->db));
625 jcr->jr.ClientId = cr.ClientId;
627 if (!jcr->client_uname) {
628 jcr->client_uname = get_pool_memory(PM_NAME);
630 pm_strcpy(jcr->client_uname, cr.Uname);
632 Dmsg2(100, "Created Client %s record %d\n", jcr->client->hdr.name,
637 bool get_or_create_fileset_record(JCR *jcr, FILESET_DBR *fsr)
640 * Get or Create FileSet record
642 memset(fsr, 0, sizeof(FILESET_DBR));
643 bstrncpy(fsr->FileSet, jcr->fileset->hdr.name, sizeof(fsr->FileSet));
644 if (jcr->fileset->have_MD5) {
645 struct MD5Context md5c;
646 unsigned char signature[16];
647 memcpy(&md5c, &jcr->fileset->md5c, sizeof(md5c));
648 MD5Final(signature, &md5c);
649 bin_to_base64(fsr->MD5, (char *)signature, 16); /* encode 16 bytes */
650 bstrncpy(jcr->fileset->MD5, fsr->MD5, sizeof(jcr->fileset->MD5));
652 Jmsg(jcr, M_WARNING, 0, _("FileSet MD5 signature not found.\n"));
654 if (!jcr->fileset->ignore_fs_changes ||
655 !db_get_fileset_record(jcr, jcr->db, fsr)) {
656 if (!db_create_fileset_record(jcr, jcr->db, fsr)) {
657 Jmsg(jcr, M_ERROR, 0, _("Could not create FileSet \"%s\" record. ERR=%s\n"),
658 fsr->FileSet, db_strerror(jcr->db));
662 jcr->jr.FileSetId = fsr->FileSetId;
663 Dmsg2(119, "Created FileSet %s record %u\n", jcr->fileset->hdr.name,
668 void init_jcr_job_record(JCR *jcr)
670 jcr->jr.SchedTime = jcr->sched_time;
671 jcr->jr.StartTime = jcr->start_time;
672 jcr->jr.EndTime = 0; /* perhaps rescheduled, clear it */
673 jcr->jr.JobType = jcr->JobType;
674 jcr->jr.JobLevel = jcr->JobLevel;
675 jcr->jr.JobStatus = jcr->JobStatus;
676 jcr->jr.JobId = jcr->JobId;
677 bstrncpy(jcr->jr.Name, jcr->job->hdr.name, sizeof(jcr->jr.Name));
678 bstrncpy(jcr->jr.Job, jcr->Job, sizeof(jcr->jr.Job));
682 * Write status and such in DB
684 void update_job_end_record(JCR *jcr)
686 jcr->jr.EndTime = time(NULL);
687 jcr->end_time = jcr->jr.EndTime;
688 jcr->jr.JobId = jcr->JobId;
689 jcr->jr.JobStatus = jcr->JobStatus;
690 jcr->jr.JobFiles = jcr->JobFiles;
691 jcr->jr.JobBytes = jcr->JobBytes;
692 jcr->jr.VolSessionId = jcr->VolSessionId;
693 jcr->jr.VolSessionTime = jcr->VolSessionTime;
694 if (!db_update_job_end_record(jcr, jcr->db, &jcr->jr)) {
695 Jmsg(jcr, M_WARNING, 0, _("Error updating job record. %s"),
696 db_strerror(jcr->db));
701 * Takes base_name and appends (unique) current
702 * date and time to form unique job name.
704 * Returns: unique job name in jcr->Job
705 * date/time in jcr->start_time
707 void create_unique_job_name(JCR *jcr, const char *base_name)
709 /* Job start mutex */
710 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
711 static time_t last_start_time = 0;
714 char dt[MAX_TIME_LENGTH];
715 char name[MAX_NAME_LENGTH];
718 /* Guarantee unique start time -- maximum one per second, and
719 * thus unique Job Name
721 P(mutex); /* lock creation of jobs */
723 while (now == last_start_time) {
724 bmicrosleep(0, 500000);
727 last_start_time = now;
728 V(mutex); /* allow creation of jobs */
729 jcr->start_time = now;
730 /* Form Unique JobName */
731 localtime_r(&now, &tm);
732 /* Use only characters that are permitted in Windows filenames */
733 strftime(dt, sizeof(dt), "%Y-%m-%d_%H.%M.%S", &tm);
734 bstrncpy(name, base_name, sizeof(name));
735 name[sizeof(name)-22] = 0; /* truncate if too long */
736 bsnprintf(jcr->Job, sizeof(jcr->Job), "%s.%s", name, dt); /* add date & time */
737 /* Convert spaces into underscores */
738 for (p=jcr->Job; *p; p++) {
745 /* Called directly from job rescheduling */
746 void dird_free_jcr_pointers(JCR *jcr)
748 if (jcr->sd_auth_key) {
749 free(jcr->sd_auth_key);
750 jcr->sd_auth_key = NULL;
756 if (jcr->file_bsock) {
757 Dmsg0(200, "Close File bsock\n");
758 bnet_close(jcr->file_bsock);
759 jcr->file_bsock = NULL;
761 if (jcr->store_bsock) {
762 Dmsg0(200, "Close Store bsock\n");
763 bnet_close(jcr->store_bsock);
764 jcr->store_bsock = NULL;
767 Dmsg0(200, "Free JCR fname\n");
768 free_pool_memory(jcr->fname);
772 Dmsg0(200, "Free JCR stime\n");
773 free_pool_memory(jcr->stime);
776 if (jcr->RestoreBootstrap) {
777 free(jcr->RestoreBootstrap);
778 jcr->RestoreBootstrap = NULL;
780 if (jcr->client_uname) {
781 free_pool_memory(jcr->client_uname);
782 jcr->client_uname = NULL;
784 if (jcr->term_wait_inited) {
785 pthread_cond_destroy(&jcr->term_wait);
786 jcr->term_wait_inited = false;
789 free_pool_memory(jcr->attr);
799 * Free the Job Control Record if no one is still using it.
800 * Called from main free_jcr() routine in src/lib/jcr.c so
801 * that we can do our Director specific cleanup of the jcr.
803 void dird_free_jcr(JCR *jcr)
805 Dmsg0(200, "Start dird free_jcr\n");
807 dird_free_jcr_pointers(jcr);
809 /* Delete lists setup to hold storage pointers */
813 jcr->job_end_push.destroy();
814 Dmsg0(200, "End dird free_jcr\n");
818 * Set some defaults in the JCR necessary to
819 * run. These items are pulled from the job
820 * definition as defaults, but can be overridden
821 * later either by the Run record in the Schedule resource,
822 * or by the Console program.
824 void set_jcr_defaults(JCR *jcr, JOB *job)
828 jcr->JobType = job->JobType;
829 switch (jcr->JobType) {
832 jcr->JobLevel = L_NONE;
835 jcr->JobLevel = job->JobLevel;
838 jcr->JobPriority = job->Priority;
839 /* Copy storage definitions -- deleted in dir_free_jcr above */
844 jcr->storage = New(alist(10, not_owned_by_alist));
845 foreach_alist(st, job->storage) {
846 jcr->storage->append(st);
850 jcr->store = (STORE *)jcr->storage->first();
852 jcr->client = job->client;
853 if (!jcr->client_name) {
854 jcr->client_name = get_pool_memory(PM_NAME);
856 pm_strcpy(jcr->client_name, jcr->client->hdr.name);
857 jcr->pool = job->pool;
858 jcr->full_pool = job->full_pool;
859 jcr->inc_pool = job->inc_pool;
860 jcr->dif_pool = job->dif_pool;
861 jcr->catalog = job->client->catalog;
862 jcr->fileset = job->fileset;
863 jcr->messages = job->messages;
864 jcr->spool_data = job->spool_data;
865 jcr->write_part_after_job = job->write_part_after_job;
866 if (jcr->RestoreBootstrap) {
867 free(jcr->RestoreBootstrap);
868 jcr->RestoreBootstrap = NULL;
870 /* This can be overridden by Console program */
871 if (job->RestoreBootstrap) {
872 jcr->RestoreBootstrap = bstrdup(job->RestoreBootstrap);
874 /* This can be overridden by Console program */
875 jcr->verify_job = job->verify_job;
876 /* If no default level given, set one */
877 if (jcr->JobLevel == 0) {
878 switch (jcr->JobType) {
880 jcr->JobLevel = L_VERIFY_CATALOG;
883 jcr->JobLevel = L_INCREMENTAL;
887 jcr->JobLevel = L_NONE;
896 * copy the storage definitions from an old JCR to a new one
898 void copy_storage(JCR *new_jcr, JCR *old_jcr)
900 if (old_jcr->storage) {
902 if (old_jcr->storage) {
903 delete old_jcr->storage;
905 new_jcr->storage = New(alist(10, not_owned_by_alist));
906 foreach_alist(st, old_jcr->storage) {
907 new_jcr->storage->append(st);
910 if (old_jcr->store) {
911 new_jcr->store = old_jcr->store;
912 } else if (new_jcr->storage) {
913 new_jcr->store = (STORE *)new_jcr->storage->first();
917 /* Set storage override */
918 void set_storage(JCR *jcr, STORE *store)
923 foreach_alist(storage, jcr->storage) {
924 if (store == storage) {
928 /* Store not in list, so add it */
929 jcr->storage->prepend(store);