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 get_job_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 = get_job_to_migrate(jcr);
142 set_migration_next_pool(jcr, &pool);
143 return true; /* no work */
146 Dmsg1(dbglevel, "Back from get_job_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 create_restore_bootstrap_file(jcr);
157 if (jcr->previous_jr.JobId == 0 || jcr->ExpectedFiles == 0) {
158 set_jcr_job_status(jcr, JS_Terminated);
159 Dmsg1(dbglevel, "JobId=%d expected files == 0\n", (int)jcr->JobId);
160 if (jcr->previous_jr.JobId == 0) {
161 Jmsg(jcr, M_INFO, 0, _("No previous Job found to %s.\n"), jcr->get_ActionName(0));
163 Jmsg(jcr, M_INFO, 0, _("Previous Job has no data to %s.\n"), jcr->get_ActionName(0));
165 set_migration_next_pool(jcr, &pool);
166 return true; /* no work */
170 Dmsg5(dbglevel, "JobId=%d: Current: Name=%s JobId=%d Type=%c Level=%c\n",
172 jcr->jr.Name, (int)jcr->jr.JobId,
173 jcr->jr.JobType, jcr->jr.JobLevel);
176 job = (JOB *)GetResWithName(R_JOB, jcr->jr.Name);
177 prev_job = (JOB *)GetResWithName(R_JOB, jcr->previous_jr.Name);
180 Jmsg(jcr, M_FATAL, 0, _("Job resource not found for \"%s\".\n"), jcr->jr.Name);
184 Jmsg(jcr, M_FATAL, 0, _("Previous Job resource not found for \"%s\".\n"),
185 jcr->previous_jr.Name);
189 jcr->spool_data = job->spool_data; /* turn on spooling if requested in job */
191 /* Create a migration jcr */
192 mig_jcr = jcr->mig_jcr = new_jcr(sizeof(JCR), dird_free_jcr);
193 memcpy(&mig_jcr->previous_jr, &jcr->previous_jr, sizeof(mig_jcr->previous_jr));
196 * Turn the mig_jcr into a "real" job that takes on the aspects of
197 * the previous backup job "prev_job".
199 set_jcr_defaults(mig_jcr, prev_job);
200 if (!setup_job(mig_jcr)) {
201 Jmsg(jcr, M_FATAL, 0, _("setup job failed.\n"));
205 /* Now reset the job record from the previous job */
206 memcpy(&mig_jcr->jr, &jcr->previous_jr, sizeof(mig_jcr->jr));
207 /* Update the jr to reflect the new values of PoolId and JobId. */
208 mig_jcr->jr.PoolId = jcr->jr.PoolId;
209 mig_jcr->jr.JobId = mig_jcr->JobId;
211 /* Don't let WatchDog checks Max*Time value on this Job */
212 mig_jcr->no_maxtime = true;
214 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
215 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
216 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
218 if (set_migration_next_pool(jcr, &pool)) {
219 /* If pool storage specified, use it for restore */
220 copy_rstorage(mig_jcr, pool->storage, _("Pool resource"));
221 copy_rstorage(jcr, pool->storage, _("Pool resource"));
223 mig_jcr->pool = jcr->pool;
224 mig_jcr->jr.PoolId = jcr->jr.PoolId;
232 * set_migration_next_pool() called by do_migration_init()
233 * at differents stages.
234 * The idea here is tofactorize the NextPool's search code and
235 * to permit do_migration_init() to return with NextPool set in jcr struct.
237 static bool set_migration_next_pool(JCR *jcr, POOL **retpool)
244 * Get the PoolId used with the original job. Then
245 * find the pool name from the database record.
247 memset(&pr, 0, sizeof(pr));
248 pr.PoolId = jcr->jr.PoolId;
249 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
250 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
251 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
254 /* Get the pool resource corresponding to the original job */
255 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
258 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
263 * If the original backup pool has a NextPool, make sure a
264 * record exists in the database. Note, in this case, we
265 * will be migrating from pool to pool->NextPool.
267 if (pool->NextPool) {
268 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->name());
269 if (jcr->jr.PoolId == 0) {
273 if (!set_migration_wstorage(jcr, pool)) {
276 jcr->pool = pool->NextPool;
277 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
279 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
286 * Do a Migration of a previous job
288 * Returns: false on failure
291 bool do_migration(JCR *jcr)
295 JCR *mig_jcr = jcr->mig_jcr; /* newly migrated job */
298 * If mig_jcr is NULL, there is nothing to do for this job,
299 * so set a normal status, cleanup and return OK.
302 set_jcr_job_status(jcr, JS_Terminated);
303 migration_cleanup(jcr, jcr->JobStatus);
307 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
308 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
309 edit_int64(jcr->previous_jr.JobId, ed1),
310 jcr->get_ActionName(0),
311 db_strerror(jcr->db));
312 set_jcr_job_status(jcr, JS_Terminated);
313 migration_cleanup(jcr, jcr->JobStatus);
316 /* Make sure this job was not already migrated */
317 if (jcr->previous_jr.JobType != JT_BACKUP) {
318 Jmsg(jcr, M_INFO, 0, _("JobId %s already %s probably by another Job. %s stopped.\n"),
319 edit_int64(jcr->previous_jr.JobId, ed1),
320 jcr->get_ActionName(1),
321 jcr->get_OperationName());
322 set_jcr_job_status(jcr, JS_Terminated);
323 migration_cleanup(jcr, jcr->JobStatus);
327 /* Print Job Start message */
328 Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
329 jcr->get_OperationName(), edit_uint64(jcr->JobId, ed1), jcr->Job);
332 * Open a message channel connection with the Storage
333 * daemon. This is to let him know that our client
334 * will be contacting him for a backup session.
337 Dmsg0(110, "Open connection with storage daemon\n");
338 set_jcr_job_status(jcr, JS_WaitSD);
339 set_jcr_job_status(mig_jcr, JS_WaitSD);
341 * Start conversation with Storage daemon
343 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
346 sd = jcr->store_bsock;
348 * Now start a job with the Storage daemon
350 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
351 ((STORE *)jcr->rstorage->first())->name(),
352 ((STORE *)jcr->wstorage->first())->name());
353 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
354 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
355 ((STORE *)jcr->rstorage->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;
484 add_unique_id(ids, row[0]);
485 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
495 static int item_compare(void *item1, void *item2)
497 uitem *i1 = (uitem *)item1;
498 uitem *i2 = (uitem *)item2;
499 return strcmp(i1->item, i2->item);
502 static int unique_name_handler(void *ctx, int num_fields, char **row)
504 dlist *list = (dlist *)ctx;
506 uitem *new_item = (uitem *)malloc(sizeof(uitem));
509 memset(new_item, 0, sizeof(uitem));
510 new_item->item = bstrdup(row[0]);
511 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
512 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
513 if (item != new_item) { /* already in list */
514 free(new_item->item);
515 free((char *)new_item);
521 /* Get Job names in Pool */
522 const char *sql_job =
523 "SELECT DISTINCT Job.Name from Job,Pool"
524 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
526 /* Get JobIds from regex'ed Job names */
527 const char *sql_jobids_from_job =
528 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
529 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
530 " ORDER by Job.StartTime";
532 /* Get Client names in Pool */
533 const char *sql_client =
534 "SELECT DISTINCT Client.Name from Client,Pool,Job"
535 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
536 " Job.PoolId=Pool.PoolId";
538 /* Get JobIds from regex'ed Client names */
539 const char *sql_jobids_from_client =
540 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
541 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
542 " AND Job.ClientId=Client.ClientId AND Job.Type IN ('B','C')"
543 " AND Job.JobStatus IN ('T','W')"
544 " ORDER by Job.StartTime";
546 /* Get Volume names in Pool */
547 const char *sql_vol =
548 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
549 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
550 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
552 /* Get JobIds from regex'ed Volume names */
553 const char *sql_jobids_from_vol =
554 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
555 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
556 " AND JobMedia.JobId=Job.JobId AND Job.Type IN ('B','C')"
557 " AND Job.JobStatus IN ('T','W') AND Media.Enabled=1"
558 " ORDER by Job.StartTime";
560 const char *sql_smallest_vol =
561 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
562 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
563 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
564 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
565 " ORDER BY VolBytes ASC LIMIT 1";
567 const char *sql_oldest_vol =
568 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
569 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
570 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
571 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
572 " ORDER BY LastWritten ASC LIMIT 1";
574 /* Get JobIds when we have selected MediaId */
575 const char *sql_jobids_from_mediaid =
576 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
577 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId IN (%s)"
578 " AND Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
579 " ORDER by Job.StartTime";
581 /* Get the number of bytes in the pool */
582 const char *sql_pool_bytes =
583 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN"
584 " (SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
585 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
586 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
587 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
588 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId)";
590 /* Get the number of bytes in the Jobs */
591 const char *sql_job_bytes =
592 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
594 /* Get Media Ids in Pool */
595 const char *sql_mediaids =
596 "SELECT MediaId FROM Media,Pool WHERE"
597 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
598 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
600 /* Get JobIds in Pool longer than specified time */
601 const char *sql_pool_time =
602 "SELECT DISTINCT Job.JobId FROM Pool,Job,Media,JobMedia WHERE"
603 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
604 " VolStatus IN ('Full','Used','Error') AND Media.Enabled=1 AND"
605 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
606 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
607 " AND Job.RealEndTime<='%s'";
609 /* Get JobIds from successfully completed backup jobs which have not been copied before */
610 const char *sql_jobids_of_pool_uncopied_jobs =
611 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
612 " WHERE Pool.Name = '%s' AND Pool.PoolId = Job.PoolId"
613 " AND Job.Type = 'B' AND Job.JobStatus IN ('T','W')"
614 " AND Job.jobBytes > 0"
615 " AND Job.JobId NOT IN"
616 " (SELECT PriorJobId FROM Job WHERE"
617 " Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
618 " AND PriorJobId != 0)"
619 " ORDER by Job.StartTime";
622 * const char *sql_ujobid =
623 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
624 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
625 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
630 * This is the central piece of code that finds a job or jobs
631 * actually JobIds to migrate. It first looks to see if one
632 * has been "manually" specified in jcr->MigrateJobId, and if
633 * so, it returns that JobId to be run. Otherwise, it
634 * examines the Selection Type to see what kind of migration
635 * we are doing (Volume, Job, Client, ...) and applies any
636 * Selection Pattern if appropriate to obtain a list of JobIds.
637 * Finally, it will loop over all the JobIds found, except the last
638 * one starting a new job with MigrationJobId set to that JobId, and
639 * finally, it returns the last JobId to the caller.
641 * Returns: -1 on error
642 * 0 if no jobs to migrate
643 * 1 if OK and jcr->previous_jr filled in
645 static int get_job_to_migrate(JCR *jcr)
647 char ed1[30], ed2[30];
648 POOL_MEM query(PM_MESSAGE);
653 idpkt ids, mid, jids;
658 char dt[MAX_TIME_LENGTH];
661 ids.list = get_pool_memory(PM_MESSAGE);
664 mid.list = get_pool_memory(PM_MESSAGE);
667 jids.list = get_pool_memory(PM_MESSAGE);
673 * If MigrateJobId is set, then we migrate only that Job,
674 * otherwise, we go through the full selection of jobs to
677 if (jcr->MigrateJobId != 0) {
678 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
679 edit_uint64(jcr->MigrateJobId, ids.list);
682 switch (jcr->job->selection_type) {
684 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
689 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
694 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
699 if (!jcr->job->selection_pattern) {
700 Jmsg(jcr, M_FATAL, 0, _("No %s SQL selection pattern specified.\n"), jcr->get_OperationName());
703 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
704 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
705 unique_dbid_handler, (void *)&ids)) {
706 Jmsg(jcr, M_FATAL, 0,
707 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
711 case MT_SMALLEST_VOL:
712 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
717 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
721 case MT_POOL_OCCUPANCY:
723 /* Find count of bytes in pool */
724 Mmsg(query, sql_pool_bytes, jcr->rpool->name());
725 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
726 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
729 if (ctx.count == 0) {
730 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
733 pool_bytes = ctx.value;
734 Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
736 if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
737 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
740 Dmsg0(dbglevel, "We should do Occupation migration.\n");
743 /* Find a list of MediaIds that could be migrated */
744 Mmsg(query, sql_mediaids, jcr->rpool->name());
745 Dmsg1(dbglevel, "query=%s\n", query.c_str());
746 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
747 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
750 if (ids.count == 0) {
751 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
754 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
756 if (!find_jobids_from_mediaid_list(jcr, &ids, "Volume")) {
759 /* ids == list of jobs */
761 for (int i=0; i < (int)ids.count; i++) {
762 stat = get_next_dbid_from_list(&p, &DBId);
763 Dmsg2(dbglevel, "get_next_dbid stat=%d JobId=%u\n", stat, (uint32_t)DBId);
765 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
767 } else if (stat == 0) {
772 Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
773 if (jids.count > 0) {
774 pm_strcat(jids.list, ",");
776 pm_strcat(jids.list, mid.list);
777 jids.count += mid.count;
779 /* Find count of bytes from Jobs */
780 Mmsg(query, sql_job_bytes, mid.list);
781 Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
782 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
783 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
786 pool_bytes -= ctx.value;
787 Dmsg2(dbglevel, "Total %s Job bytes=%s\n", jcr->get_ActionName(0), edit_int64_with_commas(ctx.value, ed1));
788 Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n",
789 edit_int64_with_commas(jcr->rpool->MigrationLowBytes, ed1),
790 edit_int64_with_commas(pool_bytes, ed2));
791 if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
792 Dmsg0(dbglevel, "We should be done.\n");
796 /* Transfer jids to ids, where the jobs list is expected */
797 ids.count = jids.count;
798 pm_strcpy(ids.list, jids.list);
799 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
802 ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
803 (void)localtime_r(&ttime, &tm);
804 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
807 Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
808 Dmsg1(dbglevel, "query=%s\n", query.c_str());
809 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
810 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
813 if (ids.count == 0) {
814 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
817 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
819 case MT_POOL_UNCOPIED_JOBS:
820 if (!find_jobids_of_pool_uncopied_jobs(jcr, &ids)) {
825 Jmsg(jcr, M_FATAL, 0, _("Unknown %s Selection Type.\n"), jcr->get_OperationName());
831 * Loop over all jobids except the last one, sending
832 * them to start_migration_job(), which will start a job
833 * for each of them. For the last JobId, we handle it below.
836 if (ids.count == 0) {
837 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
841 Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s chosen to be %s: %s\n"),
842 ids.count, (ids.count < 2) ? _(" was") : _("s were"),
843 jcr->get_ActionName(1), ids.list);
845 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
846 for (int i=1; i < (int)ids.count; i++) {
848 stat = get_next_jobid_from_list(&p, &JobId);
849 Dmsg3(dbglevel, "get_jobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
850 jcr->MigrateJobId = JobId;
851 start_migration_job(jcr);
852 Dmsg0(dbglevel, "Back from start_migration_job\n");
854 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
856 } else if (stat == 0) {
857 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
862 /* Now get the last JobId and handle it in the current job */
864 stat = get_next_jobid_from_list(&p, &JobId);
865 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
867 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
869 } else if (stat == 0) {
870 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
874 jcr->previous_jr.JobId = JobId;
875 Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
877 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
878 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
879 edit_int64(jcr->previous_jr.JobId, ed1),
880 jcr->get_ActionName(0),
881 db_strerror(jcr->db));
884 Jmsg(jcr, M_INFO, 0, _("%s using JobId=%s Job=%s\n"),
885 jcr->get_OperationName(),
886 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
887 Dmsg4(dbglevel, "%s JobId=%d using JobId=%s Job=%s\n",
888 jcr->get_OperationName(),
890 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
900 free_pool_memory(ids.list);
901 free_pool_memory(mid.list);
902 free_pool_memory(jids.list);
906 static void start_migration_job(JCR *jcr)
908 UAContext *ua = new_ua_context(jcr);
911 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
912 edit_uint64(jcr->MigrateJobId, ed1));
913 Dmsg2(dbglevel, "=============== %s cmd=%s\n", jcr->get_OperationName(), ua->cmd);
914 parse_ua_args(ua); /* parse command */
915 JobId_t jobid = run_cmd(ua, ua->cmd);
917 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
919 Jmsg(jcr, M_INFO, 0, _("%s JobId %d started.\n"), jcr->get_OperationName(), (int)jobid);
924 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
928 POOL_MEM query(PM_MESSAGE);
931 /* Basic query for MediaId */
932 Mmsg(query, query1, jcr->rpool->name());
933 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
934 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
937 if (ids->count == 0) {
938 Jmsg(jcr, M_INFO, 0, _("No %s found to %s.\n"), type, jcr->get_ActionName(0));
939 ok = true; /* Not an error */
941 } else if (ids->count != 1) {
942 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
945 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
947 ok = find_jobids_from_mediaid_list(jcr, ids, type);
954 * This routine returns:
955 * false if an error occurred
957 * ids.count number of jobids found (may be zero)
959 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
962 POOL_MEM query(PM_MESSAGE);
964 Mmsg(query, sql_jobids_from_mediaid, ids->list);
966 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
967 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
970 if (ids->count == 0) {
971 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
980 * This routine returns:
981 * false if an error occurred
983 * ids.count number of jobids found (may be zero)
985 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids)
988 POOL_MEM query(PM_MESSAGE);
990 /* Only a copy job is allowed */
991 if (jcr->get_JobType() != JT_COPY) {
992 Jmsg(jcr, M_FATAL, 0,
993 _("Selection Type 'pooluncopiedjobs' only applies to Copy Jobs"));
997 Dmsg1(dbglevel, "copy selection pattern=%s\n", jcr->rpool->name());
998 Mmsg(query, sql_jobids_of_pool_uncopied_jobs, jcr->rpool->name());
999 Dmsg1(dbglevel, "get uncopied jobs query=%s\n", query.c_str());
1000 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1001 Jmsg(jcr, M_FATAL, 0,
1002 _("SQL to get uncopied jobs failed. ERR=%s\n"), db_strerror(jcr->db));
1011 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
1012 const char *query2, const char *type)
1016 uitem *last_item = NULL;
1021 POOL_MEM query(PM_MESSAGE);
1023 item_chain = New(dlist(item, &item->link));
1024 if (!jcr->job->selection_pattern) {
1025 Jmsg(jcr, M_FATAL, 0, _("No %s %s selection pattern specified.\n"),
1026 jcr->get_OperationName(), type);
1029 Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
1030 /* Basic query for names */
1031 Mmsg(query, query1, jcr->rpool->name());
1032 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
1033 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
1034 (void *)item_chain)) {
1035 Jmsg(jcr, M_FATAL, 0,
1036 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
1039 Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
1040 if (item_chain->size() == 0) {
1041 Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to %s.\n"),
1042 jcr->rpool->name(), jcr->get_ActionName(0));
1044 goto bail_out; /* skip regex match */
1046 /* Compile regex expression */
1047 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
1049 regerror(rc, &preg, prbuf, sizeof(prbuf));
1050 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
1051 jcr->job->selection_pattern, prbuf);
1054 /* Now apply the regex to the names and remove any item not matched */
1055 foreach_dlist(item, item_chain) {
1056 const int nmatch = 30;
1057 regmatch_t pmatch[nmatch];
1059 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1060 free(last_item->item);
1061 item_chain->remove(last_item);
1063 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
1064 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
1066 last_item = NULL; /* keep this one */
1072 free(last_item->item);
1073 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1074 item_chain->remove(last_item);
1078 if (item_chain->size() == 0) {
1079 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName(0));
1081 goto bail_out; /* skip regex match */
1085 * At this point, we have a list of items in item_chain
1086 * that have been matched by the regex, so now we need
1087 * to look up their jobids.
1090 foreach_dlist(item, item_chain) {
1091 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
1092 Mmsg(query, query2, item->item, jcr->rpool->name());
1093 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
1094 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1095 Jmsg(jcr, M_FATAL, 0,
1096 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
1100 if (ids->count == 0) {
1101 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
1106 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
1107 foreach_dlist(item, item_chain) {
1115 * Release resources allocated during backup.
1117 void migration_cleanup(JCR *jcr, int TermCode)
1119 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
1120 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
1121 char ec6[50], ec7[50], ec8[50];
1122 char term_code[100], sd_term_msg[100];
1123 const char *term_msg;
1124 int msg_type = M_INFO;
1128 JCR *mig_jcr = jcr->mig_jcr;
1129 POOL_MEM query(PM_MESSAGE);
1131 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
1132 update_job_end(jcr, TermCode);
1133 memset(&mr, 0, sizeof(mr));
1136 * Check if we actually did something.
1137 * mig_jcr is jcr of the newly migrated job.
1140 char old_jobid[50], new_jobid[50];
1142 edit_uint64(jcr->previous_jr.JobId, old_jobid);
1143 edit_uint64(mig_jcr->jr.JobId, new_jobid);
1145 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
1146 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
1147 mig_jcr->VolSessionId = jcr->VolSessionId;
1148 mig_jcr->VolSessionTime = jcr->VolSessionTime;
1149 mig_jcr->jr.RealEndTime = 0;
1150 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
1152 update_job_end(mig_jcr, TermCode);
1154 /* Update final items to set them to the previous job's values */
1155 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
1156 "JobTDate=%s WHERE JobId=%s",
1157 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
1158 edit_uint64(jcr->previous_jr.JobTDate, ec1),
1160 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1163 * If we terminated a migration normally:
1164 * - mark the previous job as migrated
1165 * - move any Log records to the new JobId
1166 * - Purge the File records from the previous job
1168 if (jcr->get_JobType() == JT_MIGRATE && jcr->JobStatus == JS_Terminated) {
1169 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1170 (char)JT_MIGRATED_JOB, old_jobid);
1171 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1172 UAContext *ua = new_ua_context(jcr);
1173 /* Move JobLog to new JobId */
1174 Mmsg(query, "UPDATE Log SET JobId=%s WHERE JobId=%s",
1175 new_jobid, old_jobid);
1176 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1177 /* Purge all old file records, but leave Job record */
1178 purge_files_from_jobs(ua, old_jobid);
1179 free_ua_context(ua);
1183 * If we terminated a copy normally:
1184 * - copy any Log records to the new JobId
1185 * - set type="Job Copy" for the new job
1187 if (jcr->get_JobType() == JT_COPY && jcr->JobStatus == JS_Terminated) {
1188 /* Copy JobLog to new JobId */
1189 Mmsg(query, "INSERT INTO Log (JobId, Time, LogText ) "
1190 "SELECT %s, Time, LogText FROM Log WHERE JobId=%s",
1191 new_jobid, old_jobid);
1192 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1193 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1194 (char)JT_JOB_COPY, new_jobid);
1195 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1198 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
1199 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
1200 db_strerror(jcr->db));
1201 set_jcr_job_status(jcr, JS_ErrorTerminated);
1204 update_bootstrap_file(mig_jcr);
1206 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
1208 * Note, if the job has failed, most likely it did not write any
1209 * tape, so suppress this "error" message since in that case
1210 * it is normal. Or look at it the other way, only for a
1211 * normal exit should we complain about this error.
1213 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
1214 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
1216 mig_jcr->VolumeName[0] = 0; /* none */
1219 if (mig_jcr->VolumeName[0]) {
1220 /* Find last volume name. Multiple vols are separated by | */
1221 char *p = strrchr(mig_jcr->VolumeName, '|');
1225 p = mig_jcr->VolumeName; /* no |, take full name */
1227 bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1228 if (!db_get_media_record(jcr, jcr->db, &mr)) {
1229 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1230 mr.VolumeName, db_strerror(jcr->db));
1234 switch (jcr->JobStatus) {
1236 if (jcr->JobErrors || jcr->SDErrors) {
1237 term_msg = _("%s OK -- with warnings");
1239 term_msg = _("%s OK");
1243 case JS_ErrorTerminated:
1244 term_msg = _("*** %s Error ***");
1245 msg_type = M_ERROR; /* Generate error message */
1246 if (jcr->store_bsock) {
1247 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1248 if (jcr->SD_msg_chan) {
1249 pthread_cancel(jcr->SD_msg_chan);
1254 term_msg = _("%s Canceled");
1255 if (jcr->store_bsock) {
1256 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1257 if (jcr->SD_msg_chan) {
1258 pthread_cancel(jcr->SD_msg_chan);
1263 term_msg = _("Inappropriate %s term code");
1267 if (jcr->get_JobType() == JT_MIGRATE && jcr->previous_jr.JobId != 0) {
1268 /* Mark previous job as migrated */
1269 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1270 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1271 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
1273 term_msg = _("%s -- no files to %s");
1276 bsnprintf(term_code, sizeof(term_code), term_msg, jcr->get_OperationName(), jcr->get_ActionName(0));
1277 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1278 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1279 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1283 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1286 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1288 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
1289 " Build OS: %s %s %s\n"
1290 " Prev Backup JobId: %s\n"
1291 " Prev Backup Job: %s\n"
1292 " New Backup JobId: %s\n"
1293 " Current JobId: %s\n"
1294 " Current Job: %s\n"
1295 " Backup Level: %s%s\n"
1297 " FileSet: \"%s\" %s\n"
1298 " Read Pool: \"%s\" (From %s)\n"
1299 " Read Storage: \"%s\" (From %s)\n"
1300 " Write Pool: \"%s\" (From %s)\n"
1301 " Write Storage: \"%s\" (From %s)\n"
1302 " Catalog: \"%s\" (From %s)\n"
1305 " Elapsed time: %s\n"
1307 " SD Files Written: %s\n"
1308 " SD Bytes Written: %s (%sB)\n"
1309 " Rate: %.1f KB/s\n"
1310 " Volume name(s): %s\n"
1311 " Volume Session Id: %d\n"
1312 " Volume Session Time: %d\n"
1313 " Last Volume Bytes: %s (%sB)\n"
1315 " SD termination status: %s\n"
1316 " Termination: %s\n\n"),
1317 BACULA, my_name, VERSION, LSMDATE, edt,
1318 HOST_OS, DISTNAME, DISTVER,
1319 edit_uint64(jcr->previous_jr.JobId, ec6),
1320 jcr->previous_jr.Job,
1321 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1322 edit_uint64(jcr->jr.JobId, ec8),
1324 level_to_str(jcr->get_JobLevel()), jcr->since,
1325 jcr->client->name(),
1326 jcr->fileset->name(), jcr->FSCreateTime,
1327 jcr->rpool->name(), jcr->rpool_source,
1328 jcr->rstore?jcr->rstore->name():"*None*",
1329 NPRT(jcr->rstore_source),
1330 jcr->pool->name(), jcr->pool_source,
1331 jcr->wstore?jcr->wstore->name():"*None*",
1332 NPRT(jcr->wstore_source),
1333 jcr->catalog->name(), jcr->catalog_source,
1336 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1338 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1339 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1340 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1342 mig_jcr ? mig_jcr->VolumeName : "",
1344 jcr->VolSessionTime,
1345 edit_uint64_with_commas(mr.VolBytes, ec4),
1346 edit_uint64_with_suffix(mr.VolBytes, ec5),
1351 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1353 free_jcr(jcr->mig_jcr);
1354 jcr->mig_jcr = NULL;
1356 Dmsg0(100, "Leave migrate_cleanup()\n");
1360 * Return next DBId from comma separated list
1363 * 1 if next DBId returned
1364 * 0 if no more DBIds are in list
1365 * -1 there is an error
1367 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1369 const int maxlen = 30;
1374 for (int i=0; i<maxlen; i++) {
1377 } else if (*q == ',') {
1386 } else if (!is_a_number(id)) {
1387 return -1; /* error */
1390 *DBId = str_to_int64(id);
1394 bool set_migration_wstorage(JCR *jcr, POOL *pool)
1396 POOL *wpool = pool->NextPool;
1399 Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
1404 if (!wpool->storage || wpool->storage->size() == 0) {
1405 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
1410 /* If pool storage specified, use it instead of job storage for backup */
1411 copy_wstorage(jcr, wpool->storage, _("Storage from Pool's NextPool resource"));