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 char OKbootstrap[] = "3000 OK bootstrap\n";
59 static int get_job_to_migrate(JCR *jcr);
61 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
62 const char *query2, const char *type);
63 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
65 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type);
66 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids);
67 static void start_migration_job(JCR *jcr);
68 static int get_next_dbid_from_list(char **p, DBId_t *DBId);
69 static bool set_migration_next_pool(JCR *jcr, POOL **pool);
72 * Called here before the job is run to do the job
73 * specific setup. Note, one of the important things to
74 * complete in this init code is to make the definitive
75 * choice of input and output storage devices. This is
76 * because immediately after the init, the job is queued
77 * in the jobq.c code, and it checks that all the resources
78 * (storage resources in particular) are available, so these
79 * must all be properly defined.
81 * previous_jr refers to the job DB record of the Job that is
82 * going to be migrated.
83 * prev_job refers to the job resource of the Job that is
84 * going to be migrated.
85 * jcr is the jcr for the current "migration" job. It is a
86 * control job that is put in the DB as a migration job, which
87 * means that this job migrated a previous job to a new job.
88 * No Volume or File data is associated with this control
90 * mig_jcr refers to the newly migrated job that is run by
91 * the current jcr. It is a backup job that moves (migrates) the
92 * data written for the previous_jr into the new pool. This
93 * job (mig_jcr) becomes the new backup job that replaces
94 * the original backup job. Note, this jcr is not really run. It
95 * is simply attached to the current jcr. It will show up in
96 * the Director's status output, but not in the SD or FD, both of
97 * which deal only with the current migration job (i.e. jcr).
99 bool do_migration_init(JCR *jcr)
103 JCR *mig_jcr; /* newly migrated job */
107 apply_pool_overrides(jcr);
109 if (!allow_duplicate_job(jcr)) {
113 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
114 if (jcr->jr.PoolId == 0) {
115 Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
116 Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
120 * Note, at this point, pool is the pool for this job. We
121 * transfer it to rpool (read pool), and a bit later,
122 * pool will be changed to point to the write pool,
123 * which comes from pool->NextPool.
125 jcr->rpool = jcr->pool; /* save read pool */
126 pm_strcpy(jcr->rpool_source, jcr->pool_source);
129 Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
131 if (!get_or_create_fileset_record(jcr)) {
132 Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
133 Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
137 /* If we find a job or jobs to migrate it is previous_jr.JobId */
138 count = get_job_to_migrate(jcr);
143 set_migration_next_pool(jcr, &pool);
144 return true; /* no work */
147 Dmsg1(dbglevel, "Back from get_job_to_migrate JobId=%d\n", (int)jcr->JobId);
149 if (jcr->previous_jr.JobId == 0) {
150 Dmsg1(dbglevel, "JobId=%d no previous JobId\n", (int)jcr->JobId);
151 Jmsg(jcr, M_INFO, 0, _("No previous Job found to %s.\n"), jcr->get_ActionName(0));
152 set_migration_next_pool(jcr, &pool);
153 return true; /* no work */
156 create_restore_bootstrap_file(jcr);
158 if (jcr->previous_jr.JobId == 0 || jcr->ExpectedFiles == 0) {
159 set_jcr_job_status(jcr, JS_Terminated);
160 Dmsg1(dbglevel, "JobId=%d expected files == 0\n", (int)jcr->JobId);
161 if (jcr->previous_jr.JobId == 0) {
162 Jmsg(jcr, M_INFO, 0, _("No previous Job found to %s.\n"), jcr->get_ActionName(0));
164 Jmsg(jcr, M_INFO, 0, _("Previous Job has no data to %s.\n"), jcr->get_ActionName(0));
166 set_migration_next_pool(jcr, &pool);
167 return true; /* no work */
171 Dmsg5(dbglevel, "JobId=%d: Current: Name=%s JobId=%d Type=%c Level=%c\n",
173 jcr->jr.Name, (int)jcr->jr.JobId,
174 jcr->jr.JobType, jcr->jr.JobLevel);
177 job = (JOB *)GetResWithName(R_JOB, jcr->jr.Name);
178 prev_job = (JOB *)GetResWithName(R_JOB, jcr->previous_jr.Name);
181 Jmsg(jcr, M_FATAL, 0, _("Job resource not found for \"%s\".\n"), jcr->jr.Name);
185 Jmsg(jcr, M_FATAL, 0, _("Previous Job resource not found for \"%s\".\n"),
186 jcr->previous_jr.Name);
190 jcr->spool_data = job->spool_data; /* turn on spooling if requested in job */
192 /* Create a migration jcr */
193 mig_jcr = jcr->mig_jcr = new_jcr(sizeof(JCR), dird_free_jcr);
194 memcpy(&mig_jcr->previous_jr, &jcr->previous_jr, sizeof(mig_jcr->previous_jr));
197 * Turn the mig_jcr into a "real" job that takes on the aspects of
198 * the previous backup job "prev_job".
200 set_jcr_defaults(mig_jcr, prev_job);
201 if (!setup_job(mig_jcr)) {
202 Jmsg(jcr, M_FATAL, 0, _("setup job failed.\n"));
206 /* Now reset the job record from the previous job */
207 memcpy(&mig_jcr->jr, &jcr->previous_jr, sizeof(mig_jcr->jr));
208 /* Update the jr to reflect the new values of PoolId and JobId. */
209 mig_jcr->jr.PoolId = jcr->jr.PoolId;
210 mig_jcr->jr.JobId = mig_jcr->JobId;
212 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
213 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
214 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
216 if (set_migration_next_pool(jcr, &pool)) {
217 /* If pool storage specified, use it for restore */
218 copy_rstorage(mig_jcr, pool->storage, _("Pool resource"));
219 copy_rstorage(jcr, pool->storage, _("Pool resource"));
221 mig_jcr->pool = jcr->pool;
222 mig_jcr->jr.PoolId = jcr->jr.PoolId;
230 * set_migration_next_pool() called by do_migration_init()
231 * at differents stages.
232 * The idea here is tofactorize the NextPool's search code and
233 * to permit do_migration_init() to return with NextPool set in jcr struct.
235 static bool set_migration_next_pool(JCR *jcr, POOL **retpool)
242 * Get the PoolId used with the original job. Then
243 * find the pool name from the database record.
245 memset(&pr, 0, sizeof(pr));
246 pr.PoolId = jcr->jr.PoolId;
247 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
248 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
249 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
252 /* Get the pool resource corresponding to the original job */
253 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
256 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
261 * If the original backup pool has a NextPool, make sure a
262 * record exists in the database. Note, in this case, we
263 * will be migrating from pool to pool->NextPool.
265 if (pool->NextPool) {
266 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->name());
267 if (jcr->jr.PoolId == 0) {
271 if (!set_migration_wstorage(jcr, pool)) {
274 jcr->pool = pool->NextPool;
275 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
277 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
284 * Do a Migration of a previous job
286 * Returns: false on failure
289 bool do_migration(JCR *jcr)
293 JCR *mig_jcr = jcr->mig_jcr; /* newly migrated job */
296 * If mig_jcr is NULL, there is nothing to do for this job,
297 * so set a normal status, cleanup and return OK.
300 set_jcr_job_status(jcr, JS_Terminated);
301 migration_cleanup(jcr, jcr->JobStatus);
305 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
306 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
307 edit_int64(jcr->previous_jr.JobId, ed1),
308 jcr->get_ActionName(0),
309 db_strerror(jcr->db));
310 set_jcr_job_status(jcr, JS_Terminated);
311 migration_cleanup(jcr, jcr->JobStatus);
314 /* Make sure this job was not already migrated */
315 if (jcr->previous_jr.JobType != JT_BACKUP) {
316 Jmsg(jcr, M_INFO, 0, _("JobId %s already %s probably by another Job. %s stopped.\n"),
317 edit_int64(jcr->previous_jr.JobId, ed1),
318 jcr->get_ActionName(1),
319 jcr->get_OperationName());
320 set_jcr_job_status(jcr, JS_Terminated);
321 migration_cleanup(jcr, jcr->JobStatus);
325 /* Print Job Start message */
326 Jmsg(jcr, M_INFO, 0, _("Start %s JobId %s, Job=%s\n"),
327 jcr->get_OperationName(), edit_uint64(jcr->JobId, ed1), jcr->Job);
330 * Open a message channel connection with the Storage
331 * daemon. This is to let him know that our client
332 * will be contacting him for a backup session.
335 Dmsg0(110, "Open connection with storage daemon\n");
336 set_jcr_job_status(jcr, JS_WaitSD);
337 set_jcr_job_status(mig_jcr, JS_WaitSD);
339 * Start conversation with Storage daemon
341 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
344 sd = jcr->store_bsock;
346 * Now start a job with the Storage daemon
348 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
349 ((STORE *)jcr->rstorage->first())->name(),
350 ((STORE *)jcr->wstorage->first())->name());
351 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
352 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
353 ((STORE *)jcr->rstorage->first())->name());
356 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
359 Dmsg0(150, "Storage daemon connection OK\n");
361 if (!send_bootstrap_file(jcr, sd) ||
362 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
367 * We re-update the job start record so that the start
368 * time is set after the run before job. This avoids
369 * that any files created by the run before job will
370 * be saved twice. They will be backed up in the current
371 * job, but not in the next one unless they are changed.
372 * Without this, they will be backed up in this job and
373 * in the next job run because in that case, their date
374 * is after the start of this run.
376 jcr->start_time = time(NULL);
377 jcr->jr.StartTime = jcr->start_time;
378 jcr->jr.JobTDate = jcr->start_time;
379 set_jcr_job_status(jcr, JS_Running);
381 /* Update job start record for this migration control job */
382 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
383 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
388 mig_jcr->start_time = time(NULL);
389 mig_jcr->jr.StartTime = mig_jcr->start_time;
390 mig_jcr->jr.JobTDate = mig_jcr->start_time;
391 set_jcr_job_status(mig_jcr, JS_Running);
393 /* Update job start record for the real migration backup job */
394 if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
395 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
399 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
400 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
401 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
405 * Start the job prior to starting the message thread below
406 * to avoid two threads from using the BSOCK structure at
409 if (!sd->fsend("run")) {
414 * Now start a Storage daemon message thread
416 if (!start_storage_daemon_message_thread(jcr)) {
421 set_jcr_job_status(jcr, JS_Running);
422 set_jcr_job_status(mig_jcr, JS_Running);
424 /* Pickup Job termination data */
425 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors */
426 wait_for_storage_daemon_termination(jcr);
427 set_jcr_job_status(jcr, jcr->SDJobStatus);
428 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
429 if (jcr->JobStatus != JS_Terminated) {
433 migration_cleanup(jcr, jcr->JobStatus);
443 /* Add an item to the list if it is unique */
444 static void add_unique_id(idpkt *ids, char *item)
446 const int maxlen = 30;
450 /* Walk through current list to see if each item is the same as item */
453 for (int i=0; i<maxlen; i++) {
456 } else if (*q == ',') {
463 if (strcmp(item, id) == 0) {
467 /* Did not find item, so add it to list */
468 if (ids->count == 0) {
471 pm_strcat(ids->list, ",");
473 pm_strcat(ids->list, item);
475 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
480 * Callback handler make list of DB Ids
482 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
484 idpkt *ids = (idpkt *)ctx;
486 add_unique_id(ids, row[0]);
487 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
497 static int item_compare(void *item1, void *item2)
499 uitem *i1 = (uitem *)item1;
500 uitem *i2 = (uitem *)item2;
501 return strcmp(i1->item, i2->item);
504 static int unique_name_handler(void *ctx, int num_fields, char **row)
506 dlist *list = (dlist *)ctx;
508 uitem *new_item = (uitem *)malloc(sizeof(uitem));
511 memset(new_item, 0, sizeof(uitem));
512 new_item->item = bstrdup(row[0]);
513 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
514 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
515 if (item != new_item) { /* already in list */
516 free(new_item->item);
517 free((char *)new_item);
523 /* Get Job names in Pool */
524 const char *sql_job =
525 "SELECT DISTINCT Job.Name from Job,Pool"
526 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
528 /* Get JobIds from regex'ed Job names */
529 const char *sql_jobids_from_job =
530 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
531 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
532 " ORDER by Job.StartTime";
534 /* Get Client names in Pool */
535 const char *sql_client =
536 "SELECT DISTINCT Client.Name from Client,Pool,Job"
537 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
538 " Job.PoolId=Pool.PoolId";
540 /* Get JobIds from regex'ed Client names */
541 const char *sql_jobids_from_client =
542 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
543 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
544 " AND Job.ClientId=Client.ClientId AND Job.Type IN ('B','C')"
545 " AND Job.JobStatus IN ('T','W')"
546 " ORDER by Job.StartTime";
548 /* Get Volume names in Pool */
549 const char *sql_vol =
550 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
551 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
552 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
554 /* Get JobIds from regex'ed Volume names */
555 const char *sql_jobids_from_vol =
556 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
557 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
558 " AND JobMedia.JobId=Job.JobId AND Job.Type IN ('B','C')"
559 " AND Job.JobStatus IN ('T','W') AND Media.Enabled=1"
560 " ORDER by Job.StartTime";
562 const char *sql_smallest_vol =
563 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
564 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
565 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
566 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
567 " ORDER BY VolBytes ASC LIMIT 1";
569 const char *sql_oldest_vol =
570 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
571 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
572 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
573 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
574 " ORDER BY LastWritten ASC LIMIT 1";
576 /* Get JobIds when we have selected MediaId */
577 const char *sql_jobids_from_mediaid =
578 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
579 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId IN (%s)"
580 " AND Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
581 " ORDER by Job.StartTime";
583 /* Get the number of bytes in the pool */
584 const char *sql_pool_bytes =
585 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN"
586 " (SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
587 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
588 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
589 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
590 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId)";
592 /* Get the number of bytes in the Jobs */
593 const char *sql_job_bytes =
594 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
596 /* Get Media Ids in Pool */
597 const char *sql_mediaids =
598 "SELECT MediaId FROM Media,Pool WHERE"
599 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
600 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
602 /* Get JobIds in Pool longer than specified time */
603 const char *sql_pool_time =
604 "SELECT DISTINCT Job.JobId FROM Pool,Job,Media,JobMedia WHERE"
605 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
606 " VolStatus IN ('Full','Used','Error') AND Media.Enabled=1 AND"
607 " Job.Type IN ('B','C') AND Job.JobStatus IN ('T','W') AND"
608 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
609 " AND Job.RealEndTime<='%s'";
611 /* Get JobIds from successfully completed backup jobs which have not been copied before */
612 const char *sql_jobids_of_pool_uncopied_jobs =
613 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
614 " WHERE Pool.Name = '%s' AND Pool.PoolId = Job.PoolId"
615 " AND Job.Type = 'B' AND Job.JobStatus IN ('T','W')"
616 " AND Job.jobBytes > 0"
617 " AND Job.JobId NOT IN"
618 " (SELECT PriorJobId FROM Job WHERE"
619 " Type IN ('B','C') AND Job.JobStatus IN ('T','W')"
620 " AND PriorJobId != 0)"
621 " ORDER by Job.StartTime";
624 * const char *sql_ujobid =
625 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
626 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
627 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
632 * This is the central piece of code that finds a job or jobs
633 * actually JobIds to migrate. It first looks to see if one
634 * has been "manually" specified in jcr->MigrateJobId, and if
635 * so, it returns that JobId to be run. Otherwise, it
636 * examines the Selection Type to see what kind of migration
637 * we are doing (Volume, Job, Client, ...) and applies any
638 * Selection Pattern if appropriate to obtain a list of JobIds.
639 * Finally, it will loop over all the JobIds found, except the last
640 * one starting a new job with MigrationJobId set to that JobId, and
641 * finally, it returns the last JobId to the caller.
643 * Returns: -1 on error
644 * 0 if no jobs to migrate
645 * 1 if OK and jcr->previous_jr filled in
647 static int get_job_to_migrate(JCR *jcr)
649 char ed1[30], ed2[30];
650 POOL_MEM query(PM_MESSAGE);
655 idpkt ids, mid, jids;
660 char dt[MAX_TIME_LENGTH];
663 ids.list = get_pool_memory(PM_MESSAGE);
666 mid.list = get_pool_memory(PM_MESSAGE);
669 jids.list = get_pool_memory(PM_MESSAGE);
675 * If MigrateJobId is set, then we migrate only that Job,
676 * otherwise, we go through the full selection of jobs to
679 if (jcr->MigrateJobId != 0) {
680 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
681 edit_uint64(jcr->MigrateJobId, ids.list);
684 switch (jcr->job->selection_type) {
686 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
691 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
696 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
701 if (!jcr->job->selection_pattern) {
702 Jmsg(jcr, M_FATAL, 0, _("No %s SQL selection pattern specified.\n"), jcr->get_OperationName());
705 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
706 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
707 unique_dbid_handler, (void *)&ids)) {
708 Jmsg(jcr, M_FATAL, 0,
709 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
713 case MT_SMALLEST_VOL:
714 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
719 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
723 case MT_POOL_OCCUPANCY:
725 /* Find count of bytes in pool */
726 Mmsg(query, sql_pool_bytes, jcr->rpool->name());
727 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
728 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
731 if (ctx.count == 0) {
732 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
735 pool_bytes = ctx.value;
736 Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
738 if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
739 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
742 Dmsg0(dbglevel, "We should do Occupation migration.\n");
745 /* Find a list of MediaIds that could be migrated */
746 Mmsg(query, sql_mediaids, jcr->rpool->name());
747 Dmsg1(dbglevel, "query=%s\n", query.c_str());
748 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
749 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
752 if (ids.count == 0) {
753 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
756 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
758 if (!find_jobids_from_mediaid_list(jcr, &ids, "Volume")) {
761 /* ids == list of jobs */
763 for (int i=0; i < (int)ids.count; i++) {
764 stat = get_next_dbid_from_list(&p, &DBId);
765 Dmsg2(dbglevel, "get_next_dbid stat=%d JobId=%u\n", stat, (uint32_t)DBId);
767 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
769 } else if (stat == 0) {
774 Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
775 if (jids.count > 0) {
776 pm_strcat(jids.list, ",");
778 pm_strcat(jids.list, mid.list);
779 jids.count += mid.count;
781 /* Find count of bytes from Jobs */
782 Mmsg(query, sql_job_bytes, mid.list);
783 Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
784 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
785 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
788 pool_bytes -= ctx.value;
789 Dmsg2(dbglevel, "Total %s Job bytes=%s\n", jcr->get_ActionName(0), edit_int64_with_commas(ctx.value, ed1));
790 Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n",
791 edit_int64_with_commas(jcr->rpool->MigrationLowBytes, ed1),
792 edit_int64_with_commas(pool_bytes, ed2));
793 if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
794 Dmsg0(dbglevel, "We should be done.\n");
798 /* Transfer jids to ids, where the jobs list is expected */
799 ids.count = jids.count;
800 pm_strcpy(ids.list, jids.list);
801 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
804 ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
805 (void)localtime_r(&ttime, &tm);
806 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
809 Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
810 Dmsg1(dbglevel, "query=%s\n", query.c_str());
811 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
812 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
815 if (ids.count == 0) {
816 Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
819 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
821 case MT_POOL_UNCOPIED_JOBS:
822 if (!find_jobids_of_pool_uncopied_jobs(jcr, &ids)) {
827 Jmsg(jcr, M_FATAL, 0, _("Unknown %s Selection Type.\n"), jcr->get_OperationName());
833 * Loop over all jobids except the last one, sending
834 * them to start_migration_job(), which will start a job
835 * for each of them. For the last JobId, we handle it below.
838 if (ids.count == 0) {
839 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
843 Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s chosen to be %s: %s\n"),
844 ids.count, (ids.count < 2) ? _(" was") : _("s were"),
845 jcr->get_ActionName(1), ids.list);
847 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
848 for (int i=1; i < (int)ids.count; i++) {
850 stat = get_next_jobid_from_list(&p, &JobId);
851 Dmsg3(dbglevel, "get_jobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
852 jcr->MigrateJobId = JobId;
853 start_migration_job(jcr);
854 Dmsg0(dbglevel, "Back from start_migration_job\n");
856 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
858 } else if (stat == 0) {
859 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
864 /* Now get the last JobId and handle it in the current job */
866 stat = get_next_jobid_from_list(&p, &JobId);
867 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
869 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
871 } else if (stat == 0) {
872 Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
876 jcr->previous_jr.JobId = JobId;
877 Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
879 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
880 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
881 edit_int64(jcr->previous_jr.JobId, ed1),
882 jcr->get_ActionName(0),
883 db_strerror(jcr->db));
886 Jmsg(jcr, M_INFO, 0, _("%s using JobId=%s Job=%s\n"),
887 jcr->get_OperationName(),
888 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
889 Dmsg4(dbglevel, "%s JobId=%d using JobId=%s Job=%s\n",
890 jcr->get_OperationName(),
892 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
902 free_pool_memory(ids.list);
903 free_pool_memory(mid.list);
904 free_pool_memory(jids.list);
908 static void start_migration_job(JCR *jcr)
910 UAContext *ua = new_ua_context(jcr);
913 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
914 edit_uint64(jcr->MigrateJobId, ed1));
915 Dmsg2(dbglevel, "=============== %s cmd=%s\n", jcr->get_OperationName(), ua->cmd);
916 parse_ua_args(ua); /* parse command */
917 JobId_t jobid = run_cmd(ua, ua->cmd);
919 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
921 Jmsg(jcr, M_INFO, 0, _("%s JobId %d started.\n"), jcr->get_OperationName(), (int)jobid);
926 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
930 POOL_MEM query(PM_MESSAGE);
933 /* Basic query for MediaId */
934 Mmsg(query, query1, jcr->rpool->name());
935 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
936 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
939 if (ids->count == 0) {
940 Jmsg(jcr, M_INFO, 0, _("No %s found to %s.\n"), type, jcr->get_ActionName(0));
941 ok = true; /* Not an error */
943 } else if (ids->count != 1) {
944 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
947 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
949 ok = find_jobids_from_mediaid_list(jcr, ids, type);
956 * This routine returns:
957 * false if an error occurred
959 * ids.count number of jobids found (may be zero)
961 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
964 POOL_MEM query(PM_MESSAGE);
966 Mmsg(query, sql_jobids_from_mediaid, ids->list);
968 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
969 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
972 if (ids->count == 0) {
973 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
982 * This routine returns:
983 * false if an error occurred
985 * ids.count number of jobids found (may be zero)
987 static bool find_jobids_of_pool_uncopied_jobs(JCR *jcr, idpkt *ids)
990 POOL_MEM query(PM_MESSAGE);
992 /* Only a copy job is allowed */
993 if (jcr->get_JobType() != JT_COPY) {
994 Jmsg(jcr, M_FATAL, 0,
995 _("Selection Type 'pooluncopiedjobs' only applies to Copy Jobs"));
999 Dmsg1(dbglevel, "copy selection pattern=%s\n", jcr->rpool->name());
1000 Mmsg(query, sql_jobids_of_pool_uncopied_jobs, jcr->rpool->name());
1001 Dmsg1(dbglevel, "get uncopied jobs query=%s\n", query.c_str());
1002 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1003 Jmsg(jcr, M_FATAL, 0,
1004 _("SQL to get uncopied jobs failed. ERR=%s\n"), db_strerror(jcr->db));
1013 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
1014 const char *query2, const char *type)
1018 uitem *last_item = NULL;
1023 POOL_MEM query(PM_MESSAGE);
1025 item_chain = New(dlist(item, &item->link));
1026 if (!jcr->job->selection_pattern) {
1027 Jmsg(jcr, M_FATAL, 0, _("No %s %s selection pattern specified.\n"),
1028 jcr->get_OperationName(), type);
1031 Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
1032 /* Basic query for names */
1033 Mmsg(query, query1, jcr->rpool->name());
1034 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
1035 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
1036 (void *)item_chain)) {
1037 Jmsg(jcr, M_FATAL, 0,
1038 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
1041 Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
1042 if (item_chain->size() == 0) {
1043 Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to %s.\n"),
1044 jcr->rpool->name(), jcr->get_ActionName(0));
1046 goto bail_out; /* skip regex match */
1048 /* Compile regex expression */
1049 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
1051 regerror(rc, &preg, prbuf, sizeof(prbuf));
1052 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
1053 jcr->job->selection_pattern, prbuf);
1056 /* Now apply the regex to the names and remove any item not matched */
1057 foreach_dlist(item, item_chain) {
1058 const int nmatch = 30;
1059 regmatch_t pmatch[nmatch];
1061 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1062 free(last_item->item);
1063 item_chain->remove(last_item);
1065 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
1066 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
1068 last_item = NULL; /* keep this one */
1074 free(last_item->item);
1075 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
1076 item_chain->remove(last_item);
1080 if (item_chain->size() == 0) {
1081 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to %s.\n"), jcr->get_ActionName(0));
1083 goto bail_out; /* skip regex match */
1087 * At this point, we have a list of items in item_chain
1088 * that have been matched by the regex, so now we need
1089 * to look up their jobids.
1092 foreach_dlist(item, item_chain) {
1093 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
1094 Mmsg(query, query2, item->item, jcr->rpool->name());
1095 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
1096 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1097 Jmsg(jcr, M_FATAL, 0,
1098 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
1102 if (ids->count == 0) {
1103 Jmsg(jcr, M_INFO, 0, _("No %ss found to %s.\n"), type, jcr->get_ActionName(0));
1108 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
1109 foreach_dlist(item, item_chain) {
1117 * Release resources allocated during backup.
1119 void migration_cleanup(JCR *jcr, int TermCode)
1121 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
1122 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
1123 char ec6[50], ec7[50], ec8[50];
1124 char term_code[100], sd_term_msg[100];
1125 const char *term_msg;
1126 int msg_type = M_INFO;
1130 JCR *mig_jcr = jcr->mig_jcr;
1131 POOL_MEM query(PM_MESSAGE);
1133 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
1134 update_job_end(jcr, TermCode);
1135 memset(&mr, 0, sizeof(mr));
1138 * Check if we actually did something.
1139 * mig_jcr is jcr of the newly migrated job.
1142 char old_jobid[50], new_jobid[50];
1144 edit_uint64(jcr->previous_jr.JobId, old_jobid);
1145 edit_uint64(mig_jcr->jr.JobId, new_jobid);
1147 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
1148 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
1149 mig_jcr->VolSessionId = jcr->VolSessionId;
1150 mig_jcr->VolSessionTime = jcr->VolSessionTime;
1151 mig_jcr->jr.RealEndTime = 0;
1152 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
1154 update_job_end(mig_jcr, TermCode);
1156 /* Update final items to set them to the previous job's values */
1157 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
1158 "JobTDate=%s WHERE JobId=%s",
1159 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
1160 edit_uint64(jcr->previous_jr.JobTDate, ec1),
1162 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1165 * If we terminated a migration normally:
1166 * - mark the previous job as migrated
1167 * - move any Log records to the new JobId
1168 * - Purge the File records from the previous job
1170 if (jcr->get_JobType() == JT_MIGRATE && jcr->JobStatus == JS_Terminated) {
1171 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1172 (char)JT_MIGRATED_JOB, old_jobid);
1173 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1174 UAContext *ua = new_ua_context(jcr);
1175 /* Move JobLog to new JobId */
1176 Mmsg(query, "UPDATE Log SET JobId=%s WHERE JobId=%s",
1177 new_jobid, old_jobid);
1178 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1179 /* Purge all old file records, but leave Job record */
1180 purge_files_from_jobs(ua, old_jobid);
1181 free_ua_context(ua);
1185 * If we terminated a copy normally:
1186 * - copy any Log records to the new JobId
1187 * - set type="Job Copy" for the new job
1189 if (jcr->get_JobType() == JT_COPY && jcr->JobStatus == JS_Terminated) {
1190 /* Copy JobLog to new JobId */
1191 Mmsg(query, "INSERT INTO Log (JobId, Time, LogText ) "
1192 "SELECT %s, Time, LogText FROM Log WHERE JobId=%s",
1193 new_jobid, old_jobid);
1194 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1195 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1196 (char)JT_JOB_COPY, new_jobid);
1197 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1200 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
1201 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
1202 db_strerror(jcr->db));
1203 set_jcr_job_status(jcr, JS_ErrorTerminated);
1206 update_bootstrap_file(mig_jcr);
1208 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
1210 * Note, if the job has failed, most likely it did not write any
1211 * tape, so suppress this "error" message since in that case
1212 * it is normal. Or look at it the other way, only for a
1213 * normal exit should we complain about this error.
1215 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
1216 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
1218 mig_jcr->VolumeName[0] = 0; /* none */
1221 if (mig_jcr->VolumeName[0]) {
1222 /* Find last volume name. Multiple vols are separated by | */
1223 char *p = strrchr(mig_jcr->VolumeName, '|');
1227 p = mig_jcr->VolumeName; /* no |, take full name */
1229 bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1230 if (!db_get_media_record(jcr, jcr->db, &mr)) {
1231 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1232 mr.VolumeName, db_strerror(jcr->db));
1236 switch (jcr->JobStatus) {
1238 if (jcr->JobErrors || jcr->SDErrors) {
1239 term_msg = _("%s OK -- with warnings");
1241 term_msg = _("%s OK");
1245 case JS_ErrorTerminated:
1246 term_msg = _("*** %s Error ***");
1247 msg_type = M_ERROR; /* Generate error message */
1248 if (jcr->store_bsock) {
1249 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1250 if (jcr->SD_msg_chan) {
1251 pthread_cancel(jcr->SD_msg_chan);
1256 term_msg = _("%s Canceled");
1257 if (jcr->store_bsock) {
1258 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1259 if (jcr->SD_msg_chan) {
1260 pthread_cancel(jcr->SD_msg_chan);
1265 term_msg = _("Inappropriate %s term code");
1269 if (jcr->get_JobType() == JT_MIGRATE && jcr->previous_jr.JobId != 0) {
1270 /* Mark previous job as migrated */
1271 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1272 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1273 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
1275 term_msg = _("%s -- no files to %s");
1278 bsnprintf(term_code, sizeof(term_code), term_msg, jcr->get_OperationName(), jcr->get_ActionName(0));
1279 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1280 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1281 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1285 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1288 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1290 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s): %s\n"
1291 " Build OS: %s %s %s\n"
1292 " Prev Backup JobId: %s\n"
1293 " Prev Backup Job: %s\n"
1294 " New Backup JobId: %s\n"
1295 " Current JobId: %s\n"
1296 " Current Job: %s\n"
1297 " Backup Level: %s%s\n"
1299 " FileSet: \"%s\" %s\n"
1300 " Read Pool: \"%s\" (From %s)\n"
1301 " Read Storage: \"%s\" (From %s)\n"
1302 " Write Pool: \"%s\" (From %s)\n"
1303 " Write Storage: \"%s\" (From %s)\n"
1304 " Catalog: \"%s\" (From %s)\n"
1307 " Elapsed time: %s\n"
1309 " SD Files Written: %s\n"
1310 " SD Bytes Written: %s (%sB)\n"
1311 " Rate: %.1f KB/s\n"
1312 " Volume name(s): %s\n"
1313 " Volume Session Id: %d\n"
1314 " Volume Session Time: %d\n"
1315 " Last Volume Bytes: %s (%sB)\n"
1317 " SD termination status: %s\n"
1318 " Termination: %s\n\n"),
1319 BACULA, my_name, VERSION, LSMDATE, edt,
1320 HOST_OS, DISTNAME, DISTVER,
1321 edit_uint64(jcr->previous_jr.JobId, ec6),
1322 jcr->previous_jr.Job,
1323 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1324 edit_uint64(jcr->jr.JobId, ec8),
1326 level_to_str(jcr->get_JobLevel()), jcr->since,
1327 jcr->client->name(),
1328 jcr->fileset->name(), jcr->FSCreateTime,
1329 jcr->rpool->name(), jcr->rpool_source,
1330 jcr->rstore?jcr->rstore->name():"*None*",
1331 NPRT(jcr->rstore_source),
1332 jcr->pool->name(), jcr->pool_source,
1333 jcr->wstore?jcr->wstore->name():"*None*",
1334 NPRT(jcr->wstore_source),
1335 jcr->catalog->name(), jcr->catalog_source,
1338 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1340 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1341 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1342 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1344 mig_jcr ? mig_jcr->VolumeName : "",
1346 jcr->VolSessionTime,
1347 edit_uint64_with_commas(mr.VolBytes, ec4),
1348 edit_uint64_with_suffix(mr.VolBytes, ec5),
1353 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1355 free_jcr(jcr->mig_jcr);
1356 jcr->mig_jcr = NULL;
1358 Dmsg0(100, "Leave migrate_cleanup()\n");
1362 * Return next DBId from comma separated list
1365 * 1 if next DBId returned
1366 * 0 if no more DBIds are in list
1367 * -1 there is an error
1369 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1371 const int maxlen = 30;
1376 for (int i=0; i<maxlen; i++) {
1379 } else if (*q == ',') {
1388 } else if (!is_a_number(id)) {
1389 return -1; /* error */
1392 *DBId = str_to_int64(id);
1396 bool set_migration_wstorage(JCR *jcr, POOL *pool)
1398 POOL *wpool = pool->NextPool;
1401 Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
1406 if (!wpool->storage || wpool->storage->size() == 0) {
1407 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
1412 /* If pool storage specified, use it instead of job storage for backup */
1413 copy_wstorage(jcr, wpool->storage, _("Storage from Pool's NextPool resource"));