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