2 Bacula® - The Network Backup Solution
4 Copyright (C) 2004-2009 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.
51 #include "lib/bregex.h"
56 static const int dbglevel = 10;
58 static int getJob_to_migrate(JCR *jcr);
60 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
61 const char *query2, const char *type);
62 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
64 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type);
65 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids);
66 static void start_migration_job(JCR *jcr);
67 static int get_next_dbid_from_list(char **p, DBId_t *DBId);
68 static bool set_migration_next_pool(JCR *jcr, POOL **pool);
71 * Called here before the job is run to do the job
72 * specific setup. Note, one of the important things to
73 * complete in this init code is to make the definitive
74 * choice of input and output storage devices. This is
75 * because immediately after the init, the job is queued
76 * in the jobq.c code, and it checks that all the resources
77 * (storage resources in particular) are available, so these
78 * must all be properly defined.
80 * previous_jr refers to the job DB record of the Job that is
81 * going to be migrated.
82 * prev_job refers to the job resource of the Job that is
83 * going to be migrated.
84 * jcr is the jcr for the current "migration" job. It is a
85 * control job that is put in the DB as a migration job, which
86 * means that this job migrated a previous job to a new job.
87 * No Volume or File data is associated with this control
89 * mig_jcr refers to the newly migrated job that is run by
90 * the current jcr. It is a backup job that moves (migrates) the
91 * data written for the previous_jr into the new pool. This
92 * job (mig_jcr) becomes the new backup job that replaces
93 * the original backup job. Note, this jcr is not really run. It
94 * is simply attached to the current jcr. It will show up in
95 * the Director's status output, but not in the SD or FD, both of
96 * which deal only with the current migration job (i.e. jcr).
98 bool do_migration_init(JCR *jcr)
102 JCR *mig_jcr; /* newly migrated job */
106 apply_pool_overrides(jcr);
108 if (!allow_duplicate_job(jcr)) {
112 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
113 if (jcr->jr.PoolId == 0) {
114 Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
115 Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
119 * Note, at this point, pool is the pool for this job. We
120 * transfer it to rpool (read pool), and a bit later,
121 * pool will be changed to point to the write pool,
122 * which comes from pool->NextPool.
124 jcr->rpool = jcr->pool; /* save read pool */
125 pm_strcpy(jcr->rpool_source, jcr->pool_source);
128 Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
130 if (!get_or_create_fileset_record(jcr)) {
131 Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
132 Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
136 /* If we find a job or jobs to migrate it is previous_jr.JobId */
137 count = getJob_to_migrate(jcr);
142 set_migration_next_pool(jcr, &pool);
143 return true; /* no work */
146 Dmsg1(dbglevel, "Back from getJob_to_migrate JobId=%d\n", (int)jcr->JobId);
148 if (jcr->previous_jr.JobId == 0) {
149 Dmsg1(dbglevel, "JobId=%d no previous JobId\n", (int)jcr->JobId);
150 Jmsg(jcr, M_INFO, 0, _("No previous Job found to %s.\n"), jcr->get_ActionName(0));
151 set_migration_next_pool(jcr, &pool);
152 return true; /* no work */
155 if (create_restore_bootstrap_file(jcr) < 0) {
156 Jmsg(jcr, M_FATAL, 0, _("Create bootstrap file failed.\n"));
160 if (jcr->previous_jr.JobId == 0 || jcr->ExpectedFiles == 0) {
161 set_jcr_job_status(jcr, JS_Terminated);
162 Dmsg1(dbglevel, "JobId=%d expected files == 0\n", (int)jcr->JobId);
163 if (jcr->previous_jr.JobId == 0) {
164 Jmsg(jcr, M_INFO, 0, _("No previous Job found to %s.\n"), jcr->get_ActionName(0));
166 Jmsg(jcr, M_INFO, 0, _("Previous Job has no data to %s.\n"), jcr->get_ActionName(0));
168 set_migration_next_pool(jcr, &pool);
169 return true; /* no work */
173 Dmsg5(dbglevel, "JobId=%d: Current: Name=%s JobId=%d Type=%c Level=%c\n",
175 jcr->jr.Name, (int)jcr->jr.JobId,
176 jcr->jr.JobType, jcr->jr.JobLevel);
179 job = (JOB *)GetResWithName(R_JOB, jcr->jr.Name);
180 prev_job = (JOB *)GetResWithName(R_JOB, jcr->previous_jr.Name);
183 Jmsg(jcr, M_FATAL, 0, _("Job resource not found for \"%s\".\n"), jcr->jr.Name);
187 Jmsg(jcr, M_FATAL, 0, _("Previous Job resource not found for \"%s\".\n"),
188 jcr->previous_jr.Name);
192 jcr->spool_data = job->spool_data; /* turn on spooling if requested in job */
194 /* Create a migration jcr */
195 mig_jcr = jcr->mig_jcr = new_jcr(sizeof(JCR), dird_free_jcr);
196 memcpy(&mig_jcr->previous_jr, &jcr->previous_jr, sizeof(mig_jcr->previous_jr));
199 * Turn the mig_jcr into a "real" job that takes on the aspects of
200 * the previous backup job "prev_job".
202 set_jcr_defaults(mig_jcr, prev_job);
203 if (!setup_job(mig_jcr)) {
204 Jmsg(jcr, M_FATAL, 0, _("setup job failed.\n"));
208 /* Now reset the job record from the previous job */
209 memcpy(&mig_jcr->jr, &jcr->previous_jr, sizeof(mig_jcr->jr));
210 /* Update the jr to reflect the new values of PoolId and JobId. */
211 mig_jcr->jr.PoolId = jcr->jr.PoolId;
212 mig_jcr->jr.JobId = mig_jcr->JobId;
214 /* Don't let WatchDog checks Max*Time value on this Job */
215 mig_jcr->no_maxtime = true;
217 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
218 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
219 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
221 if (set_migration_next_pool(jcr, &pool)) {
222 /* If pool storage specified, use it for restore */
223 copy_rstorage(mig_jcr, pool->storage, _("Pool resource"));
224 copy_rstorage(jcr, pool->storage, _("Pool resource"));
226 mig_jcr->pool = jcr->pool;
227 mig_jcr->jr.PoolId = jcr->jr.PoolId;
235 * set_migration_next_pool() called by do_migration_init()
236 * at differents stages.
237 * The idea here is tofactorize the NextPool's search code and
238 * to permit do_migration_init() to return with NextPool set in jcr struct.
240 static bool set_migration_next_pool(JCR *jcr, POOL **retpool)
247 * Get the PoolId used with the original job. Then
248 * find the pool name from the database record.
250 memset(&pr, 0, sizeof(pr));
251 pr.PoolId = jcr->jr.PoolId;
252 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
253 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
254 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
257 /* Get the pool resource corresponding to the original job */
258 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
261 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
266 * If the original backup pool has a NextPool, make sure a
267 * record exists in the database. Note, in this case, we
268 * will be migrating from pool to pool->NextPool.
270 if (pool->NextPool) {
271 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->name());
272 if (jcr->jr.PoolId == 0) {
276 if (!set_migration_wstorage(jcr, pool)) {
279 jcr->pool = pool->NextPool;
280 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
282 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
289 * Do a Migration of a previous job
291 * Returns: false on failure
294 bool do_migration(JCR *jcr)
298 JCR *mig_jcr = jcr->mig_jcr; /* newly migrated job */
301 * If mig_jcr is NULL, there is nothing to do for this job,
302 * so set a normal status, cleanup and return OK.
305 set_jcr_job_status(jcr, JS_Terminated);
306 migration_cleanup(jcr, jcr->JobStatus);
310 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
311 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
312 edit_int64(jcr->previous_jr.JobId, ed1),
313 jcr->get_ActionName(0),
314 db_strerror(jcr->db));
315 set_jcr_job_status(jcr, JS_Terminated);
316 migration_cleanup(jcr, jcr->JobStatus);
319 /* Make sure this job was not already migrated */
320 if (jcr->previous_jr.JobType != JT_BACKUP &&
321 jcr->previous_jr.JobType != JT_COPY) {
322 Jmsg(jcr, M_INFO, 0, _("JobId %s already %s probably by another Job. %s stopped.\n"),
323 edit_int64(jcr->previous_jr.JobId, ed1),
324 jcr->get_ActionName(1),
325 jcr->get_OperationName());
326 set_jcr_job_status(jcr, JS_Terminated);
327 migration_cleanup(jcr, jcr->JobStatus);
331 /* Print Job Start message */
332 Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
333 jcr->get_OperationName(), edit_uint64(jcr->JobId, ed1), jcr->Job);
336 * Open a message channel connection with the Storage
337 * daemon. This is to let him know that our client
338 * will be contacting him for a backup session.
341 Dmsg0(110, "Open connection with storage daemon\n");
342 set_jcr_job_status(jcr, JS_WaitSD);
343 set_jcr_job_status(mig_jcr, JS_WaitSD);
345 * Start conversation with Storage daemon
347 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
350 sd = jcr->store_bsock;
352 * Now start a job with the Storage daemon
354 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
355 ((STORE *)jcr->rstorage->first())->name(),
356 ((STORE *)jcr->wstorage->first())->name());
358 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage, /*send_bsr*/true)) {
361 Dmsg0(150, "Storage daemon connection OK\n");
365 * We re-update the job start record so that the start
366 * time is set after the run before job. This avoids
367 * that any files created by the run before job will
368 * be saved twice. They will be backed up in the current
369 * job, but not in the next one unless they are changed.
370 * Without this, they will be backed up in this job and
371 * in the next job run because in that case, their date
372 * is after the start of this run.
374 jcr->start_time = time(NULL);
375 jcr->jr.StartTime = jcr->start_time;
376 jcr->jr.JobTDate = jcr->start_time;
377 set_jcr_job_status(jcr, JS_Running);
379 /* Update job start record for this migration control job */
380 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
381 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
386 mig_jcr->start_time = time(NULL);
387 mig_jcr->jr.StartTime = mig_jcr->start_time;
388 mig_jcr->jr.JobTDate = mig_jcr->start_time;
389 set_jcr_job_status(mig_jcr, JS_Running);
391 /* Update job start record for the real migration backup job */
392 if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
393 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
397 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
398 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
399 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
403 * Start the job prior to starting the message thread below
404 * to avoid two threads from using the BSOCK structure at
407 if (!sd->fsend("run")) {
412 * Now start a Storage daemon message thread
414 if (!start_storage_daemon_message_thread(jcr)) {
419 set_jcr_job_status(jcr, JS_Running);
420 set_jcr_job_status(mig_jcr, JS_Running);
422 /* Pickup Job termination data */
423 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
424 wait_for_storage_daemon_termination(jcr);
425 set_jcr_job_status(jcr, jcr->SDJobStatus);
426 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
427 if (jcr->JobStatus != JS_Terminated) {
431 migration_cleanup(jcr, jcr->JobStatus);
441 /* Add an item to the list if it is unique */
442 static void add_unique_id(idpkt *ids, char *item)
444 const int maxlen = 30;
448 /* Walk through current list to see if each item is the same as item */
451 for (int i=0; i<maxlen; i++) {
454 } else if (*q == ',') {
461 if (strcmp(item, id) == 0) {
465 /* Did not find item, so add it to list */
466 if (ids->count == 0) {
469 pm_strcat(ids->list, ",");
471 pm_strcat(ids->list, item);
473 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
478 * Callback handler make list of DB Ids
480 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
482 idpkt *ids = (idpkt *)ctx;
485 if (!row || !row[0]) {
486 Dmsg0(dbglevel, "dbid_hdlr error empty row\n");
487 return 1; /* stop calling us */
490 add_unique_id(ids, row[0]);
491 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
501 static int item_compare(void *item1, void *item2)
503 uitem *i1 = (uitem *)item1;
504 uitem *i2 = (uitem *)item2;
505 return strcmp(i1->item, i2->item);
508 static int unique_name_handler(void *ctx, int num_fields, char **row)
510 dlist *list = (dlist *)ctx;
512 uitem *new_item = (uitem *)malloc(sizeof(uitem));
515 memset(new_item, 0, sizeof(uitem));
516 new_item->item = bstrdup(row[0]);
517 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
518 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
519 if (item != new_item) { /* already in list */
520 free(new_item->item);
521 free((char *)new_item);
527 /* Get Job names in Pool */
528 const char *sql_job =
529 "SELECT DISTINCT Job.Name from Job,Pool"
530 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
532 /* Get JobIds from regex'ed Job names */
533 const char *sql_jobids_from_job =
534 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
535 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
536 " ORDER by Job.StartTime";
538 /* Get Client names in Pool */
539 const char *sql_client =
540 "SELECT DISTINCT Client.Name from Client,Pool,Job"
541 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
542 " Job.PoolId=Pool.PoolId";
544 /* Get JobIds from regex'ed Client names */
545 const char *sql_jobids_from_client =
546 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
547 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
548 " AND Job.ClientId=Client.ClientId AND Job.Type IN ('B','C')"
549 " AND Job.JobStatus IN ('T','W')"
550 " ORDER by Job.StartTime";
552 /* Get Volume names in Pool */
553 const char *sql_vol =
554 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
555 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
556 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
558 /* Get JobIds from regex'ed Volume names */
559 const char *sql_jobids_from_vol =
560 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
561 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
562 " AND JobMedia.JobId=Job.JobId AND Job.Type IN ('B','C')"
563 " AND Job.JobStatus IN ('T','W') AND Media.Enabled=1"
564 " ORDER by Job.StartTime";
566 const char *sql_smallest_vol =
567 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
568 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
569 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
570 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
571 " ORDER BY VolBytes ASC LIMIT 1";
573 const char *sql_oldest_vol =
574 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
575 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
576 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
577 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
578 " ORDER BY LastWritten ASC LIMIT 1";
580 /* Get JobIds when we have selected MediaId */
581 const char *sql_jobids_from_mediaid =
582 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
583 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId IN (%s)"
584 " AND Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
585 " ORDER by Job.StartTime";
587 /* Get the number of bytes in the pool */
588 const char *sql_pool_bytes =
589 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN"
590 " (SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
591 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
592 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
593 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
594 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId)";
596 /* Get the number of bytes in the Jobs */
597 const char *sql_job_bytes =
598 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
600 /* Get Media Ids in Pool */
601 const char *sql_mediaids =
602 "SELECT MediaId FROM Media,Pool WHERE"
603 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
604 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
606 /* Get JobIds in Pool longer than specified time */
607 const char *sql_pool_time =
608 "SELECT DISTINCT Job.JobId FROM Pool,Job,Media,JobMedia WHERE"
609 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
610 " VolStatus IN ('Full','Used','Error') AND Media.Enabled=1 AND"
611 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
612 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
613 " AND Job.RealEndTime<='%s'";
615 /* Get JobIds from successfully completed backup jobs which have not been copied before */
616 const char *sql_jobids_of_pool_uncopied_jobs =
617 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
618 " WHERE Pool.Name = '%s' AND Pool.PoolId = Job.PoolId"
619 " AND Job.Type = 'B' AND Job.JobStatus IN ('T','W')"
620 " AND Job.jobBytes > 0"
621 " AND Job.JobId NOT IN"
622 " (SELECT PriorJobId FROM Job WHERE"
623 " Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
624 " AND PriorJobId != 0)"
625 " ORDER by Job.StartTime";
628 * const char *sql_ujobid =
629 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
630 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
631 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
636 * This is the central piece of code that finds a job or jobs
637 * actually JobIds to migrate. It first looks to see if one
638 * has been "manually" specified in jcr->MigrateJobId, and if
639 * so, it returns that JobId to be run. Otherwise, it
640 * examines the Selection Type to see what kind of migration
641 * we are doing (Volume, Job, Client, ...) and applies any
642 * Selection Pattern if appropriate to obtain a list of JobIds.
643 * Finally, it will loop over all the JobIds found, except the last
644 * one starting a new job with MigrationJobId set to that JobId, and
645 * finally, it returns the last JobId to the caller.
647 * Returns: -1 on error
648 * 0 if no jobs to migrate
649 * 1 if OK and jcr->previous_jr filled in
651 static int getJob_to_migrate(JCR *jcr)
653 char ed1[30], ed2[30];
654 POOL_MEM query(PM_MESSAGE);
659 idpkt ids, mid, jids;
664 char dt[MAX_TIME_LENGTH];
667 ids.list = get_pool_memory(PM_MESSAGE);
670 mid.list = get_pool_memory(PM_MESSAGE);
673 jids.list = get_pool_memory(PM_MESSAGE);
679 * If MigrateJobId is set, then we migrate only that Job,
680 * otherwise, we go through the full selection of jobs to
683 if (jcr->MigrateJobId != 0) {
684 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
685 edit_uint64(jcr->MigrateJobId, ids.list);
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());
837 * Loop over all jobids except the last one, sending
838 * them to start_migration_job(), which will start a job
839 * for each of them. For the last JobId, we handle it below.
842 if (ids.count == 0) {
843 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
847 Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s chosen to be %s: %s\n"),
848 ids.count, (ids.count < 2) ? _(" was") : _("s were"),
849 jcr->get_ActionName(1), ids.list);
851 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
852 for (int i=1; i < (int)ids.count; i++) {
854 stat = get_next_jobid_from_list(&p, &JobId);
855 Dmsg3(dbglevel, "getJobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
857 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
859 } else if (stat == 0) {
860 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
863 jcr->MigrateJobId = JobId;
864 start_migration_job(jcr);
865 Dmsg0(dbglevel, "Back from start_migration_job\n");
868 /* Now get the last JobId and handle it in the current job */
870 stat = get_next_jobid_from_list(&p, &JobId);
871 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
873 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
875 } else if (stat == 0) {
876 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));
890 Jmsg(jcr, M_INFO, 0, _("%s using JobId=%s Job=%s\n"),
891 jcr->get_OperationName(),
892 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
893 Dmsg4(dbglevel, "%s JobId=%d using JobId=%s Job=%s\n",
894 jcr->get_OperationName(),
896 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
906 free_pool_memory(ids.list);
907 free_pool_memory(mid.list);
908 free_pool_memory(jids.list);
912 static void start_migration_job(JCR *jcr)
914 UAContext *ua = new_ua_context(jcr);
917 Mmsg(ua->cmd, "run job=\"%s\" jobid=%s", jcr->job->name(),
918 edit_uint64(jcr->MigrateJobId, ed1));
919 Dmsg2(dbglevel, "=============== %s cmd=%s\n", jcr->get_OperationName(), ua->cmd);
920 parse_ua_args(ua); /* parse command */
921 JobId_t jobid = run_cmd(ua, ua->cmd);
923 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
925 Jmsg(jcr, M_INFO, 0, _("%s JobId %d started.\n"), jcr->get_OperationName(), (int)jobid);
930 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
934 POOL_MEM query(PM_MESSAGE);
937 /* Basic query for MediaId */
938 Mmsg(query, query1, jcr->rpool->name());
939 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
940 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
943 if (ids->count == 0) {
944 Jmsg(jcr, M_INFO, 0, _("No %s found to %s.\n"), type, jcr->get_ActionName(0));
945 ok = true; /* Not an error */
947 } else if (ids->count != 1) {
948 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
951 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
953 ok = find_jobids_from_mediaid_list(jcr, ids, type);
960 * This routine returns:
961 * false if an error occurred
963 * ids.count number of jobids found (may be zero)
965 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
968 POOL_MEM query(PM_MESSAGE);
970 Mmsg(query, sql_jobids_from_mediaid, ids->list);
972 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
973 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
976 if (ids->count == 0) {
977 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
986 * This routine returns:
987 * false if an error occurred
989 * ids.count number of jobids found (may be zero)
991 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids)
994 POOL_MEM query(PM_MESSAGE);
996 /* Only a copy job is allowed */
997 if (jcr->getJobType() != JT_COPY) {
998 Jmsg(jcr, M_FATAL, 0,
999 _("Selection Type 'pooluncopiedjobs' only applies to Copy Jobs"));
1003 Dmsg1(dbglevel, "copy selection pattern=%s\n", jcr->rpool->name());
1004 Mmsg(query, sql_jobids_of_pool_uncopied_jobs, jcr->rpool->name());
1005 Dmsg1(dbglevel, "get uncopied jobs query=%s\n", query.c_str());
1006 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1007 Jmsg(jcr, M_FATAL, 0,
1008 _("SQL to get uncopied jobs failed. ERR=%s\n"), db_strerror(jcr->db));
1017 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
1018 const char *query2, const char *type)
1022 uitem *last_item = NULL;
1027 POOL_MEM query(PM_MESSAGE);
1029 item_chain = New(dlist(item, &item->link));
1030 if (!jcr->job->selection_pattern) {
1031 Jmsg(jcr, M_FATAL, 0, _("No %s %s selection pattern specified.\n"),
1032 jcr->get_OperationName(), type);
1035 Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
1036 /* Basic query for names */
1037 Mmsg(query, query1, jcr->rpool->name());
1038 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
1039 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
1040 (void *)item_chain)) {
1041 Jmsg(jcr, M_FATAL, 0,
1042 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
1045 Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
1046 if (item_chain->size() == 0) {
1047 Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to %s.\n"),
1048 jcr->rpool->name(), jcr->get_ActionName(0));
1050 goto bail_out; /* skip regex match */
1052 /* Compile regex expression */
1053 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
1055 regerror(rc, &preg, prbuf, sizeof(prbuf));
1056 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
1057 jcr->job->selection_pattern, prbuf);
1060 /* Now apply the regex to the names and remove any item not matched */
1061 foreach_dlist(item, item_chain) {
1062 const int nmatch = 30;
1063 regmatch_t pmatch[nmatch];
1065 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1066 free(last_item->item);
1067 item_chain->remove(last_item);
1069 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
1070 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
1072 last_item = NULL; /* keep this one */
1078 free(last_item->item);
1079 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1080 item_chain->remove(last_item);
1084 if (item_chain->size() == 0) {
1085 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName(0));
1087 goto bail_out; /* skip regex match */
1091 * At this point, we have a list of items in item_chain
1092 * that have been matched by the regex, so now we need
1093 * to look up their jobids.
1096 foreach_dlist(item, item_chain) {
1097 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
1098 Mmsg(query, query2, item->item, jcr->rpool->name());
1099 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
1100 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1101 Jmsg(jcr, M_FATAL, 0,
1102 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
1106 if (ids->count == 0) {
1107 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
1112 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
1113 foreach_dlist(item, item_chain) {
1121 * Release resources allocated during backup.
1123 void migration_cleanup(JCR *jcr, int TermCode)
1125 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
1126 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
1127 char ec6[50], ec7[50], ec8[50];
1128 char term_code[100], sd_term_msg[100];
1129 const char *term_msg;
1130 int msg_type = M_INFO;
1134 JCR *mig_jcr = jcr->mig_jcr;
1135 POOL_MEM query(PM_MESSAGE);
1137 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
1138 update_job_end(jcr, TermCode);
1139 memset(&mr, 0, sizeof(mr));
1142 * Check if we actually did something.
1143 * mig_jcr is jcr of the newly migrated job.
1146 char old_jobid[50], new_jobid[50];
1148 edit_uint64(jcr->previous_jr.JobId, old_jobid);
1149 edit_uint64(mig_jcr->jr.JobId, new_jobid);
1151 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
1152 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
1153 mig_jcr->VolSessionId = jcr->VolSessionId;
1154 mig_jcr->VolSessionTime = jcr->VolSessionTime;
1155 mig_jcr->jr.RealEndTime = 0;
1156 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
1158 update_job_end(mig_jcr, TermCode);
1160 /* Update final items to set them to the previous job's values */
1161 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
1162 "JobTDate=%s WHERE JobId=%s",
1163 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
1164 edit_uint64(jcr->previous_jr.JobTDate, ec1),
1166 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1169 * If we terminated a migration normally:
1170 * - mark the previous job as migrated
1171 * - move any Log records to the new JobId
1172 * - Purge the File records from the previous job
1174 if (jcr->getJobType() == JT_MIGRATE && jcr->JobStatus == JS_Terminated) {
1175 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1176 (char)JT_MIGRATED_JOB, old_jobid);
1177 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1178 UAContext *ua = new_ua_context(jcr);
1179 /* Move JobLog to new JobId */
1180 Mmsg(query, "UPDATE Log SET JobId=%s WHERE JobId=%s",
1181 new_jobid, old_jobid);
1182 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1183 /* Purge all old file records, but leave Job record */
1184 purge_files_from_jobs(ua, old_jobid);
1185 free_ua_context(ua);
1189 * If we terminated a Copy (rather than a Migration) normally:
1190 * - copy any Log records to the new JobId
1191 * - set type="Job Copy" for the new job
1193 if (jcr->getJobType() == JT_COPY && jcr->JobStatus == JS_Terminated) {
1194 /* Copy JobLog to new JobId */
1195 Mmsg(query, "INSERT INTO Log (JobId, Time, LogText ) "
1196 "SELECT %s, Time, LogText FROM Log WHERE JobId=%s",
1197 new_jobid, old_jobid);
1198 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1199 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1200 (char)JT_JOB_COPY, new_jobid);
1201 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1204 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
1205 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
1206 db_strerror(jcr->db));
1207 set_jcr_job_status(jcr, JS_ErrorTerminated);
1210 update_bootstrap_file(mig_jcr);
1212 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
1214 * Note, if the job has failed, most likely it did not write any
1215 * tape, so suppress this "error" message since in that case
1216 * it is normal. Or look at it the other way, only for a
1217 * normal exit should we complain about this error.
1219 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
1220 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
1222 mig_jcr->VolumeName[0] = 0; /* none */
1225 if (mig_jcr->VolumeName[0]) {
1226 /* Find last volume name. Multiple vols are separated by | */
1227 char *p = strrchr(mig_jcr->VolumeName, '|');
1231 p = mig_jcr->VolumeName; /* no |, take full name */
1233 bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1234 if (!db_get_media_record(jcr, jcr->db, &mr)) {
1235 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1236 mr.VolumeName, db_strerror(jcr->db));
1240 switch (jcr->JobStatus) {
1242 if (jcr->JobErrors || jcr->SDErrors) {
1243 term_msg = _("%s OK -- with warnings");
1245 term_msg = _("%s OK");
1249 case JS_ErrorTerminated:
1250 term_msg = _("*** %s Error ***");
1251 msg_type = M_ERROR; /* Generate error message */
1252 if (jcr->store_bsock) {
1253 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1254 if (jcr->SD_msg_chan) {
1255 pthread_cancel(jcr->SD_msg_chan);
1260 term_msg = _("%s Canceled");
1261 if (jcr->store_bsock) {
1262 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1263 if (jcr->SD_msg_chan) {
1264 pthread_cancel(jcr->SD_msg_chan);
1269 term_msg = _("Inappropriate %s term code");
1273 if (jcr->getJobType() == JT_MIGRATE && jcr->previous_jr.JobId != 0) {
1274 /* Mark previous job as migrated */
1275 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1276 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1277 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
1279 term_msg = _("%s -- no files to %s");
1282 bsnprintf(term_code, sizeof(term_code), term_msg, jcr->get_OperationName(), jcr->get_ActionName(0));
1283 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1284 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1285 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1289 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1292 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1294 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
1295 " Build OS: %s %s %s\n"
1296 " Prev Backup JobId: %s\n"
1297 " Prev Backup Job: %s\n"
1298 " New Backup JobId: %s\n"
1299 " Current JobId: %s\n"
1300 " Current Job: %s\n"
1301 " Backup Level: %s%s\n"
1303 " FileSet: \"%s\" %s\n"
1304 " Read Pool: \"%s\" (From %s)\n"
1305 " Read Storage: \"%s\" (From %s)\n"
1306 " Write Pool: \"%s\" (From %s)\n"
1307 " Write Storage: \"%s\" (From %s)\n"
1308 " Catalog: \"%s\" (From %s)\n"
1311 " Elapsed time: %s\n"
1313 " SD Files Written: %s\n"
1314 " SD Bytes Written: %s (%sB)\n"
1315 " Rate: %.1f KB/s\n"
1316 " Volume name(s): %s\n"
1317 " Volume Session Id: %d\n"
1318 " Volume Session Time: %d\n"
1319 " Last Volume Bytes: %s (%sB)\n"
1321 " SD termination status: %s\n"
1322 " Termination: %s\n\n"),
1323 BACULA, my_name, VERSION, LSMDATE, edt,
1324 HOST_OS, DISTNAME, DISTVER,
1325 edit_uint64(jcr->previous_jr.JobId, ec6),
1326 jcr->previous_jr.Job,
1327 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1328 edit_uint64(jcr->jr.JobId, ec8),
1330 level_to_str(jcr->getJobLevel()), jcr->since,
1331 jcr->client->name(),
1332 jcr->fileset->name(), jcr->FSCreateTime,
1333 jcr->rpool->name(), jcr->rpool_source,
1334 jcr->rstore?jcr->rstore->name():"*None*",
1335 NPRT(jcr->rstore_source),
1336 jcr->pool->name(), jcr->pool_source,
1337 jcr->wstore?jcr->wstore->name():"*None*",
1338 NPRT(jcr->wstore_source),
1339 jcr->catalog->name(), jcr->catalog_source,
1342 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1344 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1345 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1346 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1348 mig_jcr ? mig_jcr->VolumeName : "",
1350 jcr->VolSessionTime,
1351 edit_uint64_with_commas(mr.VolBytes, ec4),
1352 edit_uint64_with_suffix(mr.VolBytes, ec5),
1357 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1359 free_jcr(jcr->mig_jcr);
1360 jcr->mig_jcr = NULL;
1362 Dmsg0(100, "Leave migrate_cleanup()\n");
1366 * Return next DBId from comma separated list
1369 * 1 if next DBId returned
1370 * 0 if no more DBIds are in list
1371 * -1 there is an error
1373 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1375 const int maxlen = 30;
1380 for (int i=0; i<maxlen; i++) {
1383 } else if (*q == ',') {
1392 } else if (!is_a_number(id)) {
1393 return -1; /* error */
1396 *DBId = str_to_int64(id);
1400 bool set_migration_wstorage(JCR *jcr, POOL *pool)
1402 POOL *wpool = pool->NextPool;
1405 Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
1410 if (!wpool->storage || wpool->storage->size() == 0) {
1411 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
1416 /* If pool storage specified, use it instead of job storage for backup */
1417 copy_wstorage(jcr, wpool->storage, _("Storage from Pool's NextPool resource"));