2 Bacula® - The Network Backup Solution
4 Copyright (C) 2004-2010 Free Software Foundation Europe e.V.
6 The main author of Bacula is Kern Sibbald, with contributions from
7 many others, a complete list can be found in the file AUTHORS.
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version two of the GNU General Public
10 License as published by the Free Software Foundation and included
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 General Public License for more details.
18 You should have received a copy of the GNU 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
23 Bacula® is a registered trademark of Kern Sibbald.
24 The licensor of Bacula is the Free Software Foundation Europe
25 (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
26 Switzerland, email:ftf@fsfeurope.org.
30 * Bacula Director -- migrate.c -- responsible for doing
31 * migration and copy jobs.
33 * Also handles Copy jobs (March MMVIII)
35 * Kern Sibbald, September MMIV
37 * Basic tasks done here:
38 * Open DB and create records for this job.
39 * Open Message Channel with Storage daemon to tell him a job will be starting.
40 * Open connection with Storage daemon and pass him commands
42 * When the Storage daemon finishes the job, update the DB.
50 #include "lib/bregex.h"
55 static const int dbglevel = 10;
57 static int getJob_to_migrate(JCR *jcr);
59 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
60 const char *query2, const char *type);
61 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
63 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type);
64 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids);
65 static void start_migration_job(JCR *jcr);
66 static int get_next_dbid_from_list(char **p, DBId_t *DBId);
67 static bool set_migration_next_pool(JCR *jcr, POOL **pool);
70 * Called here before the job is run to do the job
71 * specific setup. Note, one of the important things to
72 * complete in this init code is to make the definitive
73 * choice of input and output storage devices. This is
74 * because immediately after the init, the job is queued
75 * in the jobq.c code, and it checks that all the resources
76 * (storage resources in particular) are available, so these
77 * must all be properly defined.
79 * previous_jr refers to the job DB record of the Job that is
80 * going to be migrated.
81 * prev_job refers to the job resource of the Job that is
82 * going to be migrated.
83 * jcr is the jcr for the current "migration" job. It is a
84 * control job that is put in the DB as a migration job, which
85 * means that this job migrated a previous job to a new job.
86 * No Volume or File data is associated with this control
88 * mig_jcr refers to the newly migrated job that is run by
89 * the current jcr. It is a backup job that moves (migrates) the
90 * data written for the previous_jr into the new pool. This
91 * job (mig_jcr) becomes the new backup job that replaces
92 * the original backup job. Note, this jcr is not really run. It
93 * is simply attached to the current jcr. It will show up in
94 * the Director's status output, but not in the SD or FD, both of
95 * which deal only with the current migration job (i.e. jcr).
97 bool do_migration_init(JCR *jcr)
101 JCR *mig_jcr; /* newly migrated job */
105 apply_pool_overrides(jcr);
107 if (!allow_duplicate_job(jcr)) {
111 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
112 if (jcr->jr.PoolId == 0) {
113 Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
114 Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
118 * Note, at this point, pool is the pool for this job. We
119 * transfer it to rpool (read pool), and a bit later,
120 * pool will be changed to point to the write pool,
121 * which comes from pool->NextPool.
123 jcr->rpool = jcr->pool; /* save read pool */
124 pm_strcpy(jcr->rpool_source, jcr->pool_source);
127 Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
129 if (!get_or_create_fileset_record(jcr)) {
130 Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
131 Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
135 /* If we find a job or jobs to migrate it is previous_jr.JobId */
136 count = getJob_to_migrate(jcr);
141 set_migration_next_pool(jcr, &pool);
142 return true; /* no work */
145 Dmsg1(dbglevel, "Back from getJob_to_migrate JobId=%d\n", (int)jcr->JobId);
147 if (jcr->previous_jr.JobId == 0) {
148 Dmsg1(dbglevel, "JobId=%d no previous JobId\n", (int)jcr->JobId);
149 Jmsg(jcr, M_INFO, 0, _("No previous Job found to %s.\n"), jcr->get_ActionName(0));
150 set_migration_next_pool(jcr, &pool);
151 return true; /* no work */
154 if (create_restore_bootstrap_file(jcr) < 0) {
155 Jmsg(jcr, M_FATAL, 0, _("Create bootstrap file failed.\n"));
159 if (jcr->previous_jr.JobId == 0 || jcr->ExpectedFiles == 0) {
160 set_jcr_job_status(jcr, JS_Terminated);
161 Dmsg1(dbglevel, "JobId=%d expected files == 0\n", (int)jcr->JobId);
162 if (jcr->previous_jr.JobId == 0) {
163 Jmsg(jcr, M_INFO, 0, _("No previous Job found to %s.\n"), jcr->get_ActionName(0));
165 Jmsg(jcr, M_INFO, 0, _("Previous Job has no data to %s.\n"), jcr->get_ActionName(0));
167 set_migration_next_pool(jcr, &pool);
168 return true; /* no work */
172 Dmsg5(dbglevel, "JobId=%d: Current: Name=%s JobId=%d Type=%c Level=%c\n",
174 jcr->jr.Name, (int)jcr->jr.JobId,
175 jcr->jr.JobType, jcr->jr.JobLevel);
178 job = (JOB *)GetResWithName(R_JOB, jcr->jr.Name);
179 prev_job = (JOB *)GetResWithName(R_JOB, jcr->previous_jr.Name);
182 Jmsg(jcr, M_FATAL, 0, _("Job resource not found for \"%s\".\n"), jcr->jr.Name);
186 Jmsg(jcr, M_FATAL, 0, _("Previous Job resource not found for \"%s\".\n"),
187 jcr->previous_jr.Name);
191 jcr->spool_data = job->spool_data; /* turn on spooling if requested in job */
193 /* Create a migration jcr */
194 mig_jcr = jcr->mig_jcr = new_jcr(sizeof(JCR), dird_free_jcr);
195 memcpy(&mig_jcr->previous_jr, &jcr->previous_jr, sizeof(mig_jcr->previous_jr));
198 * Turn the mig_jcr into a "real" job that takes on the aspects of
199 * the previous backup job "prev_job".
201 set_jcr_defaults(mig_jcr, prev_job);
202 if (!setup_job(mig_jcr)) {
203 Jmsg(jcr, M_FATAL, 0, _("setup job failed.\n"));
207 /* Now reset the job record from the previous job */
208 memcpy(&mig_jcr->jr, &jcr->previous_jr, sizeof(mig_jcr->jr));
209 /* Update the jr to reflect the new values of PoolId and JobId. */
210 mig_jcr->jr.PoolId = jcr->jr.PoolId;
211 mig_jcr->jr.JobId = mig_jcr->JobId;
213 /* Don't let WatchDog checks Max*Time value on this Job */
214 mig_jcr->no_maxtime = true;
216 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
217 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
218 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
220 if (set_migration_next_pool(jcr, &pool)) {
221 /* If pool storage specified, use it for restore */
222 copy_rstorage(mig_jcr, pool->storage, _("Pool resource"));
223 copy_rstorage(jcr, pool->storage, _("Pool resource"));
225 mig_jcr->pool = jcr->pool;
226 mig_jcr->jr.PoolId = jcr->jr.PoolId;
234 * set_migration_next_pool() called by do_migration_init()
235 * at differents stages.
236 * The idea here is tofactorize the NextPool's search code and
237 * to permit do_migration_init() to return with NextPool set in jcr struct.
239 static bool set_migration_next_pool(JCR *jcr, POOL **retpool)
246 * Get the PoolId used with the original job. Then
247 * find the pool name from the database record.
249 memset(&pr, 0, sizeof(pr));
250 pr.PoolId = jcr->jr.PoolId;
251 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
252 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
253 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
256 /* Get the pool resource corresponding to the original job */
257 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
260 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
265 * If the original backup pool has a NextPool, make sure a
266 * record exists in the database. Note, in this case, we
267 * will be migrating from pool to pool->NextPool.
269 if (pool->NextPool) {
270 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->name());
271 if (jcr->jr.PoolId == 0) {
275 if (!set_migration_wstorage(jcr, pool)) {
278 jcr->pool = pool->NextPool;
279 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
281 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
288 * Do a Migration of a previous job
290 * Returns: false on failure
293 bool do_migration(JCR *jcr)
297 JCR *mig_jcr = jcr->mig_jcr; /* newly migrated job */
300 * If mig_jcr is NULL, there is nothing to do for this job,
301 * so set a normal status, cleanup and return OK.
304 set_jcr_job_status(jcr, JS_Terminated);
305 migration_cleanup(jcr, jcr->JobStatus);
309 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
310 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
311 edit_int64(jcr->previous_jr.JobId, ed1),
312 jcr->get_ActionName(0),
313 db_strerror(jcr->db));
314 set_jcr_job_status(jcr, JS_Terminated);
315 migration_cleanup(jcr, jcr->JobStatus);
318 /* Make sure this job was not already migrated */
319 if (jcr->previous_jr.JobType != JT_BACKUP &&
320 jcr->previous_jr.JobType != JT_JOB_COPY) {
321 Jmsg(jcr, M_INFO, 0, _("JobId %s already %s probably by another Job. %s stopped.\n"),
322 edit_int64(jcr->previous_jr.JobId, ed1),
323 jcr->get_ActionName(1),
324 jcr->get_OperationName());
325 set_jcr_job_status(jcr, JS_Terminated);
326 migration_cleanup(jcr, jcr->JobStatus);
330 /* Print Job Start message */
331 Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
332 jcr->get_OperationName(), edit_uint64(jcr->JobId, ed1), jcr->Job);
335 * Open a message channel connection with the Storage
336 * daemon. This is to let him know that our client
337 * will be contacting him for a backup session.
340 Dmsg0(110, "Open connection with storage daemon\n");
341 set_jcr_job_status(jcr, JS_WaitSD);
342 set_jcr_job_status(mig_jcr, JS_WaitSD);
344 * Start conversation with Storage daemon
346 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
349 sd = jcr->store_bsock;
351 * Now start a job with the Storage daemon
353 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
354 ((STORE *)jcr->rstorage->first())->name(),
355 ((STORE *)jcr->wstorage->first())->name());
357 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage, /*send_bsr*/true)) {
360 Dmsg0(150, "Storage daemon connection OK\n");
364 * We re-update the job start record so that the start
365 * time is set after the run before job. This avoids
366 * that any files created by the run before job will
367 * be saved twice. They will be backed up in the current
368 * job, but not in the next one unless they are changed.
369 * Without this, they will be backed up in this job and
370 * in the next job run because in that case, their date
371 * is after the start of this run.
373 jcr->start_time = time(NULL);
374 jcr->jr.StartTime = jcr->start_time;
375 jcr->jr.JobTDate = jcr->start_time;
376 set_jcr_job_status(jcr, JS_Running);
378 /* Update job start record for this migration control job */
379 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
380 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
385 mig_jcr->start_time = time(NULL);
386 mig_jcr->jr.StartTime = mig_jcr->start_time;
387 mig_jcr->jr.JobTDate = mig_jcr->start_time;
388 set_jcr_job_status(mig_jcr, JS_Running);
390 /* Update job start record for the real migration backup job */
391 if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
392 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
396 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
397 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
398 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
402 * Start the job prior to starting the message thread below
403 * to avoid two threads from using the BSOCK structure at
406 if (!sd->fsend("run")) {
411 * Now start a Storage daemon message thread
413 if (!start_storage_daemon_message_thread(jcr)) {
418 set_jcr_job_status(jcr, JS_Running);
419 set_jcr_job_status(mig_jcr, JS_Running);
421 /* Pickup Job termination data */
422 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
423 wait_for_storage_daemon_termination(jcr);
424 set_jcr_job_status(jcr, jcr->SDJobStatus);
425 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
426 if (jcr->JobStatus != JS_Terminated) {
430 migration_cleanup(jcr, jcr->JobStatus);
440 /* Add an item to the list if it is unique */
441 static void add_unique_id(idpkt *ids, char *item)
443 const int maxlen = 30;
447 /* Walk through current list to see if each item is the same as item */
450 for (int i=0; i<maxlen; i++) {
453 } else if (*q == ',') {
460 if (strcmp(item, id) == 0) {
464 /* Did not find item, so add it to list */
465 if (ids->count == 0) {
468 pm_strcat(ids->list, ",");
470 pm_strcat(ids->list, item);
472 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
477 * Callback handler make list of DB Ids
479 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
481 idpkt *ids = (idpkt *)ctx;
484 if (!row || !row[0]) {
485 Dmsg0(dbglevel, "dbid_hdlr error empty row\n");
486 return 1; /* stop calling us */
489 add_unique_id(ids, row[0]);
490 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
500 static int item_compare(void *item1, void *item2)
502 uitem *i1 = (uitem *)item1;
503 uitem *i2 = (uitem *)item2;
504 return strcmp(i1->item, i2->item);
507 static int unique_name_handler(void *ctx, int num_fields, char **row)
509 dlist *list = (dlist *)ctx;
511 uitem *new_item = (uitem *)malloc(sizeof(uitem));
514 memset(new_item, 0, sizeof(uitem));
515 new_item->item = bstrdup(row[0]);
516 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
517 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
518 if (item != new_item) { /* already in list */
519 free(new_item->item);
520 free((char *)new_item);
526 /* Get Job names in Pool */
527 const char *sql_job =
528 "SELECT DISTINCT Job.Name from Job,Pool"
529 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
531 /* Get JobIds from regex'ed Job names */
532 const char *sql_jobids_from_job =
533 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
534 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
535 " ORDER by Job.StartTime";
537 /* Get Client names in Pool */
538 const char *sql_client =
539 "SELECT DISTINCT Client.Name from Client,Pool,Job"
540 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
541 " Job.PoolId=Pool.PoolId";
543 /* Get JobIds from regex'ed Client names */
544 const char *sql_jobids_from_client =
545 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
546 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
547 " AND Job.ClientId=Client.ClientId AND Job.Type IN ('B','C')"
548 " AND Job.JobStatus IN ('T','W')"
549 " ORDER by Job.StartTime";
551 /* Get Volume names in Pool */
552 const char *sql_vol =
553 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
554 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
555 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
557 /* Get JobIds from regex'ed Volume names */
558 const char *sql_jobids_from_vol =
559 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
560 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
561 " AND JobMedia.JobId=Job.JobId AND Job.Type IN ('B','C')"
562 " AND Job.JobStatus IN ('T','W') AND Media.Enabled=1"
563 " ORDER by Job.StartTime";
565 const char *sql_smallest_vol =
566 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
567 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
568 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
569 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
570 " ORDER BY VolBytes ASC LIMIT 1";
572 const char *sql_oldest_vol =
573 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
574 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
575 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
576 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
577 " ORDER BY LastWritten ASC LIMIT 1";
579 /* Get JobIds when we have selected MediaId */
580 const char *sql_jobids_from_mediaid =
581 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
582 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId IN (%s)"
583 " AND Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
584 " ORDER by Job.StartTime";
586 /* Get the number of bytes in the pool */
587 const char *sql_pool_bytes =
588 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN"
589 " (SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
590 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
591 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
592 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
593 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId)";
595 /* Get the number of bytes in the Jobs */
596 const char *sql_job_bytes =
597 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
599 /* Get Media Ids in Pool */
600 const char *sql_mediaids =
601 "SELECT MediaId FROM Media,Pool WHERE"
602 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
603 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
605 /* Get JobIds in Pool longer than specified time */
606 const char *sql_pool_time =
607 "SELECT DISTINCT Job.JobId FROM Pool,Job,Media,JobMedia WHERE"
608 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
609 " VolStatus IN ('Full','Used','Error') AND Media.Enabled=1 AND"
610 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
611 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
612 " AND Job.RealEndTime<='%s'";
614 /* Get JobIds from successfully completed backup jobs which have not been copied before */
615 const char *sql_jobids_of_pool_uncopied_jobs =
616 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
617 " WHERE Pool.Name = '%s' AND Pool.PoolId = Job.PoolId"
618 " AND Job.Type = 'B' AND Job.JobStatus IN ('T','W')"
619 " AND Job.jobBytes > 0"
620 " AND Job.JobId NOT IN"
621 " (SELECT PriorJobId FROM Job WHERE"
622 " Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
623 " AND PriorJobId != 0)"
624 " ORDER by Job.StartTime";
627 * const char *sql_ujobid =
628 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
629 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
630 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
635 * This is the central piece of code that finds a job or jobs
636 * actually JobIds to migrate. It first looks to see if one
637 * has been "manually" specified in jcr->MigrateJobId, and if
638 * so, it returns that JobId to be run. Otherwise, it
639 * examines the Selection Type to see what kind of migration
640 * we are doing (Volume, Job, Client, ...) and applies any
641 * Selection Pattern if appropriate to obtain a list of JobIds.
642 * Finally, it will loop over all the JobIds found, except the last
643 * one starting a new job with MigrationJobId set to that JobId, and
644 * finally, it returns the last JobId to the caller.
646 * Returns: -1 on error
647 * 0 if no jobs to migrate
648 * 1 if OK and jcr->previous_jr filled in
650 static int getJob_to_migrate(JCR *jcr)
652 char ed1[30], ed2[30];
653 POOL_MEM query(PM_MESSAGE);
658 idpkt ids, mid, jids;
663 char dt[MAX_TIME_LENGTH];
666 ids.list = get_pool_memory(PM_MESSAGE);
669 mid.list = get_pool_memory(PM_MESSAGE);
672 jids.list = get_pool_memory(PM_MESSAGE);
678 * If MigrateJobId is set, then we migrate only that Job,
679 * otherwise, we go through the full selection of jobs to
682 if (jcr->MigrateJobId != 0) {
683 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
684 edit_uint64(jcr->MigrateJobId, ids.list);
687 switch (jcr->job->selection_type) {
689 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
694 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
699 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
704 if (!jcr->job->selection_pattern) {
705 Jmsg(jcr, M_FATAL, 0, _("No %s SQL selection pattern specified.\n"), jcr->get_OperationName());
708 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
709 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
710 unique_dbid_handler, (void *)&ids)) {
711 Jmsg(jcr, M_FATAL, 0,
712 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
716 case MT_SMALLEST_VOL:
717 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
722 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
726 case MT_POOL_OCCUPANCY:
728 /* Find count of bytes in pool */
729 Mmsg(query, sql_pool_bytes, jcr->rpool->name());
730 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
731 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
734 if (ctx.count == 0) {
735 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
738 pool_bytes = ctx.value;
739 Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
741 if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
742 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
745 Dmsg0(dbglevel, "We should do Occupation migration.\n");
748 /* Find a list of MediaIds that could be migrated */
749 Mmsg(query, sql_mediaids, jcr->rpool->name());
750 Dmsg1(dbglevel, "query=%s\n", query.c_str());
751 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
752 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
755 if (ids.count == 0) {
756 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
759 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
761 if (!find_jobids_from_mediaid_list(jcr, &ids, "Volume")) {
764 /* ids == list of jobs */
766 for (int i=0; i < (int)ids.count; i++) {
767 stat = get_next_dbid_from_list(&p, &DBId);
768 Dmsg2(dbglevel, "get_next_dbid stat=%d JobId=%u\n", stat, (uint32_t)DBId);
770 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
772 } else if (stat == 0) {
777 Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
778 if (jids.count > 0) {
779 pm_strcat(jids.list, ",");
781 pm_strcat(jids.list, mid.list);
782 jids.count += mid.count;
784 /* Find count of bytes from Jobs */
785 Mmsg(query, sql_job_bytes, mid.list);
786 Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
787 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
788 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
791 pool_bytes -= ctx.value;
792 Dmsg2(dbglevel, "Total %s Job bytes=%s\n", jcr->get_ActionName(0), edit_int64_with_commas(ctx.value, ed1));
793 Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n",
794 edit_int64_with_commas(jcr->rpool->MigrationLowBytes, ed1),
795 edit_int64_with_commas(pool_bytes, ed2));
796 if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
797 Dmsg0(dbglevel, "We should be done.\n");
801 /* Transfer jids to ids, where the jobs list is expected */
802 ids.count = jids.count;
803 pm_strcpy(ids.list, jids.list);
804 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
807 ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
808 (void)localtime_r(&ttime, &tm);
809 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
812 Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
813 Dmsg1(dbglevel, "query=%s\n", query.c_str());
814 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
815 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
818 if (ids.count == 0) {
819 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
822 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
824 case MT_POOL_UNCOPIED_JOBS:
825 if (!find_jobids_of_pool_uncopied_jobs(jcr, &ids)) {
830 Jmsg(jcr, M_FATAL, 0, _("Unknown %s Selection Type.\n"), jcr->get_OperationName());
836 * Loop over all jobids except the last one, sending
837 * them to start_migration_job(), which will start a job
838 * for each of them. For the last JobId, we handle it below.
841 if (ids.count == 0) {
842 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
846 Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s chosen to be %s: %s\n"),
847 ids.count, (ids.count < 2) ? _(" was") : _("s were"),
848 jcr->get_ActionName(1), ids.list);
850 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
851 for (int i=1; i < (int)ids.count; i++) {
853 stat = get_next_jobid_from_list(&p, &JobId);
854 Dmsg3(dbglevel, "getJobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
856 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
858 } else if (stat == 0) {
859 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
862 jcr->MigrateJobId = JobId;
863 start_migration_job(jcr);
864 Dmsg0(dbglevel, "Back from start_migration_job\n");
867 /* Now get the last JobId and handle it in the current job */
869 stat = get_next_jobid_from_list(&p, &JobId);
870 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
872 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
874 } else if (stat == 0) {
875 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
879 jcr->previous_jr.JobId = JobId;
880 Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
882 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
883 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
884 edit_int64(jcr->previous_jr.JobId, ed1),
885 jcr->get_ActionName(0),
886 db_strerror(jcr->db));
889 Jmsg(jcr, M_INFO, 0, _("%s using JobId=%s Job=%s\n"),
890 jcr->get_OperationName(),
891 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
892 Dmsg4(dbglevel, "%s JobId=%d using JobId=%s Job=%s\n",
893 jcr->get_OperationName(),
895 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
905 free_pool_memory(ids.list);
906 free_pool_memory(mid.list);
907 free_pool_memory(jids.list);
911 static void start_migration_job(JCR *jcr)
913 UAContext *ua = new_ua_context(jcr);
916 Mmsg(ua->cmd, "run job=\"%s\" jobid=%s", jcr->job->name(),
917 edit_uint64(jcr->MigrateJobId, ed1));
918 Dmsg2(dbglevel, "=============== %s cmd=%s\n", jcr->get_OperationName(), ua->cmd);
919 parse_ua_args(ua); /* parse command */
920 JobId_t jobid = run_cmd(ua, ua->cmd);
922 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
924 Jmsg(jcr, M_INFO, 0, _("%s JobId %d started.\n"), jcr->get_OperationName(), (int)jobid);
929 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
933 POOL_MEM query(PM_MESSAGE);
936 /* Basic query for MediaId */
937 Mmsg(query, query1, jcr->rpool->name());
938 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
939 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
942 if (ids->count == 0) {
943 Jmsg(jcr, M_INFO, 0, _("No %s found to %s.\n"), type, jcr->get_ActionName(0));
944 ok = true; /* Not an error */
946 } else if (ids->count != 1) {
947 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
950 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
952 ok = find_jobids_from_mediaid_list(jcr, ids, type);
959 * This routine returns:
960 * false if an error occurred
962 * ids.count number of jobids found (may be zero)
964 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
967 POOL_MEM query(PM_MESSAGE);
969 Mmsg(query, sql_jobids_from_mediaid, ids->list);
971 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
972 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
975 if (ids->count == 0) {
976 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
985 * This routine returns:
986 * false if an error occurred
988 * ids.count number of jobids found (may be zero)
990 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids)
993 POOL_MEM query(PM_MESSAGE);
995 /* Only a copy job is allowed */
996 if (jcr->getJobType() != JT_COPY) {
997 Jmsg(jcr, M_FATAL, 0,
998 _("Selection Type 'pooluncopiedjobs' only applies to Copy Jobs"));
1002 Dmsg1(dbglevel, "copy selection pattern=%s\n", jcr->rpool->name());
1003 Mmsg(query, sql_jobids_of_pool_uncopied_jobs, jcr->rpool->name());
1004 Dmsg1(dbglevel, "get uncopied jobs query=%s\n", query.c_str());
1005 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1006 Jmsg(jcr, M_FATAL, 0,
1007 _("SQL to get uncopied jobs failed. ERR=%s\n"), db_strerror(jcr->db));
1016 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
1017 const char *query2, const char *type)
1021 uitem *last_item = NULL;
1026 POOL_MEM query(PM_MESSAGE);
1028 item_chain = New(dlist(item, &item->link));
1029 if (!jcr->job->selection_pattern) {
1030 Jmsg(jcr, M_FATAL, 0, _("No %s %s selection pattern specified.\n"),
1031 jcr->get_OperationName(), type);
1034 Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
1035 /* Basic query for names */
1036 Mmsg(query, query1, jcr->rpool->name());
1037 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
1038 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
1039 (void *)item_chain)) {
1040 Jmsg(jcr, M_FATAL, 0,
1041 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
1044 Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
1045 if (item_chain->size() == 0) {
1046 Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to %s.\n"),
1047 jcr->rpool->name(), jcr->get_ActionName(0));
1049 goto bail_out; /* skip regex match */
1051 /* Compile regex expression */
1052 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
1054 regerror(rc, &preg, prbuf, sizeof(prbuf));
1055 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
1056 jcr->job->selection_pattern, prbuf);
1059 /* Now apply the regex to the names and remove any item not matched */
1060 foreach_dlist(item, item_chain) {
1061 const int nmatch = 30;
1062 regmatch_t pmatch[nmatch];
1064 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1065 free(last_item->item);
1066 item_chain->remove(last_item);
1068 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
1069 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
1071 last_item = NULL; /* keep this one */
1077 free(last_item->item);
1078 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1079 item_chain->remove(last_item);
1083 if (item_chain->size() == 0) {
1084 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName(0));
1086 goto bail_out; /* skip regex match */
1090 * At this point, we have a list of items in item_chain
1091 * that have been matched by the regex, so now we need
1092 * to look up their jobids.
1095 foreach_dlist(item, item_chain) {
1096 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
1097 Mmsg(query, query2, item->item, jcr->rpool->name());
1098 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
1099 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1100 Jmsg(jcr, M_FATAL, 0,
1101 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
1105 if (ids->count == 0) {
1106 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
1111 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
1112 foreach_dlist(item, item_chain) {
1120 * Release resources allocated during backup.
1122 void migration_cleanup(JCR *jcr, int TermCode)
1124 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
1125 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
1126 char ec6[50], ec7[50], ec8[50];
1127 char term_code[100], sd_term_msg[100];
1128 const char *term_msg;
1129 int msg_type = M_INFO;
1133 JCR *mig_jcr = jcr->mig_jcr;
1134 POOL_MEM query(PM_MESSAGE);
1136 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
1137 update_job_end(jcr, TermCode);
1138 memset(&mr, 0, sizeof(mr));
1141 * Check if we actually did something.
1142 * mig_jcr is jcr of the newly migrated job.
1145 char old_jobid[50], new_jobid[50];
1147 edit_uint64(jcr->previous_jr.JobId, old_jobid);
1148 edit_uint64(mig_jcr->jr.JobId, new_jobid);
1150 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
1151 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
1152 mig_jcr->VolSessionId = jcr->VolSessionId;
1153 mig_jcr->VolSessionTime = jcr->VolSessionTime;
1154 mig_jcr->jr.RealEndTime = 0;
1155 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
1157 update_job_end(mig_jcr, TermCode);
1159 /* Update final items to set them to the previous job's values */
1160 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
1161 "JobTDate=%s WHERE JobId=%s",
1162 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
1163 edit_uint64(jcr->previous_jr.JobTDate, ec1),
1165 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1168 * If we terminated a migration normally:
1169 * - mark the previous job as migrated
1170 * - move any Log records to the new JobId
1171 * - Purge the File records from the previous job
1173 if (jcr->getJobType() == JT_MIGRATE && jcr->JobStatus == JS_Terminated) {
1174 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1175 (char)JT_MIGRATED_JOB, old_jobid);
1176 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1177 UAContext *ua = new_ua_context(jcr);
1178 /* Move JobLog to new JobId */
1179 Mmsg(query, "UPDATE Log SET JobId=%s WHERE JobId=%s",
1180 new_jobid, old_jobid);
1181 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1182 /* Purge all old file records, but leave Job record */
1183 purge_files_from_jobs(ua, old_jobid);
1184 free_ua_context(ua);
1188 * If we terminated a Copy (rather than a Migration) normally:
1189 * - copy any Log records to the new JobId
1190 * - set type="Job Copy" for the new job
1192 if (jcr->getJobType() == JT_COPY && jcr->JobStatus == JS_Terminated) {
1193 /* Copy JobLog to new JobId */
1194 Mmsg(query, "INSERT INTO Log (JobId, Time, LogText ) "
1195 "SELECT %s, Time, LogText FROM Log WHERE JobId=%s",
1196 new_jobid, old_jobid);
1197 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1198 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1199 (char)JT_JOB_COPY, new_jobid);
1200 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1203 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
1204 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
1205 db_strerror(jcr->db));
1206 set_jcr_job_status(jcr, JS_ErrorTerminated);
1209 update_bootstrap_file(mig_jcr);
1211 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
1213 * Note, if the job has failed, most likely it did not write any
1214 * tape, so suppress this "error" message since in that case
1215 * it is normal. Or look at it the other way, only for a
1216 * normal exit should we complain about this error.
1218 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
1219 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
1221 mig_jcr->VolumeName[0] = 0; /* none */
1224 if (mig_jcr->VolumeName[0]) {
1225 /* Find last volume name. Multiple vols are separated by | */
1226 char *p = strrchr(mig_jcr->VolumeName, '|');
1230 p = mig_jcr->VolumeName; /* no |, take full name */
1232 bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1233 if (!db_get_media_record(jcr, jcr->db, &mr)) {
1234 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1235 mr.VolumeName, db_strerror(jcr->db));
1239 switch (jcr->JobStatus) {
1241 if (jcr->JobErrors || jcr->SDErrors) {
1242 term_msg = _("%s OK -- with warnings");
1244 term_msg = _("%s OK");
1248 case JS_ErrorTerminated:
1249 term_msg = _("*** %s Error ***");
1250 msg_type = M_ERROR; /* Generate error message */
1251 if (jcr->store_bsock) {
1252 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1253 if (jcr->SD_msg_chan) {
1254 pthread_cancel(jcr->SD_msg_chan);
1259 term_msg = _("%s Canceled");
1260 if (jcr->store_bsock) {
1261 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1262 if (jcr->SD_msg_chan) {
1263 pthread_cancel(jcr->SD_msg_chan);
1268 term_msg = _("Inappropriate %s term code");
1272 if (jcr->getJobType() == JT_MIGRATE && jcr->previous_jr.JobId != 0) {
1273 /* Mark previous job as migrated */
1274 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1275 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1276 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
1278 term_msg = _("%s -- no files to %s");
1281 bsnprintf(term_code, sizeof(term_code), term_msg, jcr->get_OperationName(), jcr->get_ActionName(0));
1282 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1283 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1284 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1288 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1291 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1293 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
1294 " Build OS: %s %s %s\n"
1295 " Prev Backup JobId: %s\n"
1296 " Prev Backup Job: %s\n"
1297 " New Backup JobId: %s\n"
1298 " Current JobId: %s\n"
1299 " Current Job: %s\n"
1300 " Backup Level: %s%s\n"
1302 " FileSet: \"%s\" %s\n"
1303 " Read Pool: \"%s\" (From %s)\n"
1304 " Read Storage: \"%s\" (From %s)\n"
1305 " Write Pool: \"%s\" (From %s)\n"
1306 " Write Storage: \"%s\" (From %s)\n"
1307 " Catalog: \"%s\" (From %s)\n"
1310 " Elapsed time: %s\n"
1312 " SD Files Written: %s\n"
1313 " SD Bytes Written: %s (%sB)\n"
1314 " Rate: %.1f KB/s\n"
1315 " Volume name(s): %s\n"
1316 " Volume Session Id: %d\n"
1317 " Volume Session Time: %d\n"
1318 " Last Volume Bytes: %s (%sB)\n"
1320 " SD termination status: %s\n"
1321 " Termination: %s\n\n"),
1322 BACULA, my_name, VERSION, LSMDATE, edt,
1323 HOST_OS, DISTNAME, DISTVER,
1324 edit_uint64(jcr->previous_jr.JobId, ec6),
1325 jcr->previous_jr.Job,
1326 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1327 edit_uint64(jcr->jr.JobId, ec8),
1329 level_to_str(jcr->getJobLevel()), jcr->since,
1330 jcr->client->name(),
1331 jcr->fileset->name(), jcr->FSCreateTime,
1332 jcr->rpool->name(), jcr->rpool_source,
1333 jcr->rstore?jcr->rstore->name():"*None*",
1334 NPRT(jcr->rstore_source),
1335 jcr->pool->name(), jcr->pool_source,
1336 jcr->wstore?jcr->wstore->name():"*None*",
1337 NPRT(jcr->wstore_source),
1338 jcr->catalog->name(), jcr->catalog_source,
1341 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1343 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1344 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1345 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1347 mig_jcr ? mig_jcr->VolumeName : "",
1349 jcr->VolSessionTime,
1350 edit_uint64_with_commas(mr.VolBytes, ec4),
1351 edit_uint64_with_suffix(mr.VolBytes, ec5),
1356 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1358 free_jcr(jcr->mig_jcr);
1359 jcr->mig_jcr = NULL;
1361 Dmsg0(100, "Leave migrate_cleanup()\n");
1365 * Return next DBId from comma separated list
1368 * 1 if next DBId returned
1369 * 0 if no more DBIds are in list
1370 * -1 there is an error
1372 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1374 const int maxlen = 30;
1379 for (int i=0; i<maxlen; i++) {
1382 } else if (*q == ',') {
1391 } else if (!is_a_number(id)) {
1392 return -1; /* error */
1395 *DBId = str_to_int64(id);
1399 bool set_migration_wstorage(JCR *jcr, POOL *pool)
1401 POOL *wpool = pool->NextPool;
1404 Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
1409 if (!wpool->storage || wpool->storage->size() == 0) {
1410 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
1415 /* If pool storage specified, use it instead of job storage for backup */
1416 copy_wstorage(jcr, wpool->storage, _("Storage from Pool's NextPool resource"));