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