Index: src/dird/ua_output.c
===================================================================
---- src/dird/ua_output.c (revision 8163)
+--- src/dird/ua_output.c (revision 8203)
+++ src/dird/ua_output.c (working copy)
-@@ -454,6 +454,15 @@
+@@ -223,6 +223,7 @@
+ * list clients - list clients
+ * list nextvol job=xx - list the next vol to be used by job
+ * list nextvolume job=xx - same as above.
++ * list copies jobid=x,y,z
+ *
+ */
+
+@@ -454,6 +455,19 @@
}
}
list_nextvol(ua, n);
+ } else if (strcasecmp(ua->argk[i], NT_("copies")) == 0) {
++ char *jobids=NULL;
++ uint32_t limit=0;
+ for (j=i+1; j<ua->argc; j++) {
+ if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
-+ jr.JobId = str_to_int64(ua->argv[j]);
++ if (is_a_number_list(ua->argv[j])) {
++ jobids = ua->argv[j];
++ }
+ } else if (strcasecmp(ua->argk[j], NT_("limit")) == 0 && ua->argv[j]) {
-+ jr.limit = atoi(ua->argv[j]);
++ limit = atoi(ua->argv[j]);
+ }
+ }
-+ db_list_copies_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
++ db_list_copies_records(ua->jcr,ua->db,limit,jobids,prtit,ua,llist);
} else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
|| strcasecmp(ua->argk[i], NT_("days")) == 0) {
/* Ignore it */
Index: src/dird/migrate.c
===================================================================
---- src/dird/migrate.c (revision 8179)
+--- src/dird/migrate.c (revision 8203)
+++ src/dird/migrate.c (working copy)
@@ -1158,13 +1158,17 @@
/*
}
if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
+Index: src/dird/ua_purge.c
+===================================================================
+--- src/dird/ua_purge.c (revision 8203)
++++ src/dird/ua_purge.c (working copy)
+@@ -360,6 +360,57 @@
+ }
+
+ /*
++ * Change the type of the next copy job to backup.
++ * We need to upgrade the next copy of a normal job,
++ * and also upgrade the next copy when the normal job
++ * already have been purged.
++ *
++ * JobId: 1 PriorJobId: 0 (original)
++ * JobId: 2 PriorJobId: 1 (first copy)
++ * JobId: 3 PriorJobId: 1 (second copy)
++ *
++ * JobId: 2 PriorJobId: 1 (first copy, now regular backup)
++ * JobId: 3 PriorJobId: 1 (second copy)
++ *
++ * => Search through PriorJobId in jobid and
++ * PriorJobId in PriorJobId (jobid)
++ */
++void upgrade_copies(UAContext *ua, char *jobs)
++{
++ POOL_MEM query(PM_MESSAGE);
++
++ db_lock(ua->db);
++ /* Do it in two times for mysql */
++ Mmsg(query, "CREATE TEMPORARY TABLE cpy_tmp AS "
++ "SELECT MIN(JobId) AS JobId FROM Job " /* Choose the oldest job */
++ "WHERE Type='%c' "
++ "AND ( PriorJobId IN (%s) "
++ "OR "
++ " PriorJobId IN ( "
++ "SELECT PriorJobId "
++ "FROM Job "
++ "WHERE JobId IN (%s) "
++ " AND Type='B' "
++ ") "
++ ") "
++ "GROUP BY PriorJobId ", /* one result per copy */
++ JT_JOB_COPY, jobs, jobs);
++ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
++
++ /* Now upgrade first copy to Backup */
++ Mmsg(query, "UPDATE Job SET Type='B' " /* JT_JOB_COPY => JT_BACKUP */
++ "WHERE JobId IN ( SELECT JobId FROM cpy_tmp )");
++
++ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
++
++ Mmsg(query, "DROP TABLE cpy_tmp");
++ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
++
++ db_unlock(ua->db);
++ Dmsg1(00, "Upgrade copies Log sql=%s\n", query.c_str());
++}
++
++/*
+ * Remove all records from catalog for a list of JobIds
+ */
+ void purge_jobs_from_catalog(UAContext *ua, char *jobs)
+@@ -377,13 +428,15 @@
+ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+ Dmsg1(050, "Delete Log sql=%s\n", query.c_str());
+
++ upgrade_copies(ua, jobs);
++
+ /* Now remove the Job record itself */
+ Mmsg(query, "DELETE FROM Job WHERE JobId IN (%s)", jobs);
+ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
++
+ Dmsg1(050, "Delete Job sql=%s\n", query.c_str());
+ }
+
+-
+ void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
+ {} /* ***FIXME*** implement */
+
+Index: src/dird/ua_restore.c
+===================================================================
+--- src/dird/ua_restore.c (revision 8203)
++++ src/dird/ua_restore.c (working copy)
+@@ -444,6 +444,7 @@
+ "add_suffix", /* 17 */
+ "regexwhere", /* 18 */
+ "restoreclient", /* 19 */
++ "copies", /* 20 */
+ NULL
+ };
+
+@@ -1138,9 +1139,10 @@
+ bool ok = false;
+ FILESET_DBR fsr;
+ CLIENT_DBR cr;
++ POOL_MEM other_filter(PM_MESSAGE);
++ POOL_MEM temp_filter(PM_MESSAGE);
+ char fileset_name[MAX_NAME_LENGTH];
+ char ed1[50], ed2[50];
+- char pool_select[MAX_NAME_LENGTH];
+ int i;
+
+ /* Create temp tables */
+@@ -1196,23 +1198,32 @@
+ }
+
+ /* If Pool specified, add PoolId specification */
+- pool_select[0] = 0;
+ if (rx->pool) {
+ POOL_DBR pr;
+ memset(&pr, 0, sizeof(pr));
+ bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
+ if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
+- bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
+- edit_int64(pr.PoolId, ed1));
++ Mmsg(other_filter, " AND Media.PoolId=%s ",
++ edit_int64(pr.PoolId, ed1));
+ } else {
+ ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
+ }
+ }
++ /* include copies or not in job selection */
++ if (find_arg(ua, NT_("copies")) > 0) {
++ Mmsg(temp_filter, "%s AND Job.Type IN ('%c', '%c') ",
++ other_filter.c_str(), (char)JT_BACKUP, (char)JT_JOB_COPY);
++ } else {
++ Mmsg(temp_filter, "%s AND Job.Type = '%c' ", other_filter.c_str(),
++ (char)JT_BACKUP);
++ }
++ pm_strcpy(other_filter, temp_filter.c_str());
+
+ /* Find JobId of last Full backup for this client, fileset */
+ edit_int64(cr.ClientId, ed1);
+ Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
+- pool_select);
++ other_filter.c_str());
++ Dmsg1(0, "sql=%s\n", rx->query);
+ if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
+ ua->error_msg("%s\n", db_strerror(ua->db));
+ goto bail_out;
+@@ -1238,12 +1249,13 @@
+
+ /* Now find most recent Differental Job after Full save, if any */
+ Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
+- edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
++ edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
+ if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
+ ua->warning_msg("%s\n", db_strerror(ua->db));
+ }
+ /* Now update JobTDate to lock onto Differental, if any */
+ rx->JobTDate = 0;
++ Dmsg1(0, "sql=%s\n", rx->query);
+ if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
+ ua->warning_msg("%s\n", db_strerror(ua->db));
+ }
+@@ -1254,7 +1266,8 @@
+
+ /* Now find all Incremental Jobs after Full/dif save */
+ Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
+- edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
++ edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
++ Dmsg1(0, "sql=%s\n", rx->query);
+ if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
+ ua->warning_msg("%s\n", db_strerror(ua->db));
+ }
+@@ -1267,6 +1280,8 @@
+ }
+
+ if (rx->JobIds[0] != 0) {
++ /* Display a list of all copies */
++ db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds, prtit, ua, HORZ_LIST);
+ /* Display a list of Jobs selected for this restore */
+ db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
+ ok = true;
Index: src/dird/ua_cmds.c
===================================================================
---- src/dird/ua_cmds.c (revision 8163)
+--- src/dird/ua_cmds.c (revision 8203)
+++ src/dird/ua_cmds.c (working copy)
@@ -123,7 +123,7 @@
{ NT_("exit"), quit_cmd, _("exit = quit"), false},
{ NT_("messages"), messagescmd, _("messages"), false},
Index: src/cats/protos.h
===================================================================
---- src/cats/protos.h (revision 8163)
+--- src/cats/protos.h (revision 8203)
+++ src/cats/protos.h (working copy)
@@ -124,6 +124,7 @@
void db_list_joblog_records(JCR *jcr, B_DB *mdb, JobId_t JobId, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
int db_list_sql_query(JCR *jcr, B_DB *mdb, const char *query, DB_LIST_HANDLER *sendit, void *ctx, int verbose, e_list_type type);
void db_list_client_records(JCR *jcr, B_DB *mdb, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
-+void db_list_copies_records(JCR *jcr, B_DB *mdb, JOB_DBR *jr, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
++void db_list_copies_records(JCR *jcr, B_DB *mdb, uint32_t limit, char *jobids, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
/* sql_update.c */
bool db_update_job_start_record(JCR *jcr, B_DB *db, JOB_DBR *jr);
+Index: src/cats/sql_cmds.c
+===================================================================
+--- src/cats/sql_cmds.c (revision 8203)
++++ src/cats/sql_cmds.c (working copy)
+@@ -286,7 +286,7 @@
+ "FROM Client,Job,JobMedia,Media,FileSet WHERE Client.ClientId=%s "
+ "AND Job.ClientId=%s "
+ "AND Job.StartTime<'%s' "
+- "AND Level='F' AND JobStatus='T' AND Type='B' "
++ "AND Level='F' AND JobStatus='T' "
+ "AND JobMedia.JobId=Job.JobId "
+ "AND Media.Enabled=1 "
+ "AND JobMedia.MediaId=Media.MediaId "
+@@ -297,13 +297,14 @@
+
+ const char *uar_full =
+ "INSERT INTO temp SELECT Job.JobId,Job.JobTDate,"
+- "Job.ClientId,Job.Level,Job.JobFiles,Job.JobBytes,"
+- "StartTime,VolumeName,JobMedia.StartFile,VolSessionId,VolSessionTime "
+- "FROM temp1,Job,JobMedia,Media WHERE temp1.JobId=Job.JobId "
+- "AND Level='F' AND JobStatus='T' AND Type='B' "
+- "AND Media.Enabled=1 "
+- "AND JobMedia.JobId=Job.JobId "
+- "AND JobMedia.MediaId=Media.MediaId";
++ "Job.ClientId,Job.Level,Job.JobFiles,Job.JobBytes,"
++ "StartTime,VolumeName,JobMedia.StartFile,VolSessionId,VolSessionTime "
++ "FROM temp1,Job,JobMedia,Media "
++ "WHERE temp1.JobId=Job.JobId "
++ "AND Level='F' AND JobStatus='T' "
++ "AND Media.Enabled=1 "
++ "AND JobMedia.JobId=Job.JobId "
++ "AND JobMedia.MediaId=Media.MediaId";
+
+ const char *uar_dif =
+ "INSERT INTO temp SELECT Job.JobId,Job.JobTDate,Job.ClientId,"
+@@ -316,7 +317,7 @@
+ "AND JobMedia.JobId=Job.JobId "
+ "AND Media.Enabled=1 "
+ "AND JobMedia.MediaId=Media.MediaId "
+- "AND Job.Level='D' AND JobStatus='T' AND Type='B' "
++ "AND Job.Level='D' AND JobStatus='T' "
+ "AND Job.FileSetId=FileSet.FileSetId "
+ "AND FileSet.FileSet='%s' "
+ "%s"
+@@ -333,7 +334,7 @@
+ "AND Media.Enabled=1 "
+ "AND JobMedia.JobId=Job.JobId "
+ "AND JobMedia.MediaId=Media.MediaId "
+- "AND Job.Level='I' AND JobStatus='T' AND Type='B' "
++ "AND Job.Level='I' AND JobStatus='T' "
+ "AND Job.FileSetId=FileSet.FileSetId "
+ "AND FileSet.FileSet='%s' "
+ "%s";
Index: src/cats/sql_list.c
===================================================================
---- src/cats/sql_list.c (revision 8163)
+--- src/cats/sql_list.c (revision 8203)
+++ src/cats/sql_list.c (working copy)
@@ -242,6 +242,43 @@
}
-+void db_list_copies_records(JCR *jcr, B_DB *mdb, JOB_DBR *jr,
++void db_list_copies_records(JCR *jcr, B_DB *mdb, uint32_t limit, char *JobIds,
+ DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
+{
-+ char ed1[50];
-+ POOL_MEM limit(PM_MESSAGE);
-+ POOL_MEM jobids(PM_MESSAGE);
++ POOL_MEM str_limit(PM_MESSAGE);
++ POOL_MEM str_jobids(PM_MESSAGE);
+
-+ if (jr->limit > 0) {
-+ Mmsg(limit, " LIMIT %d", jr->limit);
++ if (limit > 0) {
++ Mmsg(str_limit, " LIMIT %d", limit);
+ }
+
-+ if (jr->JobId) {
-+ Mmsg(jobids, " AND (C.PriorJobId = %s OR C.JobId = %s) ",
-+ edit_int64(jr->JobId, ed1),ed1);
++ if (JobIds && JobIds[0]) {
++ Mmsg(str_jobids, " AND (C.PriorJobId IN (%s) OR C.JobId IN (%s)) ",
++ JobIds, JobIds);
+ }
+
+ db_lock(mdb);
+ Mmsg(mdb->cmd,
-+ "SELECT C.PriorJobId AS JobId, C.Job, C.JobId AS CopyJobId, M.MediaType "
++ "SELECT DISTINCT C.PriorJobId AS JobId, C.Job, "
++ "C.JobId AS CopyJobId, M.MediaType "
+ "FROM Job AS C "
+ "JOIN JobMedia USING (JobId) "
+ "JOIN Media AS M USING (MediaId) "
+ "WHERE C.Type = '%c' %s ORDER BY C.PriorJobId DESC %s",
-+ (char) JT_JOB_COPY, jobids.c_str(), limit.c_str());
++ (char) JT_JOB_COPY, str_jobids.c_str(), str_limit.c_str());
+
+ if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+ goto bail_out;
{
Index: src/jcr.h
===================================================================
---- src/jcr.h (revision 8163)
+--- src/jcr.h (revision 8203)
+++ src/jcr.h (working copy)
@@ -60,11 +60,12 @@
#define JT_MIGRATED_JOB 'M' /* A previous backup job that was migrated */
Index: src/lib/util.c
===================================================================
---- src/lib/util.c (revision 8163)
+--- src/lib/util.c (revision 8203)
+++ src/lib/util.c (working copy)
@@ -361,6 +361,9 @@
case JT_COPY:
case JT_CONSOLE:
str = _("Console");
break;
+Index: src/lib/protos.h
+===================================================================
+--- src/lib/protos.h (revision 8203)
++++ src/lib/protos.h (working copy)
+@@ -186,6 +186,7 @@
+ bool size_to_uint64(char *str, int str_len, uint64_t *rtn_value);
+ char *edit_utime (utime_t val, char *buf, int buf_len);
+ bool is_a_number (const char *num);
++bool is_a_number_list (const char *n);
+ bool is_an_integer (const char *n);
+ bool is_name_valid (char *name, POOLMEM **msg);
+
+Index: src/lib/edit.c
+===================================================================
+--- src/lib/edit.c (revision 8203)
++++ src/lib/edit.c (working copy)
+@@ -407,6 +407,27 @@
+ }
+
+ /*
++ * Check if specified string is a list of number or not
++ */
++bool is_a_number_list(const char *n)
++{
++ bool previous_digit = false;
++ bool digit_seen = false;
++ while (*n) {
++ if (B_ISDIGIT(*n)) {
++ previous_digit=true;
++ digit_seen = true;
++ } else if (*n == ',' && previous_digit) {
++ previous_digit = false;
++ } else {
++ return false;
++ }
++ n++;
++ }
++ return digit_seen && *n==0;
++}
++
++/*
+ * Check if the specified string is an integer
+ */
+ bool is_an_integer(const char *n)
+Index: patches/testing/copy_list_copies_cmd.patch
+===================================================================
+--- patches/testing/copy_list_copies_cmd.patch (revision 8203)
++++ patches/testing/copy_list_copies_cmd.patch (working copy)
+@@ -1,26 +1,38 @@
+ Index: src/dird/ua_output.c
+ ===================================================================
+---- src/dird/ua_output.c (revision 8163)
++--- src/dird/ua_output.c (revision 8203)
+ +++ src/dird/ua_output.c (working copy)
+-@@ -454,6 +454,15 @@
++@@ -223,6 +223,7 @@
++ * list clients - list clients
++ * list nextvol job=xx - list the next vol to be used by job
++ * list nextvolume job=xx - same as above.
+++ * list copies jobid=x,y,z
++ *
++ */
++
++@@ -454,6 +455,19 @@
+ }
+ }
+ list_nextvol(ua, n);
+ + } else if (strcasecmp(ua->argk[i], NT_("copies")) == 0) {
+++ char *jobids=NULL;
+++ uint32_t limit=0;
+ + for (j=i+1; j<ua->argc; j++) {
+ + if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
+-+ jr.JobId = str_to_int64(ua->argv[j]);
+++ if (is_a_number_list(ua->argv[j])) {
+++ jobids = ua->argv[j];
+++ }
+ + } else if (strcasecmp(ua->argk[j], NT_("limit")) == 0 && ua->argv[j]) {
+-+ jr.limit = atoi(ua->argv[j]);
+++ limit = atoi(ua->argv[j]);
+ + }
+ + }
+-+ db_list_copies_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
+++ db_list_copies_records(ua->jcr,ua->db,limit,jobids,prtit,ua,llist);
+ } else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
+ || strcasecmp(ua->argk[i], NT_("days")) == 0) {
+ /* Ignore it */
+ Index: src/dird/migrate.c
+ ===================================================================
+---- src/dird/migrate.c (revision 8179)
++--- src/dird/migrate.c (revision 8203)
+ +++ src/dird/migrate.c (working copy)
+ @@ -1158,13 +1158,17 @@
+ /*
+@@ -41,9 +53,183 @@
+ }
+
+ if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
++Index: src/dird/ua_purge.c
++===================================================================
++--- src/dird/ua_purge.c (revision 8203)
+++++ src/dird/ua_purge.c (working copy)
++@@ -360,6 +360,57 @@
++ }
++
++ /*
+++ * Change the type of the next copy job to backup.
+++ * We need to upgrade the next copy of a normal job,
+++ * and also upgrade the next copy when the normal job
+++ * already have been purged.
+++ *
+++ * JobId: 1 PriorJobId: 0 (original)
+++ * JobId: 2 PriorJobId: 1 (first copy)
+++ * JobId: 3 PriorJobId: 1 (second copy)
+++ *
+++ * JobId: 2 PriorJobId: 1 (first copy, now regular backup)
+++ * JobId: 3 PriorJobId: 1 (second copy)
+++ *
+++ * => Search through PriorJobId in jobid and
+++ * PriorJobId in PriorJobId (jobid)
+++ */
+++void upgrade_copies(UAContext *ua, char *jobs)
+++{
+++ POOL_MEM query(PM_MESSAGE);
+++
+++ db_lock(ua->db);
+++ /* Do it in two times for mysql */
+++ Mmsg(query, "CREATE TEMPORARY TABLE cpy_tmp AS "
+++ "SELECT MIN(JobId) AS JobId FROM Job " /* Choose the oldest job */
+++ "WHERE Type='%c' "
+++ "AND ( PriorJobId IN (%s) "
+++ "OR "
+++ " PriorJobId IN ( "
+++ "SELECT PriorJobId "
+++ "FROM Job "
+++ "WHERE JobId IN (%s) "
+++ " AND Type='B' "
+++ ") "
+++ ") "
+++ "GROUP BY PriorJobId ", /* one result per copy */
+++ JT_JOB_COPY, jobs, jobs);
+++ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+++
+++ /* Now upgrade first copy to Backup */
+++ Mmsg(query, "UPDATE Job SET Type='B' " /* JT_JOB_COPY => JT_BACKUP */
+++ "WHERE JobId IN ( SELECT JobId FROM cpy_tmp )");
+++
+++ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+++
+++ Mmsg(query, "DROP TABLE cpy_tmp");
+++ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+++
+++ db_unlock(ua->db);
+++ Dmsg1(00, "Upgrade copies Log sql=%s\n", query.c_str());
+++}
+++
+++/*
++ * Remove all records from catalog for a list of JobIds
++ */
++ void purge_jobs_from_catalog(UAContext *ua, char *jobs)
++@@ -377,13 +428,15 @@
++ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
++ Dmsg1(050, "Delete Log sql=%s\n", query.c_str());
++
+++ upgrade_copies(ua, jobs);
+++
++ /* Now remove the Job record itself */
++ Mmsg(query, "DELETE FROM Job WHERE JobId IN (%s)", jobs);
++ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
+++
++ Dmsg1(050, "Delete Job sql=%s\n", query.c_str());
++ }
++
++-
++ void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
++ {} /* ***FIXME*** implement */
++
++Index: src/dird/ua_restore.c
++===================================================================
++--- src/dird/ua_restore.c (revision 8203)
+++++ src/dird/ua_restore.c (working copy)
++@@ -444,6 +444,7 @@
++ "add_suffix", /* 17 */
++ "regexwhere", /* 18 */
++ "restoreclient", /* 19 */
+++ "copies", /* 20 */
++ NULL
++ };
++
++@@ -1138,9 +1139,10 @@
++ bool ok = false;
++ FILESET_DBR fsr;
++ CLIENT_DBR cr;
+++ POOL_MEM other_filter(PM_MESSAGE);
+++ POOL_MEM temp_filter(PM_MESSAGE);
++ char fileset_name[MAX_NAME_LENGTH];
++ char ed1[50], ed2[50];
++- char pool_select[MAX_NAME_LENGTH];
++ int i;
++
++ /* Create temp tables */
++@@ -1196,23 +1198,32 @@
++ }
++
++ /* If Pool specified, add PoolId specification */
++- pool_select[0] = 0;
++ if (rx->pool) {
++ POOL_DBR pr;
++ memset(&pr, 0, sizeof(pr));
++ bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
++ if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
++- bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
++- edit_int64(pr.PoolId, ed1));
+++ Mmsg(other_filter, " AND Media.PoolId=%s ",
+++ edit_int64(pr.PoolId, ed1));
++ } else {
++ ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
++ }
++ }
+++ /* include copies or not in job selection */
+++ if (find_arg(ua, NT_("copies")) > 0) {
+++ Mmsg(temp_filter, "%s AND Job.Type IN ('%c', '%c') ",
+++ other_filter.c_str(), (char)JT_BACKUP, (char)JT_JOB_COPY);
+++ } else {
+++ Mmsg(temp_filter, "%s AND Job.Type = '%c' ", other_filter.c_str(),
+++ (char)JT_BACKUP);
+++ }
+++ pm_strcpy(other_filter, temp_filter.c_str());
++
++ /* Find JobId of last Full backup for this client, fileset */
++ edit_int64(cr.ClientId, ed1);
++ Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
++- pool_select);
+++ other_filter.c_str());
+++ Dmsg1(0, "sql=%s\n", rx->query);
++ if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
++ ua->error_msg("%s\n", db_strerror(ua->db));
++ goto bail_out;
++@@ -1238,12 +1249,13 @@
++
++ /* Now find most recent Differental Job after Full save, if any */
++ Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
++- edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
+++ edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
++ if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
++ ua->warning_msg("%s\n", db_strerror(ua->db));
++ }
++ /* Now update JobTDate to lock onto Differental, if any */
++ rx->JobTDate = 0;
+++ Dmsg1(0, "sql=%s\n", rx->query);
++ if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
++ ua->warning_msg("%s\n", db_strerror(ua->db));
++ }
++@@ -1254,7 +1266,8 @@
++
++ /* Now find all Incremental Jobs after Full/dif save */
++ Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
++- edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
+++ edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
+++ Dmsg1(0, "sql=%s\n", rx->query);
++ if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
++ ua->warning_msg("%s\n", db_strerror(ua->db));
++ }
++@@ -1267,6 +1280,8 @@
++ }
++
++ if (rx->JobIds[0] != 0) {
+++ /* Display a list of all copies */
+++ db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds, prtit, ua, HORZ_LIST);
++ /* Display a list of Jobs selected for this restore */
++ db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
++ ok = true;
+ Index: src/dird/ua_cmds.c
+ ===================================================================
+---- src/dird/ua_cmds.c (revision 8163)
++--- src/dird/ua_cmds.c (revision 8203)
+ +++ src/dird/ua_cmds.c (working copy)
+ @@ -123,7 +123,7 @@
+ { NT_("exit"), quit_cmd, _("exit = quit"), false},
+@@ -56,48 +242,101 @@
+ { NT_("messages"), messagescmd, _("messages"), false},
+ Index: src/cats/protos.h
+ ===================================================================
+---- src/cats/protos.h (revision 8163)
++--- src/cats/protos.h (revision 8203)
+ +++ src/cats/protos.h (working copy)
+ @@ -124,6 +124,7 @@
+ void db_list_joblog_records(JCR *jcr, B_DB *mdb, JobId_t JobId, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
+ int db_list_sql_query(JCR *jcr, B_DB *mdb, const char *query, DB_LIST_HANDLER *sendit, void *ctx, int verbose, e_list_type type);
+ void db_list_client_records(JCR *jcr, B_DB *mdb, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
+-+void db_list_copies_records(JCR *jcr, B_DB *mdb, JOB_DBR *jr, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
+++void db_list_copies_records(JCR *jcr, B_DB *mdb, uint32_t limit, char *jobids, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
+
+ /* sql_update.c */
+ bool db_update_job_start_record(JCR *jcr, B_DB *db, JOB_DBR *jr);
++Index: src/cats/sql_cmds.c
++===================================================================
++--- src/cats/sql_cmds.c (revision 8203)
+++++ src/cats/sql_cmds.c (working copy)
++@@ -286,7 +286,7 @@
++ "FROM Client,Job,JobMedia,Media,FileSet WHERE Client.ClientId=%s "
++ "AND Job.ClientId=%s "
++ "AND Job.StartTime<'%s' "
++- "AND Level='F' AND JobStatus='T' AND Type='B' "
+++ "AND Level='F' AND JobStatus='T' "
++ "AND JobMedia.JobId=Job.JobId "
++ "AND Media.Enabled=1 "
++ "AND JobMedia.MediaId=Media.MediaId "
++@@ -297,13 +297,14 @@
++
++ const char *uar_full =
++ "INSERT INTO temp SELECT Job.JobId,Job.JobTDate,"
++- "Job.ClientId,Job.Level,Job.JobFiles,Job.JobBytes,"
++- "StartTime,VolumeName,JobMedia.StartFile,VolSessionId,VolSessionTime "
++- "FROM temp1,Job,JobMedia,Media WHERE temp1.JobId=Job.JobId "
++- "AND Level='F' AND JobStatus='T' AND Type='B' "
++- "AND Media.Enabled=1 "
++- "AND JobMedia.JobId=Job.JobId "
++- "AND JobMedia.MediaId=Media.MediaId";
+++ "Job.ClientId,Job.Level,Job.JobFiles,Job.JobBytes,"
+++ "StartTime,VolumeName,JobMedia.StartFile,VolSessionId,VolSessionTime "
+++ "FROM temp1,Job,JobMedia,Media "
+++ "WHERE temp1.JobId=Job.JobId "
+++ "AND Level='F' AND JobStatus='T' "
+++ "AND Media.Enabled=1 "
+++ "AND JobMedia.JobId=Job.JobId "
+++ "AND JobMedia.MediaId=Media.MediaId";
++
++ const char *uar_dif =
++ "INSERT INTO temp SELECT Job.JobId,Job.JobTDate,Job.ClientId,"
++@@ -316,7 +317,7 @@
++ "AND JobMedia.JobId=Job.JobId "
++ "AND Media.Enabled=1 "
++ "AND JobMedia.MediaId=Media.MediaId "
++- "AND Job.Level='D' AND JobStatus='T' AND Type='B' "
+++ "AND Job.Level='D' AND JobStatus='T' "
++ "AND Job.FileSetId=FileSet.FileSetId "
++ "AND FileSet.FileSet='%s' "
++ "%s"
++@@ -333,7 +334,7 @@
++ "AND Media.Enabled=1 "
++ "AND JobMedia.JobId=Job.JobId "
++ "AND JobMedia.MediaId=Media.MediaId "
++- "AND Job.Level='I' AND JobStatus='T' AND Type='B' "
+++ "AND Job.Level='I' AND JobStatus='T' "
++ "AND Job.FileSetId=FileSet.FileSetId "
++ "AND FileSet.FileSet='%s' "
++ "%s";
+ Index: src/cats/sql_list.c
+ ===================================================================
+---- src/cats/sql_list.c (revision 8163)
++--- src/cats/sql_list.c (revision 8203)
+ +++ src/cats/sql_list.c (working copy)
+ @@ -242,6 +242,43 @@
+ }
+
+
+-+void db_list_copies_records(JCR *jcr, B_DB *mdb, JOB_DBR *jr,
+++void db_list_copies_records(JCR *jcr, B_DB *mdb, uint32_t limit, char *JobIds,
+ + DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
+ +{
+-+ char ed1[50];
+-+ POOL_MEM limit(PM_MESSAGE);
+-+ POOL_MEM jobids(PM_MESSAGE);
+++ POOL_MEM str_limit(PM_MESSAGE);
+++ POOL_MEM str_jobids(PM_MESSAGE);
+ +
+-+ if (jr->limit > 0) {
+-+ Mmsg(limit, " LIMIT %d", jr->limit);
+++ if (limit > 0) {
+++ Mmsg(str_limit, " LIMIT %d", limit);
+ + }
+ +
+-+ if (jr->JobId) {
+-+ Mmsg(jobids, " AND (C.PriorJobId = %s OR C.JobId = %s) ",
+-+ edit_int64(jr->JobId, ed1),ed1);
+++ if (JobIds && JobIds[0]) {
+++ Mmsg(str_jobids, " AND (C.PriorJobId IN (%s) OR C.JobId IN (%s)) ",
+++ JobIds, JobIds);
+ + }
+ +
+ + db_lock(mdb);
+ + Mmsg(mdb->cmd,
+-+ "SELECT C.PriorJobId AS JobId, C.Job, C.JobId AS CopyJobId, M.MediaType "
+++ "SELECT DISTINCT C.PriorJobId AS JobId, C.Job, "
+++ "C.JobId AS CopyJobId, M.MediaType "
+ + "FROM Job AS C "
+ + "JOIN JobMedia USING (JobId) "
+ + "JOIN Media AS M USING (MediaId) "
+ + "WHERE C.Type = '%c' %s ORDER BY C.PriorJobId DESC %s",
+-+ (char) JT_JOB_COPY, jobids.c_str(), limit.c_str());
+++ (char) JT_JOB_COPY, str_jobids.c_str(), str_limit.c_str());
+ +
+ + if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
+ + goto bail_out;
+@@ -116,7 +355,7 @@
+ {
+ Index: src/jcr.h
+ ===================================================================
+---- src/jcr.h (revision 8163)
++--- src/jcr.h (revision 8203)
+ +++ src/jcr.h (working copy)
+ @@ -60,11 +60,12 @@
+ #define JT_MIGRATED_JOB 'M' /* A previous backup job that was migrated */
+@@ -135,7 +374,7 @@
+
+ Index: src/lib/util.c
+ ===================================================================
+---- src/lib/util.c (revision 8163)
++--- src/lib/util.c (revision 8203)
+ +++ src/lib/util.c (working copy)
+ @@ -361,6 +361,9 @@
+ case JT_COPY:
+@@ -147,3 +386,47 @@
+ case JT_CONSOLE:
+ str = _("Console");
+ break;
++Index: src/lib/protos.h
++===================================================================
++--- src/lib/protos.h (revision 8203)
+++++ src/lib/protos.h (working copy)
++@@ -186,6 +186,7 @@
++ bool size_to_uint64(char *str, int str_len, uint64_t *rtn_value);
++ char *edit_utime (utime_t val, char *buf, int buf_len);
++ bool is_a_number (const char *num);
+++bool is_a_number_list (const char *n);
++ bool is_an_integer (const char *n);
++ bool is_name_valid (char *name, POOLMEM **msg);
++
++Index: src/lib/edit.c
++===================================================================
++--- src/lib/edit.c (revision 8203)
+++++ src/lib/edit.c (working copy)
++@@ -407,6 +407,27 @@
++ }
++
++ /*
+++ * Check if specified string is a list of number or not
+++ */
+++bool is_a_number_list(const char *n)
+++{
+++ bool previous_digit = false;
+++ bool digit_seen = false;
+++ while (*n) {
+++ if (B_ISDIGIT(*n)) {
+++ previous_digit=true;
+++ digit_seen = true;
+++ } else if (*n == ',' && previous_digit) {
+++ previous_digit = false;
+++ } else {
+++ return false;
+++ }
+++ n++;
+++ }
+++ return digit_seen && *n==0;
+++}
+++
+++/*
++ * Check if the specified string is an integer
++ */
++ bool is_an_integer(const char *n)
+Index: patches/testing/fix_1190.patch
+===================================================================
+--- patches/testing/fix_1190.patch (revision 8203)
++++ patches/testing/fix_1190.patch (working copy)
+@@ -201,7 +201,6 @@
+ }
+ -// Dmsg3(dbglevel, "match_volblock: sblock=%u eblock=%u recblock=%u\n",
+ -// volblock->sblock, volblock->eblock, rec->Block);
+-- if (volblock->sblock <= rec->Block && volblock->eblock >= rec->Block) {
+ + Dmsg3(dbglevel, "match_volblock: sblock=%u eblock=%u recblock=%u\n",
+ + volblock->sblock, volblock->eblock, rec->Block);
+ +
+@@ -213,8 +212,8 @@
+ + * But, we are already decoding rec->Block-1Block records
+ + */
+ + uint32_t max = volblock->eblock+DEFAULT_BLOCK_SIZE;
+-+// if (volblock->sblock <= rec->Block && volblock->eblock >= rec->Block) {
+-+ if (min <= rec->Block && max >= rec->Block) {
++ if (volblock->sblock <= rec->Block && volblock->eblock >= rec->Block) {
+++// if (min <= rec->Block && max >= rec->Block) {
+ return 1;
+ }
+ /* Once we get past last eblock, we are done */
+@@ -288,3 +287,23 @@
+ }
+ /*
+ * Check for Start or End of Session Record
++Index: block.c
++===================================================================
++--- block.c (révision 8116)
+++++ block.c (copie de travail)
++@@ -1116,11 +1116,12 @@
++ dcr->EndBlock = dev->EndBlock;
++ dcr->EndFile = dev->EndFile;
++ } else {
++- uint64_t addr = dev->file_addr + block->read_len - 1;
+++ uint32_t len = MIN(block->read_len, block->block_len);
+++ uint64_t addr = dev->file_addr + len - 1;
++ dcr->EndBlock = (uint32_t)addr;
++ dcr->EndFile = (uint32_t)(addr >> 32);
++- dev->block_num = dcr->EndBlock;
++- dev->file = dcr->EndFile;
+++ dev->block_num = dev->EndBlock = dcr->EndBlock;
+++ dev->file = dev->EndFile = dcr->EndFile;
++ }
++ dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
++ dev->file_addr += block->read_len;
+Index: patches/testing/find_smallest_volfile.patch
+===================================================================
+--- patches/testing/find_smallest_volfile.patch (revision 8203)
++++ patches/testing/find_smallest_volfile.patch (working copy)
+@@ -149,74 +149,3 @@
+ return return_bsr;
+ }
+
+-@@ -386,8 +397,6 @@
+- rec->Block, bsr->volblock->sblock, bsr->volblock->eblock);
+- goto no_match;
+- }
+-- Dmsg3(dbglevel, "OK bsr Block=%u. bsr=%u,%u\n",
+-- rec->Block, bsr->volblock->sblock, bsr->volblock->eblock);
+-
+- if (!match_sesstime(bsr, bsr->sesstime, rec, 1)) {
+- Dmsg2(dbglevel, "Fail on sesstime. bsr=%u rec=%u\n",
+-@@ -411,6 +420,9 @@
+- Dmsg3(dbglevel, "match on findex=%d. bsr=%d,%d\n",
+- rec->FileIndex, bsr->FileIndex->findex, bsr->FileIndex->findex2);
+-
+-+ Dmsg3(dbglevel, "OK bsr Block=%u. bsr=%u,%u\n",
+-+ rec->Block, bsr->volblock->sblock, bsr->volblock->eblock);
+-+
+- if (!match_fileregex(bsr, rec, jcr)) {
+- Dmsg1(dbglevel, "Fail on fileregex='%s'\n", bsr->fileregex);
+- goto no_match;
+-@@ -607,14 +619,7 @@
+-
+- static int match_volblock(BSR *bsr, BSR_VOLBLOCK *volblock, DEV_RECORD *rec, bool done)
+- {
+-- /*
+-- * Currently block matching does not work correctly for disk
+-- * files in all cases, so it is "turned off" by the following
+-- * return statement.
+-- */
+-- return 1;
+-
+--
+- if (!volblock) {
+- return 1; /* no specification matches all */
+- }
+-@@ -622,8 +627,9 @@
+- if (rec->state & REC_ISTAPE) {
+- return 1; /* All File records OK for this match */
+- }
+--// Dmsg3(dbglevel, "match_volblock: sblock=%u eblock=%u recblock=%u\n",
+--// volblock->sblock, volblock->eblock, rec->Block);
+-+ Dmsg3(dbglevel, "match_volblock: sblock=%u eblock=%u recblock=%u\n",
+-+ volblock->sblock, volblock->eblock, rec->Block);
+-+
+- if (volblock->sblock <= rec->Block && volblock->eblock >= rec->Block) {
+- return 1;
+- }
+-Index: src/stored/read_record.c
+-===================================================================
+---- src/stored/read_record.c (révision 8116)
+-+++ src/stored/read_record.c (copie de travail)
+-@@ -261,8 +261,8 @@
+- Dmsg2(100, "All done=(file:block) %u:%u\n", dev->file, dev->block_num);
+- break;
+- } else if (rec->match_stat == 0) { /* no match */
+-- Dmsg4(100, "BSR no match: clear rem=%d FI=%d before set_eof pos %u:%u\n",
+-- rec->remainder, rec->FileIndex, dev->file, dev->block_num);
+-+ Dmsg7(100, "BSR no match: clear rem=%d FI=%d rec->Block=%d dev->LastBlock=%d dev->EndBlock=%d before set_eof pos %u:%u\n",
+-+ rec->remainder, rec->FileIndex, rec->Block, dev->LastBlock, dev->EndBlock, dev->file, dev->block_num);
+- rec->remainder = 0;
+- rec->state &= ~REC_PARTIAL_RECORD;
+- if (try_repositioning(jcr, rec, dcr)) {
+-@@ -346,6 +346,9 @@
+- */
+- if (dev->file > bsr->volfile->sfile ||
+- (dev->file == bsr->volfile->sfile && dev->block_num > bsr->volblock->sblock)) {
+-+ Dmsg4(dbglvl, _("Reposition from (file:block) %u:%u to %u:%u\n"),
+-+ dev->file, dev->block_num, bsr->volfile->sfile,
+-+ bsr->volblock->sblock);
+- return false;
+- }
+- if (verbose) {