2 Bacula® - The Network Backup Solution
4 Copyright (C) 2004-2012 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 to make a common subroutine for the
242 * NextPool's search code and to permit do_migration_init()
243 * to return with NextPool set in jcr struct.
245 static bool set_migration_next_pool(JCR *jcr, POOL **retpool)
252 * Get the PoolId used with the original job. Then
253 * find the pool name from the database record.
255 memset(&pr, 0, sizeof(pr));
256 pr.PoolId = jcr->jr.PoolId;
257 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
258 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
259 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
262 /* Get the pool resource corresponding to the original job */
263 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
266 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
271 * If the original backup pool has a NextPool, make sure a
272 * record exists in the database. Note, in this case, we
273 * will be migrating from pool to pool->NextPool.
275 if (pool->NextPool) {
276 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->name());
277 if (jcr->jr.PoolId == 0) {
281 if (!set_migration_wstorage(jcr, pool)) {
284 jcr->pool = pool->NextPool;
285 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
287 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
294 * Do a Migration of a previous job
296 * Returns: false on failure
299 bool do_migration(JCR *jcr)
303 JCR *mig_jcr = jcr->mig_jcr; /* newly migrated job */
306 * If mig_jcr is NULL, there is nothing to do for this job,
307 * so set a normal status, cleanup and return OK.
310 jcr->setJobStatus(JS_Terminated);
311 migration_cleanup(jcr, jcr->JobStatus);
315 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
316 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
317 edit_int64(jcr->previous_jr.JobId, ed1),
318 jcr->get_ActionName(0),
319 db_strerror(jcr->db));
320 jcr->setJobStatus(JS_Terminated);
321 migration_cleanup(jcr, jcr->JobStatus);
324 /* Make sure this job was not already migrated */
325 if (jcr->previous_jr.JobType != JT_BACKUP &&
326 jcr->previous_jr.JobType != JT_JOB_COPY) {
327 Jmsg(jcr, M_INFO, 0, _("JobId %s already %s probably by another Job. %s stopped.\n"),
328 edit_int64(jcr->previous_jr.JobId, ed1),
329 jcr->get_ActionName(1),
330 jcr->get_OperationName());
331 jcr->setJobStatus(JS_Terminated);
332 migration_cleanup(jcr, jcr->JobStatus);
336 /* Print Job Start message */
337 Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
338 jcr->get_OperationName(), edit_uint64(jcr->JobId, ed1), jcr->Job);
341 * Open a message channel connection with the Storage
342 * daemon. This is to let him know that our client
343 * will be contacting him for a backup session.
346 Dmsg0(110, "Open connection with storage daemon\n");
347 jcr->setJobStatus(JS_WaitSD);
348 mig_jcr->setJobStatus(JS_WaitSD);
350 * Start conversation with Storage daemon
352 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
355 sd = jcr->store_bsock;
357 * Now start a job with the Storage daemon
359 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
360 ((STORE *)jcr->rstorage->first())->name(),
361 ((STORE *)jcr->wstorage->first())->name());
363 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage, /*send_bsr*/true)) {
366 Dmsg0(150, "Storage daemon connection OK\n");
368 /* Declare the job started to start the MaxRunTime check */
369 jcr->setJobStarted();
372 * We re-update the job start record so that the start
373 * time is set after the run before job. This avoids
374 * that any files created by the run before job will
375 * be saved twice. They will be backed up in the current
376 * job, but not in the next one unless they are changed.
377 * Without this, they will be backed up in this job and
378 * in the next job run because in that case, their date
379 * is after the start of this run.
381 jcr->start_time = time(NULL);
382 jcr->jr.StartTime = jcr->start_time;
383 jcr->jr.JobTDate = jcr->start_time;
384 jcr->setJobStatus(JS_Running);
386 /* Update job start record for this migration control job */
387 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
388 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
393 mig_jcr->start_time = time(NULL);
394 mig_jcr->jr.StartTime = mig_jcr->start_time;
395 mig_jcr->jr.JobTDate = mig_jcr->start_time;
396 mig_jcr->setJobStatus(JS_Running);
398 /* Update job start record for the real migration backup job */
399 if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
400 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
404 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
405 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
406 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
410 * Start the job prior to starting the message thread below
411 * to avoid two threads from using the BSOCK structure at
414 if (!sd->fsend("run")) {
419 * Now start a Storage daemon message thread
421 if (!start_storage_daemon_message_thread(jcr)) {
426 jcr->setJobStatus(JS_Running);
427 mig_jcr->setJobStatus(JS_Running);
429 /* Pickup Job termination data */
430 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
431 wait_for_storage_daemon_termination(jcr);
432 jcr->setJobStatus(jcr->SDJobStatus);
433 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
434 if (jcr->JobStatus != JS_Terminated) {
438 migration_cleanup(jcr, jcr->JobStatus);
448 /* Add an item to the list if it is unique */
449 static void add_unique_id(idpkt *ids, char *item)
451 const int maxlen = 30;
455 /* Walk through current list to see if each item is the same as item */
458 for (int i=0; i<maxlen; i++) {
461 } else if (*q == ',') {
468 if (strcmp(item, id) == 0) {
472 /* Did not find item, so add it to list */
473 if (ids->count == 0) {
476 pm_strcat(ids->list, ",");
478 pm_strcat(ids->list, item);
480 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
485 * Callback handler make list of DB Ids
487 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
489 idpkt *ids = (idpkt *)ctx;
492 if (!row || !row[0]) {
493 Dmsg0(dbglevel, "dbid_hdlr error empty row\n");
494 return 1; /* stop calling us */
497 add_unique_id(ids, row[0]);
498 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
508 static int item_compare(void *item1, void *item2)
510 uitem *i1 = (uitem *)item1;
511 uitem *i2 = (uitem *)item2;
512 return strcmp(i1->item, i2->item);
515 static int unique_name_handler(void *ctx, int num_fields, char **row)
517 dlist *list = (dlist *)ctx;
519 uitem *new_item = (uitem *)malloc(sizeof(uitem));
522 memset(new_item, 0, sizeof(uitem));
523 new_item->item = bstrdup(row[0]);
524 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
525 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
526 if (item != new_item) { /* already in list */
527 free(new_item->item);
528 free((char *)new_item);
534 /* Get Job names in Pool */
535 const char *sql_job =
536 "SELECT DISTINCT Job.Name from Job,Pool"
537 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
539 /* Get JobIds from regex'ed Job names */
540 const char *sql_jobids_from_job =
541 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
542 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
543 " ORDER by Job.StartTime";
545 /* Get Client names in Pool */
546 const char *sql_client =
547 "SELECT DISTINCT Client.Name from Client,Pool,Job"
548 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
549 " Job.PoolId=Pool.PoolId";
551 /* Get JobIds from regex'ed Client names */
552 const char *sql_jobids_from_client =
553 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
554 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
555 " AND Job.ClientId=Client.ClientId AND Job.Type IN ('B','C')"
556 " AND Job.JobStatus IN ('T','W')"
557 " ORDER by Job.StartTime";
559 /* Get Volume names in Pool */
560 const char *sql_vol =
561 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
562 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
563 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
565 /* Get JobIds from regex'ed Volume names */
566 const char *sql_jobids_from_vol =
567 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
568 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
569 " AND JobMedia.JobId=Job.JobId AND Job.Type IN ('B','C')"
570 " AND Job.JobStatus IN ('T','W') AND Media.Enabled=1"
571 " ORDER by Job.StartTime";
573 const char *sql_smallest_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 VolBytes ASC LIMIT 1";
580 const char *sql_oldest_vol =
581 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
582 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
583 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
584 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
585 " ORDER BY LastWritten ASC LIMIT 1";
587 /* Get JobIds when we have selected MediaId */
588 const char *sql_jobids_from_mediaid =
589 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
590 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId IN (%s)"
591 " AND Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
592 " ORDER by Job.StartTime";
594 /* Get the number of bytes in the pool */
595 const char *sql_pool_bytes =
596 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN"
597 " (SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
598 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
599 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
600 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
601 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId)";
603 /* Get the number of bytes in the Jobs */
604 const char *sql_job_bytes =
605 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
607 /* Get Media Ids in Pool */
608 const char *sql_mediaids =
609 "SELECT MediaId FROM Media,Pool WHERE"
610 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
611 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
613 /* Get JobIds in Pool longer than specified time */
614 const char *sql_pool_time =
615 "SELECT DISTINCT Job.JobId FROM Pool,Job,Media,JobMedia WHERE"
616 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
617 " VolStatus IN ('Full','Used','Error') AND Media.Enabled=1 AND"
618 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
619 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
620 " AND Job.RealEndTime<='%s'";
622 /* Get JobIds from successfully completed backup jobs which have not been copied before */
623 const char *sql_jobids_of_pool_uncopied_jobs =
624 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
625 " WHERE Pool.Name = '%s' AND Pool.PoolId = Job.PoolId"
626 " AND Job.Type = 'B' AND Job.JobStatus IN ('T','W')"
627 " AND Job.jobBytes > 0"
628 " AND Job.JobId NOT IN"
629 " (SELECT PriorJobId FROM Job WHERE"
630 " Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
631 " AND PriorJobId != 0)"
632 " ORDER by Job.StartTime";
635 * const char *sql_ujobid =
636 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
637 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
638 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
643 * This is the central piece of code that finds a job or jobs
644 * actually JobIds to migrate. It first looks to see if one
645 * has been "manually" specified in jcr->MigrateJobId, and if
646 * so, it returns that JobId to be run. Otherwise, it
647 * examines the Selection Type to see what kind of migration
648 * we are doing (Volume, Job, Client, ...) and applies any
649 * Selection Pattern if appropriate to obtain a list of JobIds.
650 * Finally, it will loop over all the JobIds found, except the last
651 * one starting a new job with MigrationJobId set to that JobId, and
652 * finally, it returns the last JobId to the caller.
654 * Returns: -1 on error
655 * 0 if no jobs to migrate
656 * 1 if OK and jcr->previous_jr filled in
658 static int getJob_to_migrate(JCR *jcr)
660 char ed1[30], ed2[30];
661 POOL_MEM query(PM_MESSAGE);
666 idpkt ids, mid, jids;
671 char dt[MAX_TIME_LENGTH];
673 int limit = 99; /* limit + 1 is max jobs to start */
675 ids.list = get_pool_memory(PM_MESSAGE);
678 mid.list = get_pool_memory(PM_MESSAGE);
681 jids.list = get_pool_memory(PM_MESSAGE);
686 * If MigrateJobId is set, then we migrate only that Job,
687 * otherwise, we go through the full selection of jobs to
690 if (jcr->MigrateJobId != 0) {
691 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
692 JobId = jcr->MigrateJobId;
694 switch (jcr->job->selection_type) {
696 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
701 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
706 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
711 if (!jcr->job->selection_pattern) {
712 Jmsg(jcr, M_FATAL, 0, _("No %s SQL selection pattern specified.\n"), jcr->get_OperationName());
715 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
716 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
717 unique_dbid_handler, (void *)&ids)) {
718 Jmsg(jcr, M_FATAL, 0,
719 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
723 case MT_SMALLEST_VOL:
724 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
729 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
733 case MT_POOL_OCCUPANCY:
735 /* Find count of bytes in pool */
736 Mmsg(query, sql_pool_bytes, jcr->rpool->name());
737 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
738 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
741 if (ctx.count == 0) {
742 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
745 pool_bytes = ctx.value;
746 Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
748 if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
749 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
752 Dmsg0(dbglevel, "We should do Occupation migration.\n");
755 /* Find a list of MediaIds that could be migrated */
756 Mmsg(query, sql_mediaids, jcr->rpool->name());
757 Dmsg1(dbglevel, "query=%s\n", query.c_str());
758 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
759 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
762 if (ids.count == 0) {
763 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
766 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
768 if (!find_jobids_from_mediaid_list(jcr, &ids, "Volume")) {
771 /* ids == list of jobs */
773 for (int i=0; i < (int)ids.count; i++) {
774 stat = get_next_dbid_from_list(&p, &DBId);
775 Dmsg2(dbglevel, "get_next_dbid stat=%d JobId=%u\n", stat, (uint32_t)DBId);
777 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
779 } else if (stat == 0) {
784 Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
785 if (jids.count > 0) {
786 pm_strcat(jids.list, ",");
788 pm_strcat(jids.list, mid.list);
789 jids.count += mid.count;
791 /* Find count of bytes from Jobs */
792 Mmsg(query, sql_job_bytes, mid.list);
793 Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
794 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
795 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
798 pool_bytes -= ctx.value;
799 Dmsg2(dbglevel, "Total %s Job bytes=%s\n", jcr->get_ActionName(0), edit_int64_with_commas(ctx.value, ed1));
800 Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n",
801 edit_int64_with_commas(jcr->rpool->MigrationLowBytes, ed1),
802 edit_int64_with_commas(pool_bytes, ed2));
803 if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
804 Dmsg0(dbglevel, "We should be done.\n");
808 /* Transfer jids to ids, where the jobs list is expected */
809 ids.count = jids.count;
810 pm_strcpy(ids.list, jids.list);
811 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
814 ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
815 (void)localtime_r(&ttime, &tm);
816 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
819 Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
820 Dmsg1(dbglevel, "query=%s\n", query.c_str());
821 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
822 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
825 if (ids.count == 0) {
826 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
829 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
831 case MT_POOL_UNCOPIED_JOBS:
832 if (!find_jobids_of_pool_uncopied_jobs(jcr, &ids)) {
837 Jmsg(jcr, M_FATAL, 0, _("Unknown %s Selection Type.\n"), jcr->get_OperationName());
842 * Loop over all jobids except the last one, sending
843 * them to start_migration_job(), which will start a job
844 * for each of them. For the last JobId, we handle it below.
847 if (ids.count == 0) {
848 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
852 Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s chosen to be %s: %s\n"),
853 ids.count, (ids.count < 2) ? _(" was") : _("s were"),
854 jcr->get_ActionName(1), ids.list);
856 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
858 * Note: to not over load the system, limit the number
859 * of new jobs started to 100 (see limit above)
861 for (int i=1; i < (int)ids.count; i++) {
863 stat = get_next_jobid_from_list(&p, &JobId);
864 Dmsg3(dbglevel, "getJobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
866 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
868 } else if (stat == 0) {
869 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
872 jcr->MigrateJobId = JobId;
873 /* Don't start any more when limit reaches zero */
876 start_migration_job(jcr);
877 Dmsg0(dbglevel, "Back from start_migration_job\n");
881 /* Now get the last JobId and handle it in the current job */
883 stat = get_next_jobid_from_list(&p, &JobId);
884 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
886 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
888 } else if (stat == 0) {
889 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
894 jcr->previous_jr.JobId = JobId;
895 Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
897 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
898 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
899 edit_int64(jcr->previous_jr.JobId, ed1),
900 jcr->get_ActionName(0),
901 db_strerror(jcr->db));
905 Jmsg(jcr, M_INFO, 0, _("%s using JobId=%s Job=%s\n"),
906 jcr->get_OperationName(),
907 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
908 Dmsg4(dbglevel, "%s JobId=%d using JobId=%s Job=%s\n",
909 jcr->get_OperationName(),
911 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
921 free_pool_memory(ids.list);
922 free_pool_memory(mid.list);
923 free_pool_memory(jids.list);
927 static void start_migration_job(JCR *jcr)
929 UAContext *ua = new_ua_context(jcr);
932 Mmsg(ua->cmd, "run job=\"%s\" jobid=%s ignoreduplicatecheck=yes pool=\"%s\"",
933 jcr->job->name(), edit_uint64(jcr->MigrateJobId, ed1),
935 Dmsg2(dbglevel, "=============== %s cmd=%s\n", jcr->get_OperationName(), ua->cmd);
936 parse_ua_args(ua); /* parse command */
937 JobId_t jobid = run_cmd(ua, ua->cmd);
939 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
941 Jmsg(jcr, M_INFO, 0, _("%s JobId %d started.\n"), jcr->get_OperationName(), (int)jobid);
946 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
950 POOL_MEM query(PM_MESSAGE);
953 /* Basic query for MediaId */
954 Mmsg(query, query1, jcr->rpool->name());
955 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
956 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
959 if (ids->count == 0) {
960 Jmsg(jcr, M_INFO, 0, _("No %s found to %s.\n"), type, jcr->get_ActionName(0));
961 ok = true; /* Not an error */
963 } else if (ids->count != 1) {
964 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
967 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
969 ok = find_jobids_from_mediaid_list(jcr, ids, type);
976 * This routine returns:
977 * false if an error occurred
979 * ids.count number of jobids found (may be zero)
981 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
984 POOL_MEM query(PM_MESSAGE);
986 Mmsg(query, sql_jobids_from_mediaid, ids->list);
988 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
989 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
992 if (ids->count == 0) {
993 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
1002 * This routine returns:
1003 * false if an error occurred
1005 * ids.count number of jobids found (may be zero)
1007 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids)
1010 POOL_MEM query(PM_MESSAGE);
1012 /* Only a copy job is allowed */
1013 if (jcr->getJobType() != JT_COPY) {
1014 Jmsg(jcr, M_FATAL, 0,
1015 _("Selection Type 'pooluncopiedjobs' only applies to Copy Jobs"));
1019 Dmsg1(dbglevel, "copy selection pattern=%s\n", jcr->rpool->name());
1020 Mmsg(query, sql_jobids_of_pool_uncopied_jobs, jcr->rpool->name());
1021 Dmsg1(dbglevel, "get uncopied jobs query=%s\n", query.c_str());
1022 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1023 Jmsg(jcr, M_FATAL, 0,
1024 _("SQL to get uncopied jobs failed. ERR=%s\n"), db_strerror(jcr->db));
1033 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
1034 const char *query2, const char *type)
1038 uitem *last_item = NULL;
1043 POOL_MEM query(PM_MESSAGE);
1045 item_chain = New(dlist(item, &item->link));
1046 if (!jcr->job->selection_pattern) {
1047 Jmsg(jcr, M_FATAL, 0, _("No %s %s selection pattern specified.\n"),
1048 jcr->get_OperationName(), type);
1051 Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
1052 /* Basic query for names */
1053 Mmsg(query, query1, jcr->rpool->name());
1054 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
1055 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
1056 (void *)item_chain)) {
1057 Jmsg(jcr, M_FATAL, 0,
1058 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
1061 Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
1062 if (item_chain->size() == 0) {
1063 Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to %s.\n"),
1064 jcr->rpool->name(), jcr->get_ActionName(0));
1066 goto bail_out; /* skip regex match */
1068 /* Compile regex expression */
1069 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
1071 regerror(rc, &preg, prbuf, sizeof(prbuf));
1072 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
1073 jcr->job->selection_pattern, prbuf);
1076 /* Now apply the regex to the names and remove any item not matched */
1077 foreach_dlist(item, item_chain) {
1078 const int nmatch = 30;
1079 regmatch_t pmatch[nmatch];
1081 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1082 free(last_item->item);
1083 item_chain->remove(last_item);
1085 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
1086 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
1088 last_item = NULL; /* keep this one */
1094 free(last_item->item);
1095 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1096 item_chain->remove(last_item);
1100 if (item_chain->size() == 0) {
1101 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName(0));
1103 goto bail_out; /* skip regex match */
1107 * At this point, we have a list of items in item_chain
1108 * that have been matched by the regex, so now we need
1109 * to look up their jobids.
1112 foreach_dlist(item, item_chain) {
1113 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
1114 Mmsg(query, query2, item->item, jcr->rpool->name());
1115 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
1116 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1117 Jmsg(jcr, M_FATAL, 0,
1118 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
1122 if (ids->count == 0) {
1123 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
1128 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
1129 foreach_dlist(item, item_chain) {
1137 * Release resources allocated during backup.
1139 void migration_cleanup(JCR *jcr, int TermCode)
1141 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
1142 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
1143 char ec6[50], ec7[50], ec8[50];
1144 char term_code[100], sd_term_msg[100];
1145 const char *term_msg;
1146 int msg_type = M_INFO;
1150 JCR *mig_jcr = jcr->mig_jcr;
1151 POOL_MEM query(PM_MESSAGE);
1153 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
1154 update_job_end(jcr, TermCode);
1157 * Check if we actually did something.
1158 * mig_jcr is jcr of the newly migrated job.
1161 char old_jobid[50], new_jobid[50];
1163 edit_uint64(jcr->previous_jr.JobId, old_jobid);
1164 edit_uint64(mig_jcr->jr.JobId, new_jobid);
1166 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
1167 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
1168 mig_jcr->VolSessionId = jcr->VolSessionId;
1169 mig_jcr->VolSessionTime = jcr->VolSessionTime;
1170 mig_jcr->jr.RealEndTime = 0;
1171 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
1173 update_job_end(mig_jcr, TermCode);
1175 /* Update final items to set them to the previous job's values */
1176 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
1177 "JobTDate=%s WHERE JobId=%s",
1178 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
1179 edit_uint64(jcr->previous_jr.JobTDate, ec1),
1181 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1184 * If we terminated a migration normally:
1185 * - mark the previous job as migrated
1186 * - move any Log records to the new JobId
1187 * - Purge the File records from the previous job
1189 if (jcr->getJobType() == JT_MIGRATE && jcr->JobStatus == JS_Terminated) {
1190 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1191 (char)JT_MIGRATED_JOB, old_jobid);
1192 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1193 UAContext *ua = new_ua_context(jcr);
1194 /* Move JobLog to new JobId */
1195 Mmsg(query, "UPDATE Log SET JobId=%s WHERE JobId=%s",
1196 new_jobid, old_jobid);
1197 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1199 if (jcr->job->PurgeMigrateJob) {
1200 /* Purge old Job record */
1201 purge_jobs_from_catalog(ua, old_jobid);
1203 /* Purge all old file records, but leave Job record */
1204 purge_files_from_jobs(ua, old_jobid);
1207 free_ua_context(ua);
1211 * If we terminated a Copy (rather than a Migration) normally:
1212 * - copy any Log records to the new JobId
1213 * - set type="Job Copy" for the new job
1215 if (jcr->getJobType() == JT_COPY && jcr->JobStatus == JS_Terminated) {
1216 /* Copy JobLog to new JobId */
1217 Mmsg(query, "INSERT INTO Log (JobId, Time, LogText ) "
1218 "SELECT %s, Time, LogText FROM Log WHERE JobId=%s",
1219 new_jobid, old_jobid);
1220 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1221 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1222 (char)JT_JOB_COPY, new_jobid);
1223 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1226 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
1227 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
1228 db_strerror(jcr->db));
1229 jcr->setJobStatus(JS_ErrorTerminated);
1232 update_bootstrap_file(mig_jcr);
1234 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
1236 * Note, if the job has failed, most likely it did not write any
1237 * tape, so suppress this "error" message since in that case
1238 * it is normal. Or look at it the other way, only for a
1239 * normal exit should we complain about this error.
1241 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
1242 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
1244 mig_jcr->VolumeName[0] = 0; /* none */
1247 if (mig_jcr->VolumeName[0]) {
1248 /* Find last volume name. Multiple vols are separated by | */
1249 char *p = strrchr(mig_jcr->VolumeName, '|');
1253 p = mig_jcr->VolumeName; /* no |, take full name */
1255 bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1256 if (!db_get_media_record(jcr, jcr->db, &mr)) {
1257 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1258 mr.VolumeName, db_strerror(jcr->db));
1262 switch (jcr->JobStatus) {
1264 if (jcr->JobErrors || jcr->SDErrors) {
1265 term_msg = _("%s OK -- with warnings");
1267 term_msg = _("%s OK");
1271 case JS_ErrorTerminated:
1272 term_msg = _("*** %s Error ***");
1273 msg_type = M_ERROR; /* Generate error message */
1274 if (jcr->store_bsock) {
1275 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1276 if (jcr->SD_msg_chan) {
1277 pthread_cancel(jcr->SD_msg_chan);
1282 term_msg = _("%s Canceled");
1283 if (jcr->store_bsock) {
1284 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1285 if (jcr->SD_msg_chan) {
1286 pthread_cancel(jcr->SD_msg_chan);
1291 term_msg = _("Inappropriate %s term code");
1295 if (jcr->getJobType() == JT_MIGRATE && jcr->previous_jr.JobId != 0) {
1296 /* Mark previous job as migrated */
1297 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1298 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1299 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
1301 term_msg = _("%s -- no files to %s");
1304 bsnprintf(term_code, sizeof(term_code), term_msg, jcr->get_OperationName(), jcr->get_ActionName(0));
1305 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1306 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1307 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1311 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1314 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1316 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
1317 " Build OS: %s %s %s\n"
1318 " Prev Backup JobId: %s\n"
1319 " Prev Backup Job: %s\n"
1320 " New Backup JobId: %s\n"
1321 " Current JobId: %s\n"
1322 " Current Job: %s\n"
1323 " Backup Level: %s%s\n"
1325 " FileSet: \"%s\" %s\n"
1326 " Read Pool: \"%s\" (From %s)\n"
1327 " Read Storage: \"%s\" (From %s)\n"
1328 " Write Pool: \"%s\" (From %s)\n"
1329 " Write Storage: \"%s\" (From %s)\n"
1330 " Catalog: \"%s\" (From %s)\n"
1333 " Elapsed time: %s\n"
1335 " SD Files Written: %s\n"
1336 " SD Bytes Written: %s (%sB)\n"
1337 " Rate: %.1f KB/s\n"
1338 " Volume name(s): %s\n"
1339 " Volume Session Id: %d\n"
1340 " Volume Session Time: %d\n"
1341 " Last Volume Bytes: %s (%sB)\n"
1343 " SD termination status: %s\n"
1344 " Termination: %s\n\n"),
1345 BACULA, my_name, VERSION, LSMDATE,
1346 HOST_OS, DISTNAME, DISTVER,
1347 edit_uint64(jcr->previous_jr.JobId, ec6),
1348 jcr->previous_jr.Job,
1349 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1350 edit_uint64(jcr->jr.JobId, ec8),
1352 level_to_str(jcr->getJobLevel()), jcr->since,
1353 jcr->client->name(),
1354 jcr->fileset->name(), jcr->FSCreateTime,
1355 jcr->rpool->name(), jcr->rpool_source,
1356 jcr->rstore?jcr->rstore->name():"*None*",
1357 NPRT(jcr->rstore_source),
1358 jcr->pool->name(), jcr->pool_source,
1359 jcr->wstore?jcr->wstore->name():"*None*",
1360 NPRT(jcr->wstore_source),
1361 jcr->catalog->name(), jcr->catalog_source,
1364 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1366 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1367 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1368 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1370 mig_jcr ? mig_jcr->VolumeName : "",
1372 jcr->VolSessionTime,
1373 edit_uint64_with_commas(mr.VolBytes, ec4),
1374 edit_uint64_with_suffix(mr.VolBytes, ec5),
1379 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1381 free_jcr(jcr->mig_jcr);
1382 jcr->mig_jcr = NULL;
1384 Dmsg0(100, "Leave migrate_cleanup()\n");
1388 * Return next DBId from comma separated list
1391 * 1 if next DBId returned
1392 * 0 if no more DBIds are in list
1393 * -1 there is an error
1395 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1397 const int maxlen = 30;
1402 for (int i=0; i<maxlen; i++) {
1405 } else if (*q == ',') {
1414 } else if (!is_a_number(id)) {
1415 return -1; /* error */
1418 *DBId = str_to_int64(id);
1422 bool set_migration_wstorage(JCR *jcr, POOL *pool)
1424 POOL *wpool = pool->NextPool;
1427 Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
1432 if (!wpool->storage || wpool->storage->size() == 0) {
1433 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
1438 /* If pool storage specified, use it instead of job storage for backup */
1439 copy_wstorage(jcr, wpool->storage, _("Storage from Pool's NextPool resource"));