]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Minor tweaks to Migration
[bacula/bacula] / bacula / src / dird / ua_restore.c
1 /*
2  *
3  *   Bacula Director -- User Agent Database restore Command
4  *      Creates a bootstrap file for restoring files and
5  *      starts the restore job.
6  *
7  *      Tree handling routines split into ua_tree.c July MMIII.
8  *      BSR (bootstrap record) handling routines split into
9  *        bsr.c July MMIII
10  *
11  *     Kern Sibbald, July MMII
12  *
13  *   Version $Id$
14  */
15 /*
16    Copyright (C) 2002-2006 Kern Sibbald
17
18    This program is free software; you can redistribute it and/or
19    modify it under the terms of the GNU General Public License
20    version 2 as amended with additional clauses defined in the
21    file LICENSE in the main source directory.
22
23    This program is distributed in the hope that it will be useful,
24    but WITHOUT ANY WARRANTY; without even the implied warranty of
25    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
26    the file LICENSE for additional details.
27
28  */
29
30
31 #include "bacula.h"
32 #include "dird.h"
33
34
35 /* Imported functions */
36 extern void print_bsr(UAContext *ua, RBSR *bsr);
37
38
39
40 /* Forward referenced functions */
41 static int last_full_handler(void *ctx, int num_fields, char **row);
42 static int jobid_handler(void *ctx, int num_fields, char **row);
43 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
44 static int fileset_handler(void *ctx, int num_fields, char **row);
45 static void free_name_list(NAME_LIST *name_list);
46 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
47 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
48 static void free_rx(RESTORE_CTX *rx);
49 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
50 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
51 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
52                                          char *date);
53 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
54                                         char *date);
55 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
56 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
57 static int get_date(UAContext *ua, char *date, int date_len);
58 static int count_handler(void *ctx, int num_fields, char **row);
59 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
60
61 /*
62  *   Restore files
63  *
64  */
65 int restore_cmd(UAContext *ua, const char *cmd)
66 {
67    RESTORE_CTX rx;                    /* restore context */
68    JOB *job;
69    int i;
70    JCR *jcr = ua->jcr;
71
72    memset(&rx, 0, sizeof(rx));
73    rx.path = get_pool_memory(PM_FNAME);
74    rx.fname = get_pool_memory(PM_FNAME);
75    rx.JobIds = get_pool_memory(PM_FNAME);
76    rx.query = get_pool_memory(PM_FNAME);
77    rx.bsr = new_bsr();
78
79    i = find_arg_with_value(ua, "where");
80    if (i >= 0) {
81       rx.where = ua->argv[i];
82    }
83
84    if (!open_db(ua)) {
85       goto bail_out;
86    }
87
88    /* Ensure there is at least one Restore Job */
89    LockRes();
90    foreach_res(job, R_JOB) {
91       if (job->JobType == JT_RESTORE) {
92          if (!rx.restore_job) {
93             rx.restore_job = job;
94          }
95          rx.restore_jobs++;
96       }
97    }
98    UnlockRes();
99    if (!rx.restore_jobs) {
100       bsendmsg(ua, _(
101          "No Restore Job Resource found in bacula-dir.conf.\n"
102          "You must create at least one before running this command.\n"));
103       goto bail_out;
104    }
105
106    /*
107     * Request user to select JobIds or files by various different methods
108     *  last 20 jobs, where File saved, most recent backup, ...
109     *  In the end, a list of files are pumped into
110     *  add_findex()
111     */
112    switch (user_select_jobids_or_files(ua, &rx)) {
113    case 0:                            /* error */
114       goto bail_out;
115    case 1:                            /* selected by jobid */
116       if (!build_directory_tree(ua, &rx)) {
117          bsendmsg(ua, _("Restore not done.\n"));
118          goto bail_out;
119       }
120       break;
121    case 2:                            /* selected by filename, no tree needed */
122       break;
123    }
124
125    if (rx.bsr->JobId) {
126       uint32_t selected_files;
127       if (!complete_bsr(ua, rx.bsr)) {   /* find Vol, SessId, SessTime from JobIds */
128          bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
129          goto bail_out;
130       }
131       if (!(selected_files = write_bsr_file(ua, rx))) {
132          bsendmsg(ua, _("No files selected to be restored.\n"));
133          goto bail_out;
134       }
135       /* If no count of files, use bsr generated value (often wrong) */
136       if (rx.selected_files == 0) {
137          rx.selected_files = selected_files;
138       }
139       if (rx.selected_files==1) {
140          bsendmsg(ua, _("\n1 file selected to be restored.\n\n"));
141       }
142       else {
143          bsendmsg(ua, _("\n%u files selected to be restored.\n\n"), rx.selected_files);
144       }
145    } else {
146       bsendmsg(ua, _("No files selected to be restored.\n"));
147       goto bail_out;
148    }
149
150    if (rx.restore_jobs == 1) {
151       job = rx.restore_job;
152    } else {
153       job = select_restore_job_resource(ua);
154    }
155    if (!job) {
156       goto bail_out;
157    }
158
159    get_client_name(ua, &rx);
160    if (!rx.ClientName) {
161       bsendmsg(ua, _("No Restore Job resource found!\n"));
162       goto bail_out;
163    }
164
165    /* Build run command */
166    if (rx.where) {
167       Mmsg(ua->cmd,
168           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
169           " where=\"%s\" files=%d catalog=\"%s\"",
170           job->name(), rx.ClientName, rx.store?rx.store->name():"",
171           jcr->RestoreBootstrap, rx.where, rx.selected_files, ua->catalog->name());
172    } else {
173       Mmsg(ua->cmd,
174           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
175           " files=%d catalog=\"%s\"",
176           job->name(), rx.ClientName, rx.store?rx.store->name():"",
177           jcr->RestoreBootstrap, rx.selected_files, ua->catalog->name());
178    }
179    if (find_arg(ua, NT_("yes")) > 0) {
180       pm_strcat(ua->cmd, " yes");    /* pass it on to the run command */
181    }
182    Dmsg1(200, "Submitting: %s\n", ua->cmd);
183    parse_ua_args(ua);
184    run_cmd(ua, ua->cmd);
185    free_rx(&rx);
186    return 1;
187
188 bail_out:
189    free_rx(&rx);
190    return 0;
191
192 }
193
194 static void free_rx(RESTORE_CTX *rx)
195 {
196    free_bsr(rx->bsr);
197    rx->bsr = NULL;
198    if (rx->JobIds) {
199       free_pool_memory(rx->JobIds);
200       rx->JobIds = NULL;
201    }
202    if (rx->fname) {
203       free_pool_memory(rx->fname);
204       rx->fname = NULL;
205    }
206    if (rx->path) {
207       free_pool_memory(rx->path);
208       rx->path = NULL;
209    }
210    if (rx->query) {
211       free_pool_memory(rx->query);
212       rx->query = NULL;
213    }
214    free_name_list(&rx->name_list);
215 }
216
217 static bool has_value(UAContext *ua, int i)
218 {
219    if (!ua->argv[i]) {
220       bsendmsg(ua, _("Missing value for keyword: %s\n"), ua->argk[i]);
221       return false;
222    }
223    return true;
224 }
225
226 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
227 {
228    /* If no client name specified yet, get it now */
229    if (!rx->ClientName[0]) {
230       CLIENT_DBR cr;
231       /* try command line argument */
232       int i = find_arg_with_value(ua, NT_("client"));
233       if (i >= 0) {
234          if (!has_value(ua, i)) {
235             return 0;
236          }
237          bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
238          return 1;
239       }
240       memset(&cr, 0, sizeof(cr));
241       if (!get_client_dbr(ua, &cr)) {
242          return 0;
243       }
244       bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
245    }
246    return 1;
247 }
248
249
250 /*
251  * The first step in the restore process is for the user to
252  *  select a list of JobIds from which he will subsequently
253  *  select which files are to be restored.
254  *
255  *  Returns:  2  if filename list made
256  *            1  if jobid list made
257  *            0  on error
258  */
259 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
260 {
261    char *p;
262    char date[MAX_TIME_LENGTH];
263    bool have_date = false;
264    JobId_t JobId;
265    JOB_DBR jr = { (JobId_t)-1 };
266    bool done = false;
267    int i, j;
268    const char *list[] = {
269       _("List last 20 Jobs run"),
270       _("List Jobs where a given File is saved"),
271       _("Enter list of comma separated JobIds to select"),
272       _("Enter SQL list command"),
273       _("Select the most recent backup for a client"),
274       _("Select backup for a client before a specified time"),
275       _("Enter a list of files to restore"),
276       _("Enter a list of files to restore before a specified time"),
277       _("Find the JobIds of the most recent backup for a client"),
278       _("Find the JobIds for a backup for a client before a specified time"),
279       _("Enter a list of directories to restore for found JobIds"),
280       _("Cancel"),
281       NULL };
282
283    const char *kw[] = {
284        /* These keywords are handled in a for loop */
285       "jobid",     /* 0 */
286       "current",   /* 1 */
287       "before",    /* 2 */
288       "file",      /* 3 */
289       "directory", /* 4 */
290       "select",    /* 5 */
291       "pool",      /* 6 */
292       "all",       /* 7 */
293
294       /* The keyword below are handled by individual arg lookups */
295       "client",    /* 8 */
296       "storage",   /* 9 */
297       "fileset",   /* 10 */
298       "where",     /* 11 */
299       "yes",       /* 12 */
300       "bootstrap", /* 13 */
301       "done",      /* 14 */
302       NULL
303    };
304
305    *rx->JobIds = 0;
306
307    for (i=1; i<ua->argc; i++) {       /* loop through arguments */
308       bool found_kw = false;
309       for (j=0; kw[j]; j++) {         /* loop through keywords */
310          if (strcasecmp(kw[j], ua->argk[i]) == 0) {
311             found_kw = true;
312             break;
313          }
314       }
315       if (!found_kw) {
316          bsendmsg(ua, _("Unknown keyword: %s\n"), ua->argk[i]);
317          return 0;
318       }
319       /* Found keyword in kw[] list, process it */
320       switch (j) {
321       case 0:                            /* jobid */
322          if (!has_value(ua, i)) {
323             return 0;
324          }
325          if (*rx->JobIds != 0) {
326             pm_strcat(rx->JobIds, ",");
327          }
328          pm_strcat(rx->JobIds, ua->argv[i]);
329          done = true;
330          break;
331       case 1:                            /* current */
332          bstrutime(date, sizeof(date), time(NULL));
333          have_date = true;
334          break;
335       case 2:                            /* before */
336          if (!has_value(ua, i)) {
337             return 0;
338          }
339          if (str_to_utime(ua->argv[i]) == 0) {
340             bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
341             return 0;
342          }
343          bstrncpy(date, ua->argv[i], sizeof(date));
344          have_date = true;
345          break;
346       case 3:                            /* file */
347       case 4:                            /* dir */
348          if (!has_value(ua, i)) {
349             return 0;
350          }
351          if (!have_date) {
352             bstrutime(date, sizeof(date), time(NULL));
353          }
354          if (!get_client_name(ua, rx)) {
355             return 0;
356          }
357          pm_strcpy(ua->cmd, ua->argv[i]);
358          insert_one_file_or_dir(ua, rx, date, j==4);
359          return 2;
360       case 5:                            /* select */
361          if (!have_date) {
362             bstrutime(date, sizeof(date), time(NULL));
363          }
364          if (!select_backups_before_date(ua, rx, date)) {
365             return 0;
366          }
367          done = true;
368          break;
369       case 6:                            /* pool specified */
370          if (!has_value(ua, i)) {
371             return 0;
372          }
373          rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
374          if (!rx->pool) {
375             bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
376             return 0;
377          }
378          if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
379             rx->pool = NULL;
380             bsendmsg(ua, _("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
381             return 0;
382          }
383          break;
384       case 7:                         /* all specified */
385          rx->all = true;
386          break;
387       /*
388        * All keywords 7 or greater are ignored or handled by a select prompt
389        */
390       default:
391          break;
392       }
393    }
394
395    if (!done) {
396       bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
397                   "to be restored. You will be presented several methods\n"
398                   "of specifying the JobIds. Then you will be allowed to\n"
399                   "select which files from those JobIds are to be restored.\n\n"));
400    }
401
402    /* If choice not already made above, prompt */
403    for ( ; !done; ) {
404       char *fname;
405       int len;
406       bool gui_save;
407
408       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
409       for (int i=0; list[i]; i++) {
410          add_prompt(ua, list[i]);
411       }
412       done = true;
413       switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
414       case -1:                        /* error or cancel */
415          return 0;
416       case 0:                         /* list last 20 Jobs run */
417          gui_save = ua->jcr->gui;
418          ua->jcr->gui = true;
419          db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
420          ua->jcr->gui = gui_save;
421          done = false;
422          break;
423       case 1:                         /* list where a file is saved */
424          if (!get_client_name(ua, rx)) {
425             return 0;
426          }
427          if (!get_cmd(ua, _("Enter Filename (no path):"))) {
428             return 0;
429          }
430          len = strlen(ua->cmd);
431          fname = (char *)malloc(len * 2 + 1);
432          db_escape_string(fname, ua->cmd, len);
433          Mmsg(rx->query, uar_file, rx->ClientName, fname);
434          free(fname);
435          gui_save = ua->jcr->gui;
436          ua->jcr->gui = true;
437          db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
438          ua->jcr->gui = gui_save;
439          done = false;
440          break;
441       case 2:                         /* enter a list of JobIds */
442          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
443             return 0;
444          }
445          pm_strcpy(rx->JobIds, ua->cmd);
446          break;
447       case 3:                         /* Enter an SQL list command */
448          if (!get_cmd(ua, _("Enter SQL list command: "))) {
449             return 0;
450          }
451          gui_save = ua->jcr->gui;
452          ua->jcr->gui = true;
453          db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
454          ua->jcr->gui = gui_save;
455          done = false;
456          break;
457       case 4:                         /* Select the most recent backups */
458          bstrutime(date, sizeof(date), time(NULL));
459          if (!select_backups_before_date(ua, rx, date)) {
460             return 0;
461          }
462          break;
463       case 5:                         /* select backup at specified time */
464          if (!get_date(ua, date, sizeof(date))) {
465             return 0;
466          }
467          if (!select_backups_before_date(ua, rx, date)) {
468             return 0;
469          }
470          break;
471       case 6:                         /* Enter files */
472          bstrutime(date, sizeof(date), time(NULL));
473          if (!get_client_name(ua, rx)) {
474             return 0;
475          }
476          bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
477                         "containing a list of file names with paths, and terminate\n"
478                         "them with a blank line.\n"));
479          for ( ;; ) {
480             if (!get_cmd(ua, _("Enter full filename: "))) {
481                return 0;
482             }
483             len = strlen(ua->cmd);
484             if (len == 0) {
485                break;
486             }
487             insert_one_file_or_dir(ua, rx, date, false);
488          }
489          return 2;
490        case 7:                        /* enter files backed up before specified time */
491          if (!get_date(ua, date, sizeof(date))) {
492             return 0;
493          }
494          if (!get_client_name(ua, rx)) {
495             return 0;
496          }
497          bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
498                         "containing a list of file names with paths, and terminate\n"
499                         "them with a blank line.\n"));
500          for ( ;; ) {
501             if (!get_cmd(ua, _("Enter full filename: "))) {
502                return 0;
503             }
504             len = strlen(ua->cmd);
505             if (len == 0) {
506                break;
507             }
508             insert_one_file_or_dir(ua, rx, date, false);
509          }
510          return 2;
511
512       case 8:                         /* Find JobIds for current backup */
513          bstrutime(date, sizeof(date), time(NULL));
514          if (!select_backups_before_date(ua, rx, date)) {
515             return 0;
516          }
517          done = false;
518          break;
519
520       case 9:                         /* Find JobIds for give date */
521          if (!get_date(ua, date, sizeof(date))) {
522             return 0;
523          }
524          if (!select_backups_before_date(ua, rx, date)) {
525             return 0;
526          }
527          done = false;
528          break;
529
530       case 10:                        /* Enter directories */
531          if (*rx->JobIds != 0) {
532             bsendmsg(ua, _("You have already seleted the following JobIds: %s\n"),
533                rx->JobIds);
534          } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
535             if (*rx->JobIds != 0 && *ua->cmd) {
536                pm_strcat(rx->JobIds, ",");
537             }
538             pm_strcat(rx->JobIds, ua->cmd);
539          }
540          if (*rx->JobIds == 0 || *rx->JobIds == '.') {
541             return 0;                 /* nothing entered, return */
542          }
543          bstrutime(date, sizeof(date), time(NULL));
544          if (!get_client_name(ua, rx)) {
545             return 0;
546          }
547          bsendmsg(ua, _("Enter full directory names or start the name\n"
548                         "with a < to indicate it is a filename containing a list\n"
549                         "of directories and terminate them with a blank line.\n"));
550          for ( ;; ) {
551             if (!get_cmd(ua, _("Enter directory name: "))) {
552                return 0;
553             }
554             len = strlen(ua->cmd);
555             if (len == 0) {
556                break;
557             }
558             /* Add trailing slash to end of directory names */
559             if (ua->cmd[0] != '<' && ua->cmd[len-1] != '/') {
560                strcat(ua->cmd, "/");
561             }
562             insert_one_file_or_dir(ua, rx, date, true);
563          }
564          return 2;
565
566       case 11:                        /* Cancel or quit */
567          return 0;
568       }
569    }
570
571    if (*rx->JobIds == 0) {
572       bsendmsg(ua, _("No Jobs selected.\n"));
573       return 0;
574    }
575    if (strchr(rx->JobIds,',')) {
576       bsendmsg(ua, _("You have selected the following JobIds: %s\n"), rx->JobIds);
577    }
578    else {
579       bsendmsg(ua, _("You have selected the following JobId: %s\n"), rx->JobIds);
580    }
581
582
583    rx->TotalFiles = 0;
584    for (p=rx->JobIds; ; ) {
585       int stat = get_next_jobid_from_list(&p, &JobId);
586       if (stat < 0) {
587          bsendmsg(ua, _("Invalid JobId in list.\n"));
588          return 0;
589       }
590       if (stat == 0) {
591          break;
592       }
593       if (jr.JobId == JobId) {
594          continue;                    /* duplicate of last JobId */
595       }
596       memset(&jr, 0, sizeof(JOB_DBR));
597       jr.JobId = JobId;
598       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
599          char ed1[50];
600          bsendmsg(ua, _("Unable to get Job record for JobId=%s: ERR=%s\n"),
601             edit_int64(JobId, ed1), db_strerror(ua->db));
602          return 0;
603       }
604       if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
605          bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
606             jr.Name);
607          continue;
608       }
609       rx->TotalFiles += jr.JobFiles;
610    }
611    return 1;
612 }
613
614 /*
615  * Get date from user
616  */
617 static int get_date(UAContext *ua, char *date, int date_len)
618 {
619    bsendmsg(ua, _("The restored files will the most current backup\n"
620                   "BEFORE the date you specify below.\n\n"));
621    for ( ;; ) {
622       if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
623          return 0;
624       }
625       if (str_to_utime(ua->cmd) != 0) {
626          break;
627       }
628       bsendmsg(ua, _("Improper date format.\n"));
629    }
630    bstrncpy(date, ua->cmd, date_len);
631    return 1;
632 }
633
634 /*
635  * Insert a single file, or read a list of files from a file
636  */
637 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
638 {
639    FILE *ffd;
640    char file[5000];
641    char *p = ua->cmd;
642    int line = 0;
643
644    switch (*p) {
645    case '<':
646       p++;
647       if ((ffd = fopen(p, "rb")) == NULL) {
648          berrno be;
649          bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
650             p, be.strerror());
651          break;
652       }
653       while (fgets(file, sizeof(file), ffd)) {
654          line++;
655          if (dir) {
656             if (!insert_dir_into_findex_list(ua, rx, file, date)) {
657                bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
658             }
659          } else {
660             if (!insert_file_into_findex_list(ua, rx, file, date)) {
661                bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
662             }
663          }
664       }
665       fclose(ffd);
666       break;
667    case '?':
668       p++;
669       insert_table_into_findex_list(ua, rx, p);
670       break;
671    default:
672       if (dir) {
673          insert_dir_into_findex_list(ua, rx, ua->cmd, date);
674       } else {
675          insert_file_into_findex_list(ua, rx, ua->cmd, date);
676       }
677       break;
678    }
679 }
680
681 /*
682  * For a given file (path+filename), split into path and file, then
683  *   lookup the most recent backup in the catalog to get the JobId
684  *   and FileIndex, then insert them into the findex list.
685  */
686 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
687                                         char *date)
688 {
689    strip_trailing_newline(file);
690    split_path_and_filename(rx, file);
691    if (*rx->JobIds == 0) {
692       Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, 
693            rx->ClientName);
694    } else {
695       Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
696            rx->path, rx->fname, rx->ClientName);
697    }
698    rx->found = false;
699    /* Find and insert jobid and File Index */
700    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
701       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
702          rx->query, db_strerror(ua->db));
703    }
704    if (!rx->found) {
705       bsendmsg(ua, _("No database record found for: %s\n"), file);
706       return true;
707    }
708    return true;
709 }
710
711 /*
712  * For a given path lookup the most recent backup in the catalog
713  * to get the JobId and FileIndexes of all files in that directory.
714  */
715 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
716                                         char *date)
717 {
718    strip_trailing_junk(dir);
719    if (*rx->JobIds == 0) {
720       bsendmsg(ua, _("No JobId specified cannot continue.\n"));
721       return false;
722    } else {
723       Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds, 
724            dir, rx->ClientName);
725    }
726    rx->found = false;
727    /* Find and insert jobid and File Index */
728    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
729       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
730          rx->query, db_strerror(ua->db));
731    }
732    if (!rx->found) {
733       bsendmsg(ua, _("No database record found for: %s\n"), dir);
734       return true;
735    }
736    return true;
737 }
738
739 /*
740  * Get the JobId and FileIndexes of all files in the specified table
741  */
742 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
743 {
744    strip_trailing_junk(table);
745    Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
746
747    rx->found = false;
748    /* Find and insert jobid and File Index */
749    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
750       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
751          rx->query, db_strerror(ua->db));
752    }
753    if (!rx->found) {
754       bsendmsg(ua, _("No table found: %s\n"), table);
755       return true;
756    }
757    return true;
758 }
759
760 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
761 {
762    char *p, *f;
763
764    /* Find path without the filename.
765     * I.e. everything after the last / is a "filename".
766     * OK, maybe it is a directory name, but we treat it like
767     * a filename. If we don't find a / then the whole name
768     * must be a path name (e.g. c:).
769     */
770    for (p=f=name; *p; p++) {
771       if (*p == '/') {
772          f = p;                       /* set pos of last slash */
773       }
774    }
775    if (*f == '/') {                   /* did we find a slash? */
776       f++;                            /* yes, point to filename */
777    } else {                           /* no, whole thing must be path name */
778       f = p;
779    }
780
781    /* If filename doesn't exist (i.e. root directory), we
782     * simply create a blank name consisting of a single
783     * space. This makes handling zero length filenames
784     * easier.
785     */
786    rx->fnl = p - f;
787    if (rx->fnl > 0) {
788       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
789       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
790       rx->fname[rx->fnl] = 0;
791    } else {
792       rx->fname[0] = 0;
793       rx->fnl = 0;
794    }
795
796    rx->pnl = f - name;
797    if (rx->pnl > 0) {
798       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
799       memcpy(rx->path, name, rx->pnl);
800       rx->path[rx->pnl] = 0;
801    } else {
802       rx->path[0] = 0;
803       rx->pnl = 0;
804    }
805
806    Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
807 }
808
809 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
810 {
811    TREE_CTX tree;
812    JobId_t JobId, last_JobId;
813    char *p;
814    bool OK = true;
815    char ed1[50];
816
817    memset(&tree, 0, sizeof(TREE_CTX));
818    /*
819     * Build the directory tree containing JobIds user selected
820     */
821    tree.root = new_tree(rx->TotalFiles);
822    tree.ua = ua;
823    tree.all = rx->all;
824    last_JobId = 0;
825    /*
826     * For display purposes, the same JobId, with different volumes may
827     * appear more than once, however, we only insert it once.
828     */
829    int items = 0;
830    p = rx->JobIds;
831    tree.FileEstimate = 0;
832    if (get_next_jobid_from_list(&p, &JobId) > 0) {
833       /* Use first JobId as estimate of the number of files to restore */
834       Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
835       if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
836          bsendmsg(ua, "%s\n", db_strerror(ua->db));
837       }
838       if (rx->found) {
839          /* Add about 25% more than this job for over estimate */
840          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
841          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
842       }
843    }
844    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
845       char ed1[50];
846
847       if (JobId == last_JobId) {
848          continue;                    /* eliminate duplicate JobIds */
849       }
850       last_JobId = JobId;
851       bsendmsg(ua, _("\nBuilding directory tree for JobId %s ...  "), 
852          edit_int64(JobId, ed1));
853       items++;
854       /*
855        * Find files for this JobId and insert them in the tree
856        */
857       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
858       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
859          bsendmsg(ua, "%s", db_strerror(ua->db));
860       }
861    }
862    if (tree.FileCount == 0) {
863       bsendmsg(ua, _("\nThere were no files inserted into the tree, so file selection\n"
864          "is not possible.Most likely your retention policy pruned the files\n"));
865       if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
866          OK = false;
867       } else {
868          last_JobId = 0;
869          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
870              if (JobId == last_JobId) {
871                 continue;                    /* eliminate duplicate JobIds */
872              }
873              add_findex_all(rx->bsr, JobId);
874           }
875           OK = true;
876       }
877    } else {
878       char ec1[50];
879       if (items==1) {
880          if (tree.all) {
881             bsendmsg(ua, _("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
882               edit_uint64_with_commas(tree.FileCount, ec1));
883          }
884          else {
885             bsendmsg(ua, _("\n1 Job, %s files inserted into the tree.\n"),
886               edit_uint64_with_commas(tree.FileCount, ec1));
887          }
888       }
889       else {
890          if (tree.all) {
891             bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
892               items, edit_uint64_with_commas(tree.FileCount, ec1));
893          }
894          else {
895             bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree.\n"),
896               items, edit_uint64_with_commas(tree.FileCount, ec1));
897          }
898       }
899
900       if (find_arg(ua, NT_("done")) < 0) {
901          /* Let the user interact in selecting which files to restore */
902          OK = user_select_files_from_tree(&tree);
903       }
904
905       /*
906        * Walk down through the tree finding all files marked to be
907        *  extracted making a bootstrap file.
908        */
909       if (OK) {
910          for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
911             Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
912             if (node->extract || node->extract_dir) {
913                Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
914                add_findex(rx->bsr, node->JobId, node->FileIndex);
915                if (node->extract && node->type != TN_NEWDIR) {
916                   rx->selected_files++;  /* count only saved files */
917                }
918             }
919          }
920       }
921    }
922
923    free_tree(tree.root);              /* free the directory tree */
924    return OK;
925 }
926
927
928 /*
929  * This routine is used to get the current backup or a backup
930  *   before the specified date.
931  */
932 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
933 {
934    bool ok = false;
935    FILESET_DBR fsr;
936    CLIENT_DBR cr;
937    char fileset_name[MAX_NAME_LENGTH];
938    char ed1[50], ed2[50];
939    char pool_select[MAX_NAME_LENGTH];
940    int i;
941
942
943    /* Create temp tables */
944    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
945    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
946    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
947       bsendmsg(ua, "%s\n", db_strerror(ua->db));
948    }
949    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
950       bsendmsg(ua, "%s\n", db_strerror(ua->db));
951    }
952    /*
953     * Select Client from the Catalog
954     */
955    memset(&cr, 0, sizeof(cr));
956    if (!get_client_dbr(ua, &cr)) {
957       goto bail_out;
958    }
959    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
960
961    /*
962     * Get FileSet
963     */
964    memset(&fsr, 0, sizeof(fsr));
965    i = find_arg_with_value(ua, "FileSet");
966    if (i >= 0) {
967       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
968       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
969          bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
970             db_strerror(ua->db));
971          i = -1;
972       }
973    }
974    if (i < 0) {                       /* fileset not found */
975       edit_int64(cr.ClientId, ed1);
976       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
977       start_prompt(ua, _("The defined FileSet resources are:\n"));
978       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
979          bsendmsg(ua, "%s\n", db_strerror(ua->db));
980       }
981       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
982                  fileset_name, sizeof(fileset_name)) < 0) {
983          goto bail_out;
984       }
985
986       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
987       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
988          bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
989          bsendmsg(ua, _("This probably means you modified the FileSet.\n"
990                      "Continuing anyway.\n"));
991       }
992    }
993
994    /* If Pool specified, add PoolId specification */
995    pool_select[0] = 0;
996    if (rx->pool) {
997       POOL_DBR pr;
998       memset(&pr, 0, sizeof(pr));
999       bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1000       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1001          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
1002             edit_int64(pr.PoolId, ed1));
1003       } else {
1004          bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1005       }
1006    }
1007
1008    /* Find JobId of last Full backup for this client, fileset */
1009    edit_int64(cr.ClientId, ed1);
1010    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1011          pool_select);
1012    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1013       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1014       goto bail_out;
1015    }
1016
1017    /* Find all Volumes used by that JobId */
1018    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1019       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1020       goto bail_out;
1021    }
1022    /* Note, this is needed because I don't seem to get the callback
1023     * from the call just above.
1024     */
1025    rx->JobTDate = 0;
1026    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1027       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1028    }
1029    if (rx->JobTDate == 0) {
1030       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1031       goto bail_out;
1032    }
1033
1034    /* Now find most recent Differental Job after Full save, if any */
1035    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1036         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1037    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1038       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1039    }
1040    /* Now update JobTDate to lock onto Differental, if any */
1041    rx->JobTDate = 0;
1042    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1043       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1044    }
1045    if (rx->JobTDate == 0) {
1046       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1047       goto bail_out;
1048    }
1049
1050    /* Now find all Incremental Jobs after Full/dif save */
1051    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1052         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1053    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1054       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1055    }
1056
1057    /* Get the JobIds from that list */
1058    rx->JobIds[0] = 0;
1059    rx->last_jobid[0] = 0;
1060    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1061       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1062    }
1063
1064    if (rx->JobIds[0] != 0) {
1065       /* Display a list of Jobs selected for this restore */
1066       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1067       ok = true;
1068    } else {
1069       bsendmsg(ua, _("No jobs found.\n"));
1070    }
1071
1072 bail_out:
1073    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1074    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1075    return ok;
1076 }
1077
1078
1079 /* 
1080  * Return next JobId from comma separated list   
1081  *
1082  * Returns:
1083  *   1 if next JobId returned
1084  *   0 if no more JobIds are in list
1085  *  -1 there is an error
1086  */
1087 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1088 {
1089    char jobid[30];
1090    char *q = *p;
1091
1092    jobid[0] = 0;
1093    for (int i=0; i<(int)sizeof(jobid); i++) {
1094       if (*q == 0) {
1095          break;
1096       } else if (*q == ',') {
1097          q++;
1098          break;
1099       }
1100       jobid[i] = *q++;
1101       jobid[i+1] = 0;
1102    }
1103    if (jobid[0] == 0) {
1104       return 0;
1105    } else if (!is_a_number(jobid)) {
1106       return -1;                      /* error */
1107    }
1108    *p = q;
1109    *JobId = str_to_int64(jobid);
1110    return 1;
1111 }
1112
1113 static int count_handler(void *ctx, int num_fields, char **row)
1114 {
1115    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1116    rx->JobId = str_to_int64(row[0]);
1117    rx->found = true;
1118    return 0;
1119 }
1120
1121 /*
1122  * Callback handler to get JobId and FileIndex for files
1123  *   can insert more than one depending on the caller.
1124  */
1125 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1126 {
1127    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1128    rx->JobId = str_to_int64(row[0]);
1129    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1130    rx->found = true;
1131    rx->selected_files++;
1132    return 0;
1133 }
1134
1135 /*
1136  * Callback handler make list of JobIds
1137  */
1138 static int jobid_handler(void *ctx, int num_fields, char **row)
1139 {
1140    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1141
1142    if (strcmp(rx->last_jobid, row[0]) == 0) {
1143       return 0;                       /* duplicate id */
1144    }
1145    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1146    if (rx->JobIds[0] != 0) {
1147       pm_strcat(rx->JobIds, ",");
1148    }
1149    pm_strcat(rx->JobIds, row[0]);
1150    return 0;
1151 }
1152
1153
1154 /*
1155  * Callback handler to pickup last Full backup JobTDate
1156  */
1157 static int last_full_handler(void *ctx, int num_fields, char **row)
1158 {
1159    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1160
1161    rx->JobTDate = str_to_int64(row[1]);
1162    return 0;
1163 }
1164
1165 /*
1166  * Callback handler build FileSet name prompt list
1167  */
1168 static int fileset_handler(void *ctx, int num_fields, char **row)
1169 {
1170    /* row[0] = FileSet (name) */
1171    if (row[0]) {
1172       add_prompt((UAContext *)ctx, row[0]);
1173    }
1174    return 0;
1175 }
1176
1177 /*
1178  * Free names in the list
1179  */
1180 static void free_name_list(NAME_LIST *name_list)
1181 {
1182    for (int i=0; i < name_list->num_ids; i++) {
1183       free(name_list->name[i]);
1184    }
1185    if (name_list->name) {
1186       free(name_list->name);
1187       name_list->name = NULL;
1188    }
1189    name_list->max_ids = 0;
1190    name_list->num_ids = 0;
1191 }
1192
1193 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType) 
1194 {
1195    STORE *store;
1196
1197    if (rx.store) {
1198       Dmsg1(200, "Already have store=%s\n", rx.store->name());
1199       return;
1200    }
1201    /*
1202     * Try looking up Storage by name
1203     */
1204    LockRes();
1205    foreach_res(store, R_STORAGE) {
1206       if (strcmp(Storage, store->name()) == 0) {
1207          if (acl_access_ok(ua, Storage_ACL, store->name())) {
1208             rx.store = store;
1209          }
1210          break;
1211       }
1212    }
1213    UnlockRes();
1214
1215    if (rx.store) {
1216       /* Check if an explicit storage resource is given */
1217       store = NULL;
1218       int i = find_arg_with_value(ua, "storage");
1219       if (i > 0) {
1220          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1221          if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1222             store = NULL;
1223          }
1224       }
1225       if (store && (store != rx.store)) {
1226          bsendmsg(ua, _("Warning default storage overridden by \"%s\" on command line.\n"),
1227             store->name());
1228          rx.store = store;
1229          Dmsg1(200, "Set store=%s\n", rx.store->name());
1230       }
1231       return;
1232    }
1233
1234    /* If no storage resource, try to find one from MediaType */
1235    if (!rx.store) {
1236       LockRes();
1237       foreach_res(store, R_STORAGE) {
1238          if (strcmp(MediaType, store->media_type) == 0) {
1239             if (acl_access_ok(ua, Storage_ACL, store->name())) {
1240                rx.store = store;
1241                Dmsg1(200, "Set store=%s\n", rx.store->name());
1242                bsendmsg(ua, _("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1243                   Storage, store->name(), MediaType);
1244             }
1245             UnlockRes();
1246             return;
1247          }
1248       }
1249       UnlockRes();
1250       bsendmsg(ua, _("\nUnable to find Storage resource for\n"
1251          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1252    }
1253
1254    /* Take command line arg, or ask user if none */
1255    rx.store = get_storage_resource(ua, false /* don't use default */);
1256    Dmsg1(200, "Set store=%s\n", rx.store->name());
1257
1258 }