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