Пример #1
0
void display_bsr_info(UAContext *ua, RESTORE_CTX &rx)
{
   char *p;
   JobId_t JobId;

   /* Tell the user what he will need to mount */
   ua->send_msg("\n");
   ua->send_msg(_("The job will require the following\n"
                  "   Volume(s)                 Storage(s)                SD Device(s)\n"
                  "===========================================================================\n"));
   /* Create Unique list of Volumes using prompt list */
   start_prompt(ua, "");
   if (*rx.JobIds == 0) {
      /* Print Volumes in any order */
      display_vol_info(ua, rx, 0);
   } else {
      /* Ensure that the volumes are printed in JobId order */
      for (p=rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
         display_vol_info(ua, rx, JobId);
      }
   }
   for (int i=0; i < ua->num_prompts; i++) {
      ua->send_msg("   %s\n", ua->prompt[i]);
      free(ua->prompt[i]);
   }
   if (ua->num_prompts == 0) {
      ua->send_msg(_("No Volumes found to restore.\n"));
   } else {
      ua->send_msg(_("\nVolumes marked with \"*\" are online.\n"));
   }
   ua->num_prompts = 0;
   ua->send_msg("\n");

   return;
}
Пример #2
0
/*
 * Here we actually write out the details of the bsr file.
 * Note, there is one bsr for each JobId, but the bsr may
 * have multiple volumes, which have been entered in the
 * order they were written.
 *
 * The bsrs must be written out in the order the JobIds
 * are found in the jobid list.
 */
