1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 Kern Sibbald
5
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
8
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
13
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
16
17 Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20 * Bacula Director -- User Agent Database restore Command
21 * Creates a bootstrap file for restoring files and
22 * starts the restore job.
23 *
24 * Tree handling routines split into ua_tree.c July MMIII.
25 * BSR (bootstrap record) handling routines split into
26 * bsr.c July MMIII
27 *
28 * Kern Sibbald, July MMII
29 */
30
31
32 #include "bacula.h"
33 #include "dird.h"
34
35 /* Imported functions */
36 extern void print_bsr(UAContext *ua, RBSR *bsr);
37
38
39 /* Forward referenced functions */
40 static int last_full_handler(void *ctx, int num_fields, char **row);
41 static int jobid_handler(void *ctx, int num_fields, char **row);
42 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
43 static int fileset_handler(void *ctx, int num_fields, char **row);
44 static void free_name_list(NAME_LIST *name_list);
45 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
46 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
47 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *fname);
48 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
49 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
50 char *date);
51 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
52 char *date);
53 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
54 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
55 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx, char * RestoreClient);
56 static bool get_date(UAContext *ua, char *date, int date_len);
57 static int restore_count_handler(void *ctx, int num_fields, char **row);
58 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx);
59
new_rx(RESTORE_CTX * rx)60 void new_rx(RESTORE_CTX *rx)
61 {
62 RBSR *bsr = NULL;
63 memset(rx, 0, sizeof(*rx));
64 rx->path = get_pool_memory(PM_FNAME);
65 rx->path[0] = 0;
66
67 rx->fname = get_pool_memory(PM_FNAME);
68 rx->fname[0] = 0;
69
70 rx->JobIds = get_pool_memory(PM_FNAME);
71 rx->JobIds[0] = 0;
72
73 rx->component_fname = get_pool_memory(PM_FNAME);
74 rx->component_fname[0] = 0;
75
76 rx->BaseJobIds = get_pool_memory(PM_FNAME);
77 rx->BaseJobIds[0] = 0;
78
79 rx->query = get_pool_memory(PM_FNAME);
80 rx->query[0] = 0;
81
82 rx->bsr_list = New(rblist(bsr, &bsr->link));
83 rx->hardlinks_in_mem = true;
84 }
85
86
87 /*
88 * Restore files
89 *
90 */
restore_cmd(UAContext * ua,const char * cmd)91 int restore_cmd(UAContext *ua, const char *cmd)
92 {
93 RESTORE_CTX rx; /* restore context */
94 POOL_MEM buf;
95 JOB *job;
96 int i;
97 JCR *jcr = ua->jcr;
98 char *escaped_bsr_name = NULL;
99 char *escaped_where_name = NULL;
100 char *strip_prefix, *add_prefix, *add_suffix, *regexp;
101 strip_prefix = add_prefix = add_suffix = regexp = NULL;
102
103 new_rx(&rx); /* Initialize RESTORE_CTX */
104
105 if (!open_new_client_db(ua)) {
106 goto bail_out;
107 }
108
109 for (i = 0; i < ua->argc ; i++) {
110 if (strcasecmp(ua->argk[i], "fdcalled") == 0) {
111 rx.fdcalled = true;
112
113 } else if (strcasecmp(ua->argk[i], "noautoparent") == 0) {
114 rx.no_auto_parent = true;
115 }
116 if (!ua->argv[i]) {
117 continue; /* skip if no value given */
118 }
119 if (strcasecmp(ua->argk[i], "comment") == 0) {
120 rx.comment = ua->argv[i];
121 if (!is_comment_legal(ua, rx.comment)) {
122 goto bail_out;
123 }
124
125 } else if (strcasecmp(ua->argk[i], "where") == 0) {
126 rx.where = ua->argv[i];
127
128 } else if (strcasecmp(ua->argk[i], "when") == 0) {
129 rx.when = ua->argv[i];
130
131 } else if (strcasecmp(ua->argk[i], "replace") == 0) {
132 rx.replace = ua->argv[i];
133
134 } else if (strcasecmp(ua->argk[i], "strip_prefix") == 0) {
135 strip_prefix = ua->argv[i];
136
137 } else if (strcasecmp(ua->argk[i], "add_prefix") == 0) {
138 add_prefix = ua->argv[i];
139
140 } else if (strcasecmp(ua->argk[i], "add_suffix") == 0) {
141 add_suffix = ua->argv[i];
142
143 } else if (strcasecmp(ua->argk[i], "regexwhere") == 0) {
144 rx.RegexWhere = ua->argv[i];
145
146 } else if (strcasecmp(ua->argk[i], "optimizespeed") == 0) {
147 if (strcasecmp(ua->argv[i], "0") || strcasecmp(ua->argv[i], "no") ||
148 strcasecmp(ua->argv[i], "false")) {
149 rx.hardlinks_in_mem = false;
150 }
151 }
152 }
153
154 if (strip_prefix || add_suffix || add_prefix) {
155 int len = bregexp_get_build_where_size(strip_prefix, add_prefix, add_suffix);
156 regexp = (char *)bmalloc(len * sizeof(char));
157
158 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
159 rx.RegexWhere = regexp;
160 }
161
162 /* TODO: add acl for regexwhere ? */
163
164 if (rx.RegexWhere) {
165 if (!acl_access_ok(ua, Where_ACL, rx.RegexWhere)) {
166 ua->error_msg(_("\"RegexWhere\" specification not authorized.\n"));
167 goto bail_out;
168 }
169 }
170
171 if (rx.where) {
172 if (!acl_access_ok(ua, Where_ACL, rx.where)) {
173 ua->error_msg(_("\"where\" specification not authorized.\n"));
174 goto bail_out;
175 }
176 }
177
178 /* Ensure there is at least one Restore Job */
179 LockRes();
180 foreach_res(job, R_JOB) {
181 if (job->JobType == JT_RESTORE) {
182 if (!rx.restore_job) {
183 rx.restore_job = job;
184 }
185 rx.restore_jobs++;
186 }
187 }
188 UnlockRes();
189 if (!rx.restore_jobs) {
190 ua->error_msg(_(
191 "No Restore Job Resource found in bacula-dir.conf.\n"
192 "You must create at least one before running this command.\n"));
193 goto bail_out;
194 }
195
196 /*
197 * Request user to select JobIds or files by various different methods
198 * last 20 jobs, where File saved, most recent backup, ...
199 * In the end, a list of files are pumped into
200 * add_findex()
201 */
202 switch (user_select_jobids_or_files(ua, &rx)) {
203 case 0: /* error */
204 goto bail_out;
205 case 1: /* selected by jobid */
206 get_and_display_basejobs(ua, &rx);
207 if (!build_directory_tree(ua, &rx)) {
208 ua->send_msg(_("Restore not done.\n"));
209 goto bail_out;
210 }
211 break;
212 case 2: /* selected by filename, no tree needed */
213 break;
214 }
215
216 if (rx.bsr_list->size() > 0) {
217 char ed1[50];
218 if (!complete_bsr(ua, rx.bsr_list)) { /* find Vol, SessId, SessTime from JobIds */
219 ua->error_msg(_("Unable to construct a valid BSR. Cannot continue.\n"));
220 goto bail_out;
221 }
222 if (!(rx.selected_files = write_bsr_file(ua, rx))) {
223 ua->warning_msg(_("No files selected to be restored.\n"));
224 goto bail_out;
225 }
226
227 ua->send_msg(_("Bootstrap records written to %s\n"), ua->jcr->RestoreBootstrap);
228 display_bsr_info(ua, rx); /* display vols needed, etc */
229
230 if (rx.selected_files==1) {
231 ua->info_msg(_("\n1 file selected to be restored.\n\n"));
232 } else {
233 ua->info_msg(_("\n%s files selected to be restored.\n\n"),
234 edit_uint64_with_commas(rx.selected_files, ed1));
235 }
236 } else {
237 ua->warning_msg(_("No files selected to be restored.\n"));
238 goto bail_out;
239 }
240
241 if (rx.restore_jobs == 1) {
242 job = rx.restore_job;
243 } else {
244 job = get_restore_job(ua);
245 }
246 if (!job) {
247 goto bail_out;
248 }
249
250 get_client_name(ua, &rx);
251 if (!rx.ClientName[0]) {
252 ua->error_msg(_("No Client resource found!\n"));
253 goto bail_out;
254 }
255 get_restore_client_name(ua, rx, job->RestoreClient);
256
257 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
258
259 Mmsg(ua->cmd,
260 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
261 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
262 job->name(), rx.ClientName, rx.RestoreClientName,
263 rx.store?rx.store->name():"",
264 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
265 rx.selected_files, ua->catalog->name());
266
267 /* Build run command */
268 pm_strcpy(buf, "");
269 if (rx.RestoreMediaType[0]) {
270 Mmsg(buf, " mediatype=\"%s\"", rx.RestoreMediaType);
271 pm_strcat(ua->cmd, buf);
272 pm_strcpy(buf, "");
273 }
274 if (rx.RegexWhere) {
275 escaped_where_name = escape_filename(rx.RegexWhere);
276 Mmsg(buf, " regexwhere=\"%s\"",
277 escaped_where_name ? escaped_where_name : rx.RegexWhere);
278
279 } else if (rx.where) {
280 escaped_where_name = escape_filename(rx.where);
281 Mmsg(buf," where=\"%s\"",
282 escaped_where_name ? escaped_where_name : rx.where);
283 }
284 pm_strcat(ua->cmd, buf);
285
286 if (rx.replace) {
287 Mmsg(buf, " replace=%s", rx.replace);
288 pm_strcat(ua->cmd, buf);
289 }
290
291 if (rx.fdcalled) {
292 pm_strcat(ua->cmd, " fdcalled=yes");
293 }
294
295 if (rx.when) {
296 Mmsg(buf, " when=\"%s\"", rx.when);
297 pm_strcat(ua->cmd, buf);
298 }
299
300 if (rx.comment) {
301 Mmsg(buf, " comment=\"%s\"", rx.comment);
302 pm_strcat(ua->cmd, buf);
303 }
304
305 if (escaped_bsr_name != NULL) {
306 bfree(escaped_bsr_name);
307 }
308
309 if (escaped_where_name != NULL) {
310 bfree(escaped_where_name);
311 }
312
313 if (regexp) {
314 bfree(regexp);
315 }
316
317 if (find_arg(ua, NT_("yes")) > 0) {
318 pm_strcat(ua->cmd, " yes"); /* pass it on to the run command */
319 }
320 Dmsg1(200, "Submitting: %s\n", ua->cmd);
321 /*
322 * Transfer jobids, component stuff to jcr to
323 * pass to run_cmd(). Note, these are fields and
324 * other things that are not passed on the command
325 * line.
326 */
327 /* ***FIXME*** pass jobids on command line */
328 if (jcr->JobIds) {
329 free_pool_memory(jcr->JobIds);
330 }
331 jcr->JobIds = rx.JobIds;
332 rx.JobIds = NULL;
333 jcr->component_fname = rx.component_fname;
334 rx.component_fname = NULL;
335 jcr->component_fd = rx.component_fd;
336 rx.component_fd = NULL;
337 parse_ua_args(ua);
338 run_cmd(ua, ua->cmd);
339 free_rx(&rx);
340 garbage_collect_memory(); /* release unused memory */
341 return 1;
342
343 bail_out:
344 if (escaped_bsr_name != NULL) {
345 bfree(escaped_bsr_name);
346 }
347
348 if (escaped_where_name != NULL) {
349 bfree(escaped_where_name);
350 }
351
352 if (regexp) {
353 bfree(regexp);
354 }
355
356 /* Free the plugin config if needed, we don't want to re-use
357 * this part of the next try
358 */
359 free_plugin_config_items(jcr->plugin_config);
360 jcr->plugin_config = NULL;
361
362 free_rx(&rx);
363 garbage_collect_memory(); /* release unused memory */
364 return 0;
365
366 }
367
368 /*
369 * Fill the rx->BaseJobIds and display the list
370 */
get_and_display_basejobs(UAContext * ua,RESTORE_CTX * rx)371 static void get_and_display_basejobs(UAContext *ua, RESTORE_CTX *rx)
372 {
373 db_list_ctx jobids;
374
375 if (!db_get_used_base_jobids(ua->jcr, ua->db, rx->JobIds, &jobids)) {
376 ua->warning_msg("%s", db_strerror(ua->db));
377 }
378
379 if (jobids.count) {
380 POOL_MEM q;
381 Mmsg(q, uar_print_jobs, jobids.list);
382 ua->send_msg(_("The restore will use the following job(s) as Base\n"));
383 db_list_sql_query(ua->jcr, ua->db, q.c_str(), prtit, ua, 1, HORZ_LIST);
384 }
385 pm_strcpy(rx->BaseJobIds, jobids.list);
386 }
387
free_rx(RESTORE_CTX * rx)388 void free_rx(RESTORE_CTX *rx)
389 {
390 free_bsr(rx->bsr_list);
391 rx->bsr_list = NULL;
392 free_and_null_pool_memory(rx->JobIds);
393 free_and_null_pool_memory(rx->BaseJobIds);
394 free_and_null_pool_memory(rx->fname);
395 free_and_null_pool_memory(rx->path);
396 free_and_null_pool_memory(rx->query);
397 if (rx->fileregex) {
398 free(rx->fileregex);
399 rx->fileregex = NULL;
400 }
401 if (rx->component_fd) {
402 fclose(rx->component_fd);
403 rx->component_fd = NULL;
404 }
405 if (rx->component_fname) {
406 unlink(rx->component_fname);
407 }
408 free_and_null_pool_memory(rx->component_fname);
409 free_name_list(&rx->name_list);
410 }
411
has_value(UAContext * ua,int i)412 static bool has_value(UAContext *ua, int i)
413 {
414 if (!ua->argv[i]) {
415 ua->error_msg(_("Missing value for keyword: %s\n"), ua->argk[i]);
416 return false;
417 }
418 return true;
419 }
420
421 /*
422 * This gets the client name from which the backup was made
423 */
get_client_name(UAContext * ua,RESTORE_CTX * rx)424 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
425 {
426 /* If no client name specified yet, get it now */
427 if (!rx->ClientName[0]) {
428 CLIENT_DBR cr;
429 /* try command line argument */
430 int i = find_arg_with_value(ua, NT_("client"));
431 if (i < 0) {
432 i = find_arg_with_value(ua, NT_("backupclient"));
433 }
434 if (i >= 0) {
435 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
436 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
437 return 0;
438 }
439 bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
440 return 1;
441 }
442 memset(&cr, 0, sizeof(cr));
443 /* We want the name of the client where the backup was made */
444 if (!get_client_dbr(ua, &cr, JT_BACKUP_RESTORE)) {
445 return 0;
446 }
447 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
448 }
449 return 1;
450 }
451
452 /*
453 * This is where we pick up a client name to restore to.
454 */
get_restore_client_name(UAContext * ua,RESTORE_CTX & rx,char * RestoreClient)455 static int get_restore_client_name(UAContext *ua, RESTORE_CTX &rx, char * RestoreClient)
456 {
457 /* Start with same name as backup client or set in RestoreClient */
458 if (!RestoreClient){
459 bstrncpy(rx.RestoreClientName, rx.ClientName, sizeof(rx.RestoreClientName));
460 } else {
461 bstrncpy(rx.RestoreClientName, RestoreClient, sizeof(rx.RestoreClientName));
462 }
463
464 /* try command line argument */
465 int i = find_arg_with_value(ua, NT_("restoreclient"));
466 if (i >= 0) {
467 if (!is_name_valid(ua->argv[i], &ua->errmsg)) {
468 ua->error_msg("%s argument: %s", ua->argk[i], ua->errmsg);
469 return 0;
470 }
471 bstrncpy(rx.RestoreClientName, ua->argv[i], sizeof(rx.RestoreClientName));
472 return 1;
473 }
474 return 1;
475 }
476
477
478
479 /*
480 * The first step in the restore process is for the user to
481 * select a list of JobIds from which he will subsequently
482 * select which files are to be restored.
483 *
484 * Returns: 2 if filename list made
485 * 1 if jobid list made
486 * 0 on error
487 */
user_select_jobids_or_files(UAContext * ua,RESTORE_CTX * rx)488 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
489 {
490 char *p;
491 char date[MAX_TIME_LENGTH];
492 bool have_date = false;
493 /* Include current second if using current time */
494 utime_t now = time(NULL) + 1;
495 JobId_t JobId;
496 JOB_DBR jr = { (JobId_t)-1 };
497 bool done = false;
498 int i, j;
499 const char *list[] = {
500 _("List last 20 Jobs run"),
501 _("List Jobs where a given File is saved"),
502 _("Enter list of comma separated JobIds to select"),
503 _("Enter SQL list command"),
504 _("Select the most recent backup for a client"),
505 _("Select backup for a client before a specified time"),
506 _("Enter a list of files to restore"),
507 _("Enter a list of files to restore before a specified time"),
508 _("Find the JobIds of the most recent backup for a client"),
509 _("Find the JobIds for a backup for a client before a specified time"),
510 _("Enter a list of directories to restore for found JobIds"),
511 _("Select full restore to a specified Job date"),
512 _("Cancel"),
513 NULL };
514
515 const char *kw[] = {
516 /* These keywords are handled in a for loop */
517 "jobid", /* 0 */
518 "current", /* 1 */
519 "before", /* 2 */
520 "file", /* 3 */
521 "directory", /* 4 */
522 "select", /* 5 */
523 "pool", /* 6 */
524 "all", /* 7 */
525
526 /* The keyword below are handled by individual arg lookups */
527 "client", /* 8 */
528 "storage", /* 9 */
529 "fileset", /* 10 */
530 "where", /* 11 */
531 "yes", /* 12 */
532 "bootstrap", /* 13 */
533 "done", /* 14 */
534 "strip_prefix", /* 15 */
535 "add_prefix", /* 16 */
536 "add_suffix", /* 17 */
537 "regexwhere", /* 18 */
538 "restoreclient", /* 19 */
539 "copies", /* 20 */
540 "comment", /* 21 */
541 "restorejob", /* 22 */
542 "replace", /* 23 */
543 "xxxxxxxxx", /* 24 */
544 "fdcalled", /* 25 */
545 "when", /* 26 */
546 "noautoparent", /* 27 */
547 NULL
548 };
549
550 rx->JobIds[0] = 0;
551
552 for (i=1; i<ua->argc; i++) { /* loop through arguments */
553 bool found_kw = false;
554 for (j=0; kw[j]; j++) { /* loop through keywords */
555 if (strcasecmp(kw[j], ua->argk[i]) == 0) {
556 found_kw = true;
557 break;
558 }
559 }
560
561 if (!found_kw) {
562 ua->error_msg(_("Unknown keyword: %s\n"), ua->argk[i]);
563 return 0;
564 }
565 /* Found keyword in kw[] list, process it */
566 switch (j) {
567 case 0: /* jobid */
568 if (!has_value(ua, i)) {
569 return 0;
570 }
571 if (*rx->JobIds != 0) {
572 pm_strcat(rx->JobIds, ",");
573 }
574 pm_strcat(rx->JobIds, ua->argv[i]);
575 done = true;
576 break;
577 case 1: /* current */
578 /*
579 * Note, we add one second here just to include any job
580 * that may have finished within the current second,
581 * which happens a lot in scripting small jobs.
582 */
583 bstrutime(date, sizeof(date), now);
584 have_date = true;
585 break;
586 case 2: /* before */
587 if (have_date || !has_value(ua, i)) {
588 return 0;
589 }
590 if (str_to_utime(ua->argv[i]) == 0) {
591 ua->error_msg(_("Improper date format: %s\n"), ua->argv[i]);
592 return 0;
593 }
594 bstrncpy(date, ua->argv[i], sizeof(date));
595 have_date = true;
596 break;
597 case 3: /* file */
598 case 4: /* dir */
599 if (!has_value(ua, i)) {
600 return 0;
601 }
602 if (!have_date) {
603 bstrutime(date, sizeof(date), now);
604 }
605 if (!get_client_name(ua, rx)) {
606 return 0;
607 }
608 pm_strcpy(ua->cmd, ua->argv[i]);
609 insert_one_file_or_dir(ua, rx, date, j==4);
610 return 2;
611 case 5: /* select */
612 if (!have_date) {
613 bstrutime(date, sizeof(date), now);
614 }
615 if (!select_backups_before_date(ua, rx, date)) {
616 return 0;
617 }
618 done = true;
619 break;
620 case 6: /* pool specified */
621 if (!has_value(ua, i)) {
622 return 0;
623 }
624 rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
625 if (!rx->pool) {
626 ua->error_msg(_("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
627 return 0;
628 }
629 if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
630 rx->pool = NULL;
631 ua->error_msg(_("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
632 return 0;
633 }
634 break;
635 case 7: /* all specified */
636 rx->all = true;
637 break;
638 /*
639 * All keywords 7 or greater are ignored or handled by a select prompt
640 */
641 default:
642 break;
643 }
644 }
645
646 if (!done) {
647 ua->send_msg(_("\nFirst you select one or more JobIds that contain files\n"
648 "to be restored. You will be presented several methods\n"
649 "of specifying the JobIds. Then you will be allowed to\n"
650 "select which files from those JobIds are to be restored.\n\n"));
651 }
652
653 /* If choice not already made above, prompt */
654 for ( ; !done; ) {
655 char *fname;
656 int len;
657 bool gui_save;
658 db_list_ctx jobids;
659
660 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
661 for (int i=0; list[i]; i++) {
662 add_prompt(ua, list[i]);
663 }
664 done = true;
665 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
666 case -1: /* error or cancel */
667 return 0;
668 case 0: /* list last 20 Jobs run */
669 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
670 ua->error_msg(_("SQL query not authorized.\n"));
671 return 0;
672 }
673 gui_save = ua->jcr->gui;
674 ua->jcr->gui = true;
675 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
676 ua->jcr->gui = gui_save;
677 done = false;
678 break;
679 case 1: /* list where a file is saved */
680 if (!get_client_name(ua, rx)) {
681 return 0;
682 }
683 if (!get_cmd(ua, _("Enter Filename (no path):"))) {
684 return 0;
685 }
686 len = strlen(ua->cmd);
687 fname = (char *)malloc(len * 2 + 1);
688 db_escape_string(ua->jcr, ua->db, fname, ua->cmd, len);
689 Mmsg(rx->query, uar_file[db_get_type_index(ua->db)], rx->ClientName, fname);
690 free(fname);
691 gui_save = ua->jcr->gui;
692 ua->jcr->gui = true;
693 db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
694 ua->jcr->gui = gui_save;
695 done = false;
696 break;
697 case 2: /* enter a list of JobIds */
698 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
699 return 0;
700 }
701 pm_strcpy(rx->JobIds, ua->cmd);
702 break;
703 case 3: /* Enter an SQL list command */
704 if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
705 ua->error_msg(_("SQL query not authorized.\n"));
706 return 0;
707 }
708 if (!get_cmd(ua, _("Enter SQL list command: "))) {
709 return 0;
710 }
711 gui_save = ua->jcr->gui;
712 ua->jcr->gui = true;
713 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
714 ua->jcr->gui = gui_save;
715 done = false;
716 break;
717 case 4: /* Select the most recent backups */
718 if (!have_date) {
719 bstrutime(date, sizeof(date), now);
720 }
721 if (!select_backups_before_date(ua, rx, date)) {
722 return 0;
723 }
724 break;
725 case 5: /* select backup at specified time */
726 if (!have_date) {
727 if (!get_date(ua, date, sizeof(date))) {
728 return 0;
729 }
730 }
731 if (!select_backups_before_date(ua, rx, date)) {
732 return 0;
733 }
734 break;
735 case 6: /* Enter files */
736 if (!have_date) {
737 bstrutime(date, sizeof(date), now);
738 }
739 if (!get_client_name(ua, rx)) {
740 return 0;
741 }
742 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
743 "containing a list of file names with paths, and terminate\n"
744 "them with a blank line.\n"));
745 for ( ;; ) {
746 if (!get_cmd(ua, _("Enter full filename: "))) {
747 return 0;
748 }
749 len = strlen(ua->cmd);
750 if (len == 0) {
751 break;
752 }
753 insert_one_file_or_dir(ua, rx, date, false);
754 }
755 return 2;
756 case 7: /* enter files backed up before specified time */
757 if (!have_date) {
758 if (!get_date(ua, date, sizeof(date))) {
759 return 0;
760 }
761 }
762 if (!get_client_name(ua, rx)) {
763 return 0;
764 }
765 ua->send_msg(_("Enter file names with paths, or < to enter a filename\n"
766 "containing a list of file names with paths, and terminate\n"
767 "them with a blank line.\n"));
768 for ( ;; ) {
769 if (!get_cmd(ua, _("Enter full filename: "))) {
770 return 0;
771 }
772 len = strlen(ua->cmd);
773 if (len == 0) {
774 break;
775 }
776 insert_one_file_or_dir(ua, rx, date, false);
777 }
778 return 2;
779
780 case 8: /* Find JobIds for current backup */
781 if (!have_date) {
782 bstrutime(date, sizeof(date), now);
783 }
784 if (!select_backups_before_date(ua, rx, date)) {
785 return 0;
786 }
787 done = false;
788 break;
789
790 case 9: /* Find JobIds for give date */
791 if (!have_date) {
792 if (!get_date(ua, date, sizeof(date))) {
793 return 0;
794 }
795 }
796 if (!select_backups_before_date(ua, rx, date)) {
797 return 0;
798 }
799 done = false;
800 break;
801
802 case 10: /* Enter directories */
803 if (*rx->JobIds != 0) {
804 ua->send_msg(_("You have already selected the following JobIds: %s\n"),
805 rx->JobIds);
806 } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
807 if (*rx->JobIds != 0 && *ua->cmd) {
808 pm_strcat(rx->JobIds, ",");
809 }
810 pm_strcat(rx->JobIds, ua->cmd);
811 }
812 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
813 *rx->JobIds = 0;
814 return 0; /* nothing entered, return */
815 }
816 if (!have_date) {
817 bstrutime(date, sizeof(date), now);
818 }
819 if (!get_client_name(ua, rx)) {
820 return 0;
821 }
822 ua->send_msg(_("Enter full directory names or start the name\n"
823 "with a < to indicate it is a filename containing a list\n"
824 "of directories and terminate them with a blank line.\n"));
825 for ( ;; ) {
826 if (!get_cmd(ua, _("Enter directory name: "))) {
827 return 0;
828 }
829 len = strlen(ua->cmd);
830 if (len == 0) {
831 break;
832 }
833 /* Add trailing slash to end of directory names */
834 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len-1])) {
835 strcat(ua->cmd, "/");
836 }
837 insert_one_file_or_dir(ua, rx, date, true);
838 }
839 return 2;
840
841 case 11: /* Choose a jobid and select jobs */
842 if (!get_cmd(ua, _("Enter JobId to get the state to restore: ")) ||
843 !is_an_integer(ua->cmd))
844 {
845 return 0;
846 }
847
848 memset(&jr, 0, sizeof(JOB_DBR));
849 jr.JobId = str_to_int64(ua->cmd);
850 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
851 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
852 ua->cmd, db_strerror(ua->db));
853 return 0;
854 }
855 ua->send_msg(_("Selecting jobs to build the Full state at %s\n"),
856 jr.cStartTime);
857 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
858 if (!db_get_accurate_jobids(ua->jcr, ua->db, &jr, &jobids)) {
859 return 0;
860 }
861 pm_strcpy(rx->JobIds, jobids.list);
862 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
863 break;
864 case 12: /* Cancel or quit */
865 return 0;
866 }
867 }
868
869 memset(&jr, 0, sizeof(JOB_DBR));
870 POOLMEM *JobIds = get_pool_memory(PM_FNAME);
871 *JobIds = 0;
872 rx->TotalFiles = 0;
873 /*
874 * Find total number of files to be restored, and filter the JobId
875 * list to contain only ones permitted by the ACL conditions.
876 */
877 for (p=rx->JobIds; ; ) {
878 char ed1[50];
879 int stat = get_next_jobid_from_list(&p, &JobId);
880 if (stat < 0) {
881 ua->error_msg(_("Invalid JobId in list.\n"));
882 free_pool_memory(JobIds);
883 return 0;
884 }
885 if (stat == 0) {
886 break;
887 }
888 if (jr.JobId == JobId) {
889 continue; /* duplicate of last JobId */
890 }
891 memset(&jr, 0, sizeof(JOB_DBR));
892 jr.JobId = JobId;
893 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
894 ua->error_msg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
895 edit_int64(JobId, ed1), db_strerror(ua->db));
896 free_pool_memory(JobIds);
897 return 0;
898 }
899 if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
900 ua->error_msg(_("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
901 edit_int64(JobId, ed1), jr.Name);
902 continue;
903 }
904 if (*JobIds != 0) {
905 pm_strcat(JobIds, ",");
906 }
907 pm_strcat(JobIds, edit_int64(JobId, ed1));
908 rx->TotalFiles += jr.JobFiles;
909 }
910 pm_strcpy(rx->JobIds, JobIds); /* Set ACL filtered list */
911 free_pool_memory(JobIds);
912 if (*rx->JobIds == 0) {
913 ua->warning_msg(_("No Jobs selected.\n"));
914 return 0;
915 }
916
917 if (strchr(rx->JobIds,',')) {
918 ua->info_msg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
919 } else {
920 ua->info_msg(_("You have selected the following JobId: %s\n"), rx->JobIds);
921 }
922 return 1;
923 }
924
925 /*
926 * Get date from user
927 */
get_date(UAContext * ua,char * date,int date_len)928 static bool get_date(UAContext *ua, char *date, int date_len)
929 {
930 ua->send_msg(_("The restored files will the most current backup\n"
931 "BEFORE the date you specify below.\n\n"));
932 for ( ;; ) {
933 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
934 return false;
935 }
936 if (str_to_utime(ua->cmd) != 0) {
937 break;
938 }
939 ua->error_msg(_("Improper date format.\n"));
940 }
941 bstrncpy(date, ua->cmd, date_len);
942 return true;
943 }
944
945 /*
946 * Insert a single file, or read a list of files from a file
947 */
insert_one_file_or_dir(UAContext * ua,RESTORE_CTX * rx,char * date,bool dir)948 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
949 {
950 FILE *ffd;
951 char file[5000];
952 char *p = ua->cmd;
953 int line = 0;
954
955 switch (*p) {
956 case '<':
957 p++;
958 if ((ffd = bfopen(p, "rb")) == NULL) {
959 berrno be;
960 ua->error_msg(_("Cannot open file %s: ERR=%s\n"),
961 p, be.bstrerror());
962 break;
963 }
964 while (fgets(file, sizeof(file), ffd)) {
965 line++;
966 if (dir) {
967 if (!insert_dir_into_findex_list(ua, rx, file, date)) {
968 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
969 }
970 } else {
971 if (!insert_file_into_findex_list(ua, rx, file, date)) {
972 ua->error_msg(_("Error occurred on line %d of file \"%s\"\n"), line, p);
973 }
974 }
975 }
976 fclose(ffd);
977 break;
978 case '?':
979 p++;
980 insert_table_into_findex_list(ua, rx, p);
981 break;
982 default:
983 if (dir) {
984 insert_dir_into_findex_list(ua, rx, ua->cmd, date);
985 } else {
986 insert_file_into_findex_list(ua, rx, ua->cmd, date);
987 }
988 break;
989 }
990 }
991
992 /*
993 * For a given file (path+filename), split into path and file, then
994 * lookup the most recent backup in the catalog to get the JobId
995 * and FileIndex, then insert them into the findex list.
996 */
insert_file_into_findex_list(UAContext * ua,RESTORE_CTX * rx,char * file,char * date)997 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
998 char *date)
999 {
1000 strip_trailing_newline(file);
1001 split_path_and_filename(ua, rx, file);
1002 if (*rx->JobIds == 0) {
1003 Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname,
1004 rx->ClientName);
1005 } else {
1006 Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
1007 rx->path, rx->fname, rx->ClientName);
1008 /*
1009 * Note: we have just edited the JobIds into the query, so
1010 * we need to clear JobIds, or they will be added
1011 * back into JobIds with the query below, and then
1012 * restored twice. Fixes bug #2212.
1013 */
1014 rx->JobIds[0] = 0;
1015 }
1016 rx->found = false;
1017 /* Find and insert jobid and File Index */
1018 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1019 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1020 rx->query, db_strerror(ua->db));
1021 }
1022 if (!rx->found) {
1023 ua->error_msg(_("No database record found for: %s\n"), file);
1024 // ua->error_msg("Query=%s\n", rx->query);
1025 return true;
1026 }
1027 return true;
1028 }
1029
1030 /*
1031 * For a given path lookup the most recent backup in the catalog
1032 * to get the JobId and FileIndexes of all files in that directory.
1033 */
insert_dir_into_findex_list(UAContext * ua,RESTORE_CTX * rx,char * dir,char * date)1034 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
1035 char *date)
1036 {
1037 strip_trailing_junk(dir);
1038 if (*rx->JobIds == 0) {
1039 ua->error_msg(_("No JobId specified cannot continue.\n"));
1040 return false;
1041 } else {
1042 Mmsg(rx->query, uar_jobid_fileindex_from_dir[db_get_type_index(ua->db)], rx->JobIds, dir, rx->ClientName);
1043 }
1044 rx->found = false;
1045 /* Find and insert jobid and File Index */
1046 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1047 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1048 rx->query, db_strerror(ua->db));
1049 }
1050 if (!rx->found) {
1051 ua->error_msg(_("No database record found for: %s\n"), dir);
1052 return true;
1053 }
1054 return true;
1055 }
1056
1057 /*
1058 * Get the JobId and FileIndexes of all files in the specified table
1059 */
insert_table_into_findex_list(UAContext * ua,RESTORE_CTX * rx,char * table)1060 bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
1061 {
1062 strip_trailing_junk(table);
1063 Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
1064
1065 rx->found = false;
1066 /* Find and insert jobid and File Index */
1067 if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
1068 ua->error_msg(_("Query failed: %s. ERR=%s\n"),
1069 rx->query, db_strerror(ua->db));
1070 }
1071 if (!rx->found) {
1072 ua->error_msg(_("No table found: %s\n"), table);
1073 return true;
1074 }
1075 return true;
1076 }
1077
split_path_and_filename(UAContext * ua,RESTORE_CTX * rx,char * name)1078 static void split_path_and_filename(UAContext *ua, RESTORE_CTX *rx, char *name)
1079 {
1080 char *p, *f;
1081
1082 /* Find path without the filename.
1083 * I.e. everything after the last / is a "filename".
1084 * OK, maybe it is a directory name, but we treat it like
1085 * a filename. If we don't find a / then the whole name
1086 * must be a path name (e.g. c:).
1087 */
1088 for (p=f=name; *p; p++) {
1089 if (IsPathSeparator(*p)) {
1090 f = p; /* set pos of last slash */
1091 }
1092 }
1093 if (IsPathSeparator(*f)) { /* did we find a slash? */
1094 f++; /* yes, point to filename */
1095 } else { /* no, whole thing must be path name */
1096 f = p;
1097 }
1098
1099 /* If filename doesn't exist (i.e. root directory), we
1100 * simply create a blank name consisting of a single
1101 * space. This makes handling zero length filenames
1102 * easier.
1103 */
1104 rx->fnl = p - f;
1105 if (rx->fnl > 0) {
1106 rx->fname = check_pool_memory_size(rx->fname, 2*(rx->fnl)+1);
1107 db_escape_string(ua->jcr, ua->db, rx->fname, f, rx->fnl);
1108 } else {
1109 rx->fname[0] = 0;
1110 rx->fnl = 0;
1111 }
1112
1113 rx->pnl = f - name;
1114 if (rx->pnl > 0) {
1115 rx->path = check_pool_memory_size(rx->path, 2*(rx->pnl)+1);
1116 db_escape_string(ua->jcr, ua->db, rx->path, name, rx->pnl);
1117 } else {
1118 rx->path[0] = 0;
1119 rx->pnl = 0;
1120 }
1121
1122 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1123 }
1124
can_restore_all_files(UAContext * ua)1125 static bool can_restore_all_files(UAContext *ua)
1126 {
1127 alist *lst;
1128 if (ua->cons) {
1129 lst = ua->cons->ACL_lists[Directory_ACL];
1130 /* ACL not defined, or the first entry is not *all* */
1131 /* TODO: See if we search for *all* in all the list */
1132 if (!lst || strcasecmp((char*)lst->get(0), "*all*") != 0) {
1133 return false;
1134 }
1135 if (!lst || strcasecmp((char *)lst->get(0), "*all*") != 0) {
1136 return false;
1137 }
1138 }
1139 return true;
1140 }
1141
ask_for_fileregex(UAContext * ua,RESTORE_CTX * rx)1142 static bool ask_for_fileregex(UAContext *ua, RESTORE_CTX *rx)
1143 {
1144 bool can_restore=can_restore_all_files(ua);
1145
1146 if (can_restore && find_arg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1147 return true; /* select everything */
1148 }
1149
1150 ua->send_msg(_("\n\nFor one or more of the JobIds selected, no files were found,\n"
1151 "so file selection is not possible.\n"
1152 "Most likely your retention policy pruned the files.\n"));
1153
1154 if (!can_restore) {
1155 ua->error_msg(_("\nThe current Console has UserId or Directory restrictions. "
1156 "The full restore is not allowed.\n"));
1157 return false;
1158 }
1159
1160 if (get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1161 if (ua->pint32_val == 1)
1162 return true;
1163 while (get_cmd(ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1164 if (ua->cmd[0] == '\0') {
1165 break;
1166 } else {
1167 regex_t *fileregex_re = NULL;
1168 int rc;
1169 char errmsg[500] = "";
1170
1171 fileregex_re = (regex_t *)bmalloc(sizeof(regex_t));
1172 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED|REG_NOSUB);
1173 if (rc != 0) {
1174 regerror(rc, fileregex_re, errmsg, sizeof(errmsg));
1175 }
1176 regfree(fileregex_re);
1177 free(fileregex_re);
1178 if (*errmsg) {
1179 ua->send_msg(_("Regex compile error: %s\n"), errmsg);
1180 } else {
1181 rx->fileregex = bstrdup(ua->cmd);
1182 return true;
1183 }
1184 }
1185 }
1186 }
1187 return false;
1188 }
1189
1190 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1191 * TODO: Optimize for bootstrap creation, remove recursion
1192 * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1193 * should insert as
1194 * 0, 1, 2, 3, 4, 5, 6
1195 */
add_delta_list_findex(RESTORE_CTX * rx,struct delta_list * lst)1196 static void add_delta_list_findex(RESTORE_CTX *rx, struct delta_list *lst)
1197 {
1198 if (lst == NULL) {
1199 return;
1200 }
1201 if (lst->next) {
1202 add_delta_list_findex(rx, lst->next);
1203 }
1204 add_findex(rx->bsr_list, lst->JobId, lst->FileIndex);
1205 }
1206
1207 /*
1208 * This is a list of all the files (components) that the
1209 * user has requested for restore. It is requested by
1210 * the plugin (for now hard coded only for VSS).
1211 * In the future, this will be requested by a RestoreObject
1212 * and the plugin name will be sent to the FD.
1213 */
write_component_file(UAContext * ua,RESTORE_CTX * rx,char * fname)1214 static bool write_component_file(UAContext *ua, RESTORE_CTX *rx, char *fname)
1215 {
1216 int fd;
1217 if (!rx->component_fd) {
1218 Mmsg(rx->component_fname, "%s/%s.restore.sel.XXXXXX", working_directory, my_name);
1219 fd = mkstemp(rx->component_fname);
1220 if (fd < 0) {
1221 berrno be;
1222 ua->error_msg(_("Unable to create component file %s. ERR=%s\n"),
1223 rx->component_fname, be.bstrerror());
1224 return false;
1225 }
1226 rx->component_fd = fdopen(fd, "w+");
1227 if (!rx->component_fd) {
1228 berrno be;
1229 ua->error_msg(_("Unable to fdopen component file %s. ERR=%s\n"),
1230 rx->component_fname, be.bstrerror());
1231 return false;
1232 }
1233 }
1234 fprintf(rx->component_fd, "%s\n", fname);
1235 if (ferror(rx->component_fd)) {
1236 ua->error_msg(_("Error writing component file.\n"));
1237 fclose(rx->component_fd);
1238 unlink(rx->component_fname);
1239 rx->component_fd = NULL;
1240 return false;
1241 }
1242 return true;
1243 }
1244
build_directory_tree(UAContext * ua,RESTORE_CTX * rx)1245 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
1246 {
1247 TREE_CTX tree;
1248 JobId_t JobId, last_JobId;
1249 char *p;
1250 bool OK = true;
1251 char ed1[50];
1252
1253 memset(&tree, 0, sizeof(TREE_CTX));
1254 /*
1255 * Build the directory tree containing JobIds user selected
1256 */
1257 tree.root = new_tree(rx->TotalFiles);
1258 tree.ua = ua;
1259 tree.all = rx->all;
1260 tree.hardlinks_in_mem = rx->hardlinks_in_mem;
1261 tree.no_auto_parent = rx->no_auto_parent;
1262 last_JobId = 0;
1263 tree.last_dir_acl = NULL;
1264 /*
1265 * For display purposes, the same JobId, with different volumes may
1266 * appear more than once, however, we only insert it once.
1267 */
1268 p = rx->JobIds;
1269 tree.FileEstimate = 0;
1270 if (get_next_jobid_from_list(&p, &JobId) > 0) {
1271 /* Use first JobId as estimate of the number of files to restore */
1272 Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
1273 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1274 ua->error_msg("%s\n", db_strerror(ua->db));
1275 }
1276 if (rx->found) {
1277 /* Add about 25% more than this job for over estimate */
1278 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1279 tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
1280 }
1281 }
1282
1283 ua->info_msg(_("\nBuilding directory tree for JobId(s) %s ... "),
1284 rx->JobIds);
1285
1286 #define new_get_file_list
1287 #ifdef new_get_file_list
1288 if (!db_get_file_list(ua->jcr, ua->db,
1289 rx->JobIds, DBL_USE_DELTA,
1290 insert_tree_handler, (void *)&tree))
1291 {
1292 ua->error_msg("%s", db_strerror(ua->db));
1293 }
1294 if (*rx->BaseJobIds) {
1295 pm_strcat(rx->JobIds, ",");
1296 pm_strcat(rx->JobIds, rx->BaseJobIds);
1297 }
1298 #else
1299 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1300 char ed1[50];
1301
1302 if (JobId == last_JobId) {
1303 continue; /* eliminate duplicate JobIds */
1304 }
1305 last_JobId = JobId;
1306 /*
1307 * Find files for this JobId and insert them in the tree
1308 */
1309 Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
1310 if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
1311 ua->error_msg("%s", db_strerror(ua->db));
1312 }
1313 }
1314 #endif
1315 /*
1316 * At this point, the tree is built, so we can garbage collect
1317 * any memory released by the SQL engine that RedHat has
1318 * not returned to the OS :-(
1319 */
1320 garbage_collect_memory();
1321
1322 /*
1323 * Look at the first JobId on the list (presumably the oldest) and
1324 * if it is marked purged, don't do the manual selection because
1325 * the Job was pruned, so the tree is incomplete.
1326 */
1327 if (tree.FileCount != 0) {
1328 /* Find out if any Job is purged */
1329 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)", rx->JobIds);
1330 if (!db_sql_query(ua->db, rx->query, restore_count_handler, (void *)rx)) {
1331 ua->error_msg("%s\n", db_strerror(ua->db));
1332 }
1333 /* rx->JobId is the PurgedFiles flag */
1334 if (rx->found && rx->JobId > 0) {
1335 tree.FileCount = 0; /* set count to zero, no tree selection */
1336 }
1337 }
1338 if (tree.FileCount == 0) {
1339 OK = ask_for_fileregex(ua, rx);
1340 if (OK) {
1341 last_JobId = 0;
1342 for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
1343 if (JobId == last_JobId) {
1344 continue; /* eliminate duplicate JobIds */
1345 }
1346 add_findex_all(rx->bsr_list, JobId, rx->fileregex);
1347 }
1348 }
1349 } else {
1350 char ec1[50];
1351 if (tree.all) {
1352 ua->info_msg(_("\n%s files inserted into the tree and marked for extraction.\n"),
1353 edit_uint64_with_commas(tree.FileCount, ec1));
1354 } else {
1355 ua->info_msg(_("\n%s files inserted into the tree.\n"),
1356 edit_uint64_with_commas(tree.FileCount, ec1));
1357 }
1358
1359 if (find_arg(ua, NT_("done")) < 0) {
1360 /* Let the user interact in selecting which files to restore */
1361 OK = user_select_files_from_tree(&tree);
1362 }
1363
1364 /*
1365 * Walk down through the tree finding all files marked to be
1366 * extracted making a bootstrap file.
1367 */
1368 if (OK) {
1369 char cwd[2000];
1370 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
1371 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1372 if (node->extract || node->extract_dir) {
1373 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId, node->type, node->FileIndex);
1374 /* TODO: optimize bsr insertion when jobid are non sorted */
1375 add_delta_list_findex(rx, node->delta_list);
1376 add_findex(rx->bsr_list, node->JobId, node->FileIndex);
1377 /*
1378 * Special VSS plugin code to return selected
1379 * components. For the moment, it is hard coded
1380 * for the VSS plugin.
1381 */
1382 if (fnmatch(":component_info_*", node->fname, 0) == 0) {
1383 tree_getpath(node, cwd, sizeof(cwd));
1384 if (!write_component_file(ua, rx, cwd)) {
1385 OK = false;
1386 break;
1387 }
1388 }
1389 if (node->extract && node->type != TN_NEWDIR) {
1390 rx->selected_files++; /* count only saved files */
1391 }
1392 }
1393 }
1394 }
1395 }
1396 if (tree.uid_acl) {
1397 delete tree.uid_acl;
1398 delete tree.gid_acl;
1399 delete tree.dir_acl;
1400 }
1401 free_tree(tree.root); /* free the directory tree */
1402 return OK;
1403 }
1404
1405
1406 /*
1407 * This routine is used to get the current backup or a backup
1408 * before the specified date.
1409 */
select_backups_before_date(UAContext * ua,RESTORE_CTX * rx,char * date)1410 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1411 {
1412 bool ok = false;
1413 FILESET_DBR fsr;
1414 CLIENT_DBR cr;
1415 char fileset_name[MAX_NAME_LENGTH];
1416 char ed1[50], ed2[50];
1417 char pool_select[MAX_NAME_LENGTH];
1418 int i;
1419
1420 /* Create temp tables */
1421 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1422 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1423 if (!db_sql_query(ua->db, uar_create_temp[db_get_type_index(ua->db)], NULL, NULL)) {
1424 ua->error_msg("%s\n", db_strerror(ua->db));
1425 }
1426 if (!db_sql_query(ua->db, uar_create_temp1[db_get_type_index(ua->db)], NULL, NULL)) {
1427 ua->error_msg("%s\n", db_strerror(ua->db));
1428 }
1429 /*
1430 * Select Client from the Catalog
1431 */
1432 memset(&cr, 0, sizeof(cr));
1433 if (!get_client_dbr(ua, &cr, JT_BACKUP_RESTORE)) {
1434 goto bail_out;
1435 }
1436 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1437
1438 /*
1439 * Get FileSet
1440 */
1441 memset(&fsr, 0, sizeof(fsr));
1442 i = find_arg_with_value(ua, "FileSet");
1443
1444 if (i >= 0 && is_name_valid(ua->argv[i], &ua->errmsg)) {
1445 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1446 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1447 ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1448 db_strerror(ua->db));
1449 i = -1;
1450 }
1451 } else if (i >= 0) { /* name is invalid */
1452 ua->error_msg(_("FileSet argument: %s\n"), ua->errmsg);
1453 }
1454
1455 if (i < 0) { /* fileset not found */
1456 edit_int64(cr.ClientId, ed1);
1457 Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1458 start_prompt(ua, _("The defined FileSet resources are:\n"));
1459 if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1460 ua->error_msg("%s\n", db_strerror(ua->db));
1461 }
1462 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1463 fileset_name, sizeof(fileset_name)) < 0) {
1464 ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1465 goto bail_out;
1466 }
1467
1468 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1469 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1470 ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1471 ua->send_msg(_("This probably means you modified the FileSet.\n"
1472 "Continuing anyway.\n"));
1473 }
1474 }
1475
1476 /* If Pool specified, add PoolId specification */
1477 pool_select[0] = 0;
1478 if (rx->pool) {
1479 POOL_DBR pr;
1480 memset(&pr, 0, sizeof(pr));
1481 bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1482 if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1483 bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1484 edit_int64(pr.PoolId, ed1));
1485 } else {
1486 ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1487 }
1488 }
1489
1490 /* Find JobId of last Full backup for this client, fileset */
1491 edit_int64(cr.ClientId, ed1);
1492 Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1493 pool_select);
1494 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1495 ua->error_msg("%s\n", db_strerror(ua->db));
1496 goto bail_out;
1497 }
1498
1499 /* Find all Volumes used by that JobId */
1500 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1501 ua->error_msg("%s\n", db_strerror(ua->db));
1502 goto bail_out;
1503 }
1504
1505 /* Note, this is needed because I don't seem to get the callback
1506 * from the call just above.
1507 */
1508 rx->JobTDate = 0;
1509 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1510 ua->warning_msg("%s\n", db_strerror(ua->db));
1511 }
1512 if (rx->JobTDate == 0) {
1513 ua->error_msg(_("No Full backup before %s found.\n"), date);
1514 goto bail_out;
1515 }
1516
1517 /* Now find most recent Differental Job after Full save, if any */
1518 Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1519 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1520 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1521 ua->warning_msg("%s\n", db_strerror(ua->db));
1522 }
1523 /* Now update JobTDate to look into Differental, if any */
1524 rx->JobTDate = 0;
1525 if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1526 ua->warning_msg("%s\n", db_strerror(ua->db));
1527 }
1528 if (rx->JobTDate == 0) {
1529 ua->error_msg(_("No Full backup before %s found.\n"), date);
1530 goto bail_out;
1531 }
1532
1533 /* Now find all Incremental Jobs after Full/dif save */
1534 Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1535 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1536 if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1537 ua->warning_msg("%s\n", db_strerror(ua->db));
1538 }
1539
1540 /* Get the JobIds from that list */
1541 rx->last_jobid[0] = rx->JobIds[0] = 0;
1542
1543 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1544 ua->warning_msg("%s\n", db_strerror(ua->db));
1545 }
1546
1547 if (rx->JobIds[0] != 0) {
1548 if (find_arg(ua, NT_("copies")) > 0) {
1549 /* Display a list of all copies */
1550 db_list_copies_records(ua->jcr, ua->db, 0, rx->JobIds,
1551 prtit, ua, HORZ_LIST);
1552 }
1553 /* Display a list of Jobs selected for this restore */
1554 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1,HORZ_LIST);
1555 ok = true;
1556
1557 } else {
1558 ua->warning_msg(_("No jobs found.\n"));
1559 }
1560
1561 bail_out:
1562 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1563 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1564 return ok;
1565 }
1566
restore_count_handler(void * ctx,int num_fields,char ** row)1567 static int restore_count_handler(void *ctx, int num_fields, char **row)
1568 {
1569 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1570 rx->JobId = str_to_int64(row[0]);
1571 rx->found = true;
1572 return 0;
1573 }
1574
1575 /*
1576 * Callback handler to get JobId and FileIndex for files
1577 * can insert more than one depending on the caller.
1578 */
jobid_fileindex_handler(void * ctx,int num_fields,char ** row)1579 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1580 {
1581 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1582 JobId_t JobId = str_to_int64(row[0]);
1583
1584 Dmsg3(200, "JobId=%s JobIds=%s FileIndex=%s\n", row[0], rx->JobIds, row[1]);
1585
1586 /* New JobId, add it to JobIds
1587 * The list is sorted by JobId, so we need a cache for the previous value
1588 *
1589 * It will permit to find restore objects to send during the restore
1590 */
1591 if (rx->JobId != JobId) {
1592 if (*rx->JobIds) {
1593 pm_strcat(rx->JobIds, ",");
1594 }
1595 pm_strcat(rx->JobIds, row[0]);
1596 rx->JobId = JobId;
1597 }
1598
1599 add_findex(rx->bsr_list, rx->JobId, str_to_int64(row[1]));
1600 rx->found = true;
1601 rx->selected_files++;
1602 return 0;
1603 }
1604
1605 /*
1606 * Callback handler make list of JobIds
1607 */
jobid_handler(void * ctx,int num_fields,char ** row)1608 static int jobid_handler(void *ctx, int num_fields, char **row)
1609 {
1610 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1611
1612 if (strcmp(rx->last_jobid, row[0]) == 0) {
1613 return 0; /* duplicate id */
1614 }
1615 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1616 if (rx->JobIds[0] != 0) {
1617 pm_strcat(rx->JobIds, ",");
1618 }
1619 pm_strcat(rx->JobIds, row[0]);
1620 return 0;
1621 }
1622
1623
1624 /*
1625 * Callback handler to pickup last Full backup JobTDate
1626 */
last_full_handler(void * ctx,int num_fields,char ** row)1627 static int last_full_handler(void *ctx, int num_fields, char **row)
1628 {
1629 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1630
1631 rx->JobTDate = str_to_int64(row[1]);
1632 return 0;
1633 }
1634
1635 /*
1636 * Callback handler build FileSet name prompt list
1637 */
fileset_handler(void * ctx,int num_fields,char ** row)1638 static int fileset_handler(void *ctx, int num_fields, char **row)
1639 {
1640 /* row[0] = FileSet (name) */
1641 if (row[0]) {
1642 add_prompt((UAContext *)ctx, row[0]);
1643 }
1644 return 0;
1645 }
1646
1647 /*
1648 * Free names in the list
1649 */
free_name_list(NAME_LIST * name_list)1650 static void free_name_list(NAME_LIST *name_list)
1651 {
1652 for (int i=0; i < name_list->num_ids; i++) {
1653 free(name_list->name[i]);
1654 }
1655 bfree_and_null(name_list->name);
1656 name_list->max_ids = 0;
1657 name_list->num_ids = 0;
1658 }
1659
find_storage_resource(UAContext * ua,RESTORE_CTX & rx,char * Storage,char * MediaType)1660 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType)
1661 {
1662 STORE *store;
1663
1664 if (rx.store) {
1665 Dmsg1(200, "Already have store=%s\n", rx.store->name());
1666 return;
1667 }
1668 /*
1669 * Try looking up Storage by name
1670 */
1671 LockRes();
1672 foreach_res(store, R_STORAGE) {
1673 if (strcmp(Storage, store->name()) == 0) {
1674 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1675 rx.store = store;
1676 }
1677 break;
1678 }
1679 }
1680 UnlockRes();
1681
1682 if (rx.store) {
1683 /* Check if an explicit storage resource is given */
1684 store = NULL;
1685 int i = find_arg_with_value(ua, "storage");
1686 if (i > 0) {
1687 store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1688 if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1689 store = NULL;
1690 }
1691 }
1692 if (store && (store != rx.store)) {
1693 ua->info_msg(_("\nWarning Storage is overridden by \"%s\" on the command line.\n"),
1694 store->name());
1695 rx.store = store;
1696 bstrncpy(rx.RestoreMediaType, MediaType, sizeof(rx.RestoreMediaType));
1697 if (strcmp(MediaType, store->media_type) != 0) {
1698 ua->info_msg(_("This may not work because of two different MediaTypes:\n"
1699 " Storage MediaType=\"%s\"\n"
1700 " Volume MediaType=\"%s\".\n\n"),
1701 store->media_type, MediaType);
1702 }
1703 Dmsg2(200, "Set store=%s MediaType=%s\n", rx.store->name(), rx.RestoreMediaType);
1704 }
1705 return;
1706 }
1707
1708 /* If no storage resource, try to find one from MediaType */
1709 if (!rx.store) {
1710 LockRes();
1711 foreach_res(store, R_STORAGE) {
1712 if (strcmp(MediaType, store->media_type) == 0) {
1713 if (acl_access_ok(ua, Storage_ACL, store->name())) {
1714 rx.store = store;
1715 Dmsg1(200, "Set store=%s\n", rx.store->name());
1716 if (Storage == NULL || Storage[0] == 0) {
1717 ua->warning_msg(_("Using Storage \"%s\" from MediaType \"%s\".\n"),
1718 store->name(), MediaType);
1719 } else {
1720 ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1721 Storage, store->name(), MediaType);
1722 }
1723 }
1724 UnlockRes();
1725 return;
1726 }
1727 }
1728 UnlockRes();
1729 ua->warning_msg(_("\nUnable to find Storage resource for\n"
1730 "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1731 }
1732
1733 /* Take command line arg, or ask user if none */
1734 rx.store = get_storage_resource(ua, false /* don't use default */);
1735 if (rx.store) {
1736 Dmsg1(200, "Set store=%s\n", rx.store->name());
1737 }
1738
1739 }
1740