2 Bacula® - The Network Backup Solution
4 Copyright (C) 2004-2008 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 John Walker.
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
33 * Kern Sibbald, September MMIV
35 * Basic tasks done here:
36 * Open DB and create records for this job.
37 * Open Message Channel with Storage daemon to tell him a job will be starting.
38 * Open connection with Storage daemon and pass him commands
40 * When the Storage daemon finishes the job, update the DB.
49 #include "lib/bregex.h"
54 static const int dbglevel = 10;
56 static char OKbootstrap[] = "3000 OK bootstrap\n";
57 static int get_job_to_migrate(JCR *jcr);
59 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
60 const char *query2, const char *type);
61 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
63 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type);
64 static void start_migration_job(JCR *jcr);
65 static int get_next_dbid_from_list(char **p, DBId_t *DBId);
68 * Called here before the job is run to do the job
69 * specific setup. Note, one of the important things to
70 * complete in this init code is to make the definitive
71 * choice of input and output storage devices. This is
72 * because immediately after the init, the job is queued
73 * in the jobq.c code, and it checks that all the resources
74 * (storage resources in particular) are available, so these
75 * must all be properly defined.
77 * previous_jr refers to the job DB record of the Job that is
78 * going to be migrated.
79 * prev_job refers to the job resource of the Job that is
80 * going to be migrated.
81 * jcr is the jcr for the current "migration" job. It is a
82 * control job that is put in the DB as a migration job, which
83 * means that this job migrated a previous job to a new job.
84 * No Volume or File data is associated with this control
86 * mig_jcr refers to the newly migrated job that is run by
87 * the current jcr. It is a backup job that moves (migrates) the
88 * data written for the previous_jr into the new pool. This
89 * job (mig_jcr) becomes the new backup job that replaces
90 * the original backup job. Note, this jcr is not really run. It
91 * is simply attached to the current jcr. It will show up in
92 * the Director's status output, but not in the SD or FD, both of
93 * which deal only with the current migration job (i.e. jcr).
95 bool do_migration_init(JCR *jcr)
101 JCR *mig_jcr; /* newly migrated job */
105 apply_pool_overrides(jcr);
107 if (!allow_duplicate_job(jcr)) {
111 jcr->jr.PoolId = get_or_create_pool_record(jcr, jcr->pool->name());
112 if (jcr->jr.PoolId == 0) {
113 Dmsg1(dbglevel, "JobId=%d no PoolId\n", (int)jcr->JobId);
114 Jmsg(jcr, M_FATAL, 0, _("Could not get or create a Pool record.\n"));
118 * Note, at this point, pool is the pool for this job. We
119 * transfer it to rpool (read pool), and a bit later,
120 * pool will be changed to point to the write pool,
121 * which comes from pool->NextPool.
123 jcr->rpool = jcr->pool; /* save read pool */
124 pm_strcpy(jcr->rpool_source, jcr->pool_source);
127 Dmsg2(dbglevel, "Read pool=%s (From %s)\n", jcr->rpool->name(), jcr->rpool_source);
129 /* If we find a job or jobs to migrate it is previous_jr.JobId */
130 count = get_job_to_migrate(jcr);
138 Dmsg1(dbglevel, "Back from get_job_to_migrate JobId=%d\n", (int)jcr->JobId);
140 if (jcr->previous_jr.JobId == 0) {
141 Dmsg1(dbglevel, "JobId=%d no previous JobId\n", (int)jcr->JobId);
142 Jmsg(jcr, M_INFO, 0, _("No previous Job found to migrate.\n"));
143 return true; /* no work */
146 if (!get_or_create_fileset_record(jcr)) {
147 Dmsg1(dbglevel, "JobId=%d no FileSet\n", (int)jcr->JobId);
148 Jmsg(jcr, M_FATAL, 0, _("Could not get or create the FileSet record.\n"));
152 create_restore_bootstrap_file(jcr);
154 if (jcr->previous_jr.JobId == 0 || jcr->ExpectedFiles == 0) {
155 set_jcr_job_status(jcr, JS_Terminated);
156 Dmsg1(dbglevel, "JobId=%d expected files == 0\n", (int)jcr->JobId);
157 if (jcr->previous_jr.JobId == 0) {
158 Jmsg(jcr, M_INFO, 0, _("No previous Job found to migrate.\n"));
160 Jmsg(jcr, M_INFO, 0, _("Previous Job has no data to migrate.\n"));
162 return true; /* no work */
165 Dmsg5(dbglevel, "JobId=%d: Previous: Name=%s JobId=%d Type=%c Level=%c\n",
167 jcr->previous_jr.Name, (int)jcr->previous_jr.JobId,
168 jcr->previous_jr.JobType, jcr->previous_jr.JobLevel);
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 migation 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);
216 * Get the PoolId used with the original job. Then
217 * find the pool name from the database record.
219 memset(&pr, 0, sizeof(pr));
220 pr.PoolId = mig_jcr->previous_jr.PoolId;
221 if (!db_get_pool_record(jcr, jcr->db, &pr)) {
222 Jmsg(jcr, M_FATAL, 0, _("Pool for JobId %s not in database. ERR=%s\n"),
223 edit_int64(pr.PoolId, ed1), db_strerror(jcr->db));
226 /* Get the pool resource corresponding to the original job */
227 pool = (POOL *)GetResWithName(R_POOL, pr.Name);
229 Jmsg(jcr, M_FATAL, 0, _("Pool resource \"%s\" not found.\n"), pr.Name);
233 /* If pool storage specified, use it for restore */
234 copy_rstorage(mig_jcr, pool->storage, _("Pool resource"));
235 copy_rstorage(jcr, pool->storage, _("Pool resource"));
238 * If the original backup pool has a NextPool, make sure a
239 * record exists in the database. Note, in this case, we
240 * will be migrating from pool to pool->NextPool.
242 if (pool->NextPool) {
243 jcr->jr.PoolId = get_or_create_pool_record(jcr, pool->NextPool->name());
244 if (jcr->jr.PoolId == 0) {
248 if (!set_migration_wstorage(jcr, pool)) {
251 mig_jcr->pool = jcr->pool = pool->NextPool;
252 pm_strcpy(jcr->pool_source, _("Job Pool's NextPool resource"));
253 mig_jcr->jr.PoolId = jcr->jr.PoolId;
255 Dmsg2(dbglevel, "Write pool=%s read rpool=%s\n", jcr->pool->name(), jcr->rpool->name());
260 * Do a Migration of a previous job
262 * Returns: false on failure
265 bool do_migration(JCR *jcr)
269 JCR *mig_jcr = jcr->mig_jcr; /* newly migrated job */
272 * If mig_jcr is NULL, there is nothing to do for this job,
273 * so set a normal status, cleanup and return OK.
276 set_jcr_job_status(jcr, JS_Terminated);
277 migration_cleanup(jcr, jcr->JobStatus);
281 /* Print Job Start message */
282 Jmsg(jcr, M_INFO, 0, _("Start Migration JobId %s, Job=%s\n"),
283 edit_uint64(jcr->JobId, ed1), jcr->Job);
288 * Open a message channel connection with the Storage
289 * daemon. This is to let him know that our client
290 * will be contacting him for a backup session.
293 Dmsg0(110, "Open connection with storage daemon\n");
294 set_jcr_job_status(jcr, JS_WaitSD);
295 set_jcr_job_status(mig_jcr, JS_WaitSD);
297 * Start conversation with Storage daemon
299 if (!connect_to_storage_daemon(jcr, 10, SDConnectTimeout, 1)) {
302 sd = jcr->store_bsock;
304 * Now start a job with the Storage daemon
306 Dmsg2(dbglevel, "Read store=%s, write store=%s\n",
307 ((STORE *)jcr->rstorage->first())->name(),
308 ((STORE *)jcr->wstorage->first())->name());
309 if (((STORE *)jcr->rstorage->first())->name() == ((STORE *)jcr->wstorage->first())->name()) {
310 Jmsg(jcr, M_FATAL, 0, _("Read storage \"%s\" same as write storage.\n"),
311 ((STORE *)jcr->rstorage->first())->name());
314 if (!start_storage_daemon_job(jcr, jcr->rstorage, jcr->wstorage)) {
317 Dmsg0(150, "Storage daemon connection OK\n");
319 if (!send_bootstrap_file(jcr, sd) ||
320 !response(jcr, sd, OKbootstrap, "Bootstrap", DISPLAY_ERROR)) {
325 * We re-update the job start record so that the start
326 * time is set after the run before job. This avoids
327 * that any files created by the run before job will
328 * be saved twice. They will be backed up in the current
329 * job, but not in the next one unless they are changed.
330 * Without this, they will be backed up in this job and
331 * in the next job run because in that case, their date
332 * is after the start of this run.
334 jcr->start_time = time(NULL);
335 jcr->jr.StartTime = jcr->start_time;
336 jcr->jr.JobTDate = jcr->start_time;
337 set_jcr_job_status(jcr, JS_Running);
339 /* Update job start record for this migration control job */
340 if (!db_update_job_start_record(jcr, jcr->db, &jcr->jr)) {
341 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(jcr->db));
346 mig_jcr->start_time = time(NULL);
347 mig_jcr->jr.StartTime = mig_jcr->start_time;
348 mig_jcr->jr.JobTDate = mig_jcr->start_time;
349 set_jcr_job_status(mig_jcr, JS_Running);
351 /* Update job start record for the real migration backup job */
352 if (!db_update_job_start_record(mig_jcr, mig_jcr->db, &mig_jcr->jr)) {
353 Jmsg(jcr, M_FATAL, 0, "%s", db_strerror(mig_jcr->db));
357 Dmsg4(dbglevel, "mig_jcr: Name=%s JobId=%d Type=%c Level=%c\n",
358 mig_jcr->jr.Name, (int)mig_jcr->jr.JobId,
359 mig_jcr->jr.JobType, mig_jcr->jr.JobLevel);
363 * Start the job prior to starting the message thread below
364 * to avoid two threads from using the BSOCK structure at
367 if (!bnet_fsend(sd, "run")) {
372 * Now start a Storage daemon message thread
374 if (!start_storage_daemon_message_thread(jcr)) {
379 set_jcr_job_status(jcr, JS_Running);
380 set_jcr_job_status(mig_jcr, JS_Running);
382 /* Pickup Job termination data */
383 /* Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/Errors */
384 wait_for_storage_daemon_termination(jcr);
385 set_jcr_job_status(jcr, jcr->SDJobStatus);
386 db_write_batch_file_records(jcr); /* used by bulk batch file insert */
387 if (jcr->JobStatus != JS_Terminated) {
391 migration_cleanup(jcr, jcr->JobStatus);
394 UAContext *ua = new_ua_context(jcr);
395 edit_uint64(jcr->previous_jr.JobId, jobid);
396 /* Purge all old file records, but leave Job record */
397 purge_files_from_jobs(ua, jobid);
408 /* Add an item to the list if it is unique */
409 static void add_unique_id(idpkt *ids, char *item)
414 /* Walk through current list to see if each item is the same as item */
417 for (int i=0; i<(int)sizeof(id); i++) {
420 } else if (*q == ',') {
427 if (strcmp(item, id) == 0) {
431 /* Did not find item, so add it to list */
432 if (ids->count == 0) {
435 pm_strcat(ids->list, ",");
437 pm_strcat(ids->list, item);
439 // Dmsg3(0, "add_uniq count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
444 * Callback handler make list of DB Ids
446 static int unique_dbid_handler(void *ctx, int num_fields, char **row)
448 idpkt *ids = (idpkt *)ctx;
450 add_unique_id(ids, row[0]);
451 Dmsg3(dbglevel, "dbid_hdlr count=%d Ids=%p %s\n", ids->count, ids->list, ids->list);
461 static int item_compare(void *item1, void *item2)
463 uitem *i1 = (uitem *)item1;
464 uitem *i2 = (uitem *)item2;
465 return strcmp(i1->item, i2->item);
468 static int unique_name_handler(void *ctx, int num_fields, char **row)
470 dlist *list = (dlist *)ctx;
472 uitem *new_item = (uitem *)malloc(sizeof(uitem));
475 memset(new_item, 0, sizeof(uitem));
476 new_item->item = bstrdup(row[0]);
477 Dmsg1(dbglevel, "Unique_name_hdlr Item=%s\n", row[0]);
478 item = (uitem *)list->binary_insert((void *)new_item, item_compare);
479 if (item != new_item) { /* already in list */
480 free(new_item->item);
481 free((char *)new_item);
487 /* Get Job names in Pool */
488 const char *sql_job =
489 "SELECT DISTINCT Job.Name from Job,Pool"
490 " WHERE Pool.Name='%s' AND Job.PoolId=Pool.PoolId";
492 /* Get JobIds from regex'ed Job names */
493 const char *sql_jobids_from_job =
494 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool"
495 " WHERE Job.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
496 " ORDER by Job.StartTime";
498 /* Get Client names in Pool */
499 const char *sql_client =
500 "SELECT DISTINCT Client.Name from Client,Pool,Job"
501 " WHERE Pool.Name='%s' AND Job.ClientId=Client.ClientId AND"
502 " Job.PoolId=Pool.PoolId";
504 /* Get JobIds from regex'ed Client names */
505 const char *sql_jobids_from_client =
506 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Job,Pool,Client"
507 " WHERE Client.Name='%s' AND Pool.Name='%s' AND Job.PoolId=Pool.PoolId"
508 " AND Job.ClientId=Client.ClientId AND Job.Type='B'"
509 " ORDER by Job.StartTime";
511 /* Get Volume names in Pool */
512 const char *sql_vol =
513 "SELECT DISTINCT VolumeName FROM Media,Pool WHERE"
514 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
515 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
517 /* Get JobIds from regex'ed Volume names */
518 const char *sql_jobids_from_vol =
519 "SELECT DISTINCT Job.JobId,Job.StartTime FROM Media,JobMedia,Job"
520 " WHERE Media.VolumeName='%s' AND Media.MediaId=JobMedia.MediaId"
521 " AND JobMedia.JobId=Job.JobId AND Job.Type='B'"
522 " ORDER by Job.StartTime";
525 const char *sql_smallest_vol =
526 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
527 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
528 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
529 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
530 " ORDER BY VolBytes ASC LIMIT 1";
532 const char *sql_oldest_vol =
533 "SELECT Media.MediaId FROM Media,Pool,JobMedia WHERE"
534 " Media.MediaId in (SELECT DISTINCT MediaId from JobMedia) AND"
535 " Media.VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
536 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'"
537 " ORDER BY LastWritten ASC LIMIT 1";
539 /* Get JobIds when we have selected MediaId */
540 const char *sql_jobids_from_mediaid =
541 "SELECT DISTINCT Job.JobId,Job.StartTime FROM JobMedia,Job"
542 " WHERE JobMedia.JobId=Job.JobId AND JobMedia.MediaId=%s"
544 " ORDER by Job.StartTime";
546 /* Get tne number of bytes in the pool */
547 const char *sql_pool_bytes =
548 "SELECT SUM(VolBytes) FROM Media,Pool WHERE"
549 " VolStatus in ('Full','Used','Error','Append') AND Media.Enabled=1 AND"
550 " Media.PoolId=Pool.PoolId AND Pool.Name='%s'";
552 /* Get tne number of bytes in the Jobs */
553 const char *sql_job_bytes =
554 "SELECT SUM(JobBytes) FROM Job WHERE JobId IN (%s)";
557 /* Get Media Ids in Pool */
558 const char *sql_mediaids =
559 "SELECT MediaId FROM Media,Pool WHERE"
560 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
561 " Media.PoolId=Pool.PoolId AND Pool.Name='%s' ORDER BY LastWritten ASC";
563 /* Get JobIds in Pool longer than specified time */
564 const char *sql_pool_time =
565 "SELECT DISTINCT Job.JobId from Pool,Job,Media,JobMedia WHERE"
566 " Pool.Name='%s' AND Media.PoolId=Pool.PoolId AND"
567 " VolStatus in ('Full','Used','Error') AND Media.Enabled=1 AND"
569 " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId"
570 " AND Job.RealEndTime<='%s'";
573 * const char *sql_ujobid =
574 * "SELECT DISTINCT Job.Job from Client,Pool,Media,Job,JobMedia "
575 * " WHERE Media.PoolId=Pool.PoolId AND Pool.Name='%s' AND"
576 * " JobMedia.JobId=Job.JobId AND Job.PoolId=Media.PoolId";
583 * This is the central piece of code that finds a job or jobs
584 * actually JobIds to migrate. It first looks to see if one
585 * has been "manually" specified in jcr->MigrateJobId, and if
586 * so, it returns that JobId to be run. Otherwise, it
587 * examines the Selection Type to see what kind of migration
588 * we are doing (Volume, Job, Client, ...) and applies any
589 * Selection Pattern if appropriate to obtain a list of JobIds.
590 * Finally, it will loop over all the JobIds found, except the last
591 * one starting a new job with MigrationJobId set to that JobId, and
592 * finally, it returns the last JobId to the caller.
594 * Returns: -1 on error
595 * 0 if no jobs to migrate
596 * 1 if OK and jcr->previous_jr filled in
598 static int get_job_to_migrate(JCR *jcr)
600 char ed1[30], ed2[30];
601 POOL_MEM query(PM_MESSAGE);
606 idpkt ids, mid, jids;
611 char dt[MAX_TIME_LENGTH];
614 ids.list = get_pool_memory(PM_MESSAGE);
617 mid.list = get_pool_memory(PM_MESSAGE);
620 jids.list = get_pool_memory(PM_MESSAGE);
626 * If MigrateJobId is set, then we migrate only that Job,
627 * otherwise, we go through the full selection of jobs to
630 if (jcr->MigrateJobId != 0) {
631 Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
632 edit_uint64(jcr->MigrateJobId, ids.list);
635 switch (jcr->job->selection_type) {
637 if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
642 if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
647 if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
652 if (!jcr->job->selection_pattern) {
653 Jmsg(jcr, M_FATAL, 0, _("No Migration SQL selection pattern specified.\n"));
656 Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
657 if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
658 unique_dbid_handler, (void *)&ids)) {
659 Jmsg(jcr, M_FATAL, 0,
660 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
664 case MT_SMALLEST_VOL:
665 if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
670 if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
675 case MT_POOL_OCCUPANCY:
677 /* Find count of bytes in pool */
678 Mmsg(query, sql_pool_bytes, jcr->rpool->name());
679 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
680 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
683 if (ctx.count == 0) {
684 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
687 pool_bytes = ctx.value;
688 Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
690 if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
691 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
694 Dmsg0(dbglevel, "We should do Occupation migration.\n");
697 /* Find a list of MediaIds that could be migrated */
698 Mmsg(query, sql_mediaids, jcr->rpool->name());
699 Dmsg1(dbglevel, "query=%s\n", query.c_str());
700 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
701 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
704 if (ids.count == 0) {
705 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
708 Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);
710 if (!find_jobids_from_mediaid_list(jcr, &ids, "Volumes")) {
713 /* ids == list of jobs */
715 for (int i=0; i < (int)ids.count; i++) {
716 stat = get_next_dbid_from_list(&p, &DBId);
717 Dmsg2(dbglevel, "get_next_dbid stat=%d JobId=%u\n", stat, (uint32_t)DBId);
719 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
721 } else if (stat == 0) {
726 Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
727 if (jids.count > 0) {
728 pm_strcat(jids.list, ",");
730 pm_strcat(jids.list, mid.list);
731 jids.count += mid.count;
733 /* Find count of bytes from Jobs */
734 Mmsg(query, sql_job_bytes, mid.list);
735 Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
736 if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
737 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
740 pool_bytes -= ctx.value;
741 Dmsg1(dbglevel, "Total migrate Job bytes=%s\n", edit_int64(ctx.value, ed1));
742 Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n",
743 edit_int64(jcr->rpool->MigrationLowBytes, ed1),
744 edit_int64(pool_bytes, ed2));
745 if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
746 Dmsg0(dbglevel, "We should be done.\n");
750 /* Transfer jids to ids, where the jobs list is expected */
751 ids.count = jids.count;
752 pm_strcpy(ids.list, jids.list);
753 Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
757 ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
758 (void)localtime_r(&ttime, &tm);
759 strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);
762 Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
763 Dmsg1(dbglevel, "query=%s\n", query.c_str());
764 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
765 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
768 if (ids.count == 0) {
769 Jmsg(jcr, M_INFO, 0, _("No Volumes found to migrate.\n"));
772 Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
776 Jmsg(jcr, M_FATAL, 0, _("Unknown Migration Selection Type.\n"));
782 * Loop over all jobids except the last one, sending
783 * them to start_migration_job(), which will start a job
784 * for each of them. For the last JobId, we handle it below.
787 if (ids.count == 0) {
788 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
792 Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s were chosen to be migrated: %s\n"),
793 ids.count, ids.count==0?"":"s", ids.list);
795 Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
796 for (int i=1; i < (int)ids.count; i++) {
798 stat = get_next_jobid_from_list(&p, &JobId);
799 Dmsg3(dbglevel, "get_jobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
800 jcr->MigrateJobId = JobId;
801 start_migration_job(jcr);
802 Dmsg0(dbglevel, "Back from start_migration_job\n");
804 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
806 } else if (stat == 0) {
807 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
812 /* Now get the last JobId and handle it in the current job */
814 stat = get_next_jobid_from_list(&p, &JobId);
815 Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
817 Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
819 } else if (stat == 0) {
820 Jmsg(jcr, M_INFO, 0, _("No JobIds found to migrate.\n"));
824 jcr->previous_jr.JobId = JobId;
825 Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);
827 if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
828 Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to migrate. ERR=%s"),
829 edit_int64(jcr->previous_jr.JobId, ed1),
830 db_strerror(jcr->db));
833 Jmsg(jcr, M_INFO, 0, _("Migration using JobId=%s Job=%s\n"),
834 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
835 Dmsg3(dbglevel, "Migration JobId=%d using JobId=%s Job=%s\n",
837 edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
847 free_pool_memory(ids.list);
848 free_pool_memory(mid.list);
849 free_pool_memory(jids.list);
853 static void start_migration_job(JCR *jcr)
855 UAContext *ua = new_ua_context(jcr);
858 Mmsg(ua->cmd, "run %s jobid=%s", jcr->job->hdr.name,
859 edit_uint64(jcr->MigrateJobId, ed1));
860 Dmsg1(dbglevel, "=============== Migration cmd=%s\n", ua->cmd);
861 parse_ua_args(ua); /* parse command */
862 JobId_t jobid = run_cmd(ua, ua->cmd);
864 Jmsg(jcr, M_ERROR, 0, _("Could not start migration job.\n"));
866 Jmsg(jcr, M_INFO, 0, _("Migration JobId %d started.\n"), (int)jobid);
871 static bool find_mediaid_then_jobids(JCR *jcr, idpkt *ids, const char *query1,
875 POOL_MEM query(PM_MESSAGE);
878 /* Basic query for MediaId */
879 Mmsg(query, query1, jcr->rpool->name());
880 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
881 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
884 if (ids->count == 0) {
885 Jmsg(jcr, M_INFO, 0, _("No %s found to migrate.\n"), type);
886 ok = true; /* Not an error */
888 } else if (ids->count != 1) {
889 Jmsg(jcr, M_FATAL, 0, _("SQL error. Expected 1 MediaId got %d\n"), ids->count);
892 Dmsg2(dbglevel, "%s MediaIds=%s\n", type, ids->list);
894 ok = find_jobids_from_mediaid_list(jcr, ids, type);
901 * This routine returns:
902 * false if an error occurred
904 * ids.count number of jobids found (may be zero)
906 static bool find_jobids_from_mediaid_list(JCR *jcr, idpkt *ids, const char *type)
909 POOL_MEM query(PM_MESSAGE);
911 Mmsg(query, sql_jobids_from_mediaid, ids->list);
913 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
914 Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
917 if (ids->count == 0) {
918 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
926 static bool regex_find_jobids(JCR *jcr, idpkt *ids, const char *query1,
927 const char *query2, const char *type)
931 uitem *last_item = NULL;
936 POOL_MEM query(PM_MESSAGE);
938 item_chain = New(dlist(item, &item->link));
939 if (!jcr->job->selection_pattern) {
940 Jmsg(jcr, M_FATAL, 0, _("No Migration %s selection pattern specified.\n"),
944 Dmsg1(dbglevel, "regex-sel-pattern=%s\n", jcr->job->selection_pattern);
945 /* Basic query for names */
946 Mmsg(query, query1, jcr->rpool->name());
947 Dmsg1(dbglevel, "get name query1=%s\n", query.c_str());
948 if (!db_sql_query(jcr->db, query.c_str(), unique_name_handler,
949 (void *)item_chain)) {
950 Jmsg(jcr, M_FATAL, 0,
951 _("SQL to get %s failed. ERR=%s\n"), type, db_strerror(jcr->db));
954 Dmsg1(dbglevel, "query1 returned %d names\n", item_chain->size());
955 if (item_chain->size() == 0) {
956 Jmsg(jcr, M_INFO, 0, _("Query of Pool \"%s\" returned no Jobs to migrate.\n"),
959 goto bail_out; /* skip regex match */
961 /* Compile regex expression */
962 rc = regcomp(&preg, jcr->job->selection_pattern, REG_EXTENDED);
964 regerror(rc, &preg, prbuf, sizeof(prbuf));
965 Jmsg(jcr, M_FATAL, 0, _("Could not compile regex pattern \"%s\" ERR=%s\n"),
966 jcr->job->selection_pattern, prbuf);
969 /* Now apply the regex to the names and remove any item not matched */
970 foreach_dlist(item, item_chain) {
971 const int nmatch = 30;
972 regmatch_t pmatch[nmatch];
974 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
975 free(last_item->item);
976 item_chain->remove(last_item);
978 Dmsg1(dbglevel, "get name Item=%s\n", item->item);
979 rc = regexec(&preg, item->item, nmatch, pmatch, 0);
981 last_item = NULL; /* keep this one */
987 free(last_item->item);
988 Dmsg1(dbglevel, "Remove item %s\n", last_item->item);
989 item_chain->remove(last_item);
993 if (item_chain->size() == 0) {
994 Jmsg(jcr, M_INFO, 0, _("Regex pattern matched no Jobs to migrate.\n"));
996 goto bail_out; /* skip regex match */
1000 * At this point, we have a list of items in item_chain
1001 * that have been matched by the regex, so now we need
1002 * to look up their jobids.
1005 foreach_dlist(item, item_chain) {
1006 Dmsg2(dbglevel, "Got %s: %s\n", type, item->item);
1007 Mmsg(query, query2, item->item, jcr->rpool->name());
1008 Dmsg1(dbglevel, "get id from name query2=%s\n", query.c_str());
1009 if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)ids)) {
1010 Jmsg(jcr, M_FATAL, 0,
1011 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
1015 if (ids->count == 0) {
1016 Jmsg(jcr, M_INFO, 0, _("No %ss found to migrate.\n"), type);
1021 Dmsg2(dbglevel, "Count=%d Jobids=%s\n", ids->count, ids->list);
1022 foreach_dlist(item, item_chain) {
1031 * Release resources allocated during backup.
1033 void migration_cleanup(JCR *jcr, int TermCode)
1035 char sdt[MAX_TIME_LENGTH], edt[MAX_TIME_LENGTH];
1036 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], elapsed[50];
1037 char ec6[50], ec7[50], ec8[50];
1038 char term_code[100], sd_term_msg[100];
1039 const char *term_msg;
1040 int msg_type = M_INFO;
1044 JCR *mig_jcr = jcr->mig_jcr;
1045 POOL_MEM query(PM_MESSAGE);
1047 Dmsg2(100, "Enter migrate_cleanup %d %c\n", TermCode, TermCode);
1048 update_job_end(jcr, TermCode);
1049 memset(&mr, 0, sizeof(mr));
1052 * Check if we actually did something.
1053 * mig_jcr is jcr of the newly migrated job.
1056 mig_jcr->JobFiles = jcr->JobFiles = jcr->SDJobFiles;
1057 mig_jcr->JobBytes = jcr->JobBytes = jcr->SDJobBytes;
1058 mig_jcr->VolSessionId = jcr->VolSessionId;
1059 mig_jcr->VolSessionTime = jcr->VolSessionTime;
1060 mig_jcr->jr.RealEndTime = 0;
1061 mig_jcr->jr.PriorJobId = jcr->previous_jr.JobId;
1063 update_job_end(mig_jcr, TermCode);
1065 /* Update final items to set them to the previous job's values */
1066 Mmsg(query, "UPDATE Job SET StartTime='%s',EndTime='%s',"
1067 "JobTDate=%s WHERE JobId=%s",
1068 jcr->previous_jr.cStartTime, jcr->previous_jr.cEndTime,
1069 edit_uint64(jcr->previous_jr.JobTDate, ec1),
1070 edit_uint64(mig_jcr->jr.JobId, ec2));
1071 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1073 /* Now mark the previous job as migrated if it terminated normally */
1074 if (jcr->JobType == JT_MIGRATE && jcr->JobStatus == JS_Terminated) {
1075 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1076 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1077 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
1080 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
1081 Jmsg(jcr, M_WARNING, 0, _("Error getting Job record for Job report: ERR=%s"),
1082 db_strerror(jcr->db));
1083 set_jcr_job_status(jcr, JS_ErrorTerminated);
1086 update_bootstrap_file(mig_jcr);
1088 if (!db_get_job_volume_names(mig_jcr, mig_jcr->db, mig_jcr->jr.JobId, &mig_jcr->VolumeName)) {
1090 * Note, if the job has failed, most likely it did not write any
1091 * tape, so suppress this "error" message since in that case
1092 * it is normal. Or look at it the other way, only for a
1093 * normal exit should we complain about this error.
1095 if (jcr->JobStatus == JS_Terminated && jcr->jr.JobBytes) {
1096 Jmsg(jcr, M_ERROR, 0, "%s", db_strerror(mig_jcr->db));
1098 mig_jcr->VolumeName[0] = 0; /* none */
1101 if (mig_jcr->VolumeName[0]) {
1102 /* Find last volume name. Multiple vols are separated by | */
1103 char *p = strrchr(mig_jcr->VolumeName, '|');
1107 p = mig_jcr->VolumeName; /* no |, take full name */
1109 bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1110 if (!db_get_media_record(jcr, jcr->db, &mr)) {
1111 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1112 mr.VolumeName, db_strerror(jcr->db));
1116 switch (jcr->JobStatus) {
1118 if (jcr->Errors || jcr->SDErrors) {
1119 term_msg = _("%s OK -- with warnings");
1121 term_msg = _("%s OK");
1125 case JS_ErrorTerminated:
1126 term_msg = _("*** %s Error ***");
1127 msg_type = M_ERROR; /* Generate error message */
1128 if (jcr->store_bsock) {
1129 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1130 if (jcr->SD_msg_chan) {
1131 pthread_cancel(jcr->SD_msg_chan);
1136 term_msg = _("%s Canceled");
1137 if (jcr->store_bsock) {
1138 bnet_sig(jcr->store_bsock, BNET_TERMINATE);
1139 if (jcr->SD_msg_chan) {
1140 pthread_cancel(jcr->SD_msg_chan);
1145 term_msg = _("Inappropriate %s term code");
1149 if (jcr->JobType == JT_MIGRATE && jcr->previous_jr.JobId != 0) {
1150 /* Mark previous job as migrated */
1151 Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
1152 (char)JT_MIGRATED_JOB, edit_uint64(jcr->previous_jr.JobId, ec1));
1153 db_sql_query(jcr->db, query.c_str(), NULL, NULL);
1155 term_msg = _("%s -- no files to migrate");
1158 bsnprintf(term_code, sizeof(term_code), term_msg, "Migration");
1159 bstrftimes(sdt, sizeof(sdt), jcr->jr.StartTime);
1160 bstrftimes(edt, sizeof(edt), jcr->jr.EndTime);
1161 RunTime = jcr->jr.EndTime - jcr->jr.StartTime;
1165 kbps = (double)jcr->SDJobBytes / (1000 * RunTime);
1169 jobstatus_to_ascii(jcr->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1171 Jmsg(jcr, msg_type, 0, _("Bacula %s %s (%s): %s\n"
1172 " Build OS: %s %s %s\n"
1173 " Prev Backup JobId: %s\n"
1174 " New Backup JobId: %s\n"
1175 " Migration JobId: %s\n"
1176 " Migration Job: %s\n"
1177 " Backup Level: %s%s\n"
1179 " FileSet: \"%s\" %s\n"
1180 " Read Pool: \"%s\" (From %s)\n"
1181 " Read Storage: \"%s\" (From %s)\n"
1182 " Write Pool: \"%s\" (From %s)\n"
1183 " Write Storage: \"%s\" (From %s)\n"
1184 " Catalog: \"%s\" (From %s)\n"
1187 " Elapsed time: %s\n"
1189 " SD Files Written: %s\n"
1190 " SD Bytes Written: %s (%sB)\n"
1191 " Rate: %.1f KB/s\n"
1192 " Volume name(s): %s\n"
1193 " Volume Session Id: %d\n"
1194 " Volume Session Time: %d\n"
1195 " Last Volume Bytes: %s (%sB)\n"
1197 " SD termination status: %s\n"
1198 " Termination: %s\n\n"),
1199 my_name, VERSION, LSMDATE, edt,
1200 HOST_OS, DISTNAME, DISTVER,
1201 edit_uint64(jcr->previous_jr.JobId, ec6),
1202 mig_jcr ? edit_uint64(mig_jcr->jr.JobId, ec7) : "0",
1203 edit_uint64(jcr->jr.JobId, ec8),
1205 level_to_str(jcr->JobLevel), jcr->since,
1206 jcr->client->name(),
1207 jcr->fileset->name(), jcr->FSCreateTime,
1208 jcr->rpool->name(), jcr->rpool_source,
1209 jcr->rstore?jcr->rstore->name():"*None*",
1210 NPRT(jcr->rstore_source),
1211 jcr->pool->name(), jcr->pool_source,
1212 jcr->wstore?jcr->wstore->name():"*None*",
1213 NPRT(jcr->wstore_source),
1214 jcr->catalog->name(), jcr->catalog_source,
1217 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1219 edit_uint64_with_commas(jcr->SDJobFiles, ec1),
1220 edit_uint64_with_commas(jcr->SDJobBytes, ec2),
1221 edit_uint64_with_suffix(jcr->SDJobBytes, ec3),
1223 mig_jcr ? mig_jcr->VolumeName : "",
1225 jcr->VolSessionTime,
1226 edit_uint64_with_commas(mr.VolBytes, ec4),
1227 edit_uint64_with_suffix(mr.VolBytes, ec5),
1232 Dmsg1(100, "migrate_cleanup() mig_jcr=0x%x\n", jcr->mig_jcr);
1234 free_jcr(jcr->mig_jcr);
1235 jcr->mig_jcr = NULL;
1237 Dmsg0(100, "Leave migrate_cleanup()\n");
1241 * Return next DBId from comma separated list
1244 * 1 if next DBId returned
1245 * 0 if no more DBIds are in list
1246 * -1 there is an error
1248 static int get_next_dbid_from_list(char **p, DBId_t *DBId)
1254 for (int i=0; i<(int)sizeof(id); i++) {
1257 } else if (*q == ',') {
1266 } else if (!is_a_number(id)) {
1267 return -1; /* error */
1270 *DBId = str_to_int64(id);
1274 bool set_migration_wstorage(JCR *jcr, POOL *pool)
1276 POOL *wpool = pool->NextPool;
1279 Jmsg(jcr, M_FATAL, 0, _("No Next Pool specification found in Pool \"%s\".\n"),
1284 if (!wpool->storage || wpool->storage->size() == 0) {
1285 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Next Pool \"%s\".\n"),
1290 /* If pool storage specified, use it instead of job storage for backup */
1291 copy_wstorage(jcr, wpool->storage, _("Storage from Pool's NextPool resource"));