uint32_t write_bsr(UAContext *ua, RESTORE_CTX &rx, POOL_MEM *buffer)
{
   bool first = true;
   uint32_t LastIndex = 0;
   uint32_t total_count = 0;
   char *p;
   JobId_t JobId;
   RBSR *bsr;

   if (*rx.JobIds == 0) {
      for (bsr = rx.bsr; bsr; bsr = bsr->next) {
         total_count += write_bsr_item(bsr, ua, rx, buffer, first, LastIndex);
      }
      return total_count;
   }

   for (p = rx.JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
      for (bsr = rx.bsr; bsr; bsr = bsr->next) {
         if (JobId == bsr->JobId) {
            total_count += write_bsr_item(bsr, ua, rx, buffer, first, LastIndex);
         }
      }
   }

   return total_count;
}
Пример #3
0
static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
{
   TREE_CTX tree;
   JobId_t JobId, last_JobId;
   char *p;
   bool OK = true;
   char ed1[50];

   memset(&tree, 0, sizeof(TREE_CTX));
   /*
    * Build the directory tree containing JobIds user selected
    */
   tree.root = new_tree(rx->TotalFiles);
   tree.ua = ua;
   tree.all = rx->all;
   last_JobId = 0;
   /*
    * For display purposes, the same JobId, with different volumes may
    * appear more than once, however, we only insert it once.
    */
   p = rx->JobIds;
   tree.FileEstimate = 0;
   if (get_next_jobid_from_list(&p, &JobId) > 0) {
      /* Use first JobId as estimate of the number of files to restore */
      Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
      if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
         ua->error_msg("%s\n", db_strerror(ua->db));
      }
      if (rx->found) {
         /* Add about 25% more than this job for over estimate */
         tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
         tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
      }
Пример #4
0
/*
 * The first step in the restore process is for the user to
 *  select a list of JobIds from which he will subsequently
 *  select which files are to be restored.
 *
 *  Returns:  2  if filename list made
 *            1  if jobid list made
 *            0  on error
 */
static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
{
   char *p;
   char date[MAX_TIME_LENGTH];
   bool have_date = false;
   /* Include current second if using current time */
   utime_t now = time(NULL) + 1;
   JobId_t JobId;
   JOB_DBR jr = { (JobId_t)-1 };
   bool done = false;
   int i, j;
   const char *list[] = {
      _("List last 20 Jobs run"),
      _("List Jobs where a given File is saved"),
      _("Enter list of comma separated JobIds to select"),
      _("Enter SQL list command"),
      _("Select the most recent backup for a client"),
      _("Select backup for a client before a specified time"),
      _("Enter a list of files to restore"),
      _("Enter a list of files to restore before a specified time"),
      _("Find the JobIds of the most recent backup for a client"),
      _("Find the JobIds for a backup for a client before a specified time"),
      _("Enter a list of directories to restore for found JobIds"),
      _("Select full restore to a specified Job date"),
      _("Cancel"),
      NULL
   };

   const char *kw[] = {
       /*
        * These keywords are handled in a for loop
        */
      "jobid",         /* 0 */
      "current",       /* 1 */
      "before",        /* 2 */
      "file",          /* 3 */
      "directory",     /* 4 */
      "select",        /* 5 */
      "pool",          /* 6 */
      "all",           /* 7 */

      /*
       * The keyword below are handled by individual arg lookups
       */
      "client",        /* 8 */
      "storage",       /* 9 */
      "fileset",       /* 10 */
      "where",         /* 11 */
      "yes",           /* 12 */
      "bootstrap",     /* 13 */
      "done",          /* 14 */
      "strip_prefix",  /* 15 */
      "add_prefix",    /* 16 */
      "add_suffix",    /* 17 */
      "regexwhere",    /* 18 */
      "restoreclient", /* 19 */
      "copies",        /* 20 */
      "comment",       /* 21 */
      "restorejob",    /* 22 */
      "replace",       /* 23 */
      "pluginoptions", /* 24 */
      NULL
   };

   rx->JobIds[0] = 0;

   for (i = 1; i<ua->argc; i++) {        /* loop through arguments */
      bool found_kw = false;
      for (j = 0; kw[j]; j++) {          /* loop through keywords */
         if (bstrcasecmp(kw[j], ua->argk[i])) {
            found_kw = true;
            break;
         }
      }
      if (!found_kw) {
         ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
         return 0;
      }
      /* Found keyword in kw[] list, process it */
      switch (j) {
      case 0:                            /* jobid */
         if (!has_value(ua, i)) {
            return 0;
         }
         if (*rx->JobIds != 0) {
            pm_strcat(rx->JobIds, ",");
         }
         pm_strcat(rx->JobIds, ua->argv[i]);
         done = true;
         break;
      case 1:                            /* current */
         /*
          * Note, we add one second here just to include any job
          *  that may have finished within the current second,
          *  which happens a lot in scripting small jobs.
          */
         bstrutime(date, sizeof(date), now);
         have_date = true;
         break;
      case 2:                            /* before */
         if (have_date || !has_value(ua, i)) {
            return 0;
         }
         if (str_to_utime(ua->argv[i]) == 0) {
            ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
            return 0;
         }
         bstrncpy(date, ua->argv[i], sizeof(date));
         have_date = true;
         break;
      case 3:                            /* file */
      case 4:                            /* dir */
         if (!has_value(ua, i)) {
            return 0;
         }
         if (!have_date) {
            bstrutime(date, sizeof(date), now);
         }
         if (!get_client_name(ua, rx)) {
            return 0;
         }
         pm_strcpy(ua->cmd, ua->argv[i]);
         insert_one_file_or_dir(ua, rx, date, j==4);
         return 2;
      case 5:                            /* select */
         if (!have_date) {
            bstrutime(date, sizeof(date), now);
         }
         if (!select_backups_before_date(ua, rx, date)) {
            return 0;
         }
         done = true;
         break;
      case 6:                            /* pool specified */
         if (!has_value(ua, i)) {
            return 0;
         }
         rx->pool = (POOLRES *)GetResWithName(R_POOL, ua->argv[i]);
         if (!rx->pool) {
            ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
            return 0;
         }
         if (!acl_access_ok(ua, Pool_ACL, ua->argv[i], true)) {
            rx->pool = NULL;
            ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
            return 0;
         }
         break;
      case 7:                         /* all specified */
         rx->all = true;
         break;
      default:
         /*
          * All keywords 7 or greater are ignored or handled by a select prompt
          */
         break;
      }
   }

   if (!done) {
      ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
                  "to be restored. You will be presented several methods\n"
                  "of specifying the JobIds. Then you will be allowed to\n"
                  "select which files from those JobIds are to be restored.\n\n"));
   }

   /* If choice not already made above, prompt */
   for ( ; !done; ) {
      char *fname;
      int len;
      bool gui_save;
      db_list_ctx jobids;

      start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
      for (int i=0; list[i]; i++) {
         add_prompt(ua, list[i]);
      }
      done = true;
      switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
      case -1:                        /* error or cancel */
         return 0;
      case 0:                         /* list last 20 Jobs run */
         if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), true)) {
            ua->error_msg(_("SQL query not authorized.\n"));
            return 0;
         }
         gui_save = ua->jcr->gui;
         ua->jcr->gui = true;
         db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, ua->send, HORZ_LIST, true);
         ua->jcr->gui = gui_save;
         done = false;
         break;
      case 1:                         /* list where a file is saved */
         if (!get_client_name(ua, rx)) {
            return 0;
         }
         if (!get_cmd(ua, _("Enter Filename (no path):"))) {
            return 0;
         }
         len = strlen(ua->cmd);
         fname = (char *)malloc(len * 2 + 1);
         db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
         Mmsg(rx->query, uar_file[db_get_type_index(ua->db)], rx->ClientName, fname);
         free(fname);
         gui_save = ua->jcr->gui;
         ua->jcr->gui = true;
         db_list_sql_query(ua->jcr, ua->db, rx->query, ua->send, HORZ_LIST, true);
         ua->jcr->gui = gui_save;
         done = false;
         break;
      case 2:                         /* enter a list of JobIds */
         if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
            return 0;
         }
         pm_strcpy(rx->JobIds, ua->cmd);
         break;
      case 3:                         /* Enter an SQL list command */
         if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), true)) {
            ua->error_msg(_("SQL query not authorized.\n"));
            return 0;
         }
         if (!get_cmd(ua, _("Enter SQL list command: "))) {
            return 0;
         }
         gui_save = ua->jcr->gui;
         ua->jcr->gui = true;
         db_list_sql_query(ua->jcr, ua->db, ua->cmd, ua->send, HORZ_LIST, true);
         ua->jcr->gui = gui_save;
         done = false;
         break;
      case 4:                         /* Select the most recent backups */
         if (!have_date) {
            bstrutime(date, sizeof(date), now);
         }
         if (!select_backups_before_date(ua, rx, date)) {
            return 0;
         }
         break;
      case 5:                         /* select backup at specified time */
         if (!have_date) {
            if (!get_date(ua, date, sizeof(date))) {
               return 0;
            }
         }
         if (!select_backups_before_date(ua, rx, date)) {
            return 0;
         }
         break;
      case 6:                         /* Enter files */
         if (!have_date) {
            bstrutime(date, sizeof(date), now);
         }
         if (!get_client_name(ua, rx)) {
            return 0;
         }
         ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
                        "containing a list of file names with paths, and terminate\n"
                        "them with a blank line.\n"));
         for ( ;; ) {
            if (!get_cmd(ua, _("Enter full filename: "))) {
               return 0;
            }
            len = strlen(ua->cmd);
            if (len == 0) {
               break;
            }
            insert_one_file_or_dir(ua, rx, date, false);
         }
         return 2;
       case 7:                        /* enter files backed up before specified time */
         if (!have_date) {
            if (!get_date(ua, date, sizeof(date))) {
               return 0;
            }
         }
         if (!get_client_name(ua, rx)) {
            return 0;
         }
         ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
                        "containing a list of file names with paths, and terminate\n"
                        "them with a blank line.\n"));
         for ( ;; ) {
            if (!get_cmd(ua, _("Enter full filename: "))) {
               return 0;
            }
            len = strlen(ua->cmd);
            if (len == 0) {
               break;
            }
            insert_one_file_or_dir(ua, rx, date, false);
         }
         return 2;

      case 8:                         /* Find JobIds for current backup */
         if (!have_date) {
            bstrutime(date, sizeof(date), now);
         }
         if (!select_backups_before_date(ua, rx, date)) {
            return 0;
         }
         done = false;
         break;

      case 9:                         /* Find JobIds for give date */
         if (!have_date) {
            if (!get_date(ua, date, sizeof(date))) {
               return 0;
            }
         }
         if (!select_backups_before_date(ua, rx, date)) {
            return 0;
         }
         done = false;
         break;

      case 10:                        /* Enter directories */
         if (*rx->JobIds != 0) {
            ua->send_msg(_("You have already selected the following JobIds: %s\n"),
               rx->JobIds);
         } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
            if (*rx->JobIds != 0 && *ua->cmd) {
               pm_strcat(rx->JobIds, ",");
            }
            pm_strcat(rx->JobIds, ua->cmd);
         }
         if (*rx->JobIds == 0 || *rx->JobIds == '.') {
            *rx->JobIds = 0;
            return 0;                 /* nothing entered, return */
         }
         if (!have_date) {
            bstrutime(date, sizeof(date), now);
         }
         if (!get_client_name(ua, rx)) {
            return 0;
         }
         ua->send_msg(_("Enter full directory names or start the name\n"
                        "with a < to indicate it is a filename containing a list\n"
                        "of directories and terminate them with a blank line.\n"));
         for ( ;; ) {
            if (!get_cmd(ua, _("Enter directory name: "))) {
               return 0;
            }
            len = strlen(ua->cmd);
            if (len == 0) {
               break;
            }
            /* Add trailing slash to end of directory names */
            if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
               strcat(ua->cmd, "/");
            }
            insert_one_file_or_dir(ua, rx, date, true);
         }
         return 2;

      case 11:                        /* Choose a jobid and select jobs */
         if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
             !is_an_integer(ua->cmd))
         {
            return 0;
         }

         memset(&jr, 0, sizeof(jr));
         jr.JobId = str_to_int64(ua->cmd);
         if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
            ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
                          ua->cmd, db_strerror(ua->db));
            return 0;
         }
         ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
                      jr.cStartTime);
         jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
         if (!db_accurate_get_jobids(ua->jcr, ua->db, &jr, &jobids)) {
            return 0;
         }
         pm_strcpy(rx->JobIds, jobids.list);
         Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
         break;
      case 12:                        /* Cancel or quit */
         return 0;
      }
   }

   memset(&jr, 0, sizeof(jr));
   POOLMEM *JobIds = get_pool_memory(PM_FNAME);
   *JobIds = 0;
   rx->TotalFiles = 0;
   /*
    * Find total number of files to be restored, and filter the JobId
    *  list to contain only ones permitted by the ACL conditions.
    */
   for (p=rx->JobIds; ; ) {
      char ed1[50];
      int status = get_next_jobid_from_list(&p, &JobId);
      if (status < 0) {
         ua->error_msg(_("Invalid JobId in list.\n"));
         free_pool_memory(JobIds);
         return 0;
      }
      if (status == 0) {
         break;
      }
      if (jr.JobId == JobId) {
         continue;                    /* duplicate of last JobId */
      }
      memset(&jr, 0, sizeof(jr));
      jr.JobId = JobId;
      if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
         ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
            edit_int64(JobId, ed1), db_strerror(ua->db));
         free_pool_memory(JobIds);
         return 0;
      }
      if (!acl_access_ok(ua, Job_ACL, jr.Name, true)) {
         ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
            edit_int64(JobId, ed1), jr.Name);
         continue;
      }
      if (*JobIds != 0) {
         pm_strcat(JobIds, ",");
      }
      pm_strcat(JobIds, edit_int64(JobId, ed1));
      rx->TotalFiles += jr.JobFiles;
   }
   free_pool_memory(rx->JobIds);
   rx->JobIds = JobIds;               /* Set ACL filtered list */
   if (*rx->JobIds == 0) {
      ua->warning_msg(_("No Jobs selected.\n"));
      return 0;
   }

   if (strchr(rx->JobIds,',')) {
      ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
   } else {
      ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
   }
   return true;
}
Пример #5
0
/*
 *
 * This is the central piece of code that finds a job or jobs
 *   actually JobIds to migrate.  It first looks to see if one
 *   has been "manually" specified in jcr->MigrateJobId, and if
 *   so, it returns that JobId to be run.  Otherwise, it
 *   examines the Selection Type to see what kind of migration
 *   we are doing (Volume, Job, Client, ...) and applies any
 *   Selection Pattern if appropriate to obtain a list of JobIds.
 *   Finally, it will loop over all the JobIds found, except the last
 *   one starting a new job with MigrationJobId set to that JobId, and
 *   finally, it returns the last JobId to the caller.
 *
 * Returns: -1  on error
 *           0  if no jobs to migrate
 *           1  if OK and jcr->previous_jr filled in
 */
