1 Index: src/dird/ua_output.c
2 ===================================================================
3 --- src/dird/ua_output.c (revision 8203)
4 +++ src/dird/ua_output.c (working copy)
6 * list clients - list clients
7 * list nextvol job=xx - list the next vol to be used by job
8 * list nextvolume job=xx - same as above.
9 + * list copies jobid=x,y,z
17 + } else if (strcasecmp(ua->argk[i], NT_("copies")) == 0) {
20 + for (j=i+1; j<ua->argc; j++) {
21 + if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
22 + if (is_a_number_list(ua->argv[j])) {
23 + jobids = ua->argv[j];
25 + } else if (strcasecmp(ua->argk[j], NT_("limit")) == 0 && ua->argv[j]) {
26 + limit = atoi(ua->argv[j]);
29 + db_list_copies_records(ua->jcr,ua->db,limit,jobids,prtit,ua,llist);
30 } else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
31 || strcasecmp(ua->argk[i], NT_("days")) == 0) {
33 Index: src/dird/migrate.c
34 ===================================================================
35 --- src/dird/migrate.c (revision 8203)
36 +++ src/dird/migrate.c (working copy)
37 @@ -1158,13 +1158,17 @@
39 * If we terminated a copy normally:
40 * - copy any Log records to the new JobId
41 + * - set type="Job Copy" for the new job
43 if (jcr->get_JobType() == JT_COPY && jcr->JobStatus == JS_Terminated) {
44 /* Copy JobLog to new JobId */
45 Mmsg(query, "INSERT INTO Log (JobId, Time, LogText ) "
46 "SELECT %s, Time, LogText FROM Log WHERE JobId=%s",
47 - new_jobid, old_jobid);
48 + edit_uint64(mig_jcr->jr.JobId, ec7), old_jobid);
49 db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
50 + Mmsg(query, "UPDATE Job SET Type='%c' WHERE JobId=%s",
51 + (char)JT_JOB_COPY, ec7);
52 + db_sql_query(mig_jcr->db, query.c_str(), NULL, NULL);
55 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
56 Index: src/dird/ua_purge.c
57 ===================================================================
58 --- src/dird/ua_purge.c (revision 8203)
59 +++ src/dird/ua_purge.c (working copy)
64 + * Change the type of the next copy job to backup.
65 + * We need to upgrade the next copy of a normal job,
66 + * and also upgrade the next copy when the normal job
67 + * already have been purged.
69 + * JobId: 1 PriorJobId: 0 (original)
70 + * JobId: 2 PriorJobId: 1 (first copy)
71 + * JobId: 3 PriorJobId: 1 (second copy)
73 + * JobId: 2 PriorJobId: 1 (first copy, now regular backup)
74 + * JobId: 3 PriorJobId: 1 (second copy)
76 + * => Search through PriorJobId in jobid and
77 + * PriorJobId in PriorJobId (jobid)
79 +void upgrade_copies(UAContext *ua, char *jobs)
81 + POOL_MEM query(PM_MESSAGE);
84 + /* Do it in two times for mysql */
85 + Mmsg(query, "CREATE TEMPORARY TABLE cpy_tmp AS "
86 + "SELECT MIN(JobId) AS JobId FROM Job " /* Choose the oldest job */
88 + "AND ( PriorJobId IN (%s) "
91 + "SELECT PriorJobId "
93 + "WHERE JobId IN (%s) "
97 + "GROUP BY PriorJobId ", /* one result per copy */
98 + JT_JOB_COPY, jobs, jobs);
99 + db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
101 + /* Now upgrade first copy to Backup */
102 + Mmsg(query, "UPDATE Job SET Type='B' " /* JT_JOB_COPY => JT_BACKUP */
103 + "WHERE JobId IN ( SELECT JobId FROM cpy_tmp )");
105 + db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
107 + Mmsg(query, "DROP TABLE cpy_tmp");
108 + db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
111 + Dmsg1(00, "Upgrade copies Log sql=%s\n", query.c_str());
115 * Remove all records from catalog for a list of JobIds
117 void purge_jobs_from_catalog(UAContext *ua, char *jobs)
118 @@ -377,13 +428,15 @@
119 db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
120 Dmsg1(050, "Delete Log sql=%s\n", query.c_str());
122 + upgrade_copies(ua, jobs);
124 /* Now remove the Job record itself */
125 Mmsg(query, "DELETE FROM Job WHERE JobId IN (%s)", jobs);
126 db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
128 Dmsg1(050, "Delete Job sql=%s\n", query.c_str());
132 void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
133 {} /* ***FIXME*** implement */
135 Index: src/dird/ua_restore.c
136 ===================================================================
137 --- src/dird/ua_restore.c (revision 8203)
138 +++ src/dird/ua_restore.c (working copy)
140 "add_suffix", /* 17 */
141 "regexwhere", /* 18 */
142 "restoreclient", /* 19 */
147 @@ -1138,9 +1139,10 @@
151 + POOL_MEM other_filter(PM_MESSAGE);
152 + POOL_MEM temp_filter(PM_MESSAGE);
153 char fileset_name[MAX_NAME_LENGTH];
154 char ed1[50], ed2[50];
155 - char pool_select[MAX_NAME_LENGTH];
158 /* Create temp tables */
159 @@ -1196,23 +1198,32 @@
162 /* If Pool specified, add PoolId specification */
163 - pool_select[0] = 0;
166 memset(&pr, 0, sizeof(pr));
167 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
168 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
169 - bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
170 - edit_int64(pr.PoolId, ed1));
171 + Mmsg(other_filter, " AND Media.PoolId=%s ",
172 + edit_int64(pr.PoolId, ed1));
174 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
177 + /* include copies or not in job selection */
178 + if (find_arg(ua, NT_("copies")) > 0) {
179 + Mmsg(temp_filter, "%s AND Job.Type IN ('%c', '%c') ",
180 + other_filter.c_str(), (char)JT_BACKUP, (char)JT_JOB_COPY);
182 + Mmsg(temp_filter, "%s AND Job.Type = '%c' ", other_filter.c_str(),
185 + pm_strcpy(other_filter, temp_filter.c_str());
187 /* Find JobId of last Full backup for this client, fileset */
188 edit_int64(cr.ClientId, ed1);
189 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
191 + other_filter.c_str());
192 + Dmsg1(0, "sql=%s\n", rx->query);
193 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
194 ua->error_msg("%s\n", db_strerror(ua->db));
196 @@ -1238,12 +1249,13 @@
198 /* Now find most recent Differental Job after Full save, if any */
199 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
200 - edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
201 + edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
202 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
203 ua->warning_msg("%s\n", db_strerror(ua->db));
205 /* Now update JobTDate to lock onto Differental, if any */
207 + Dmsg1(0, "sql=%s\n", rx->query);
208 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
209 ua->warning_msg("%s\n", db_strerror(ua->db));
211 @@ -1254,7 +1266,8 @@
213 /* Now find all Incremental Jobs after Full/dif save */
214 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
215 - edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
216 + edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
217 + Dmsg1(0, "sql=%s\n", rx->query);
218 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
219 ua->warning_msg("%s\n", db_strerror(ua->db));
221 @@ -1267,6 +1280,8 @@
224 if (rx->JobIds[0] != 0) {
225 + /* Display a list of all copies */
226 + db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds, prtit, ua, HORZ_LIST);
227 /* Display a list of Jobs selected for this restore */
228 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
230 Index: src/dird/ua_cmds.c
231 ===================================================================
232 --- src/dird/ua_cmds.c (revision 8203)
233 +++ src/dird/ua_cmds.c (working copy)
235 { NT_("exit"), quit_cmd, _("exit = quit"), false},
236 { NT_("gui"), gui_cmd, _("gui [on|off] -- non-interactive gui mode"), false},
237 { NT_("help"), help_cmd, _("print this command"), false},
238 - { NT_("list"), list_cmd, _("list [pools | jobs | jobtotals | media <pool=pool-name> | files <jobid=nn>]; from catalog"), true},
239 + { NT_("list"), list_cmd, _("list [pools | jobs | jobtotals | media <pool=pool-name> | files <jobid=nn> | copies <jobid=nn>]; from catalog"), true},
240 { NT_("label"), label_cmd, _("label a tape"), false},
241 { NT_("llist"), llist_cmd, _("full or long list like list command"), true},
242 { NT_("messages"), messagescmd, _("messages"), false},
243 Index: src/cats/protos.h
244 ===================================================================
245 --- src/cats/protos.h (revision 8203)
246 +++ src/cats/protos.h (working copy)
248 void db_list_joblog_records(JCR *jcr, B_DB *mdb, JobId_t JobId, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
249 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);
250 void db_list_client_records(JCR *jcr, B_DB *mdb, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
251 +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);
254 bool db_update_job_start_record(JCR *jcr, B_DB *db, JOB_DBR *jr);
255 Index: src/cats/sql_cmds.c
256 ===================================================================
257 --- src/cats/sql_cmds.c (revision 8203)
258 +++ src/cats/sql_cmds.c (working copy)
260 "FROM Client,Job,JobMedia,Media,FileSet WHERE Client.ClientId=%s "
261 "AND Job.ClientId=%s "
262 "AND Job.StartTime<'%s' "
263 - "AND Level='F' AND JobStatus='T' AND Type='B' "
264 + "AND Level='F' AND JobStatus='T' "
265 "AND JobMedia.JobId=Job.JobId "
266 "AND Media.Enabled=1 "
267 "AND JobMedia.MediaId=Media.MediaId "
268 @@ -297,13 +297,14 @@
270 const char *uar_full =
271 "INSERT INTO temp SELECT Job.JobId,Job.JobTDate,"
272 - "Job.ClientId,Job.Level,Job.JobFiles,Job.JobBytes,"
273 - "StartTime,VolumeName,JobMedia.StartFile,VolSessionId,VolSessionTime "
274 - "FROM temp1,Job,JobMedia,Media WHERE temp1.JobId=Job.JobId "
275 - "AND Level='F' AND JobStatus='T' AND Type='B' "
276 - "AND Media.Enabled=1 "
277 - "AND JobMedia.JobId=Job.JobId "
278 - "AND JobMedia.MediaId=Media.MediaId";
279 + "Job.ClientId,Job.Level,Job.JobFiles,Job.JobBytes,"
280 + "StartTime,VolumeName,JobMedia.StartFile,VolSessionId,VolSessionTime "
281 + "FROM temp1,Job,JobMedia,Media "
282 + "WHERE temp1.JobId=Job.JobId "
283 + "AND Level='F' AND JobStatus='T' "
284 + "AND Media.Enabled=1 "
285 + "AND JobMedia.JobId=Job.JobId "
286 + "AND JobMedia.MediaId=Media.MediaId";
288 const char *uar_dif =
289 "INSERT INTO temp SELECT Job.JobId,Job.JobTDate,Job.ClientId,"
291 "AND JobMedia.JobId=Job.JobId "
292 "AND Media.Enabled=1 "
293 "AND JobMedia.MediaId=Media.MediaId "
294 - "AND Job.Level='D' AND JobStatus='T' AND Type='B' "
295 + "AND Job.Level='D' AND JobStatus='T' "
296 "AND Job.FileSetId=FileSet.FileSetId "
297 "AND FileSet.FileSet='%s' "
300 "AND Media.Enabled=1 "
301 "AND JobMedia.JobId=Job.JobId "
302 "AND JobMedia.MediaId=Media.MediaId "
303 - "AND Job.Level='I' AND JobStatus='T' AND Type='B' "
304 + "AND Job.Level='I' AND JobStatus='T' "
305 "AND Job.FileSetId=FileSet.FileSetId "
306 "AND FileSet.FileSet='%s' "
308 Index: src/cats/sql_list.c
309 ===================================================================
310 --- src/cats/sql_list.c (revision 8203)
311 +++ src/cats/sql_list.c (working copy)
316 +void db_list_copies_records(JCR *jcr, B_DB *mdb, uint32_t limit, char *JobIds,
317 + DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
319 + POOL_MEM str_limit(PM_MESSAGE);
320 + POOL_MEM str_jobids(PM_MESSAGE);
323 + Mmsg(str_limit, " LIMIT %d", limit);
326 + if (JobIds && JobIds[0]) {
327 + Mmsg(str_jobids, " AND (C.PriorJobId IN (%s) OR C.JobId IN (%s)) ",
333 + "SELECT DISTINCT C.PriorJobId AS JobId, C.Job, "
334 + "C.JobId AS CopyJobId, M.MediaType "
336 + "JOIN JobMedia USING (JobId) "
337 + "JOIN Media AS M USING (MediaId) "
338 + "WHERE C.Type = '%c' %s ORDER BY C.PriorJobId DESC %s",
339 + (char) JT_JOB_COPY, str_jobids.c_str(), str_limit.c_str());
341 + if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
345 + list_result(jcr, mdb, sendit, ctx, type);
347 + sql_free_result(mdb);
353 void db_list_joblog_records(JCR *jcr, B_DB *mdb, uint32_t JobId,
354 DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
357 ===================================================================
358 --- src/jcr.h (revision 8203)
359 +++ src/jcr.h (working copy)
361 #define JT_MIGRATED_JOB 'M' /* A previous backup job that was migrated */
362 #define JT_VERIFY 'V' /* Verify Job */
363 #define JT_RESTORE 'R' /* Restore Job */
364 -#define JT_CONSOLE 'c' /* console program */
365 +#define JT_CONSOLE 'U' /* console program */
366 #define JT_SYSTEM 'I' /* internal system "job" */
367 #define JT_ADMIN 'D' /* admin job */
368 #define JT_ARCHIVE 'A' /* Archive Job */
369 -#define JT_COPY 'C' /* Copy Job */
370 +#define JT_JOB_COPY 'C' /* Copy of a Job */
371 +#define JT_COPY 'c' /* Copy Job */
372 #define JT_MIGRATE 'g' /* Migration Job */
373 #define JT_SCAN 'S' /* Scan Job */
375 Index: src/lib/util.c
376 ===================================================================
377 --- src/lib/util.c (revision 8203)
378 +++ src/lib/util.c (working copy)
384 + str = _("Job Copy");
389 Index: src/lib/protos.h
390 ===================================================================
391 --- src/lib/protos.h (revision 8203)
392 +++ src/lib/protos.h (working copy)
394 bool size_to_uint64(char *str, int str_len, uint64_t *rtn_value);
395 char *edit_utime (utime_t val, char *buf, int buf_len);
396 bool is_a_number (const char *num);
397 +bool is_a_number_list (const char *n);
398 bool is_an_integer (const char *n);
399 bool is_name_valid (char *name, POOLMEM **msg);
401 Index: src/lib/edit.c
402 ===================================================================
403 --- src/lib/edit.c (revision 8203)
404 +++ src/lib/edit.c (working copy)
409 + * Check if specified string is a list of number or not
411 +bool is_a_number_list(const char *n)
413 + bool previous_digit = false;
414 + bool digit_seen = false;
416 + if (B_ISDIGIT(*n)) {
417 + previous_digit=true;
419 + } else if (*n == ',' && previous_digit) {
420 + previous_digit = false;
426 + return digit_seen && *n==0;
430 * Check if the specified string is an integer
432 bool is_an_integer(const char *n)
433 Index: patches/testing/copy_list_copies_cmd.patch
434 ===================================================================
435 --- patches/testing/copy_list_copies_cmd.patch (revision 8203)
436 +++ patches/testing/copy_list_copies_cmd.patch (working copy)
438 Index: src/dird/ua_output.c
439 ===================================================================
440 ---- src/dird/ua_output.c (revision 8163)
441 +--- src/dird/ua_output.c (revision 8203)
442 +++ src/dird/ua_output.c (working copy)
443 -@@ -454,6 +454,15 @@
445 + * list clients - list clients
446 + * list nextvol job=xx - list the next vol to be used by job
447 + * list nextvolume job=xx - same as above.
448 ++ * list copies jobid=x,y,z
452 +@@ -454,6 +455,19 @@
456 + } else if (strcasecmp(ua->argk[i], NT_("copies")) == 0) {
457 ++ char *jobids=NULL;
459 + for (j=i+1; j<ua->argc; j++) {
460 + if (strcasecmp(ua->argk[j], NT_("jobid")) == 0 && ua->argv[j]) {
461 -+ jr.JobId = str_to_int64(ua->argv[j]);
462 ++ if (is_a_number_list(ua->argv[j])) {
463 ++ jobids = ua->argv[j];
465 + } else if (strcasecmp(ua->argk[j], NT_("limit")) == 0 && ua->argv[j]) {
466 -+ jr.limit = atoi(ua->argv[j]);
467 ++ limit = atoi(ua->argv[j]);
470 -+ db_list_copies_records(ua->jcr, ua->db, &jr, prtit, ua, llist);
471 ++ db_list_copies_records(ua->jcr,ua->db,limit,jobids,prtit,ua,llist);
472 } else if (strcasecmp(ua->argk[i], NT_("limit")) == 0
473 || strcasecmp(ua->argk[i], NT_("days")) == 0) {
475 Index: src/dird/migrate.c
476 ===================================================================
477 ---- src/dird/migrate.c (revision 8179)
478 +--- src/dird/migrate.c (revision 8203)
479 +++ src/dird/migrate.c (working copy)
480 @@ -1158,13 +1158,17 @@
485 if (!db_get_job_record(jcr, jcr->db, &jcr->jr)) {
486 +Index: src/dird/ua_purge.c
487 +===================================================================
488 +--- src/dird/ua_purge.c (revision 8203)
489 ++++ src/dird/ua_purge.c (working copy)
490 +@@ -360,6 +360,57 @@
494 ++ * Change the type of the next copy job to backup.
495 ++ * We need to upgrade the next copy of a normal job,
496 ++ * and also upgrade the next copy when the normal job
497 ++ * already have been purged.
499 ++ * JobId: 1 PriorJobId: 0 (original)
500 ++ * JobId: 2 PriorJobId: 1 (first copy)
501 ++ * JobId: 3 PriorJobId: 1 (second copy)
503 ++ * JobId: 2 PriorJobId: 1 (first copy, now regular backup)
504 ++ * JobId: 3 PriorJobId: 1 (second copy)
506 ++ * => Search through PriorJobId in jobid and
507 ++ * PriorJobId in PriorJobId (jobid)
509 ++void upgrade_copies(UAContext *ua, char *jobs)
511 ++ POOL_MEM query(PM_MESSAGE);
514 ++ /* Do it in two times for mysql */
515 ++ Mmsg(query, "CREATE TEMPORARY TABLE cpy_tmp AS "
516 ++ "SELECT MIN(JobId) AS JobId FROM Job " /* Choose the oldest job */
517 ++ "WHERE Type='%c' "
518 ++ "AND ( PriorJobId IN (%s) "
520 ++ " PriorJobId IN ( "
521 ++ "SELECT PriorJobId "
523 ++ "WHERE JobId IN (%s) "
527 ++ "GROUP BY PriorJobId ", /* one result per copy */
528 ++ JT_JOB_COPY, jobs, jobs);
529 ++ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
531 ++ /* Now upgrade first copy to Backup */
532 ++ Mmsg(query, "UPDATE Job SET Type='B' " /* JT_JOB_COPY => JT_BACKUP */
533 ++ "WHERE JobId IN ( SELECT JobId FROM cpy_tmp )");
535 ++ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
537 ++ Mmsg(query, "DROP TABLE cpy_tmp");
538 ++ db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
540 ++ db_unlock(ua->db);
541 ++ Dmsg1(00, "Upgrade copies Log sql=%s\n", query.c_str());
545 + * Remove all records from catalog for a list of JobIds
547 + void purge_jobs_from_catalog(UAContext *ua, char *jobs)
548 +@@ -377,13 +428,15 @@
549 + db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
550 + Dmsg1(050, "Delete Log sql=%s\n", query.c_str());
552 ++ upgrade_copies(ua, jobs);
554 + /* Now remove the Job record itself */
555 + Mmsg(query, "DELETE FROM Job WHERE JobId IN (%s)", jobs);
556 + db_sql_query(ua->db, query.c_str(), NULL, (void *)NULL);
558 + Dmsg1(050, "Delete Job sql=%s\n", query.c_str());
562 + void purge_files_from_volume(UAContext *ua, MEDIA_DBR *mr )
563 + {} /* ***FIXME*** implement */
565 +Index: src/dird/ua_restore.c
566 +===================================================================
567 +--- src/dird/ua_restore.c (revision 8203)
568 ++++ src/dird/ua_restore.c (working copy)
570 + "add_suffix", /* 17 */
571 + "regexwhere", /* 18 */
572 + "restoreclient", /* 19 */
573 ++ "copies", /* 20 */
577 +@@ -1138,9 +1139,10 @@
581 ++ POOL_MEM other_filter(PM_MESSAGE);
582 ++ POOL_MEM temp_filter(PM_MESSAGE);
583 + char fileset_name[MAX_NAME_LENGTH];
584 + char ed1[50], ed2[50];
585 +- char pool_select[MAX_NAME_LENGTH];
588 + /* Create temp tables */
589 +@@ -1196,23 +1198,32 @@
592 + /* If Pool specified, add PoolId specification */
593 +- pool_select[0] = 0;
596 + memset(&pr, 0, sizeof(pr));
597 + bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
598 + if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
599 +- bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
600 +- edit_int64(pr.PoolId, ed1));
601 ++ Mmsg(other_filter, " AND Media.PoolId=%s ",
602 ++ edit_int64(pr.PoolId, ed1));
604 + ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
607 ++ /* include copies or not in job selection */
608 ++ if (find_arg(ua, NT_("copies")) > 0) {
609 ++ Mmsg(temp_filter, "%s AND Job.Type IN ('%c', '%c') ",
610 ++ other_filter.c_str(), (char)JT_BACKUP, (char)JT_JOB_COPY);
612 ++ Mmsg(temp_filter, "%s AND Job.Type = '%c' ", other_filter.c_str(),
615 ++ pm_strcpy(other_filter, temp_filter.c_str());
617 + /* Find JobId of last Full backup for this client, fileset */
618 + edit_int64(cr.ClientId, ed1);
619 + Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
621 ++ other_filter.c_str());
622 ++ Dmsg1(0, "sql=%s\n", rx->query);
623 + if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
624 + ua->error_msg("%s\n", db_strerror(ua->db));
626 +@@ -1238,12 +1249,13 @@
628 + /* Now find most recent Differental Job after Full save, if any */
629 + Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
630 +- edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
631 ++ edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
632 + if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
633 + ua->warning_msg("%s\n", db_strerror(ua->db));
635 + /* Now update JobTDate to lock onto Differental, if any */
637 ++ Dmsg1(0, "sql=%s\n", rx->query);
638 + if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
639 + ua->warning_msg("%s\n", db_strerror(ua->db));
641 +@@ -1254,7 +1266,8 @@
643 + /* Now find all Incremental Jobs after Full/dif save */
644 + Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
645 +- edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
646 ++ edit_int64(cr.ClientId, ed2), fsr.FileSet, other_filter.c_str());
647 ++ Dmsg1(0, "sql=%s\n", rx->query);
648 + if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
649 + ua->warning_msg("%s\n", db_strerror(ua->db));
651 +@@ -1267,6 +1280,8 @@
654 + if (rx->JobIds[0] != 0) {
655 ++ /* Display a list of all copies */
656 ++ db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds, prtit, ua, HORZ_LIST);
657 + /* Display a list of Jobs selected for this restore */
658 + db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
660 Index: src/dird/ua_cmds.c
661 ===================================================================
662 ---- src/dird/ua_cmds.c (revision 8163)
663 +--- src/dird/ua_cmds.c (revision 8203)
664 +++ src/dird/ua_cmds.c (working copy)
666 { NT_("exit"), quit_cmd, _("exit = quit"), false},
667 @@ -56,48 +242,101 @@
668 { NT_("messages"), messagescmd, _("messages"), false},
669 Index: src/cats/protos.h
670 ===================================================================
671 ---- src/cats/protos.h (revision 8163)
672 +--- src/cats/protos.h (revision 8203)
673 +++ src/cats/protos.h (working copy)
675 void db_list_joblog_records(JCR *jcr, B_DB *mdb, JobId_t JobId, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
676 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);
677 void db_list_client_records(JCR *jcr, B_DB *mdb, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
678 -+void db_list_copies_records(JCR *jcr, B_DB *mdb, JOB_DBR *jr, DB_LIST_HANDLER *sendit, void *ctx, e_list_type type);
679 ++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);
682 bool db_update_job_start_record(JCR *jcr, B_DB *db, JOB_DBR *jr);
683 +Index: src/cats/sql_cmds.c
684 +===================================================================
685 +--- src/cats/sql_cmds.c (revision 8203)
686 ++++ src/cats/sql_cmds.c (working copy)
688 + "FROM Client,Job,JobMedia,Media,FileSet WHERE Client.ClientId=%s "
689 + "AND Job.ClientId=%s "
690 + "AND Job.StartTime<'%s' "
691 +- "AND Level='F' AND JobStatus='T' AND Type='B' "
692 ++ "AND Level='F' AND JobStatus='T' "
693 + "AND JobMedia.JobId=Job.JobId "
694 + "AND Media.Enabled=1 "
695 + "AND JobMedia.MediaId=Media.MediaId "
696 +@@ -297,13 +297,14 @@
698 + const char *uar_full =
699 + "INSERT INTO temp SELECT Job.JobId,Job.JobTDate,"
700 +- "Job.ClientId,Job.Level,Job.JobFiles,Job.JobBytes,"
701 +- "StartTime,VolumeName,JobMedia.StartFile,VolSessionId,VolSessionTime "
702 +- "FROM temp1,Job,JobMedia,Media WHERE temp1.JobId=Job.JobId "
703 +- "AND Level='F' AND JobStatus='T' AND Type='B' "
704 +- "AND Media.Enabled=1 "
705 +- "AND JobMedia.JobId=Job.JobId "
706 +- "AND JobMedia.MediaId=Media.MediaId";
707 ++ "Job.ClientId,Job.Level,Job.JobFiles,Job.JobBytes,"
708 ++ "StartTime,VolumeName,JobMedia.StartFile,VolSessionId,VolSessionTime "
709 ++ "FROM temp1,Job,JobMedia,Media "
710 ++ "WHERE temp1.JobId=Job.JobId "
711 ++ "AND Level='F' AND JobStatus='T' "
712 ++ "AND Media.Enabled=1 "
713 ++ "AND JobMedia.JobId=Job.JobId "
714 ++ "AND JobMedia.MediaId=Media.MediaId";
716 + const char *uar_dif =
717 + "INSERT INTO temp SELECT Job.JobId,Job.JobTDate,Job.ClientId,"
719 + "AND JobMedia.JobId=Job.JobId "
720 + "AND Media.Enabled=1 "
721 + "AND JobMedia.MediaId=Media.MediaId "
722 +- "AND Job.Level='D' AND JobStatus='T' AND Type='B' "
723 ++ "AND Job.Level='D' AND JobStatus='T' "
724 + "AND Job.FileSetId=FileSet.FileSetId "
725 + "AND FileSet.FileSet='%s' "
728 + "AND Media.Enabled=1 "
729 + "AND JobMedia.JobId=Job.JobId "
730 + "AND JobMedia.MediaId=Media.MediaId "
731 +- "AND Job.Level='I' AND JobStatus='T' AND Type='B' "
732 ++ "AND Job.Level='I' AND JobStatus='T' "
733 + "AND Job.FileSetId=FileSet.FileSetId "
734 + "AND FileSet.FileSet='%s' "
736 Index: src/cats/sql_list.c
737 ===================================================================
738 ---- src/cats/sql_list.c (revision 8163)
739 +--- src/cats/sql_list.c (revision 8203)
740 +++ src/cats/sql_list.c (working copy)
745 -+void db_list_copies_records(JCR *jcr, B_DB *mdb, JOB_DBR *jr,
746 ++void db_list_copies_records(JCR *jcr, B_DB *mdb, uint32_t limit, char *JobIds,
747 + DB_LIST_HANDLER *sendit, void *ctx, e_list_type type)
750 -+ POOL_MEM limit(PM_MESSAGE);
751 -+ POOL_MEM jobids(PM_MESSAGE);
752 ++ POOL_MEM str_limit(PM_MESSAGE);
753 ++ POOL_MEM str_jobids(PM_MESSAGE);
755 -+ if (jr->limit > 0) {
756 -+ Mmsg(limit, " LIMIT %d", jr->limit);
758 ++ Mmsg(str_limit, " LIMIT %d", limit);
762 -+ Mmsg(jobids, " AND (C.PriorJobId = %s OR C.JobId = %s) ",
763 -+ edit_int64(jr->JobId, ed1),ed1);
764 ++ if (JobIds && JobIds[0]) {
765 ++ Mmsg(str_jobids, " AND (C.PriorJobId IN (%s) OR C.JobId IN (%s)) ",
771 -+ "SELECT C.PriorJobId AS JobId, C.Job, C.JobId AS CopyJobId, M.MediaType "
772 ++ "SELECT DISTINCT C.PriorJobId AS JobId, C.Job, "
773 ++ "C.JobId AS CopyJobId, M.MediaType "
775 + "JOIN JobMedia USING (JobId) "
776 + "JOIN Media AS M USING (MediaId) "
777 + "WHERE C.Type = '%c' %s ORDER BY C.PriorJobId DESC %s",
778 -+ (char) JT_JOB_COPY, jobids.c_str(), limit.c_str());
779 ++ (char) JT_JOB_COPY, str_jobids.c_str(), str_limit.c_str());
781 + if (!QUERY_DB(jcr, mdb, mdb->cmd)) {
786 ===================================================================
787 ---- src/jcr.h (revision 8163)
788 +--- src/jcr.h (revision 8203)
789 +++ src/jcr.h (working copy)
791 #define JT_MIGRATED_JOB 'M' /* A previous backup job that was migrated */
794 Index: src/lib/util.c
795 ===================================================================
796 ---- src/lib/util.c (revision 8163)
797 +--- src/lib/util.c (revision 8203)
798 +++ src/lib/util.c (working copy)
805 +Index: src/lib/protos.h
806 +===================================================================
807 +--- src/lib/protos.h (revision 8203)
808 ++++ src/lib/protos.h (working copy)
810 + bool size_to_uint64(char *str, int str_len, uint64_t *rtn_value);
811 + char *edit_utime (utime_t val, char *buf, int buf_len);
812 + bool is_a_number (const char *num);
813 ++bool is_a_number_list (const char *n);
814 + bool is_an_integer (const char *n);
815 + bool is_name_valid (char *name, POOLMEM **msg);
817 +Index: src/lib/edit.c
818 +===================================================================
819 +--- src/lib/edit.c (revision 8203)
820 ++++ src/lib/edit.c (working copy)
821 +@@ -407,6 +407,27 @@
825 ++ * Check if specified string is a list of number or not
827 ++bool is_a_number_list(const char *n)
829 ++ bool previous_digit = false;
830 ++ bool digit_seen = false;
832 ++ if (B_ISDIGIT(*n)) {
833 ++ previous_digit=true;
834 ++ digit_seen = true;
835 ++ } else if (*n == ',' && previous_digit) {
836 ++ previous_digit = false;
842 ++ return digit_seen && *n==0;
846 + * Check if the specified string is an integer
848 + bool is_an_integer(const char *n)
849 Index: patches/testing/fix_1190.patch
850 ===================================================================
851 --- patches/testing/fix_1190.patch (revision 8203)
852 +++ patches/testing/fix_1190.patch (working copy)
855 -// Dmsg3(dbglevel, "match_volblock: sblock=%u eblock=%u recblock=%u\n",
856 -// volblock->sblock, volblock->eblock, rec->Block);
857 -- if (volblock->sblock <= rec->Block && volblock->eblock >= rec->Block) {
858 + Dmsg3(dbglevel, "match_volblock: sblock=%u eblock=%u recblock=%u\n",
859 + volblock->sblock, volblock->eblock, rec->Block);
862 + * But, we are already decoding rec->Block-1Block records
864 + uint32_t max = volblock->eblock+DEFAULT_BLOCK_SIZE;
865 -+// if (volblock->sblock <= rec->Block && volblock->eblock >= rec->Block) {
866 -+ if (min <= rec->Block && max >= rec->Block) {
867 + if (volblock->sblock <= rec->Block && volblock->eblock >= rec->Block) {
868 ++// if (min <= rec->Block && max >= rec->Block) {
871 /* Once we get past last eblock, we are done */
875 * Check for Start or End of Session Record
877 +===================================================================
878 +--- block.c (révision 8116)
879 ++++ block.c (copie de travail)
880 +@@ -1116,11 +1116,12 @@
881 + dcr->EndBlock = dev->EndBlock;
882 + dcr->EndFile = dev->EndFile;
884 +- uint64_t addr = dev->file_addr + block->read_len - 1;
885 ++ uint32_t len = MIN(block->read_len, block->block_len);
886 ++ uint64_t addr = dev->file_addr + len - 1;
887 + dcr->EndBlock = (uint32_t)addr;
888 + dcr->EndFile = (uint32_t)(addr >> 32);
889 +- dev->block_num = dcr->EndBlock;
890 +- dev->file = dcr->EndFile;
891 ++ dev->block_num = dev->EndBlock = dcr->EndBlock;
892 ++ dev->file = dev->EndFile = dcr->EndFile;
894 + dcr->VolMediaId = dev->VolCatInfo.VolMediaId;
895 + dev->file_addr += block->read_len;
896 Index: patches/testing/find_smallest_volfile.patch
897 ===================================================================
898 --- patches/testing/find_smallest_volfile.patch (revision 8203)
899 +++ patches/testing/find_smallest_volfile.patch (working copy)
905 - rec->Block, bsr->volblock->sblock, bsr->volblock->eblock);
908 -- Dmsg3(dbglevel, "OK bsr Block=%u. bsr=%u,%u\n",
909 -- rec->Block, bsr->volblock->sblock, bsr->volblock->eblock);
911 - if (!match_sesstime(bsr, bsr->sesstime, rec, 1)) {
912 - Dmsg2(dbglevel, "Fail on sesstime. bsr=%u rec=%u\n",
914 - Dmsg3(dbglevel, "match on findex=%d. bsr=%d,%d\n",
915 - rec->FileIndex, bsr->FileIndex->findex, bsr->FileIndex->findex2);
917 -+ Dmsg3(dbglevel, "OK bsr Block=%u. bsr=%u,%u\n",
918 -+ rec->Block, bsr->volblock->sblock, bsr->volblock->eblock);
920 - if (!match_fileregex(bsr, rec, jcr)) {
921 - Dmsg1(dbglevel, "Fail on fileregex='%s'\n", bsr->fileregex);
923 -@@ -607,14 +619,7 @@
925 - static int match_volblock(BSR *bsr, BSR_VOLBLOCK *volblock, DEV_RECORD *rec, bool done)
928 -- * Currently block matching does not work correctly for disk
929 -- * files in all cases, so it is "turned off" by the following
930 -- * return statement.
936 - return 1; /* no specification matches all */
939 - if (rec->state & REC_ISTAPE) {
940 - return 1; /* All File records OK for this match */
942 --// Dmsg3(dbglevel, "match_volblock: sblock=%u eblock=%u recblock=%u\n",
943 --// volblock->sblock, volblock->eblock, rec->Block);
944 -+ Dmsg3(dbglevel, "match_volblock: sblock=%u eblock=%u recblock=%u\n",
945 -+ volblock->sblock, volblock->eblock, rec->Block);
947 - if (volblock->sblock <= rec->Block && volblock->eblock >= rec->Block) {
950 -Index: src/stored/read_record.c
951 -===================================================================
952 ---- src/stored/read_record.c (révision 8116)
953 -+++ src/stored/read_record.c (copie de travail)
955 - Dmsg2(100, "All done=(file:block) %u:%u\n", dev->file, dev->block_num);
957 - } else if (rec->match_stat == 0) { /* no match */
958 -- Dmsg4(100, "BSR no match: clear rem=%d FI=%d before set_eof pos %u:%u\n",
959 -- rec->remainder, rec->FileIndex, dev->file, dev->block_num);
960 -+ 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",
961 -+ rec->remainder, rec->FileIndex, rec->Block, dev->LastBlock, dev->EndBlock, dev->file, dev->block_num);
962 - rec->remainder = 0;
963 - rec->state &= ~REC_PARTIAL_RECORD;
964 - if (try_repositioning(jcr, rec, dcr)) {
967 - if (dev->file > bsr->volfile->sfile ||
968 - (dev->file == bsr->volfile->sfile && dev->block_num > bsr->volblock->sblock)) {
969 -+ Dmsg4(dbglvl, _("Reposition from (file:block) %u:%u to %u:%u\n"),
970 -+ dev->file, dev->block_num, bsr->volfile->sfile,
971 -+ bsr->volblock->sblock);