]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
07f00a0c3d10f99e0c90a6a303f9ec4af2312dcb
[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          /* ***FIXME*** restrict clients on restricted console */
418          gui_save = ua->jcr->gui;
419          ua->jcr->gui = true;
420          db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
421          ua->jcr->gui = gui_save;
422          done = false;
423          break;
424       case 1:                         /* list where a file is saved */
425          if (!get_client_name(ua, rx)) {
426             return 0;
427          }
428          if (!get_cmd(ua, _("Enter Filename (no path):"))) {
429             return 0;
430          }
431          len = strlen(ua->cmd);
432          fname = (char *)malloc(len * 2 + 1);
433          db_escape_string(fname, ua->cmd, len);
434          Mmsg(rx->query, uar_file, rx->ClientName, fname);
435          free(fname);
436          gui_save = ua->jcr->gui;
437          ua->jcr->gui = true;
438          db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
439          ua->jcr->gui = gui_save;
440          done = false;
441          break;
442       case 2:                         /* enter a list of JobIds */
443          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
444             return 0;
445          }
446          pm_strcpy(rx->JobIds, ua->cmd);
447          break;
448       case 3:                         /* Enter an SQL list command */
449          if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
450             return 0;
451          }
452          if (!get_cmd(ua, _("Enter SQL list command: "))) {
453             return 0;
454          }
455          gui_save = ua->jcr->gui;
456          ua->jcr->gui = true;
457          db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
458          ua->jcr->gui = gui_save;
459          done = false;
460          break;
461       case 4:                         /* Select the most recent backups */
462          bstrutime(date, sizeof(date), time(NULL));
463          if (!select_backups_before_date(ua, rx, date)) {
464             return 0;
465          }
466          break;
467       case 5:                         /* select backup at specified time */
468          if (!get_date(ua, date, sizeof(date))) {
469             return 0;
470          }
471          if (!select_backups_before_date(ua, rx, date)) {
472             return 0;
473          }
474          break;
475       case 6:                         /* Enter files */
476          bstrutime(date, sizeof(date), time(NULL));
477          if (!get_client_name(ua, rx)) {
478             return 0;
479          }
480          bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
481                         "containing a list of file names with paths, and terminate\n"
482                         "them with a blank line.\n"));
483          for ( ;; ) {
484             if (!get_cmd(ua, _("Enter full filename: "))) {
485                return 0;
486             }
487             len = strlen(ua->cmd);
488             if (len == 0) {
489                break;
490             }
491             insert_one_file_or_dir(ua, rx, date, false);
492          }
493          return 2;
494        case 7:                        /* enter files backed up before specified time */
495          if (!get_date(ua, date, sizeof(date))) {
496             return 0;
497          }
498          if (!get_client_name(ua, rx)) {
499             return 0;
500          }
501          bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
502                         "containing a list of file names with paths, and terminate\n"
503                         "them with a blank line.\n"));
504          for ( ;; ) {
505             if (!get_cmd(ua, _("Enter full filename: "))) {
506                return 0;
507             }
508             len = strlen(ua->cmd);
509             if (len == 0) {
510                break;
511             }
512             insert_one_file_or_dir(ua, rx, date, false);
513          }
514          return 2;
515
516       case 8:                         /* Find JobIds for current backup */
517          bstrutime(date, sizeof(date), time(NULL));
518          if (!select_backups_before_date(ua, rx, date)) {
519             return 0;
520          }
521          done = false;
522          break;
523
524       case 9:                         /* Find JobIds for give date */
525          if (!get_date(ua, date, sizeof(date))) {
526             return 0;
527          }
528          if (!select_backups_before_date(ua, rx, date)) {
529             return 0;
530          }
531          done = false;
532          break;
533
534       case 10:                        /* Enter directories */
535          if (*rx->JobIds != 0) {
536             bsendmsg(ua, _("You have already seleted the following JobIds: %s\n"),
537                rx->JobIds);
538          } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
539             if (*rx->JobIds != 0 && *ua->cmd) {
540                pm_strcat(rx->JobIds, ",");
541             }
542             pm_strcat(rx->JobIds, ua->cmd);
543          }
544          if (*rx->JobIds == 0 || *rx->JobIds == '.') {
545             return 0;                 /* nothing entered, return */
546          }
547          bstrutime(date, sizeof(date), time(NULL));
548          if (!get_client_name(ua, rx)) {
549             return 0;
550          }
551          bsendmsg(ua, _("Enter full directory names or start the name\n"
552                         "with a < to indicate it is a filename containing a list\n"
553                         "of directories and terminate them with a blank line.\n"));
554          for ( ;; ) {
555             if (!get_cmd(ua, _("Enter directory name: "))) {
556                return 0;
557             }
558             len = strlen(ua->cmd);
559             if (len == 0) {
560                break;
561             }
562             /* Add trailing slash to end of directory names */
563             if (ua->cmd[0] != '<' && ua->cmd[len-1] != '/') {
564                strcat(ua->cmd, "/");
565             }
566             insert_one_file_or_dir(ua, rx, date, true);
567          }
568          return 2;
569
570       case 11:                        /* Cancel or quit */
571          return 0;
572       }
573    }
574
575    if (*rx->JobIds == 0) {
576       bsendmsg(ua, _("No Jobs selected.\n"));
577       return 0;
578    }
579    if (strchr(rx->JobIds,',')) {
580       bsendmsg(ua, _("You have selected the following JobIds: %s\n"), rx->JobIds);
581    }
582    else {
583       bsendmsg(ua, _("You have selected the following JobId: %s\n"), rx->JobIds);
584    }
585
586
587    rx->TotalFiles = 0;
588    for (p=rx->JobIds; ; ) {
589       int stat = get_next_jobid_from_list(&p, &JobId);
590       if (stat < 0) {
591          bsendmsg(ua, _("Invalid JobId in list.\n"));
592          return 0;
593       }
594       if (stat == 0) {
595          break;
596       }
597       if (jr.JobId == JobId) {
598          continue;                    /* duplicate of last JobId */
599       }
600       memset(&jr, 0, sizeof(JOB_DBR));
601       jr.JobId = JobId;
602       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
603          char ed1[50];
604          bsendmsg(ua, _("Unable to get Job record for JobId=%s: ERR=%s\n"),
605             edit_int64(JobId, ed1), db_strerror(ua->db));
606          return 0;
607       }
608       if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
609          bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
610             jr.Name);
611          continue;
612       }
613       rx->TotalFiles += jr.JobFiles;
614    }
615    return 1;
616 }
617
618 /*
619  * Get date from user
620  */
621 static int get_date(UAContext *ua, char *date, int date_len)
622 {
623    bsendmsg(ua, _("The restored files will the most current backup\n"
624                   "BEFORE the date you specify below.\n\n"));
625    for ( ;; ) {
626       if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
627          return 0;
628       }
629       if (str_to_utime(ua->cmd) != 0) {
630          break;
631       }
632       bsendmsg(ua, _("Improper date format.\n"));
633    }
634    bstrncpy(date, ua->cmd, date_len);
635    return 1;
636 }
637
638 /*
639  * Insert a single file, or read a list of files from a file
640  */
641 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
642 {
643    FILE *ffd;
644    char file[5000];
645    char *p = ua->cmd;
646    int line = 0;
647
648    switch (*p) {
649    case '<':
650       p++;
651       if ((ffd = fopen(p, "rb")) == NULL) {
652          berrno be;
653          bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
654             p, be.strerror());
655          break;
656       }
657       while (fgets(file, sizeof(file), ffd)) {
658          line++;
659          if (dir) {
660             if (!insert_dir_into_findex_list(ua, rx, file, date)) {
661                bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
662             }
663          } else {
664             if (!insert_file_into_findex_list(ua, rx, file, date)) {
665                bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
666             }
667          }
668       }
669       fclose(ffd);
670       break;
671    case '?':
672       p++;
673       insert_table_into_findex_list(ua, rx, p);
674       break;
675    default:
676       if (dir) {
677          insert_dir_into_findex_list(ua, rx, ua->cmd, date);
678       } else {
679          insert_file_into_findex_list(ua, rx, ua->cmd, date);
680       }
681       break;
682    }
683 }
684
685 /*
686  * For a given file (path+filename), split into path and file, then
687  *   lookup the most recent backup in the catalog to get the JobId
688  *   and FileIndex, then insert them into the findex list.
689  */
690 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
691                                         char *date)
692 {
693    strip_trailing_newline(file);
694    split_path_and_filename(rx, file);
695    if (*rx->JobIds == 0) {
696       Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, 
697            rx->ClientName);
698    } else {
699       Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
700            rx->path, rx->fname, rx->ClientName);
701    }
702    rx->found = false;
703    /* Find and insert jobid and File Index */
704    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
705       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
706          rx->query, db_strerror(ua->db));
707    }
708    if (!rx->found) {
709       bsendmsg(ua, _("No database record found for: %s\n"), file);
710       return true;
711    }
712    return true;
713 }
714
715 /*
716  * For a given path lookup the most recent backup in the catalog
717  * to get the JobId and FileIndexes of all files in that directory.
718  */
719 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
720                                         char *date)
721 {
722    strip_trailing_junk(dir);
723    if (*rx->JobIds == 0) {
724       bsendmsg(ua, _("No JobId specified cannot continue.\n"));
725       return false;
726    } else {
727       Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds, 
728            dir, rx->ClientName);
729    }
730    rx->found = false;
731    /* Find and insert jobid and File Index */
732    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
733       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
734          rx->query, db_strerror(ua->db));
735    }
736    if (!rx->found) {
737       bsendmsg(ua, _("No database record found for: %s\n"), dir);
738       return true;
739    }
740    return true;
741 }
742
743 /*
744  * Get the JobId and FileIndexes of all files in the specified table
745  */
746 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
747 {
748    strip_trailing_junk(table);
749    Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
750
751    rx->found = false;
752    /* Find and insert jobid and File Index */
753    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
754       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
755          rx->query, db_strerror(ua->db));
756    }
757    if (!rx->found) {
758       bsendmsg(ua, _("No table found: %s\n"), table);
759       return true;
760    }
761    return true;
762 }
763
764 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
765 {
766    char *p, *f;
767
768    /* Find path without the filename.
769     * I.e. everything after the last / is a "filename".
770     * OK, maybe it is a directory name, but we treat it like
771     * a filename. If we don't find a / then the whole name
772     * must be a path name (e.g. c:).
773     */
774    for (p=f=name; *p; p++) {
775       if (*p == '/') {
776          f = p;                       /* set pos of last slash */
777       }
778    }
779    if (*f == '/') {                   /* did we find a slash? */
780       f++;                            /* yes, point to filename */
781    } else {                           /* no, whole thing must be path name */
782       f = p;
783    }
784
785    /* If filename doesn't exist (i.e. root directory), we
786     * simply create a blank name consisting of a single
787     * space. This makes handling zero length filenames
788     * easier.
789     */
790    rx->fnl = p - f;
791    if (rx->fnl > 0) {
792       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
793       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
794       rx->fname[rx->fnl] = 0;
795    } else {
796       rx->fname[0] = 0;
797       rx->fnl = 0;
798    }
799
800    rx->pnl = f - name;
801    if (rx->pnl > 0) {
802       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
803       memcpy(rx->path, name, rx->pnl);
804       rx->path[rx->pnl] = 0;
805    } else {
806       rx->path[0] = 0;
807       rx->pnl = 0;
808    }
809
810    Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
811 }
812
813 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
814 {
815    TREE_CTX tree;
816    JobId_t JobId, last_JobId;
817    char *p;
818    bool OK = true;
819    char ed1[50];
820
821    memset(&tree, 0, sizeof(TREE_CTX));
822    /*
823     * Build the directory tree containing JobIds user selected
824     */
825    tree.root = new_tree(rx->TotalFiles);
826    tree.ua = ua;
827    tree.all = rx->all;
828    last_JobId = 0;
829    /*
830     * For display purposes, the same JobId, with different volumes may
831     * appear more than once, however, we only insert it once.
832     */
833    int items = 0;
834    p = rx->JobIds;
835    tree.FileEstimate = 0;
836    if (get_next_jobid_from_list(&p, &JobId) > 0) {
837       /* Use first JobId as estimate of the number of files to restore */
838       Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
839       if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
840          bsendmsg(ua, "%s\n", db_strerror(ua->db));
841       }
842       if (rx->found) {
843          /* Add about 25% more than this job for over estimate */
844          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
845          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
846       }
847    }
848    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
849       char ed1[50];
850
851       if (JobId == last_JobId) {
852          continue;                    /* eliminate duplicate JobIds */
853       }
854       last_JobId = JobId;
855       bsendmsg(ua, _("\nBuilding directory tree for JobId %s ...  "), 
856          edit_int64(JobId, ed1));
857       items++;
858       /*
859        * Find files for this JobId and insert them in the tree
860        */
861       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
862       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
863          bsendmsg(ua, "%s", db_strerror(ua->db));
864       }
865    }
866    if (tree.FileCount == 0) {
867       bsendmsg(ua, _("\nThere were no files inserted into the tree, so file selection\n"
868          "is not possible.Most likely your retention policy pruned the files\n"));
869       if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
870          OK = false;
871       } else {
872          last_JobId = 0;
873          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
874              if (JobId == last_JobId) {
875                 continue;                    /* eliminate duplicate JobIds */
876              }
877              add_findex_all(rx->bsr, JobId);
878           }
879           OK = true;
880       }
881    } else {
882       char ec1[50];
883       if (items==1) {
884          if (tree.all) {
885             bsendmsg(ua, _("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
886               edit_uint64_with_commas(tree.FileCount, ec1));
887          }
888          else {
889             bsendmsg(ua, _("\n1 Job, %s files inserted into the tree.\n"),
890               edit_uint64_with_commas(tree.FileCount, ec1));
891          }
892       }
893       else {
894          if (tree.all) {
895             bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
896               items, edit_uint64_with_commas(tree.FileCount, ec1));
897          }
898          else {
899             bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree.\n"),
900               items, edit_uint64_with_commas(tree.FileCount, ec1));
901          }
902       }
903
904       if (find_arg(ua, NT_("done")) < 0) {
905          /* Let the user interact in selecting which files to restore */
906          OK = user_select_files_from_tree(&tree);
907       }
908
909       /*
910        * Walk down through the tree finding all files marked to be
911        *  extracted making a bootstrap file.
912        */
913       if (OK) {
914          for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
915             Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
916             if (node->extract || node->extract_dir) {
917                Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
918                add_findex(rx->bsr, node->JobId, node->FileIndex);
919                if (node->extract && node->type != TN_NEWDIR) {
920                   rx->selected_files++;  /* count only saved files */
921                }
922             }
923          }
924       }
925    }
926
927    free_tree(tree.root);              /* free the directory tree */
928    return OK;
929 }
930
931
932 /*
933  * This routine is used to get the current backup or a backup
934  *   before the specified date.
935  */
936 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
937 {
938    bool ok = false;
939    FILESET_DBR fsr;
940    CLIENT_DBR cr;
941    char fileset_name[MAX_NAME_LENGTH];
942    char ed1[50], ed2[50];
943    char pool_select[MAX_NAME_LENGTH];
944    int i;
945
946
947    /* Create temp tables */
948    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
949    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
950    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
951       bsendmsg(ua, "%s\n", db_strerror(ua->db));
952    }
953    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
954       bsendmsg(ua, "%s\n", db_strerror(ua->db));
955    }
956    /*
957     * Select Client from the Catalog
958     */
959    memset(&cr, 0, sizeof(cr));
960    if (!get_client_dbr(ua, &cr)) {
961       goto bail_out;
962    }
963    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
964
965    /*
966     * Get FileSet
967     */
968    memset(&fsr, 0, sizeof(fsr));
969    i = find_arg_with_value(ua, "FileSet");
970    if (i >= 0) {
971       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
972       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
973          bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
974             db_strerror(ua->db));
975          i = -1;
976       }
977    }
978    if (i < 0) {                       /* fileset not found */
979       edit_int64(cr.ClientId, ed1);
980       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
981       start_prompt(ua, _("The defined FileSet resources are:\n"));
982       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
983          bsendmsg(ua, "%s\n", db_strerror(ua->db));
984       }
985       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
986                  fileset_name, sizeof(fileset_name)) < 0) {
987          goto bail_out;
988       }
989
990       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
991       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
992          bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
993          bsendmsg(ua, _("This probably means you modified the FileSet.\n"
994                      "Continuing anyway.\n"));
995       }
996    }
997
998    /* If Pool specified, add PoolId specification */
999    pool_select[0] = 0;
1000    if (rx->pool) {
1001       POOL_DBR pr;
1002       memset(&pr, 0, sizeof(pr));
1003       bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1004       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1005          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
1006             edit_int64(pr.PoolId, ed1));
1007       } else {
1008          bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1009       }
1010    }
1011
1012    /* Find JobId of last Full backup for this client, fileset */
1013    edit_int64(cr.ClientId, ed1);
1014    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1015          pool_select);
1016    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1017       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1018       goto bail_out;
1019    }
1020
1021    /* Find all Volumes used by that JobId */
1022    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1023       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1024       goto bail_out;
1025    }
1026    /* Note, this is needed because I don't seem to get the callback
1027     * from the call just above.
1028     */
1029    rx->JobTDate = 0;
1030    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1031       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1032    }
1033    if (rx->JobTDate == 0) {
1034       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1035       goto bail_out;
1036    }
1037
1038    /* Now find most recent Differental Job after Full save, if any */
1039    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1040         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1041    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1042       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1043    }
1044    /* Now update JobTDate to lock onto Differental, if any */
1045    rx->JobTDate = 0;
1046    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1047       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1048    }
1049    if (rx->JobTDate == 0) {
1050       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1051       goto bail_out;
1052    }
1053
1054    /* Now find all Incremental Jobs after Full/dif save */
1055    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1056         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1057    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1058       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1059    }
1060
1061    /* Get the JobIds from that list */
1062    rx->JobIds[0] = 0;
1063    rx->last_jobid[0] = 0;
1064    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1065       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1066    }
1067
1068    if (rx->JobIds[0] != 0) {
1069       /* Display a list of Jobs selected for this restore */
1070       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1071       ok = true;
1072    } else {
1073       bsendmsg(ua, _("No jobs found.\n"));
1074    }
1075
1076 bail_out:
1077    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1078    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1079    return ok;
1080 }
1081
1082
1083 /* 
1084  * Return next JobId from comma separated list   
1085  *
1086  * Returns:
1087  *   1 if next JobId returned
1088  *   0 if no more JobIds are in list
1089  *  -1 there is an error
1090  */
1091 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1092 {
1093    char jobid[30];
1094    char *q = *p;
1095
1096    jobid[0] = 0;
1097    for (int i=0; i<(int)sizeof(jobid); i++) {
1098       if (*q == 0) {
1099          break;
1100       } else if (*q == ',') {
1101          q++;
1102          break;
1103       }
1104       jobid[i] = *q++;
1105       jobid[i+1] = 0;
1106    }
1107    if (jobid[0] == 0) {
1108       return 0;
1109    } else if (!is_a_number(jobid)) {
1110       return -1;                      /* error */
1111    }
1112    *p = q;
1113    *JobId = str_to_int64(jobid);
1114    return 1;
1115 }
1116
1117 static int count_handler(void *ctx, int num_fields, char **row)
1118 {
1119    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1120    rx->JobId = str_to_int64(row[0]);
1121    rx->found = true;
1122    return 0;
1123 }
1124
1125 /*
1126  * Callback handler to get JobId and FileIndex for files
1127  *   can insert more than one depending on the caller.
1128  */
1129 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1130 {
1131    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1132    rx->JobId = str_to_int64(row[0]);
1133    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1134    rx->found = true;
1135    rx->selected_files++;
1136    return 0;
1137 }
1138
1139 /*
1140  * Callback handler make list of JobIds
1141  */
1142 static int jobid_handler(void *ctx, int num_fields, char **row)
1143 {
1144    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1145
1146    if (strcmp(rx->last_jobid, row[0]) == 0) {
1147       return 0;                       /* duplicate id */
1148    }
1149    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1150    if (rx->JobIds[0] != 0) {
1151       pm_strcat(rx->JobIds, ",");
1152    }
1153    pm_strcat(rx->JobIds, row[0]);
1154    return 0;
1155 }
1156
1157
1158 /*
1159  * Callback handler to pickup last Full backup JobTDate
1160  */
1161 static int last_full_handler(void *ctx, int num_fields, char **row)
1162 {
1163    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1164
1165    rx->JobTDate = str_to_int64(row[1]);
1166    return 0;
1167 }
1168
1169 /*
1170  * Callback handler build FileSet name prompt list
1171  */
1172 static int fileset_handler(void *ctx, int num_fields, char **row)
1173 {
1174    /* row[0] = FileSet (name) */
1175    if (row[0]) {
1176       add_prompt((UAContext *)ctx, row[0]);
1177    }
1178    return 0;
1179 }
1180
1181 /*
1182  * Free names in the list
1183  */
1184 static void free_name_list(NAME_LIST *name_list)
1185 {
1186    for (int i=0; i < name_list->num_ids; i++) {
1187       free(name_list->name[i]);
1188    }
1189    if (name_list->name) {
1190       free(name_list->name);
1191       name_list->name = NULL;
1192    }
1193    name_list->max_ids = 0;
1194    name_list->num_ids = 0;
1195 }
1196
1197 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType) 
1198 {
1199    STORE *store;
1200
1201    if (rx.store) {
1202       Dmsg1(200, "Already have store=%s\n", rx.store->name());
1203       return;
1204    }
1205    /*
1206     * Try looking up Storage by name
1207     */
1208    LockRes();
1209    foreach_res(store, R_STORAGE) {
1210       if (strcmp(Storage, store->name()) == 0) {
1211          if (acl_access_ok(ua, Storage_ACL, store->name())) {
1212             rx.store = store;
1213          }
1214          break;
1215       }
1216    }
1217    UnlockRes();
1218
1219    if (rx.store) {
1220       /* Check if an explicit storage resource is given */
1221       store = NULL;
1222       int i = find_arg_with_value(ua, "storage");
1223       if (i > 0) {
1224          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1225          if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1226             store = NULL;
1227          }
1228       }
1229       if (store && (store != rx.store)) {
1230          bsendmsg(ua, _("Warning default storage overridden by \"%s\" on command line.\n"),
1231             store->name());
1232          rx.store = store;
1233          Dmsg1(200, "Set store=%s\n", rx.store->name());
1234       }
1235       return;
1236    }
1237
1238    /* If no storage resource, try to find one from MediaType */
1239    if (!rx.store) {
1240       LockRes();
1241       foreach_res(store, R_STORAGE) {
1242          if (strcmp(MediaType, store->media_type) == 0) {
1243             if (acl_access_ok(ua, Storage_ACL, store->name())) {
1244                rx.store = store;
1245                Dmsg1(200, "Set store=%s\n", rx.store->name());
1246                bsendmsg(ua, _("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1247                   Storage, store->name(), MediaType);
1248             }
1249             UnlockRes();
1250             return;
1251          }
1252       }
1253       UnlockRes();
1254       bsendmsg(ua, _("\nUnable to find Storage resource for\n"
1255          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1256    }
1257
1258    /* Take command line arg, or ask user if none */
1259    rx.store = get_storage_resource(ua, false /* don't use default */);
1260    Dmsg1(200, "Set store=%s\n", rx.store->name());
1261
1262 }