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