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 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
212 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
213 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
215 if (set_migration_next_pool(jcr, &pool)) {
216 /* If pool storage specified, use it for restore */
217 copy_rstorage(mig_jcr, pool->storage, _("Pool resource"));
218 copy_rstorage(jcr, pool->storage, _("Pool resource"));
220 mig_jcr->pool = jcr->pool;
221 mig_jcr->jr.PoolId = jcr->jr.PoolId;
229 * set_migration_next_pool() called by do_migration_init()
230 * at differents stages.
231 * The idea here is tofactorize the NextPool's search code and
232 * to permit do_migration_init() to return with NextPool set in jcr struct.
234 static bool set_migration_next_pool(JCR *jcr, POOL **retpool)
241 * Get the PoolId used with the original job. Then
242 * find the pool name from the database record.
244 memset(&pr, 0, sizeof(pr));
245 pr.PoolId = jcr->jr.PoolId;
246 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
247 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
248 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
251 /* Get the pool resource corresponding to the original job */
252 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
255 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
260 * If the original backup pool has a NextPool, make sure a
261 * record exists in the database. Note, in this case, we
262 * will be migrating from pool to pool->NextPool.
264 if (pool->NextPool) {
265 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->name());
266 if (jcr->jr.PoolId == 0) {
270 if (!set_migration_wstorage(jcr, pool)) {
273 jcr->pool = pool->NextPool;
274 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
276 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
283 * Do a Migration of a previous job
285 * Returns: false on failure
288 bool do_migration(JCR *jcr)
292 JCR *mig_jcr = jcr->mig_jcr; /* newly migrated job */
295 * If mig_jcr is NULL, there is nothing to do for this job,
296 * so set a normal status, cleanup and return OK.
299 set_jcr_job_status(jcr, JS_Terminated);
300 migration_cleanup(jcr, jcr->JobStatus);
304 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
305 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
306 edit_int64(jcr->previous_jr.JobId, ed1),
307 jcr->get_ActionName(0),
308 db_strerror(jcr->db));
309 set_jcr_job_status(jcr, JS_Terminated);
310 migration_cleanup(jcr, jcr->JobStatus);
313 /* Make sure this job was not already migrated */
314 if (jcr->previous_jr.JobType != JT_BACKUP) {
315 Jmsg(jcr, M_INFO, 0, _("JobId %s already %s probably by another Job. %s stopped.\n"),
316 edit_int64(jcr->previous_jr.JobId, ed1),
317 jcr->get_ActionName(1),
318 jcr->get_OperationName());
319 set_jcr_job_status(jcr, JS_Terminated);
320 migration_cleanup(jcr, jcr->JobStatus);
324 /* Print Job Start message */
325 Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
326 jcr->get_OperationName(), edit_uint64(jcr->JobId, ed1), jcr->Job);
329 * Open a message channel connection with the Storage
330 * daemon. This is to let him know that our client
331 * will be contacting him for a backup session.
334 Dmsg0(110, "Open connection with storage daemon\n");
335 set_jcr_job_status(jcr, JS_WaitSD);
336 set_jcr_job_status(mig_jcr, JS_WaitSD);
338 * Start conversation with Storage daemon
340 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
343 sd = jcr->store_bsock;
345 * Now start a job with the Storage daemon
347 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
348 ((STORE *)jcr->rstorage->first())->name(),
349 ((STORE *)jcr->wstorage->first())->name());
350 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
351 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
352 ((STORE *)jcr->rstorage->first())->name());
355 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage, /*send_bsr*/true)) {
358 Dmsg0(150, "Storage daemon connection OK\n");
362 * We re-update the job start record so that the start
363 * time is set after the run before job. This avoids
364 * that any files created by the run before job will
365 * be saved twice. They will be backed up in the current
366 * job, but not in the next one unless they are changed.
367 * Without this, they will be backed up in this job and
368 * in the next job run because in that case, their date
369 * is after the start of this run.
371 jcr->start_time = time(NULL);
372 jcr->jr.StartTime = jcr->start_time;
373 jcr->jr.JobTDate = jcr->start_time;
374 set_jcr_job_status(jcr, JS_Running);
376 /* Update job start record for this migration control job */
377 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
378 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
383 mig_jcr->start_time = time(NULL);
384 mig_jcr->jr.StartTime = mig_jcr->start_time;
385 mig_jcr->jr.JobTDate = mig_jcr->start_time;
386 set_jcr_job_status(mig_jcr, JS_Running);
388 /* Update job start record for the real migration backup job */
389 if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
390 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
394 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
395 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
396 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
400 * Start the job prior to starting the message thread below
401 * to avoid two threads from using the BSOCK structure at
404 if (!sd->fsend("run")) {
409 * Now start a Storage daemon message thread
411 if (!start_storage_daemon_message_thread(jcr)) {
416 set_jcr_job_status(jcr, JS_Running);
417 set_jcr_job_status(mig_jcr, JS_Running);
419 /* Pickup Job termination data */
420 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
421 wait_for_storage_daemon_termination(jcr);
422 set_jcr_job_status(jcr, jcr->SDJobStatus);
423 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
424 if (jcr->JobStatus != JS_Terminated) {
428 migration_cleanup(jcr, jcr->JobStatus);
438 /* Add an item to the list if it is unique */
439 static void add_unique_id(idpkt *ids, char *item)
441 const int maxlen = 30;
445 /* Walk through current list to see if each item is the same as item */
448 for (int i=0; i<maxlen; i++) {
451 } else if (*q == ',') {
458 if (strcmp(item, id) == 0) {
462 /* Did not find item, so add it to list */
463 if (ids->count == 0) {
466 pm_strcat(ids->list, ",");
468 pm_strcat(ids->list, item);
470 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
475 * Callback handler make list of DB Ids
477 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
479 idpkt *ids = (idpkt *)ctx;
481 add_unique_id(ids, row[0]);
482 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
492 static int item_compare(void *item1, void *item2)
494 uitem *i1 = (uitem *)item1;
495 uitem *i2 = (uitem *)item2;
496 return strcmp(i1->item, i2->item);
499 static int unique_name_handler(void *ctx, int num_fields, char **row)
501 dlist *list = (dlist *)ctx;
503 uitem *new_item = (uitem *)malloc(sizeof(uitem));
506 memset(new_item, 0, sizeof(uitem));
507 new_item->item = bstrdup(row[0]);
508 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
509 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
510 if (item != new_item) { /* already in list */
511 free(new_item->item);
512 free((char *)new_item);
518 /* Get Job names in Pool */
519 const char *sql_job =
520 "SELECT DISTINCT Job.Name from Job,Pool"
521 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
523 /* Get JobIds from regex'ed Job names */
524 const char *sql_jobids_from_job =
525 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
526 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
527 " ORDER by Job.StartTime";
529 /* Get Client names in Pool */
530 const char *sql_client =
531 "SELECT DISTINCT Client.Name from Client,Pool,Job"
532 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
533 " Job.PoolId=Pool.PoolId";
535 /* Get JobIds from regex'ed Client names */
536 const char *sql_jobids_from_client =
537 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
538 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
539 " AND Job.ClientId=Client.ClientId AND Job.Type IN ('B','C')"
540 " AND Job.JobStatus IN ('T','W')"
541 " ORDER by Job.StartTime";
543 /* Get Volume names in Pool */
544 const char *sql_vol =
545 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
546 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
547 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
549 /* Get JobIds from regex'ed Volume names */
550 const char *sql_jobids_from_vol =
551 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
552 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
553 " AND JobMedia.JobId=Job.JobId AND Job.Type IN ('B','C')"
554 " AND Job.JobStatus IN ('T','W') AND Media.Enabled=1"
555 " ORDER by Job.StartTime";
557 const char *sql_smallest_vol =
558 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
559 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
560 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
561 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
562 " ORDER BY VolBytes ASC LIMIT 1";
564 const char *sql_oldest_vol =
565 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
566 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
567 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
568 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
569 " ORDER BY LastWritten ASC LIMIT 1";
571 /* Get JobIds when we have selected MediaId */
572 const char *sql_jobids_from_mediaid =
573 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
574 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId IN (%s)"
575 " AND Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
576 " ORDER by Job.StartTime";
578 /* Get the number of bytes in the pool */
579 const char *sql_pool_bytes =
580 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN"
581 " (SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
582 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
583 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
584 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
585 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId)";
587 /* Get the number of bytes in the Jobs */
588 const char *sql_job_bytes =
589 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
591 /* Get Media Ids in Pool */
592 const char *sql_mediaids =
593 "SELECT MediaId FROM Media,Pool WHERE"
594 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
595 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
597 /* Get JobIds in Pool longer than specified time */
598 const char *sql_pool_time =
599 "SELECT DISTINCT Job.JobId FROM Pool,Job,Media,JobMedia WHERE"
600 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
601 " VolStatus IN ('Full','Used','Error') AND Media.Enabled=1 AND"
602 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
603 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
604 " AND Job.RealEndTime<='%s'";
606 /* Get JobIds from successfully completed backup jobs which have not been copied before */
607 const char *sql_jobids_of_pool_uncopied_jobs =
608 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
609 " WHERE Pool.Name = '%s' AND Pool.PoolId = Job.PoolId"
610 " AND Job.Type = 'B' AND Job.JobStatus IN ('T','W')"
611 " AND Job.jobBytes > 0"
612 " AND Job.JobId NOT IN"
613 " (SELECT PriorJobId FROM Job WHERE"
614 " Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
615 " AND PriorJobId != 0)"
616 " ORDER by Job.StartTime";
619 * const char *sql_ujobid =
620 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
621 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
622 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
627 * This is the central piece of code that finds a job or jobs
628 * actually JobIds to migrate. It first looks to see if one
629 * has been "manually" specified in jcr->MigrateJobId, and if
630 * so, it returns that JobId to be run. Otherwise, it
631 * examines the Selection Type to see what kind of migration
632 * we are doing (Volume, Job, Client, ...) and applies any
633 * Selection Pattern if appropriate to obtain a list of JobIds.
634 * Finally, it will loop over all the JobIds found, except the last
635 * one starting a new job with MigrationJobId set to that JobId, and
636 * finally, it returns the last JobId to the caller.
638 * Returns: -1 on error
639 * 0 if no jobs to migrate
640 * 1 if OK and jcr->previous_jr filled in
642 static int get_job_to_migrate(JCR *jcr)
644 char ed1[30], ed2[30];
645 POOL_MEM query(PM_MESSAGE);
650 idpkt ids, mid, jids;
655 char dt[MAX_TIME_LENGTH];
658 ids.list = get_pool_memory(PM_MESSAGE);
661 mid.list = get_pool_memory(PM_MESSAGE);
664 jids.list = get_pool_memory(PM_MESSAGE);
670 * If MigrateJobId is set, then we migrate only that Job,
671 * otherwise, we go through the full selection of jobs to
674 if (jcr->MigrateJobId != 0) {
675 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
676 edit_uint64(jcr->MigrateJobId, ids.list);
679 switch (jcr->job->selection_type) {
681 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
686 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
691 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
696 if (!jcr->job->selection_pattern) {
697 Jmsg(jcr, M_FATAL, 0, _("No %s SQL selection pattern specified.\n"), jcr->get_OperationName());
700 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
701 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
702 unique_dbid_handler, (void *)&ids)) {
703 Jmsg(jcr, M_FATAL, 0,
704 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
708 case MT_SMALLEST_VOL:
709 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
714 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
718 case MT_POOL_OCCUPANCY:
720 /* Find count of bytes in pool */
721 Mmsg(query, sql_pool_bytes, jcr->rpool->name());
722 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
723 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
726 if (ctx.count == 0) {
727 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
730 pool_bytes = ctx.value;
731 Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
733 if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
734 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
737 Dmsg0(dbglevel, "We should do Occupation migration.\n");
740 /* Find a list of MediaIds that could be migrated */
741 Mmsg(query, sql_mediaids, jcr->rpool->name());
742 Dmsg1(dbglevel, "query=%s\n", query.c_str());
743 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
744 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
747 if (ids.count == 0) {
748 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
751 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
753 if (!find_jobids_from_mediaid_list(jcr, &ids, "Volume")) {
756 /* ids == list of jobs */
758 for (int i=0; i < (int)ids.count; i++) {
759 stat = get_next_dbid_from_list(&p, &DBId);
760 Dmsg2(dbglevel, "get_next_dbid stat=%d JobId=%u\n", stat, (uint32_t)DBId);
762 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
764 } else if (stat == 0) {
769 Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
770 if (jids.count > 0) {
771 pm_strcat(jids.list, ",");
773 pm_strcat(jids.list, mid.list);
774 jids.count += mid.count;
776 /* Find count of bytes from Jobs */
777 Mmsg(query, sql_job_bytes, mid.list);
778 Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
779 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
780 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
783 pool_bytes -= ctx.value;
784 Dmsg2(dbglevel, "Total %s Job bytes=%s\n", jcr->get_ActionName(0), edit_int64_with_commas(ctx.value, ed1));
785 Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n",
786 edit_int64_with_commas(jcr->rpool->MigrationLowBytes, ed1),
787 edit_int64_with_commas(pool_bytes, ed2));
788 if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
789 Dmsg0(dbglevel, "We should be done.\n");
793 /* Transfer jids to ids, where the jobs list is expected */
794 ids.count = jids.count;
795 pm_strcpy(ids.list, jids.list);
796 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
799 ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
800 (void)localtime_r(&ttime, &tm);
801 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
804 Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
805 Dmsg1(dbglevel, "query=%s\n", query.c_str());
806 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
807 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
810 if (ids.count == 0) {
811 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
814 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
816 case MT_POOL_UNCOPIED_JOBS:
817 if (!find_jobids_of_pool_uncopied_jobs(jcr, &ids)) {
822 Jmsg(jcr, M_FATAL, 0, _("Unknown %s Selection Type.\n"), jcr->get_OperationName());
828 * Loop over all jobids except the last one, sending
829 * them to start_migration_job(), which will start a job
830 * for each of them. For the last JobId, we handle it below.
833 if (ids.count == 0) {
834 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
838 Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s chosen to be %s: %s\n"),
839 ids.count, (ids.count < 2) ? _(" was") : _("s were"),
840 jcr->get_ActionName(1), ids.list);
842 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
843 for (int i=1; i < (int)ids.count; i++) {
845 stat = get_next_jobid_from_list(&p, &JobId);
846 Dmsg3(dbglevel, "get_jobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
847 jcr->MigrateJobId = JobId;
848 start_migration_job(jcr);
849 Dmsg0(dbglevel, "Back from start_migration_job\n");
851 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
853 } else if (stat == 0) {
854 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
859 /* Now get the last JobId and handle it in the current job */
861 stat = get_next_jobid_from_list(&p, &JobId);
862 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
864 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
866 } else if (stat == 0) {
867 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
871 jcr->previous_jr.JobId = JobId;
872 Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
874 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
875 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
876 edit_int64(jcr->previous_jr.JobId, ed1),
877 jcr->get_ActionName(0),
878 db_strerror(jcr->db));
881 Jmsg(jcr, M_INFO, 0, _("%s using JobId=%s Job=%s\n"),
882 jcr->get_OperationName(),
883 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
884 Dmsg4(dbglevel, "%s JobId=%d using JobId=%s Job=%s\n",
885 jcr->get_OperationName(),
887 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
897 free_pool_memory(ids.list);
898 free_pool_memory(mid.list);
899 free_pool_memory(jids.list);
903 static void start_migration_job(JCR *jcr)
905 UAContext *ua = new_ua_context(jcr);
908 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
909 edit_uint64(jcr->MigrateJobId, ed1));
910 Dmsg2(dbglevel, "=============== %s cmd=%s\n", jcr->get_OperationName(), ua->cmd);
911 parse_ua_args(ua); /* parse command */
912 JobId_t jobid = run_cmd(ua, ua->cmd);
914 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
916 Jmsg(jcr, M_INFO, 0, _("%s JobId %d started.\n"), jcr->get_OperationName(), (int)jobid);
921 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
925 POOL_MEM query(PM_MESSAGE);
928 /* Basic query for MediaId */
929 Mmsg(query, query1, jcr->rpool->name());
930 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
931 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
934 if (ids->count == 0) {
935 Jmsg(jcr, M_INFO, 0, _("No %s found to %s.\n"), type, jcr->get_ActionName(0));
936 ok = true; /* Not an error */
938 } else if (ids->count != 1) {
939 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
942 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
944 ok = find_jobids_from_mediaid_list(jcr, ids, type);
951 * This routine returns:
952 * false if an error occurred
954 * ids.count number of jobids found (may be zero)
956 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
959 POOL_MEM query(PM_MESSAGE);
961 Mmsg(query, sql_jobids_from_mediaid, ids->list);
963 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
964 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
967 if (ids->count == 0) {
968 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
977 * This routine returns:
978 * false if an error occurred
980 * ids.count number of jobids found (may be zero)
982 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids)
985 POOL_MEM query(PM_MESSAGE);
987 /* Only a copy job is allowed */
988 if (jcr->get_JobType() != JT_COPY) {
989 Jmsg(jcr, M_FATAL, 0,
990 _("Selection Type 'pooluncopiedjobs' only applies to Copy Jobs"));
994 Dmsg1(dbglevel, "copy selection pattern=%s\n", jcr->rpool->name());
995 Mmsg(query, sql_jobids_of_pool_uncopied_jobs, jcr->rpool->name());
996 Dmsg1(dbglevel, "get uncopied jobs query=%s\n", query.c_str());
997 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
998 Jmsg(jcr, M_FATAL, 0,
999 _("SQL to get uncopied jobs failed. ERR=%s\n"), db_strerror(jcr->db));
1008 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
1009 const char *query2, const char *type)
1013 uitem *last_item = NULL;
1018 POOL_MEM query(PM_MESSAGE);
1020 item_chain = New(dlist(item, &item->link));
1021 if (!jcr->job->selection_pattern) {
1022 Jmsg(jcr, M_FATAL, 0, _("No %s %s selection pattern specified.\n"),
1023 jcr->get_OperationName(), type);
1026 Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
1027 /* Basic query for names */
1028 Mmsg(query, query1, jcr->rpool->name());
1029 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
1030 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
1031 (void *)item_chain)) {
1032 Jmsg(jcr, M_FATAL, 0,
1033 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
1036 Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
1037 if (item_chain->size() == 0) {
1038 Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to %s.\n"),
1039 jcr->rpool->name(), jcr->get_ActionName(0));
1041 goto bail_out; /* skip regex match */
1043 /* Compile regex expression */
1044 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
1046 regerror(rc, &preg, prbuf, sizeof(prbuf));
1047 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
1048 jcr->job->selection_pattern, prbuf);
1051 /* Now apply the regex to the names and remove any item not matched */
1052 foreach_dlist(item, item_chain) {
1053 const int nmatch = 30;
1054 regmatch_t pmatch[nmatch];
1056 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1057 free(last_item->item);
1058 item_chain->remove(last_item);
1060 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
1061 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
1063 last_item = NULL; /* keep this one */
1069 free(last_item->item);
1070 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1071 item_chain->remove(last_item);
1075 if (item_chain->size() == 0) {
1076 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName(0));
1078 goto bail_out; /* skip regex match */
1082 * At this point, we have a list of items in item_chain
1083 * that have been matched by the regex, so now we need
1084 * to look up their jobids.
1087 foreach_dlist(item, item_chain) {
1088 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
1089 Mmsg(query, query2, item->item, jcr->rpool->name());
1090 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
1091 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1092 Jmsg(jcr, M_FATAL, 0,
1093 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
1097 if (ids->count == 0) {
1098 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
1103 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
1104 foreach_dlist(item, item_chain) {
1112 * Release resources allocated during backup.
1114 void migration_cleanup(JCR *jcr, int TermCode)
1116 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
1117 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
1118 char ec6[50], ec7[50], ec8[50];
1119 char term_code[100], sd_term_msg[100];
1120 const char *term_msg;
1121 int msg_type = M_INFO;
1125 JCR *mig_jcr = jcr->mig_jcr;
1126 POOL_MEM query(PM_MESSAGE);
1128 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
1129 update_job_end(jcr, TermCode);
1130 memset(&mr, 0, sizeof(mr));
1133 * Check if we actually did something.
1134 * mig_jcr is jcr of the newly migrated job.
1137 char old_jobid[50], new_jobid[50];
1139 edit_uint64(jcr->previous_jr.JobId, old_jobid);
1140 edit_uint64(mig_jcr->jr.JobId, new_jobid);
1142 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
1143 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
1144 mig_jcr->VolSessionId = jcr->VolSessionId;
1145 mig_jcr->VolSessionTime = jcr->VolSessionTime;
1146 mig_jcr->jr.RealEndTime = 0;
1147 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
1149 update_job_end(mig_jcr, TermCode);
1151 /* Update final items to set them to the previous job's values */
1152 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
1153 "JobTDate=%s WHERE JobId=%s",
1154 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
1155 edit_uint64(jcr->previous_jr.JobTDate, ec1),
1157 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1160 * If we terminated a migration normally:
1161 * - mark the previous job as migrated
1162 * - move any Log records to the new JobId
1163 * - Purge the File records from the previous job
1165 if (jcr->get_JobType() == JT_MIGRATE && jcr->JobStatus == JS_Terminated) {
1166 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1167 (char)JT_MIGRATED_JOB, old_jobid);
1168 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1169 UAContext *ua = new_ua_context(jcr);
1170 /* Move JobLog to new JobId */
1171 Mmsg(query, "UPDATE Log SET JobId=%s WHERE JobId=%s",
1172 new_jobid, old_jobid);
1173 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1174 /* Purge all old file records, but leave Job record */
1175 purge_files_from_jobs(ua, old_jobid);
1176 free_ua_context(ua);
1180 * If we terminated a copy normally:
1181 * - copy any Log records to the new JobId
1182 * - set type="Job Copy" for the new job
1184 if (jcr->get_JobType() == JT_COPY && jcr->JobStatus == JS_Terminated) {
1185 /* Copy JobLog to new JobId */
1186 Mmsg(query, "INSERT INTO Log (JobId, Time, LogText ) "
1187 "SELECT %s, Time, LogText FROM Log WHERE JobId=%s",
1188 new_jobid, old_jobid);
1189 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1190 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1191 (char)JT_JOB_COPY, new_jobid);
1192 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1195 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
1196 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
1197 db_strerror(jcr->db));
1198 set_jcr_job_status(jcr, JS_ErrorTerminated);
1201 update_bootstrap_file(mig_jcr);
1203 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
1205 * Note, if the job has failed, most likely it did not write any
1206 * tape, so suppress this "error" message since in that case
1207 * it is normal. Or look at it the other way, only for a
1208 * normal exit should we complain about this error.
1210 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
1211 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
1213 mig_jcr->VolumeName[0] = 0; /* none */
1216 if (mig_jcr->VolumeName[0]) {
1217 /* Find last volume name. Multiple vols are separated by | */
1218 char *p = strrchr(mig_jcr->VolumeName, '|');
1222 p = mig_jcr->VolumeName; /* no |, take full name */
1224 bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1225 if (!db_get_media_record(jcr, jcr->db, &mr)) {
1226 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1227 mr.VolumeName, db_strerror(jcr->db));
1231 switch (jcr->JobStatus) {
1233 if (jcr->JobErrors || jcr->SDErrors) {
1234 term_msg = _("%s OK -- with warnings");
1236 term_msg = _("%s OK");
1240 case JS_ErrorTerminated:
1241 term_msg = _("*** %s Error ***");
1242 msg_type = M_ERROR; /* Generate error message */
1243 if (jcr->store_bsock) {
1244 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1245 if (jcr->SD_msg_chan) {
1246 pthread_cancel(jcr->SD_msg_chan);
1251 term_msg = _("%s Canceled");
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 = _("Inappropriate %s term code");
1264 if (jcr->get_JobType() == JT_MIGRATE && jcr->previous_jr.JobId != 0) {
1265 /* Mark previous job as migrated */
1266 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1267 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1268 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
1270 term_msg = _("%s -- no files to %s");
1273 bsnprintf(term_code, sizeof(term_code), term_msg, jcr->get_OperationName(), jcr->get_ActionName(0));
1274 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1275 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1276 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1280 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1283 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1285 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
1286 " Build OS: %s %s %s\n"
1287 " Prev Backup JobId: %s\n"
1288 " Prev Backup Job: %s\n"
1289 " New Backup JobId: %s\n"
1290 " Current JobId: %s\n"
1291 " Current Job: %s\n"
1292 " Backup Level: %s%s\n"
1294 " FileSet: \"%s\" %s\n"
1295 " Read Pool: \"%s\" (From %s)\n"
1296 " Read Storage: \"%s\" (From %s)\n"
1297 " Write Pool: \"%s\" (From %s)\n"
1298 " Write Storage: \"%s\" (From %s)\n"
1299 " Catalog: \"%s\" (From %s)\n"
1302 " Elapsed time: %s\n"
1304 " SD Files Written: %s\n"
1305 " SD Bytes Written: %s (%sB)\n"
1306 " Rate: %.1f KB/s\n"
1307 " Volume name(s): %s\n"
1308 " Volume Session Id: %d\n"
1309 " Volume Session Time: %d\n"
1310 " Last Volume Bytes: %s (%sB)\n"
1312 " SD termination status: %s\n"
1313 " Termination: %s\n\n"),
1314 BACULA, my_name, VERSION, LSMDATE, edt,
1315 HOST_OS, DISTNAME, DISTVER,
1316 edit_uint64(jcr->previous_jr.JobId, ec6),
1317 jcr->previous_jr.Job,
1318 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1319 edit_uint64(jcr->jr.JobId, ec8),
1321 level_to_str(jcr->get_JobLevel()), jcr->since,
1322 jcr->client->name(),
1323 jcr->fileset->name(), jcr->FSCreateTime,
1324 jcr->rpool->name(), jcr->rpool_source,
1325 jcr->rstore?jcr->rstore->name():"*None*",
1326 NPRT(jcr->rstore_source),
1327 jcr->pool->name(), jcr->pool_source,
1328 jcr->wstore?jcr->wstore->name():"*None*",
1329 NPRT(jcr->wstore_source),
1330 jcr->catalog->name(), jcr->catalog_source,
1333 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1335 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1336 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1337 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1339 mig_jcr ? mig_jcr->VolumeName : "",
1341 jcr->VolSessionTime,
1342 edit_uint64_with_commas(mr.VolBytes, ec4),
1343 edit_uint64_with_suffix(mr.VolBytes, ec5),
1348 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1350 free_jcr(jcr->mig_jcr);
1351 jcr->mig_jcr = NULL;
1353 Dmsg0(100, "Leave migrate_cleanup()\n");
1357 * Return next DBId from comma separated list
1360 * 1 if next DBId returned
1361 * 0 if no more DBIds are in list
1362 * -1 there is an error
1364 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1366 const int maxlen = 30;
1371 for (int i=0; i<maxlen; i++) {
1374 } else if (*q == ',') {
1383 } else if (!is_a_number(id)) {
1384 return -1; /* error */
1387 *DBId = str_to_int64(id);
1391 bool set_migration_wstorage(JCR *jcr, POOL *pool)
1393 POOL *wpool = pool->NextPool;
1396 Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
1401 if (!wpool->storage || wpool->storage->size() == 0) {
1402 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
1407 /* If pool storage specified, use it instead of job storage for backup */
1408 copy_wstorage(jcr, wpool->storage, _("Storage from Pool's NextPool resource"));