int getJob_to_migrate(JCR *jcr)
{
   char ed1[30], ed2[30];
   POOL_MEM query(PM_MESSAGE);
   JobId_t JobId;
   DBId_t DBId = 0;
   int stat;
   char *p;
   idpkt ids, mid, jids;
   db_int64_ctx ctx;
   int64_t pool_bytes;
   time_t ttime;
   struct tm tm;
   char dt[MAX_TIME_LENGTH];
   int count = 0;
   int limit = jcr->job->MaxSpawnedJobs;   /* limit is max jobs to start */

   ids.list = get_pool_memory(PM_MESSAGE);
   ids.list[0] = 0;
   ids.count = 0;
   mid.list = get_pool_memory(PM_MESSAGE);
   mid.list[0] = 0;
   mid.count = 0;
   jids.list = get_pool_memory(PM_MESSAGE);
   jids.list[0] = 0;
   jids.count = 0;

   /*
    * If MigrateJobId is set, then we migrate only that Job,
    *  otherwise, we go through the full selection of jobs to
    *  migrate.
    */
   if (jcr->MigrateJobId != 0) {
      Dmsg1(dbglevel, "At Job start previous jobid=%u\n", jcr->MigrateJobId);
      JobId = jcr->MigrateJobId;
   } else {
      switch (jcr->job->selection_type) {
      case MT_JOB:
         if (!regex_find_jobids(jcr, &ids, sql_job, sql_jobids_from_job, "Job")) {
            goto bail_out;
         }
         break;
      case MT_CLIENT:
         if (!regex_find_jobids(jcr, &ids, sql_client, sql_jobids_from_client, "Client")) {
            goto bail_out;
         }
         break;
      case MT_VOLUME:
         if (!regex_find_jobids(jcr, &ids, sql_vol, sql_jobids_from_vol, "Volume")) {
            goto bail_out;
         }
         break;
      case MT_SQLQUERY:
         if (!jcr->job->selection_pattern) {
            Jmsg(jcr, M_FATAL, 0, _("No %s SQL selection pattern specified.\n"), jcr->get_OperationName());
            goto bail_out;
         }
         Dmsg1(dbglevel, "SQL=%s\n", jcr->job->selection_pattern);
         if (!db_sql_query(jcr->db, jcr->job->selection_pattern,
              unique_dbid_handler, (void *)&ids)) {
            Jmsg(jcr, M_FATAL, 0,
                 _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
            goto bail_out;
         }
         break;
      case MT_SMALLEST_VOL:
         if (!find_mediaid_then_jobids(jcr, &ids, sql_smallest_vol, "Smallest Volume")) {
            goto bail_out;
         }
         break;
      case MT_OLDEST_VOL:
         if (!find_mediaid_then_jobids(jcr, &ids, sql_oldest_vol, "Oldest Volume")) {
            goto bail_out;
         }
         break;
      case MT_POOL_OCCUPANCY:
         ctx.count = 0;
         /* Find count of bytes in pool */
         Mmsg(query, sql_pool_bytes, jcr->rpool->name());
         if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
            Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
            goto bail_out;
         }
         if (ctx.count == 0) {
            Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
            goto ok_out;
         }
         pool_bytes = ctx.value;
         Dmsg2(dbglevel, "highbytes=%lld pool=%lld\n", jcr->rpool->MigrationHighBytes,
               pool_bytes);
         if (pool_bytes < (int64_t)jcr->rpool->MigrationHighBytes) {
            Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
            goto ok_out;
         }
         Dmsg0(dbglevel, "We should do Occupation migration.\n");

         ids.count = 0;
         /* Find a list of MediaIds that could be migrated */
         Mmsg(query, sql_mediaids, jcr->rpool->name());
         Dmsg1(dbglevel, "query=%s\n", query.c_str());
         if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
            Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
            goto bail_out;
         }
         if (ids.count == 0) {
            Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
            goto ok_out;
         }
         Dmsg2(dbglevel, "Pool Occupancy ids=%d MediaIds=%s\n", ids.count, ids.list);

         if (!find_jobids_from_mediaid_list(jcr, &ids, "Volume")) {
            goto bail_out;
         }
         /* ids == list of jobs  */
         p = ids.list;
         for (int i=0; i < (int)ids.count; i++) {
            stat = get_next_dbid_from_list(&p, &DBId);
            Dmsg2(dbglevel, "get_next_dbid stat=%d JobId=%u\n", stat, (uint32_t)DBId);
            if (stat < 0) {
               Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
               goto bail_out;
            } else if (stat == 0) {
               break;
            }

            mid.count = 1;
            Mmsg(mid.list, "%s", edit_int64(DBId, ed1));
            if (jids.count > 0) {
               pm_strcat(jids.list, ",");
            }
            pm_strcat(jids.list, mid.list);
            jids.count += mid.count;

            /* Find count of bytes from Jobs */
            Mmsg(query, sql_job_bytes, mid.list);
            Dmsg1(dbglevel, "Jobbytes query: %s\n", query.c_str());
            if (!db_sql_query(jcr->db, query.c_str(), db_int64_handler, (void *)&ctx)) {
               Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
               goto bail_out;
            }
            pool_bytes -= ctx.value;
            Dmsg2(dbglevel, "Total %s Job bytes=%s\n", jcr->get_ActionName(0), edit_int64_with_commas(ctx.value, ed1));
            Dmsg2(dbglevel, "lowbytes=%s poolafter=%s\n",
                  edit_int64_with_commas(jcr->rpool->MigrationLowBytes, ed1),
                  edit_int64_with_commas(pool_bytes, ed2));
            if (pool_bytes <= (int64_t)jcr->rpool->MigrationLowBytes) {
               Dmsg0(dbglevel, "We should be done.\n");
               break;
            }
         }
         /* Transfer jids to ids, where the jobs list is expected */
         ids.count = jids.count;
         pm_strcpy(ids.list, jids.list);
         Dmsg2(dbglevel, "Pool Occupancy ids=%d JobIds=%s\n", ids.count, ids.list);
         break;
      case MT_POOL_TIME:
         ttime = time(NULL) - (time_t)jcr->rpool->MigrationTime;
         (void)localtime_r(&ttime, &tm);
         strftime(dt, sizeof(dt), "%Y-%m-%d %H:%M:%S", &tm);

         ids.count = 0;
         Mmsg(query, sql_pool_time, jcr->rpool->name(), dt);
         Dmsg1(dbglevel, "query=%s\n", query.c_str());
         if (!db_sql_query(jcr->db, query.c_str(), unique_dbid_handler, (void *)&ids)) {
            Jmsg(jcr, M_FATAL, 0, _("SQL failed. ERR=%s\n"), db_strerror(jcr->db));
            goto bail_out;
         }
         if (ids.count == 0) {
            Jmsg(jcr, M_INFO, 0, _("No Volumes found to %s.\n"), jcr->get_ActionName(0));
            goto ok_out;
         }
         Dmsg2(dbglevel, "PoolTime ids=%d JobIds=%s\n", ids.count, ids.list);
         break;
      case MT_POOL_UNCOPIED_JOBS:
         if (!find_jobids_of_pool_uncopied_jobs(jcr, &ids)) {
            goto bail_out;
         }
         break;
      default:
         Jmsg(jcr, M_FATAL, 0, _("Unknown %s Selection Type.\n"), jcr->get_OperationName());
         goto bail_out;
      }

      /*
       * Loop over all jobids except the last one, sending
       * them to start_mac_job(), which will start a job
       * for each of them.  For the last JobId, we handle it below.
       */
      p = ids.list;
      if (ids.count == 0) {
         Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
         goto ok_out;
      }

      Jmsg(jcr, M_INFO, 0, _("The following %u JobId%s chosen to be %s: %s\n"),
         ids.count, (ids.count < 2) ? _(" was") : _("s were"),
         jcr->get_ActionName(1), ids.list);

      Dmsg2(dbglevel, "Before loop count=%d ids=%s\n", ids.count, ids.list);
      /*
       * Note: to not over load the system, limit the number
       *  of new jobs started to 100 (see limit above)
       */
      for (int i=1; i < (int)ids.count; i++) {
         JobId = 0;
         stat = get_next_jobid_from_list(&p, &JobId);
         Dmsg3(dbglevel, "getJobid_no=%d stat=%d JobId=%u\n", i, stat, JobId);
         if (stat < 0) {
            Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
            goto bail_out;
         } else if (stat == 0) {
            Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
            goto ok_out;
         }
         jcr->MigrateJobId = JobId;
         /* Don't start any more when limit reaches zero */
         limit--;
         if (limit > 0) {
            start_mac_job(jcr);
            Dmsg0(dbglevel, "Back from start_mac_job\n");
         }
      }

      /* Now get the last JobId and handle it in the current job */
      JobId = 0;
      stat = get_next_jobid_from_list(&p, &JobId);
      Dmsg2(dbglevel, "Last get_next_jobid stat=%d JobId=%u\n", stat, (int)JobId);
      if (stat < 0) {
         Jmsg(jcr, M_FATAL, 0, _("Invalid JobId found.\n"));
         goto bail_out;
      } else if (stat == 0) {
         Jmsg(jcr, M_INFO, 0, _("No JobIds found to %s.\n"), jcr->get_ActionName(0));
         goto ok_out;
      }
   }

   jcr->previous_jr.JobId = JobId;
   Dmsg1(dbglevel, "Previous jobid=%d\n", (int)jcr->previous_jr.JobId);

   if (!db_get_job_record(jcr, jcr->db, &jcr->previous_jr)) {
      Jmsg(jcr, M_FATAL, 0, _("Could not get job record for JobId %s to %s. ERR=%s"),
           edit_int64(jcr->previous_jr.JobId, ed1),
           jcr->get_ActionName(0),
           db_strerror(jcr->db));
      goto bail_out;
   }

   Jmsg(jcr, M_INFO, 0, _("%s using JobId=%s Job=%s\n"),
      jcr->get_OperationName(),
      edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
   Dmsg4(dbglevel, "%s JobId=%d  using JobId=%s Job=%s\n",
      jcr->get_OperationName(),
      jcr->JobId,
      edit_int64(jcr->previous_jr.JobId, ed1), jcr->previous_jr.Job);
   count = 1;

ok_out:
   goto out;

bail_out:
   count = -1;

out:
   free_pool_memory(ids.list);
   free_pool_memory(mid.list);
   free_pool_memory(jids.list);
   return count;
}