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;
216 /* Don't check for duplicates on migration and copy jobs */
217 mig_jcr->no_check_duplicates = true;
219 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
220 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
221 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
223 if (set_migration_next_pool(jcr, &pool)) {
224 /* If pool storage specified, use it for restore */
225 copy_rstorage(mig_jcr, pool->storage, _("Pool resource"));
226 copy_rstorage(jcr, pool->storage, _("Pool resource"));
228 mig_jcr->pool = jcr->pool;
229 mig_jcr->jr.PoolId = jcr->jr.PoolId;
237 * set_migration_next_pool() called by do_migration_init()
238 * at differents stages.
239 * The idea here is tofactorize the NextPool's search code and
240 * to permit do_migration_init() to return with NextPool set in jcr struct.
242 static bool set_migration_next_pool(JCR *jcr, POOL **retpool)
249 * Get the PoolId used with the original job. Then
250 * find the pool name from the database record.
252 memset(&pr, 0, sizeof(pr));
253 pr.PoolId = jcr->jr.PoolId;
254 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
255 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
256 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
259 /* Get the pool resource corresponding to the original job */
260 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
263 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
268 * If the original backup pool has a NextPool, make sure a
269 * record exists in the database. Note, in this case, we
270 * will be migrating from pool to pool->NextPool.
272 if (pool->NextPool) {
273 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->name());
274 if (jcr->jr.PoolId == 0) {
278 if (!set_migration_wstorage(jcr, pool)) {
281 jcr->pool = pool->NextPool;
282 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
284 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
291 * Do a Migration of a previous job
293 * Returns: false on failure
296 bool do_migration(JCR *jcr)
300 JCR *mig_jcr = jcr->mig_jcr; /* newly migrated job */
303 * If mig_jcr is NULL, there is nothing to do for this job,
304 * so set a normal status, cleanup and return OK.
307 jcr->setJobStatus(JS_Terminated);
308 migration_cleanup(jcr, jcr->JobStatus);
312 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
313 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
314 edit_int64(jcr->previous_jr.JobId, ed1),
315 jcr->get_ActionName(0),
316 db_strerror(jcr->db));
317 jcr->setJobStatus(JS_Terminated);
318 migration_cleanup(jcr, jcr->JobStatus);
321 /* Make sure this job was not already migrated */
322 if (jcr->previous_jr.JobType != JT_BACKUP &&
323 jcr->previous_jr.JobType != JT_JOB_COPY) {
324 Jmsg(jcr, M_INFO, 0, _("JobId %s already %s probably by another Job. %s stopped.\n"),
325 edit_int64(jcr->previous_jr.JobId, ed1),
326 jcr->get_ActionName(1),
327 jcr->get_OperationName());
328 jcr->setJobStatus(JS_Terminated);
329 migration_cleanup(jcr, jcr->JobStatus);
333 /* Print Job Start message */
334 Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
335 jcr->get_OperationName(), edit_uint64(jcr->JobId, ed1), jcr->Job);
338 * Open a message channel connection with the Storage
339 * daemon. This is to let him know that our client
340 * will be contacting him for a backup session.
343 Dmsg0(110, "Open connection with storage daemon\n");
344 jcr->setJobStatus(JS_WaitSD);
345 mig_jcr->setJobStatus(JS_WaitSD);
347 * Start conversation with Storage daemon
349 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
352 sd = jcr->store_bsock;
354 * Now start a job with the Storage daemon
356 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
357 ((STORE *)jcr->rstorage->first())->name(),
358 ((STORE *)jcr->wstorage->first())->name());
360 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage, /*send_bsr*/true)) {
363 Dmsg0(150, "Storage daemon connection OK\n");
367 * We re-update the job start record so that the start
368 * time is set after the run before job. This avoids
369 * that any files created by the run before job will
370 * be saved twice. They will be backed up in the current
371 * job, but not in the next one unless they are changed.
372 * Without this, they will be backed up in this job and
373 * in the next job run because in that case, their date
374 * is after the start of this run.
376 jcr->start_time = time(NULL);
377 jcr->jr.StartTime = jcr->start_time;
378 jcr->jr.JobTDate = jcr->start_time;
379 jcr->setJobStatus(JS_Running);
381 /* Update job start record for this migration control job */
382 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
383 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
388 mig_jcr->start_time = time(NULL);
389 mig_jcr->jr.StartTime = mig_jcr->start_time;
390 mig_jcr->jr.JobTDate = mig_jcr->start_time;
391 mig_jcr->setJobStatus(JS_Running);
393 /* Update job start record for the real migration backup job */
394 if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
395 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
399 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
400 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
401 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
405 * Start the job prior to starting the message thread below
406 * to avoid two threads from using the BSOCK structure at
409 if (!sd->fsend("run")) {
414 * Now start a Storage daemon message thread
416 if (!start_storage_daemon_message_thread(jcr)) {
421 jcr->setJobStatus(JS_Running);
422 mig_jcr->setJobStatus(JS_Running);
424 /* Pickup Job termination data */
425 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
426 wait_for_storage_daemon_termination(jcr);
427 jcr->setJobStatus(jcr->SDJobStatus);
428 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
429 if (jcr->JobStatus != JS_Terminated) {
433 migration_cleanup(jcr, jcr->JobStatus);
443 /* Add an item to the list if it is unique */
444 static void add_unique_id(idpkt *ids, char *item)
446 const int maxlen = 30;
450 /* Walk through current list to see if each item is the same as item */
453 for (int i=0; i<maxlen; i++) {
456 } else if (*q == ',') {
463 if (strcmp(item, id) == 0) {
467 /* Did not find item, so add it to list */
468 if (ids->count == 0) {
471 pm_strcat(ids->list, ",");
473 pm_strcat(ids->list, item);
475 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
480 * Callback handler make list of DB Ids
482 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
484 idpkt *ids = (idpkt *)ctx;
487 if (!row || !row[0]) {
488 Dmsg0(dbglevel, "dbid_hdlr error empty row\n");
489 return 1; /* stop calling us */
492 add_unique_id(ids, row[0]);
493 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
503 static int item_compare(void *item1, void *item2)
505 uitem *i1 = (uitem *)item1;
506 uitem *i2 = (uitem *)item2;
507 return strcmp(i1->item, i2->item);
510 static int unique_name_handler(void *ctx, int num_fields, char **row)
512 dlist *list = (dlist *)ctx;
514 uitem *new_item = (uitem *)malloc(sizeof(uitem));
517 memset(new_item, 0, sizeof(uitem));
518 new_item->item = bstrdup(row[0]);
519 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
520 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
521 if (item != new_item) { /* already in list */
522 free(new_item->item);
523 free((char *)new_item);
529 /* Get Job names in Pool */
530 const char *sql_job =
531 "SELECT DISTINCT Job.Name from Job,Pool"
532 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
534 /* Get JobIds from regex'ed Job names */
535 const char *sql_jobids_from_job =
536 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
537 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
538 " ORDER by Job.StartTime";
540 /* Get Client names in Pool */
541 const char *sql_client =
542 "SELECT DISTINCT Client.Name from Client,Pool,Job"
543 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
544 " Job.PoolId=Pool.PoolId";
546 /* Get JobIds from regex'ed Client names */
547 const char *sql_jobids_from_client =
548 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
549 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
550 " AND Job.ClientId=Client.ClientId AND Job.Type IN ('B','C')"
551 " AND Job.JobStatus IN ('T','W')"
552 " ORDER by Job.StartTime";
554 /* Get Volume names in Pool */
555 const char *sql_vol =
556 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
557 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
558 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
560 /* Get JobIds from regex'ed Volume names */
561 const char *sql_jobids_from_vol =
562 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
563 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
564 " AND JobMedia.JobId=Job.JobId AND Job.Type IN ('B','C')"
565 " AND Job.JobStatus IN ('T','W') AND Media.Enabled=1"
566 " ORDER by Job.StartTime";
568 const char *sql_smallest_vol =
569 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
570 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
571 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
572 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
573 " ORDER BY VolBytes ASC LIMIT 1";
575 const char *sql_oldest_vol =
576 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
577 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
578 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
579 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
580 " ORDER BY LastWritten ASC LIMIT 1";
582 /* Get JobIds when we have selected MediaId */
583 const char *sql_jobids_from_mediaid =
584 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
585 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId IN (%s)"
586 " AND Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
587 " ORDER by Job.StartTime";
589 /* Get the number of bytes in the pool */
590 const char *sql_pool_bytes =
591 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN"
592 " (SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
593 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
594 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
595 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
596 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId)";
598 /* Get the number of bytes in the Jobs */
599 const char *sql_job_bytes =
600 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
602 /* Get Media Ids in Pool */
603 const char *sql_mediaids =
604 "SELECT MediaId FROM Media,Pool WHERE"
605 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
606 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
608 /* Get JobIds in Pool longer than specified time */
609 const char *sql_pool_time =
610 "SELECT DISTINCT Job.JobId FROM Pool,Job,Media,JobMedia WHERE"
611 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
612 " VolStatus IN ('Full','Used','Error') AND Media.Enabled=1 AND"
613 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
614 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
615 " AND Job.RealEndTime<='%s'";
617 /* Get JobIds from successfully completed backup jobs which have not been copied before */
618 const char *sql_jobids_of_pool_uncopied_jobs =
619 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
620 " WHERE Pool.Name = '%s' AND Pool.PoolId = Job.PoolId"
621 " AND Job.Type = 'B' AND Job.JobStatus IN ('T','W')"
622 " AND Job.jobBytes > 0"
623 " AND Job.JobId NOT IN"
624 " (SELECT PriorJobId FROM Job WHERE"
625 " Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
626 " AND PriorJobId != 0)"
627 " ORDER by Job.StartTime";
630 * const char *sql_ujobid =
631 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
632 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
633 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
638 * This is the central piece of code that finds a job or jobs
639 * actually JobIds to migrate. It first looks to see if one
640 * has been "manually" specified in jcr->MigrateJobId, and if
641 * so, it returns that JobId to be run. Otherwise, it
642 * examines the Selection Type to see what kind of migration
643 * we are doing (Volume, Job, Client, ...) and applies any
644 * Selection Pattern if appropriate to obtain a list of JobIds.
645 * Finally, it will loop over all the JobIds found, except the last
646 * one starting a new job with MigrationJobId set to that JobId, and
647 * finally, it returns the last JobId to the caller.
649 * Returns: -1 on error
650 * 0 if no jobs to migrate
651 * 1 if OK and jcr->previous_jr filled in
653 static int getJob_to_migrate(JCR *jcr)
655 char ed1[30], ed2[30];
656 POOL_MEM query(PM_MESSAGE);
661 idpkt ids, mid, jids;
666 char dt[MAX_TIME_LENGTH];
669 ids.list = get_pool_memory(PM_MESSAGE);
672 mid.list = get_pool_memory(PM_MESSAGE);
675 jids.list = get_pool_memory(PM_MESSAGE);
680 * If MigrateJobId is set, then we migrate only that Job,
681 * otherwise, we go through the full selection of jobs to
684 if (jcr->MigrateJobId != 0) {
685 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
686 JobId = jcr->MigrateJobId;
688 switch (jcr->job->selection_type) {
690 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
695 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
700 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
705 if (!jcr->job->selection_pattern) {
706 Jmsg(jcr, M_FATAL, 0, _("No %s SQL selection pattern specified.\n"), jcr->get_OperationName());
709 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
710 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
711 unique_dbid_handler, (void *)&ids)) {
712 Jmsg(jcr, M_FATAL, 0,
713 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
717 case MT_SMALLEST_VOL:
718 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
723 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
727 case MT_POOL_OCCUPANCY:
729 /* Find count of bytes in pool */
730 Mmsg(query, sql_pool_bytes, jcr->rpool->name());
731 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
732 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
735 if (ctx.count == 0) {
736 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
739 pool_bytes = ctx.value;
740 Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
742 if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
743 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
746 Dmsg0(dbglevel, "We should do Occupation migration.\n");
749 /* Find a list of MediaIds that could be migrated */
750 Mmsg(query, sql_mediaids, jcr->rpool->name());
751 Dmsg1(dbglevel, "query=%s\n", query.c_str());
752 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
753 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
756 if (ids.count == 0) {
757 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
760 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
762 if (!find_jobids_from_mediaid_list(jcr, &ids, "Volume")) {
765 /* ids == list of jobs */
767 for (int i=0; i < (int)ids.count; i++) {
768 stat = get_next_dbid_from_list(&p, &DBId);
769 Dmsg2(dbglevel, "get_next_dbid stat=%d JobId=%u\n", stat, (uint32_t)DBId);
771 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
773 } else if (stat == 0) {
778 Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
779 if (jids.count > 0) {
780 pm_strcat(jids.list, ",");
782 pm_strcat(jids.list, mid.list);
783 jids.count += mid.count;
785 /* Find count of bytes from Jobs */
786 Mmsg(query, sql_job_bytes, mid.list);
787 Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
788 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
789 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
792 pool_bytes -= ctx.value;
793 Dmsg2(dbglevel, "Total %s Job bytes=%s\n", jcr->get_ActionName(0), edit_int64_with_commas(ctx.value, ed1));
794 Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n",
795 edit_int64_with_commas(jcr->rpool->MigrationLowBytes, ed1),
796 edit_int64_with_commas(pool_bytes, ed2));
797 if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
798 Dmsg0(dbglevel, "We should be done.\n");
802 /* Transfer jids to ids, where the jobs list is expected */
803 ids.count = jids.count;
804 pm_strcpy(ids.list, jids.list);
805 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
808 ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
809 (void)localtime_r(&ttime, &tm);
810 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
813 Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
814 Dmsg1(dbglevel, "query=%s\n", query.c_str());
815 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
816 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
819 if (ids.count == 0) {
820 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
823 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
825 case MT_POOL_UNCOPIED_JOBS:
826 if (!find_jobids_of_pool_uncopied_jobs(jcr, &ids)) {
831 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));
880 jcr->previous_jr.JobId = JobId;
881 Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
883 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
884 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
885 edit_int64(jcr->previous_jr.JobId, ed1),
886 jcr->get_ActionName(0),
887 db_strerror(jcr->db));
891 Jmsg(jcr, M_INFO, 0, _("%s using JobId=%s Job=%s\n"),
892 jcr->get_OperationName(),
893 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
894 Dmsg4(dbglevel, "%s JobId=%d using JobId=%s Job=%s\n",
895 jcr->get_OperationName(),
897 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
907 free_pool_memory(ids.list);
908 free_pool_memory(mid.list);
909 free_pool_memory(jids.list);
913 static void start_migration_job(JCR *jcr)
915 UAContext *ua = new_ua_context(jcr);
918 Mmsg(ua->cmd, "run job=\"%s\" jobid=%s allowduplicates=yes", jcr->job->name(),
919 edit_uint64(jcr->MigrateJobId, ed1));
920 Dmsg2(dbglevel, "=============== %s cmd=%s\n", jcr->get_OperationName(), ua->cmd);
921 parse_ua_args(ua); /* parse command */
922 JobId_t jobid = run_cmd(ua, ua->cmd);
924 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
926 Jmsg(jcr, M_INFO, 0, _("%s JobId %d started.\n"), jcr->get_OperationName(), (int)jobid);
931 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
935 POOL_MEM query(PM_MESSAGE);
938 /* Basic query for MediaId */
939 Mmsg(query, query1, jcr->rpool->name());
940 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
941 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
944 if (ids->count == 0) {
945 Jmsg(jcr, M_INFO, 0, _("No %s found to %s.\n"), type, jcr->get_ActionName(0));
946 ok = true; /* Not an error */
948 } else if (ids->count != 1) {
949 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
952 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
954 ok = find_jobids_from_mediaid_list(jcr, ids, type);
961 * This routine returns:
962 * false if an error occurred
964 * ids.count number of jobids found (may be zero)
966 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
969 POOL_MEM query(PM_MESSAGE);
971 Mmsg(query, sql_jobids_from_mediaid, ids->list);
973 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
974 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
977 if (ids->count == 0) {
978 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
987 * This routine returns:
988 * false if an error occurred
990 * ids.count number of jobids found (may be zero)
992 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids)
995 POOL_MEM query(PM_MESSAGE);
997 /* Only a copy job is allowed */
998 if (jcr->getJobType() != JT_COPY) {
999 Jmsg(jcr, M_FATAL, 0,
1000 _("Selection Type 'pooluncopiedjobs' only applies to Copy Jobs"));
1004 Dmsg1(dbglevel, "copy selection pattern=%s\n", jcr->rpool->name());
1005 Mmsg(query, sql_jobids_of_pool_uncopied_jobs, jcr->rpool->name());
1006 Dmsg1(dbglevel, "get uncopied jobs query=%s\n", query.c_str());
1007 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1008 Jmsg(jcr, M_FATAL, 0,
1009 _("SQL to get uncopied jobs failed. ERR=%s\n"), db_strerror(jcr->db));
1018 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
1019 const char *query2, const char *type)
1023 uitem *last_item = NULL;
1028 POOL_MEM query(PM_MESSAGE);
1030 item_chain = New(dlist(item, &item->link));
1031 if (!jcr->job->selection_pattern) {
1032 Jmsg(jcr, M_FATAL, 0, _("No %s %s selection pattern specified.\n"),
1033 jcr->get_OperationName(), type);
1036 Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
1037 /* Basic query for names */
1038 Mmsg(query, query1, jcr->rpool->name());
1039 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
1040 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
1041 (void *)item_chain)) {
1042 Jmsg(jcr, M_FATAL, 0,
1043 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
1046 Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
1047 if (item_chain->size() == 0) {
1048 Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to %s.\n"),
1049 jcr->rpool->name(), jcr->get_ActionName(0));
1051 goto bail_out; /* skip regex match */
1053 /* Compile regex expression */
1054 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
1056 regerror(rc, &preg, prbuf, sizeof(prbuf));
1057 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
1058 jcr->job->selection_pattern, prbuf);
1061 /* Now apply the regex to the names and remove any item not matched */
1062 foreach_dlist(item, item_chain) {
1063 const int nmatch = 30;
1064 regmatch_t pmatch[nmatch];
1066 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1067 free(last_item->item);
1068 item_chain->remove(last_item);
1070 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
1071 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
1073 last_item = NULL; /* keep this one */
1079 free(last_item->item);
1080 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1081 item_chain->remove(last_item);
1085 if (item_chain->size() == 0) {
1086 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName(0));
1088 goto bail_out; /* skip regex match */
1092 * At this point, we have a list of items in item_chain
1093 * that have been matched by the regex, so now we need
1094 * to look up their jobids.
1097 foreach_dlist(item, item_chain) {
1098 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
1099 Mmsg(query, query2, item->item, jcr->rpool->name());
1100 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
1101 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1102 Jmsg(jcr, M_FATAL, 0,
1103 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
1107 if (ids->count == 0) {
1108 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
1113 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
1114 foreach_dlist(item, item_chain) {
1122 * Release resources allocated during backup.
1124 void migration_cleanup(JCR *jcr, int TermCode)
1126 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
1127 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
1128 char ec6[50], ec7[50], ec8[50];
1129 char term_code[100], sd_term_msg[100];
1130 const char *term_msg;
1131 int msg_type = M_INFO;
1135 JCR *mig_jcr = jcr->mig_jcr;
1136 POOL_MEM query(PM_MESSAGE);
1138 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
1139 update_job_end(jcr, TermCode);
1140 memset(&mr, 0, sizeof(mr));
1143 * Check if we actually did something.
1144 * mig_jcr is jcr of the newly migrated job.
1147 char old_jobid[50], new_jobid[50];
1149 edit_uint64(jcr->previous_jr.JobId, old_jobid);
1150 edit_uint64(mig_jcr->jr.JobId, new_jobid);
1152 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
1153 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
1154 mig_jcr->VolSessionId = jcr->VolSessionId;
1155 mig_jcr->VolSessionTime = jcr->VolSessionTime;
1156 mig_jcr->jr.RealEndTime = 0;
1157 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
1159 update_job_end(mig_jcr, TermCode);
1161 /* Update final items to set them to the previous job's values */
1162 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
1163 "JobTDate=%s WHERE JobId=%s",
1164 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
1165 edit_uint64(jcr->previous_jr.JobTDate, ec1),
1167 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1170 * If we terminated a migration normally:
1171 * - mark the previous job as migrated
1172 * - move any Log records to the new JobId
1173 * - Purge the File records from the previous job
1175 if (jcr->getJobType() == JT_MIGRATE && jcr->JobStatus == JS_Terminated) {
1176 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1177 (char)JT_MIGRATED_JOB, old_jobid);
1178 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1179 UAContext *ua = new_ua_context(jcr);
1180 /* Move JobLog to new JobId */
1181 Mmsg(query, "UPDATE Log SET JobId=%s WHERE JobId=%s",
1182 new_jobid, old_jobid);
1183 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1185 if (jcr->job->PurgeMigrateJob) {
1186 /* Purge old Job record */
1187 purge_jobs_from_catalog(ua, old_jobid);
1189 /* Purge all old file records, but leave Job record */
1190 purge_files_from_jobs(ua, old_jobid);
1193 free_ua_context(ua);
1197 * If we terminated a Copy (rather than a Migration) normally:
1198 * - copy any Log records to the new JobId
1199 * - set type="Job Copy" for the new job
1201 if (jcr->getJobType() == JT_COPY && jcr->JobStatus == JS_Terminated) {
1202 /* Copy JobLog to new JobId */
1203 Mmsg(query, "INSERT INTO Log (JobId, Time, LogText ) "
1204 "SELECT %s, Time, LogText FROM Log WHERE JobId=%s",
1205 new_jobid, old_jobid);
1206 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1207 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1208 (char)JT_JOB_COPY, new_jobid);
1209 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1212 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
1213 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
1214 db_strerror(jcr->db));
1215 jcr->setJobStatus(JS_ErrorTerminated);
1218 update_bootstrap_file(mig_jcr);
1220 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
1222 * Note, if the job has failed, most likely it did not write any
1223 * tape, so suppress this "error" message since in that case
1224 * it is normal. Or look at it the other way, only for a
1225 * normal exit should we complain about this error.
1227 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
1228 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
1230 mig_jcr->VolumeName[0] = 0; /* none */
1233 if (mig_jcr->VolumeName[0]) {
1234 /* Find last volume name. Multiple vols are separated by | */
1235 char *p = strrchr(mig_jcr->VolumeName, '|');
1239 p = mig_jcr->VolumeName; /* no |, take full name */
1241 bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1242 if (!db_get_media_record(jcr, jcr->db, &mr)) {
1243 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1244 mr.VolumeName, db_strerror(jcr->db));
1248 switch (jcr->JobStatus) {
1250 if (jcr->JobErrors || jcr->SDErrors) {
1251 term_msg = _("%s OK -- with warnings");
1253 term_msg = _("%s OK");
1257 case JS_ErrorTerminated:
1258 term_msg = _("*** %s Error ***");
1259 msg_type = M_ERROR; /* Generate error message */
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 = _("%s Canceled");
1269 if (jcr->store_bsock) {
1270 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1271 if (jcr->SD_msg_chan) {
1272 pthread_cancel(jcr->SD_msg_chan);
1277 term_msg = _("Inappropriate %s term code");
1281 if (jcr->getJobType() == JT_MIGRATE && jcr->previous_jr.JobId != 0) {
1282 /* Mark previous job as migrated */
1283 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1284 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1285 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
1287 term_msg = _("%s -- no files to %s");
1290 bsnprintf(term_code, sizeof(term_code), term_msg, jcr->get_OperationName(), jcr->get_ActionName(0));
1291 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1292 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1293 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1297 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1300 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1302 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
1303 " Build OS: %s %s %s\n"
1304 " Prev Backup JobId: %s\n"
1305 " Prev Backup Job: %s\n"
1306 " New Backup JobId: %s\n"
1307 " Current JobId: %s\n"
1308 " Current Job: %s\n"
1309 " Backup Level: %s%s\n"
1311 " FileSet: \"%s\" %s\n"
1312 " Read Pool: \"%s\" (From %s)\n"
1313 " Read Storage: \"%s\" (From %s)\n"
1314 " Write Pool: \"%s\" (From %s)\n"
1315 " Write Storage: \"%s\" (From %s)\n"
1316 " Catalog: \"%s\" (From %s)\n"
1319 " Elapsed time: %s\n"
1321 " SD Files Written: %s\n"
1322 " SD Bytes Written: %s (%sB)\n"
1323 " Rate: %.1f KB/s\n"
1324 " Volume name(s): %s\n"
1325 " Volume Session Id: %d\n"
1326 " Volume Session Time: %d\n"
1327 " Last Volume Bytes: %s (%sB)\n"
1329 " SD termination status: %s\n"
1330 " Termination: %s\n\n"),
1331 BACULA, my_name, VERSION, LSMDATE,
1332 HOST_OS, DISTNAME, DISTVER,
1333 edit_uint64(jcr->previous_jr.JobId, ec6),
1334 jcr->previous_jr.Job,
1335 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1336 edit_uint64(jcr->jr.JobId, ec8),
1338 level_to_str(jcr->getJobLevel()), jcr->since,
1339 jcr->client->name(),
1340 jcr->fileset->name(), jcr->FSCreateTime,
1341 jcr->rpool->name(), jcr->rpool_source,
1342 jcr->rstore?jcr->rstore->name():"*None*",
1343 NPRT(jcr->rstore_source),
1344 jcr->pool->name(), jcr->pool_source,
1345 jcr->wstore?jcr->wstore->name():"*None*",
1346 NPRT(jcr->wstore_source),
1347 jcr->catalog->name(), jcr->catalog_source,
1350 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1352 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1353 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1354 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1356 mig_jcr ? mig_jcr->VolumeName : "",
1358 jcr->VolSessionTime,
1359 edit_uint64_with_commas(mr.VolBytes, ec4),
1360 edit_uint64_with_suffix(mr.VolBytes, ec5),
1365 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1367 free_jcr(jcr->mig_jcr);
1368 jcr->mig_jcr = NULL;
1370 Dmsg0(100, "Leave migrate_cleanup()\n");
1374 * Return next DBId from comma separated list
1377 * 1 if next DBId returned
1378 * 0 if no more DBIds are in list
1379 * -1 there is an error
1381 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1383 const int maxlen = 30;
1388 for (int i=0; i<maxlen; i++) {
1391 } else if (*q == ',') {
1400 } else if (!is_a_number(id)) {
1401 return -1; /* error */
1404 *DBId = str_to_int64(id);
1408 bool set_migration_wstorage(JCR *jcr, POOL *pool)
1410 POOL *wpool = pool->NextPool;
1413 Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
1418 if (!wpool->storage || wpool->storage->size() == 0) {
1419 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
1424 /* If pool storage specified, use it instead of job storage for backup */
1425 copy_wstorage(jcr, wpool->storage, _("Storage from Pool's NextPool resource"));