From 60d3906935e64cb504c9e47c519224862e6d36c1 Mon Sep 17 00:00:00 2001 From: Eric Bollengier Date: Sat, 20 Dec 2008 21:17:54 +0000 Subject: [PATCH] ebl Update copy patch git-svn-id: https://bacula.svn.sourceforge.net/svnroot/bacula/trunk@8207 91ce42f0-d328-0410-95d8-f526ca767f89 --- .../testing/copy_list_copies_cmd.patch | 871 +++++++++++++++++- 1 file changed, 848 insertions(+), 23 deletions(-) diff --git a/bacula/patches/testing/copy_list_copies_cmd.patch b/bacula/patches/testing/copy_list_copies_cmd.patch index 2a7fb19d4b..e7a5c81f5c 100644 --- a/bacula/patches/testing/copy_list_copies_cmd.patch +++ b/bacula/patches/testing/copy_list_copies_cmd.patch @@ -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; jargc; 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 @@ Index: src/dird/migrate.c } 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 @@ Index: src/dird/ua_cmds.c { 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/cats/sql_list.c { 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/jcr.h 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,589 @@ Index: src/lib/util.c 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; jargc; 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) { -- 2.39.5