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 three of the GNU Affero 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 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
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 jcr->setJobStatus(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;
217 * Don't check for duplicates on migration and copy jobs
219 mig_jcr->job->IgnoreDuplicateJobChecking = true;
221 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
222 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
223 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
225 if (set_migration_next_pool(jcr, &pool)) {
226 /* If pool storage specified, use it for restore */
227 copy_rstorage(mig_jcr, pool->storage, _("Pool resource"));
228 copy_rstorage(jcr, pool->storage, _("Pool resource"));
230 mig_jcr->pool = jcr->pool;
231 mig_jcr->jr.PoolId = jcr->jr.PoolId;
239 * set_migration_next_pool() called by do_migration_init()
240 * at differents stages.
241 * The idea here is tofactorize the NextPool's search code and
242 * to permit do_migration_init() to return with NextPool set in jcr struct.
244 static bool set_migration_next_pool(JCR *jcr, POOL **retpool)
251 * Get the PoolId used with the original job. Then
252 * find the pool name from the database record.
254 memset(&pr, 0, sizeof(pr));
255 pr.PoolId = jcr->jr.PoolId;
256 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
257 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
258 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
261 /* Get the pool resource corresponding to the original job */
262 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
265 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
270 * If the original backup pool has a NextPool, make sure a
271 * record exists in the database. Note, in this case, we
272 * will be migrating from pool to pool->NextPool.
274 if (pool->NextPool) {
275 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->name());
276 if (jcr->jr.PoolId == 0) {
280 if (!set_migration_wstorage(jcr, pool)) {
283 jcr->pool = pool->NextPool;
284 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
286 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
293 * Do a Migration of a previous job
295 * Returns: false on failure
298 bool do_migration(JCR *jcr)
302 JCR *mig_jcr = jcr->mig_jcr; /* newly migrated job */
305 * If mig_jcr is NULL, there is nothing to do for this job,
306 * so set a normal status, cleanup and return OK.
309 jcr->setJobStatus(JS_Terminated);
310 migration_cleanup(jcr, jcr->JobStatus);
314 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
315 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
316 edit_int64(jcr->previous_jr.JobId, ed1),
317 jcr->get_ActionName(0),
318 db_strerror(jcr->db));
319 jcr->setJobStatus(JS_Terminated);
320 migration_cleanup(jcr, jcr->JobStatus);
323 /* Make sure this job was not already migrated */
324 if (jcr->previous_jr.JobType != JT_BACKUP &&
325 jcr->previous_jr.JobType != JT_JOB_COPY) {
326 Jmsg(jcr, M_INFO, 0, _("JobId %s already %s probably by another Job. %s stopped.\n"),
327 edit_int64(jcr->previous_jr.JobId, ed1),
328 jcr->get_ActionName(1),
329 jcr->get_OperationName());
330 jcr->setJobStatus(JS_Terminated);
331 migration_cleanup(jcr, jcr->JobStatus);
335 /* Print Job Start message */
336 Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
337 jcr->get_OperationName(), edit_uint64(jcr->JobId, ed1), jcr->Job);
340 * Open a message channel connection with the Storage
341 * daemon. This is to let him know that our client
342 * will be contacting him for a backup session.
345 Dmsg0(110, "Open connection with storage daemon\n");
346 jcr->setJobStatus(JS_WaitSD);
347 mig_jcr->setJobStatus(JS_WaitSD);
349 * Start conversation with Storage daemon
351 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
354 sd = jcr->store_bsock;
356 * Now start a job with the Storage daemon
358 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
359 ((STORE *)jcr->rstorage->first())->name(),
360 ((STORE *)jcr->wstorage->first())->name());
362 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage, /*send_bsr*/true)) {
365 Dmsg0(150, "Storage daemon connection OK\n");
369 * We re-update the job start record so that the start
370 * time is set after the run before job. This avoids
371 * that any files created by the run before job will
372 * be saved twice. They will be backed up in the current
373 * job, but not in the next one unless they are changed.
374 * Without this, they will be backed up in this job and
375 * in the next job run because in that case, their date
376 * is after the start of this run.
378 jcr->start_time = time(NULL);
379 jcr->jr.StartTime = jcr->start_time;
380 jcr->jr.JobTDate = jcr->start_time;
381 jcr->setJobStatus(JS_Running);
383 /* Update job start record for this migration control job */
384 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
385 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
390 mig_jcr->start_time = time(NULL);
391 mig_jcr->jr.StartTime = mig_jcr->start_time;
392 mig_jcr->jr.JobTDate = mig_jcr->start_time;
393 mig_jcr->setJobStatus(JS_Running);
395 /* Update job start record for the real migration backup job */
396 if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
397 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
401 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
402 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
403 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
407 * Start the job prior to starting the message thread below
408 * to avoid two threads from using the BSOCK structure at
411 if (!sd->fsend("run")) {
416 * Now start a Storage daemon message thread
418 if (!start_storage_daemon_message_thread(jcr)) {
423 jcr->setJobStatus(JS_Running);
424 mig_jcr->setJobStatus(JS_Running);
426 /* Pickup Job termination data */
427 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
428 wait_for_storage_daemon_termination(jcr);
429 jcr->setJobStatus(jcr->SDJobStatus);
430 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
431 if (jcr->JobStatus != JS_Terminated) {
435 migration_cleanup(jcr, jcr->JobStatus);
445 /* Add an item to the list if it is unique */
446 static void add_unique_id(idpkt *ids, char *item)
448 const int maxlen = 30;
452 /* Walk through current list to see if each item is the same as item */
455 for (int i=0; i<maxlen; i++) {
458 } else if (*q == ',') {
465 if (strcmp(item, id) == 0) {
469 /* Did not find item, so add it to list */
470 if (ids->count == 0) {
473 pm_strcat(ids->list, ",");
475 pm_strcat(ids->list, item);
477 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
482 * Callback handler make list of DB Ids
484 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
486 idpkt *ids = (idpkt *)ctx;
489 if (!row || !row[0]) {
490 Dmsg0(dbglevel, "dbid_hdlr error empty row\n");
491 return 1; /* stop calling us */
494 add_unique_id(ids, row[0]);
495 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
505 static int item_compare(void *item1, void *item2)
507 uitem *i1 = (uitem *)item1;
508 uitem *i2 = (uitem *)item2;
509 return strcmp(i1->item, i2->item);
512 static int unique_name_handler(void *ctx, int num_fields, char **row)
514 dlist *list = (dlist *)ctx;
516 uitem *new_item = (uitem *)malloc(sizeof(uitem));
519 memset(new_item, 0, sizeof(uitem));
520 new_item->item = bstrdup(row[0]);
521 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
522 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
523 if (item != new_item) { /* already in list */
524 free(new_item->item);
525 free((char *)new_item);
531 /* Get Job names in Pool */
532 const char *sql_job =
533 "SELECT DISTINCT Job.Name from Job,Pool"
534 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
536 /* Get JobIds from regex'ed Job names */
537 const char *sql_jobids_from_job =
538 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
539 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
540 " ORDER by Job.StartTime";
542 /* Get Client names in Pool */
543 const char *sql_client =
544 "SELECT DISTINCT Client.Name from Client,Pool,Job"
545 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
546 " Job.PoolId=Pool.PoolId";
548 /* Get JobIds from regex'ed Client names */
549 const char *sql_jobids_from_client =
550 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
551 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
552 " AND Job.ClientId=Client.ClientId AND Job.Type IN ('B','C')"
553 " AND Job.JobStatus IN ('T','W')"
554 " ORDER by Job.StartTime";
556 /* Get Volume names in Pool */
557 const char *sql_vol =
558 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
559 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
560 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
562 /* Get JobIds from regex'ed Volume names */
563 const char *sql_jobids_from_vol =
564 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
565 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
566 " AND JobMedia.JobId=Job.JobId AND Job.Type IN ('B','C')"
567 " AND Job.JobStatus IN ('T','W') AND Media.Enabled=1"
568 " ORDER by Job.StartTime";
570 const char *sql_smallest_vol =
571 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
572 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
573 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
574 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
575 " ORDER BY VolBytes ASC LIMIT 1";
577 const char *sql_oldest_vol =
578 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
579 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
580 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
581 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
582 " ORDER BY LastWritten ASC LIMIT 1";
584 /* Get JobIds when we have selected MediaId */
585 const char *sql_jobids_from_mediaid =
586 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
587 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId IN (%s)"
588 " AND Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
589 " ORDER by Job.StartTime";
591 /* Get the number of bytes in the pool */
592 const char *sql_pool_bytes =
593 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN"
594 " (SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
595 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
596 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
597 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
598 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId)";
600 /* Get the number of bytes in the Jobs */
601 const char *sql_job_bytes =
602 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
604 /* Get Media Ids in Pool */
605 const char *sql_mediaids =
606 "SELECT MediaId FROM Media,Pool WHERE"
607 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
608 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
610 /* Get JobIds in Pool longer than specified time */
611 const char *sql_pool_time =
612 "SELECT DISTINCT Job.JobId FROM Pool,Job,Media,JobMedia WHERE"
613 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
614 " VolStatus IN ('Full','Used','Error') AND Media.Enabled=1 AND"
615 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
616 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
617 " AND Job.RealEndTime<='%s'";
619 /* Get JobIds from successfully completed backup jobs which have not been copied before */
620 const char *sql_jobids_of_pool_uncopied_jobs =
621 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
622 " WHERE Pool.Name = '%s' AND Pool.PoolId = Job.PoolId"
623 " AND Job.Type = 'B' AND Job.JobStatus IN ('T','W')"
624 " AND Job.jobBytes > 0"
625 " AND Job.JobId NOT IN"
626 " (SELECT PriorJobId FROM Job WHERE"
627 " Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
628 " AND PriorJobId != 0)"
629 " ORDER by Job.StartTime";
632 * const char *sql_ujobid =
633 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
634 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
635 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
640 * This is the central piece of code that finds a job or jobs
641 * actually JobIds to migrate. It first looks to see if one
642 * has been "manually" specified in jcr->MigrateJobId, and if
643 * so, it returns that JobId to be run. Otherwise, it
644 * examines the Selection Type to see what kind of migration
645 * we are doing (Volume, Job, Client, ...) and applies any
646 * Selection Pattern if appropriate to obtain a list of JobIds.
647 * Finally, it will loop over all the JobIds found, except the last
648 * one starting a new job with MigrationJobId set to that JobId, and
649 * finally, it returns the last JobId to the caller.
651 * Returns: -1 on error
652 * 0 if no jobs to migrate
653 * 1 if OK and jcr->previous_jr filled in
655 static int getJob_to_migrate(JCR *jcr)
657 char ed1[30], ed2[30];
658 POOL_MEM query(PM_MESSAGE);
663 idpkt ids, mid, jids;
668 char dt[MAX_TIME_LENGTH];
671 ids.list = get_pool_memory(PM_MESSAGE);
674 mid.list = get_pool_memory(PM_MESSAGE);
677 jids.list = get_pool_memory(PM_MESSAGE);
682 * If MigrateJobId is set, then we migrate only that Job,
683 * otherwise, we go through the full selection of jobs to
686 if (jcr->MigrateJobId != 0) {
687 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
688 JobId = jcr->MigrateJobId;
690 switch (jcr->job->selection_type) {
692 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
697 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
702 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
707 if (!jcr->job->selection_pattern) {
708 Jmsg(jcr, M_FATAL, 0, _("No %s SQL selection pattern specified.\n"), jcr->get_OperationName());
711 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
712 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
713 unique_dbid_handler, (void *)&ids)) {
714 Jmsg(jcr, M_FATAL, 0,
715 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
719 case MT_SMALLEST_VOL:
720 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
725 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
729 case MT_POOL_OCCUPANCY:
731 /* Find count of bytes in pool */
732 Mmsg(query, sql_pool_bytes, jcr->rpool->name());
733 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
734 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
737 if (ctx.count == 0) {
738 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
741 pool_bytes = ctx.value;
742 Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
744 if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
745 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
748 Dmsg0(dbglevel, "We should do Occupation migration.\n");
751 /* Find a list of MediaIds that could be migrated */
752 Mmsg(query, sql_mediaids, jcr->rpool->name());
753 Dmsg1(dbglevel, "query=%s\n", query.c_str());
754 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
755 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
758 if (ids.count == 0) {
759 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
762 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
764 if (!find_jobids_from_mediaid_list(jcr, &ids, "Volume")) {
767 /* ids == list of jobs */
769 for (int i=0; i < (int)ids.count; i++) {
770 stat = get_next_dbid_from_list(&p, &DBId);
771 Dmsg2(dbglevel, "get_next_dbid stat=%d JobId=%u\n", stat, (uint32_t)DBId);
773 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
775 } else if (stat == 0) {
780 Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
781 if (jids.count > 0) {
782 pm_strcat(jids.list, ",");
784 pm_strcat(jids.list, mid.list);
785 jids.count += mid.count;
787 /* Find count of bytes from Jobs */
788 Mmsg(query, sql_job_bytes, mid.list);
789 Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
790 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
791 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
794 pool_bytes -= ctx.value;
795 Dmsg2(dbglevel, "Total %s Job bytes=%s\n", jcr->get_ActionName(0), edit_int64_with_commas(ctx.value, ed1));
796 Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n",
797 edit_int64_with_commas(jcr->rpool->MigrationLowBytes, ed1),
798 edit_int64_with_commas(pool_bytes, ed2));
799 if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
800 Dmsg0(dbglevel, "We should be done.\n");
804 /* Transfer jids to ids, where the jobs list is expected */
805 ids.count = jids.count;
806 pm_strcpy(ids.list, jids.list);
807 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
810 ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
811 (void)localtime_r(&ttime, &tm);
812 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
815 Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
816 Dmsg1(dbglevel, "query=%s\n", query.c_str());
817 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
818 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
821 if (ids.count == 0) {
822 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
825 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
827 case MT_POOL_UNCOPIED_JOBS:
828 if (!find_jobids_of_pool_uncopied_jobs(jcr, &ids)) {
833 Jmsg(jcr, M_FATAL, 0, _("Unknown %s Selection Type.\n"), jcr->get_OperationName());
838 * Loop over all jobids except the last one, sending
839 * them to start_migration_job(), which will start a job
840 * for each of them. For the last JobId, we handle it below.
843 if (ids.count == 0) {
844 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
848 Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s chosen to be %s: %s\n"),
849 ids.count, (ids.count < 2) ? _(" was") : _("s were"),
850 jcr->get_ActionName(1), ids.list);
852 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
853 for (int i=1; i < (int)ids.count; i++) {
855 stat = get_next_jobid_from_list(&p, &JobId);
856 Dmsg3(dbglevel, "getJobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
858 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
860 } else if (stat == 0) {
861 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
864 jcr->MigrateJobId = JobId;
865 start_migration_job(jcr);
866 Dmsg0(dbglevel, "Back from start_migration_job\n");
869 /* Now get the last JobId and handle it in the current job */
871 stat = get_next_jobid_from_list(&p, &JobId);
872 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
874 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
876 } else if (stat == 0) {
877 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
882 jcr->previous_jr.JobId = JobId;
883 Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
885 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
886 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
887 edit_int64(jcr->previous_jr.JobId, ed1),
888 jcr->get_ActionName(0),
889 db_strerror(jcr->db));
893 Jmsg(jcr, M_INFO, 0, _("%s using JobId=%s Job=%s\n"),
894 jcr->get_OperationName(),
895 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
896 Dmsg4(dbglevel, "%s JobId=%d using JobId=%s Job=%s\n",
897 jcr->get_OperationName(),
899 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
909 free_pool_memory(ids.list);
910 free_pool_memory(mid.list);
911 free_pool_memory(jids.list);
915 static void start_migration_job(JCR *jcr)
917 UAContext *ua = new_ua_context(jcr);
920 Mmsg(ua->cmd, "run job=\"%s\" jobid=%s ignoreduplicatecheck=yes pool=\"%s\"",
921 jcr->job->name(), edit_uint64(jcr->MigrateJobId, ed1),
923 Dmsg2(dbglevel, "=============== %s cmd=%s\n", jcr->get_OperationName(), ua->cmd);
924 parse_ua_args(ua); /* parse command */
925 JobId_t jobid = run_cmd(ua, ua->cmd);
927 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
929 Jmsg(jcr, M_INFO, 0, _("%s JobId %d started.\n"), jcr->get_OperationName(), (int)jobid);
934 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
938 POOL_MEM query(PM_MESSAGE);
941 /* Basic query for MediaId */
942 Mmsg(query, query1, jcr->rpool->name());
943 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
944 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
947 if (ids->count == 0) {
948 Jmsg(jcr, M_INFO, 0, _("No %s found to %s.\n"), type, jcr->get_ActionName(0));
949 ok = true; /* Not an error */
951 } else if (ids->count != 1) {
952 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
955 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
957 ok = find_jobids_from_mediaid_list(jcr, ids, type);
964 * This routine returns:
965 * false if an error occurred
967 * ids.count number of jobids found (may be zero)
969 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
972 POOL_MEM query(PM_MESSAGE);
974 Mmsg(query, sql_jobids_from_mediaid, ids->list);
976 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
977 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
980 if (ids->count == 0) {
981 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
990 * This routine returns:
991 * false if an error occurred
993 * ids.count number of jobids found (may be zero)
995 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids)
998 POOL_MEM query(PM_MESSAGE);
1000 /* Only a copy job is allowed */
1001 if (jcr->getJobType() != JT_COPY) {
1002 Jmsg(jcr, M_FATAL, 0,
1003 _("Selection Type 'pooluncopiedjobs' only applies to Copy Jobs"));
1007 Dmsg1(dbglevel, "copy selection pattern=%s\n", jcr->rpool->name());
1008 Mmsg(query, sql_jobids_of_pool_uncopied_jobs, jcr->rpool->name());
1009 Dmsg1(dbglevel, "get uncopied jobs query=%s\n", query.c_str());
1010 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1011 Jmsg(jcr, M_FATAL, 0,
1012 _("SQL to get uncopied jobs failed. ERR=%s\n"), db_strerror(jcr->db));
1021 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
1022 const char *query2, const char *type)
1026 uitem *last_item = NULL;
1031 POOL_MEM query(PM_MESSAGE);
1033 item_chain = New(dlist(item, &item->link));
1034 if (!jcr->job->selection_pattern) {
1035 Jmsg(jcr, M_FATAL, 0, _("No %s %s selection pattern specified.\n"),
1036 jcr->get_OperationName(), type);
1039 Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
1040 /* Basic query for names */
1041 Mmsg(query, query1, jcr->rpool->name());
1042 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
1043 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
1044 (void *)item_chain)) {
1045 Jmsg(jcr, M_FATAL, 0,
1046 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
1049 Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
1050 if (item_chain->size() == 0) {
1051 Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to %s.\n"),
1052 jcr->rpool->name(), jcr->get_ActionName(0));
1054 goto bail_out; /* skip regex match */
1056 /* Compile regex expression */
1057 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
1059 regerror(rc, &preg, prbuf, sizeof(prbuf));
1060 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
1061 jcr->job->selection_pattern, prbuf);
1064 /* Now apply the regex to the names and remove any item not matched */
1065 foreach_dlist(item, item_chain) {
1066 const int nmatch = 30;
1067 regmatch_t pmatch[nmatch];
1069 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1070 free(last_item->item);
1071 item_chain->remove(last_item);
1073 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
1074 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
1076 last_item = NULL; /* keep this one */
1082 free(last_item->item);
1083 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1084 item_chain->remove(last_item);
1088 if (item_chain->size() == 0) {
1089 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName(0));
1091 goto bail_out; /* skip regex match */
1095 * At this point, we have a list of items in item_chain
1096 * that have been matched by the regex, so now we need
1097 * to look up their jobids.
1100 foreach_dlist(item, item_chain) {
1101 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
1102 Mmsg(query, query2, item->item, jcr->rpool->name());
1103 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
1104 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1105 Jmsg(jcr, M_FATAL, 0,
1106 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
1110 if (ids->count == 0) {
1111 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
1116 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
1117 foreach_dlist(item, item_chain) {
1125 * Release resources allocated during backup.
1127 void migration_cleanup(JCR *jcr, int TermCode)
1129 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
1130 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
1131 char ec6[50], ec7[50], ec8[50];
1132 char term_code[100], sd_term_msg[100];
1133 const char *term_msg;
1134 int msg_type = M_INFO;
1138 JCR *mig_jcr = jcr->mig_jcr;
1139 POOL_MEM query(PM_MESSAGE);
1141 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
1142 update_job_end(jcr, TermCode);
1143 memset(&mr, 0, sizeof(mr));
1146 * Check if we actually did something.
1147 * mig_jcr is jcr of the newly migrated job.
1150 char old_jobid[50], new_jobid[50];
1152 edit_uint64(jcr->previous_jr.JobId, old_jobid);
1153 edit_uint64(mig_jcr->jr.JobId, new_jobid);
1155 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
1156 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
1157 mig_jcr->VolSessionId = jcr->VolSessionId;
1158 mig_jcr->VolSessionTime = jcr->VolSessionTime;
1159 mig_jcr->jr.RealEndTime = 0;
1160 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
1162 update_job_end(mig_jcr, TermCode);
1164 /* Update final items to set them to the previous job's values */
1165 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
1166 "JobTDate=%s WHERE JobId=%s",
1167 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
1168 edit_uint64(jcr->previous_jr.JobTDate, ec1),
1170 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1173 * If we terminated a migration normally:
1174 * - mark the previous job as migrated
1175 * - move any Log records to the new JobId
1176 * - Purge the File records from the previous job
1178 if (jcr->getJobType() == JT_MIGRATE && jcr->JobStatus == JS_Terminated) {
1179 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1180 (char)JT_MIGRATED_JOB, old_jobid);
1181 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1182 UAContext *ua = new_ua_context(jcr);
1183 /* Move JobLog to new JobId */
1184 Mmsg(query, "UPDATE Log SET JobId=%s WHERE JobId=%s",
1185 new_jobid, old_jobid);
1186 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1188 if (jcr->job->PurgeMigrateJob) {
1189 /* Purge old Job record */
1190 purge_jobs_from_catalog(ua, old_jobid);
1192 /* Purge all old file records, but leave Job record */
1193 purge_files_from_jobs(ua, old_jobid);
1196 free_ua_context(ua);
1200 * If we terminated a Copy (rather than a Migration) normally:
1201 * - copy any Log records to the new JobId
1202 * - set type="Job Copy" for the new job
1204 if (jcr->getJobType() == JT_COPY && jcr->JobStatus == JS_Terminated) {
1205 /* Copy JobLog to new JobId */
1206 Mmsg(query, "INSERT INTO Log (JobId, Time, LogText ) "
1207 "SELECT %s, Time, LogText FROM Log WHERE JobId=%s",
1208 new_jobid, old_jobid);
1209 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1210 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1211 (char)JT_JOB_COPY, new_jobid);
1212 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1215 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
1216 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
1217 db_strerror(jcr->db));
1218 jcr->setJobStatus(JS_ErrorTerminated);
1221 update_bootstrap_file(mig_jcr);
1223 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
1225 * Note, if the job has failed, most likely it did not write any
1226 * tape, so suppress this "error" message since in that case
1227 * it is normal. Or look at it the other way, only for a
1228 * normal exit should we complain about this error.
1230 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
1231 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
1233 mig_jcr->VolumeName[0] = 0; /* none */
1236 if (mig_jcr->VolumeName[0]) {
1237 /* Find last volume name. Multiple vols are separated by | */
1238 char *p = strrchr(mig_jcr->VolumeName, '|');
1242 p = mig_jcr->VolumeName; /* no |, take full name */
1244 bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1245 if (!db_get_media_record(jcr, jcr->db, &mr)) {
1246 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1247 mr.VolumeName, db_strerror(jcr->db));
1251 switch (jcr->JobStatus) {
1253 if (jcr->JobErrors || jcr->SDErrors) {
1254 term_msg = _("%s OK -- with warnings");
1256 term_msg = _("%s OK");
1260 case JS_ErrorTerminated:
1261 term_msg = _("*** %s Error ***");
1262 msg_type = M_ERROR; /* Generate error message */
1263 if (jcr->store_bsock) {
1264 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1265 if (jcr->SD_msg_chan) {
1266 pthread_cancel(jcr->SD_msg_chan);
1271 term_msg = _("%s Canceled");
1272 if (jcr->store_bsock) {
1273 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1274 if (jcr->SD_msg_chan) {
1275 pthread_cancel(jcr->SD_msg_chan);
1280 term_msg = _("Inappropriate %s term code");
1284 if (jcr->getJobType() == JT_MIGRATE && jcr->previous_jr.JobId != 0) {
1285 /* Mark previous job as migrated */
1286 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1287 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1288 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
1290 term_msg = _("%s -- no files to %s");
1293 bsnprintf(term_code, sizeof(term_code), term_msg, jcr->get_OperationName(), jcr->get_ActionName(0));
1294 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1295 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1296 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1300 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1303 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1305 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
1306 " Build OS: %s %s %s\n"
1307 " Prev Backup JobId: %s\n"
1308 " Prev Backup Job: %s\n"
1309 " New Backup JobId: %s\n"
1310 " Current JobId: %s\n"
1311 " Current Job: %s\n"
1312 " Backup Level: %s%s\n"
1314 " FileSet: \"%s\" %s\n"
1315 " Read Pool: \"%s\" (From %s)\n"
1316 " Read Storage: \"%s\" (From %s)\n"
1317 " Write Pool: \"%s\" (From %s)\n"
1318 " Write Storage: \"%s\" (From %s)\n"
1319 " Catalog: \"%s\" (From %s)\n"
1322 " Elapsed time: %s\n"
1324 " SD Files Written: %s\n"
1325 " SD Bytes Written: %s (%sB)\n"
1326 " Rate: %.1f KB/s\n"
1327 " Volume name(s): %s\n"
1328 " Volume Session Id: %d\n"
1329 " Volume Session Time: %d\n"
1330 " Last Volume Bytes: %s (%sB)\n"
1332 " SD termination status: %s\n"
1333 " Termination: %s\n\n"),
1334 BACULA, my_name, VERSION, LSMDATE,
1335 HOST_OS, DISTNAME, DISTVER,
1336 edit_uint64(jcr->previous_jr.JobId, ec6),
1337 jcr->previous_jr.Job,
1338 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1339 edit_uint64(jcr->jr.JobId, ec8),
1341 level_to_str(jcr->getJobLevel()), jcr->since,
1342 jcr->client->name(),
1343 jcr->fileset->name(), jcr->FSCreateTime,
1344 jcr->rpool->name(), jcr->rpool_source,
1345 jcr->rstore?jcr->rstore->name():"*None*",
1346 NPRT(jcr->rstore_source),
1347 jcr->pool->name(), jcr->pool_source,
1348 jcr->wstore?jcr->wstore->name():"*None*",
1349 NPRT(jcr->wstore_source),
1350 jcr->catalog->name(), jcr->catalog_source,
1353 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1355 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1356 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1357 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1359 mig_jcr ? mig_jcr->VolumeName : "",
1361 jcr->VolSessionTime,
1362 edit_uint64_with_commas(mr.VolBytes, ec4),
1363 edit_uint64_with_suffix(mr.VolBytes, ec5),
1368 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1370 free_jcr(jcr->mig_jcr);
1371 jcr->mig_jcr = NULL;
1373 Dmsg0(100, "Leave migrate_cleanup()\n");
1377 * Return next DBId from comma separated list
1380 * 1 if next DBId returned
1381 * 0 if no more DBIds are in list
1382 * -1 there is an error
1384 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1386 const int maxlen = 30;
1391 for (int i=0; i<maxlen; i++) {
1394 } else if (*q == ',') {
1403 } else if (!is_a_number(id)) {
1404 return -1; /* error */
1407 *DBId = str_to_int64(id);
1411 bool set_migration_wstorage(JCR *jcr, POOL *pool)
1413 POOL *wpool = pool->NextPool;
1416 Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
1421 if (!wpool->storage || wpool->storage->size() == 0) {
1422 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
1427 /* If pool storage specified, use it instead of job storage for backup */
1428 copy_wstorage(jcr, wpool->storage, _("Storage from Pool's NextPool resource"));