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