]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
kes Print the Volume purged message only for real jobs to keep
[bacula/bacula] / bacula / src / dird / ua_restore.c
1 /*
2  *
3  *   Bacula Director -- User Agent Database restore Command
4  *      Creates a bootstrap file for restoring files and
5  *      starts the restore job.
6  *
7  *      Tree handling routines split into ua_tree.c July MMIII.
8  *      BSR (bootstrap record) handling routines split into
9  *        bsr.c July MMIII
10  *
11  *     Kern Sibbald, July MMII
12  *
13  *   Version $Id$
14  */
15 /*
16    Copyright (C) 2002-2006 Kern Sibbald
17
18    This program is free software; you can redistribute it and/or
19    modify it under the terms of the GNU General Public License
20    version 2 as amended with additional clauses defined in the
21    file LICENSE in the main source directory.
22
23    This program is distributed in the hope that it will be useful,
24    but WITHOUT ANY WARRANTY; without even the implied warranty of
25    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
26    the file LICENSE for additional details.
27
28  */
29
30
31 #include "bacula.h"
32 #include "dird.h"
33
34
35 /* Imported functions */
36 extern void print_bsr(UAContext *ua, RBSR *bsr);
37
38
39
40 /* Forward referenced functions */
41 static int last_full_handler(void *ctx, int num_fields, char **row);
42 static int jobid_handler(void *ctx, int num_fields, char **row);
43 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
44 static int fileset_handler(void *ctx, int num_fields, char **row);
45 static void free_name_list(NAME_LIST *name_list);
46 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
47 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
48 static void free_rx(RESTORE_CTX *rx);
49 static void split_path_and_filename(RESTORE_CTX *rx, char *fname);
50 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row);
51 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
52                                          char *date);
53 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
54                                         char *date);
55 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir);
56 static int get_client_name(UAContext *ua, RESTORE_CTX *rx);
57 static int get_date(UAContext *ua, char *date, int date_len);
58 static int count_handler(void *ctx, int num_fields, char **row);
59 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table);
60
61 /*
62  *   Restore files
63  *
64  */
65 int restore_cmd(UAContext *ua, const char *cmd)
66 {
67    RESTORE_CTX rx;                    /* restore context */
68    JOB *job;
69    int i;
70    JCR *jcr = ua->jcr;
71
72    memset(&rx, 0, sizeof(rx));
73    rx.path = get_pool_memory(PM_FNAME);
74    rx.fname = get_pool_memory(PM_FNAME);
75    rx.JobIds = get_pool_memory(PM_FNAME);
76    rx.query = get_pool_memory(PM_FNAME);
77    rx.bsr = new_bsr();
78
79    i = find_arg_with_value(ua, "where");
80    if (i >= 0) {
81       rx.where = ua->argv[i];
82       if (!acl_access_ok(ua, Where_ACL, rx.where)) {
83          bsendmsg(ua, _("Forbidden \"where\" specified.\n"));
84          goto bail_out;
85       }
86    }
87
88    if (!open_db(ua)) {
89       goto bail_out;
90    }
91
92    /* Ensure there is at least one Restore Job */
93    LockRes();
94    foreach_res(job, R_JOB) {
95       if (job->JobType == JT_RESTORE) {
96          if (!rx.restore_job) {
97             rx.restore_job = job;
98          }
99          rx.restore_jobs++;
100       }
101    }
102    UnlockRes();
103    if (!rx.restore_jobs) {
104       bsendmsg(ua, _(
105          "No Restore Job Resource found in bacula-dir.conf.\n"
106          "You must create at least one before running this command.\n"));
107       goto bail_out;
108    }
109
110    /*
111     * Request user to select JobIds or files by various different methods
112     *  last 20 jobs, where File saved, most recent backup, ...
113     *  In the end, a list of files are pumped into
114     *  add_findex()
115     */
116    switch (user_select_jobids_or_files(ua, &rx)) {
117    case 0:                            /* error */
118       goto bail_out;
119    case 1:                            /* selected by jobid */
120       if (!build_directory_tree(ua, &rx)) {
121          bsendmsg(ua, _("Restore not done.\n"));
122          goto bail_out;
123       }
124       break;
125    case 2:                            /* selected by filename, no tree needed */
126       break;
127    }
128
129    if (rx.bsr->JobId) {
130       uint32_t selected_files;
131       if (!complete_bsr(ua, rx.bsr)) {   /* find Vol, SessId, SessTime from JobIds */
132          bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
133          goto bail_out;
134       }
135       if (!(selected_files = write_bsr_file(ua, rx))) {
136          bsendmsg(ua, _("No files selected to be restored.\n"));
137          goto bail_out;
138       }
139       /* If no count of files, use bsr generated value (often wrong) */
140       if (rx.selected_files == 0) {
141          rx.selected_files = selected_files;
142       }
143       if (rx.selected_files==1) {
144          bsendmsg(ua, _("\n1 file selected to be restored.\n\n"));
145       }
146       else {
147          bsendmsg(ua, _("\n%u files selected to be restored.\n\n"), rx.selected_files);
148       }
149    } else {
150       bsendmsg(ua, _("No files selected to be restored.\n"));
151       goto bail_out;
152    }
153
154    if (rx.restore_jobs == 1) {
155       job = rx.restore_job;
156    } else {
157       job = select_restore_job_resource(ua);
158    }
159    if (!job) {
160       goto bail_out;
161    }
162
163    get_client_name(ua, &rx);
164    if (!rx.ClientName) {
165       bsendmsg(ua, _("No Restore Job resource found!\n"));
166       goto bail_out;
167    }
168
169    /* Build run command */
170    if (rx.where) {
171       if (!acl_access_ok(ua, Where_ACL, rx.where)) {
172          bsendmsg(ua, _("Forbidden \"where\" specified.\n"));
173          goto bail_out;
174       }
175       Mmsg(ua->cmd,
176           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
177           " where=\"%s\" files=%d catalog=\"%s\"",
178           job->name(), rx.ClientName, rx.store?rx.store->name():"",
179           jcr->RestoreBootstrap, rx.where, rx.selected_files, ua->catalog->name());
180    } else {
181       Mmsg(ua->cmd,
182           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s\""
183           " files=%d catalog=\"%s\"",
184           job->name(), rx.ClientName, rx.store?rx.store->name():"",
185           jcr->RestoreBootstrap, rx.selected_files, ua->catalog->name());
186    }
187    if (find_arg(ua, NT_("yes")) > 0) {
188       pm_strcat(ua->cmd, " yes");    /* pass it on to the run command */
189    }
190    Dmsg1(200, "Submitting: %s\n", ua->cmd);
191    parse_ua_args(ua);
192    run_cmd(ua, ua->cmd);
193    free_rx(&rx);
194    return 1;
195
196 bail_out:
197    free_rx(&rx);
198    return 0;
199
200 }
201
202 static void free_rx(RESTORE_CTX *rx)
203 {
204    free_bsr(rx->bsr);
205    rx->bsr = NULL;
206    if (rx->JobIds) {
207       free_pool_memory(rx->JobIds);
208       rx->JobIds = NULL;
209    }
210    if (rx->fname) {
211       free_pool_memory(rx->fname);
212       rx->fname = NULL;
213    }
214    if (rx->path) {
215       free_pool_memory(rx->path);
216       rx->path = NULL;
217    }
218    if (rx->query) {
219       free_pool_memory(rx->query);
220       rx->query = NULL;
221    }
222    free_name_list(&rx->name_list);
223 }
224
225 static bool has_value(UAContext *ua, int i)
226 {
227    if (!ua->argv[i]) {
228       bsendmsg(ua, _("Missing value for keyword: %s\n"), ua->argk[i]);
229       return false;
230    }
231    return true;
232 }
233
234 static int get_client_name(UAContext *ua, RESTORE_CTX *rx)
235 {
236    /* If no client name specified yet, get it now */
237    if (!rx->ClientName[0]) {
238       CLIENT_DBR cr;
239       /* try command line argument */
240       int i = find_arg_with_value(ua, NT_("client"));
241       if (i >= 0) {
242          if (!has_value(ua, i)) {
243             return 0;
244          }
245          bstrncpy(rx->ClientName, ua->argv[i], sizeof(rx->ClientName));
246          return 1;
247       }
248       memset(&cr, 0, sizeof(cr));
249       if (!get_client_dbr(ua, &cr)) {
250          return 0;
251       }
252       bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
253    }
254    return 1;
255 }
256
257
258 /*
259  * The first step in the restore process is for the user to
260  *  select a list of JobIds from which he will subsequently
261  *  select which files are to be restored.
262  *
263  *  Returns:  2  if filename list made
264  *            1  if jobid list made
265  *            0  on error
266  */
267 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
268 {
269    char *p;
270    char date[MAX_TIME_LENGTH];
271    bool have_date = false;
272    JobId_t JobId;
273    JOB_DBR jr = { (JobId_t)-1 };
274    bool done = false;
275    int i, j;
276    const char *list[] = {
277       _("List last 20 Jobs run"),
278       _("List Jobs where a given File is saved"),
279       _("Enter list of comma separated JobIds to select"),
280       _("Enter SQL list command"),
281       _("Select the most recent backup for a client"),
282       _("Select backup for a client before a specified time"),
283       _("Enter a list of files to restore"),
284       _("Enter a list of files to restore before a specified time"),
285       _("Find the JobIds of the most recent backup for a client"),
286       _("Find the JobIds for a backup for a client before a specified time"),
287       _("Enter a list of directories to restore for found JobIds"),
288       _("Cancel"),
289       NULL };
290
291    const char *kw[] = {
292        /* These keywords are handled in a for loop */
293       "jobid",     /* 0 */
294       "current",   /* 1 */
295       "before",    /* 2 */
296       "file",      /* 3 */
297       "directory", /* 4 */
298       "select",    /* 5 */
299       "pool",      /* 6 */
300       "all",       /* 7 */
301
302       /* The keyword below are handled by individual arg lookups */
303       "client",    /* 8 */
304       "storage",   /* 9 */
305       "fileset",   /* 10 */
306       "where",     /* 11 */
307       "yes",       /* 12 */
308       "bootstrap", /* 13 */
309       "done",      /* 14 */
310       NULL
311    };
312
313    *rx->JobIds = 0;
314
315    for (i=1; i<ua->argc; i++) {       /* loop through arguments */
316       bool found_kw = false;
317       for (j=0; kw[j]; j++) {         /* loop through keywords */
318          if (strcasecmp(kw[j], ua->argk[i]) == 0) {
319             found_kw = true;
320             break;
321          }
322       }
323       if (!found_kw) {
324          bsendmsg(ua, _("Unknown keyword: %s\n"), ua->argk[i]);
325          return 0;
326       }
327       /* Found keyword in kw[] list, process it */
328       switch (j) {
329       case 0:                            /* jobid */
330          if (!has_value(ua, i)) {
331             return 0;
332          }
333          if (*rx->JobIds != 0) {
334             pm_strcat(rx->JobIds, ",");
335          }
336          pm_strcat(rx->JobIds, ua->argv[i]);
337          done = true;
338          break;
339       case 1:                            /* current */
340          bstrutime(date, sizeof(date), time(NULL));
341          have_date = true;
342          break;
343       case 2:                            /* before */
344          if (!has_value(ua, i)) {
345             return 0;
346          }
347          if (str_to_utime(ua->argv[i]) == 0) {
348             bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
349             return 0;
350          }
351          bstrncpy(date, ua->argv[i], sizeof(date));
352          have_date = true;
353          break;
354       case 3:                            /* file */
355       case 4:                            /* dir */
356          if (!has_value(ua, i)) {
357             return 0;
358          }
359          if (!have_date) {
360             bstrutime(date, sizeof(date), time(NULL));
361          }
362          if (!get_client_name(ua, rx)) {
363             return 0;
364          }
365          pm_strcpy(ua->cmd, ua->argv[i]);
366          insert_one_file_or_dir(ua, rx, date, j==4);
367          return 2;
368       case 5:                            /* select */
369          if (!have_date) {
370             bstrutime(date, sizeof(date), time(NULL));
371          }
372          if (!select_backups_before_date(ua, rx, date)) {
373             return 0;
374          }
375          done = true;
376          break;
377       case 6:                            /* pool specified */
378          if (!has_value(ua, i)) {
379             return 0;
380          }
381          rx->pool = (POOL *)GetResWithName(R_POOL, ua->argv[i]);
382          if (!rx->pool) {
383             bsendmsg(ua, _("Error: Pool resource \"%s\" does not exist.\n"), ua->argv[i]);
384             return 0;
385          }
386          if (!acl_access_ok(ua, Pool_ACL, ua->argv[i])) {
387             rx->pool = NULL;
388             bsendmsg(ua, _("Error: Pool resource \"%s\" access not allowed.\n"), ua->argv[i]);
389             return 0;
390          }
391          break;
392       case 7:                         /* all specified */
393          rx->all = true;
394          break;
395       /*
396        * All keywords 7 or greater are ignored or handled by a select prompt
397        */
398       default:
399          break;
400       }
401    }
402
403    if (!done) {
404       bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
405                   "to be restored. You will be presented several methods\n"
406                   "of specifying the JobIds. Then you will be allowed to\n"
407                   "select which files from those JobIds are to be restored.\n\n"));
408    }
409
410    /* If choice not already made above, prompt */
411    for ( ; !done; ) {
412       char *fname;
413       int len;
414       bool gui_save;
415
416       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
417       for (int i=0; list[i]; i++) {
418          add_prompt(ua, list[i]);
419       }
420       done = true;
421       switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
422       case -1:                        /* error or cancel */
423          return 0;
424       case 0:                         /* list last 20 Jobs run */
425          if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
426             bsendmsg(ua, _("SQL query not authorized.\n"));
427             return 0;
428          }
429          gui_save = ua->jcr->gui;
430          ua->jcr->gui = true;
431          db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
432          ua->jcr->gui = gui_save;
433          done = false;
434          break;
435       case 1:                         /* list where a file is saved */
436          if (!get_client_name(ua, rx)) {
437             return 0;
438          }
439          if (!get_cmd(ua, _("Enter Filename (no path):"))) {
440             return 0;
441          }
442          len = strlen(ua->cmd);
443          fname = (char *)malloc(len * 2 + 1);
444          db_escape_string(fname, ua->cmd, len);
445          Mmsg(rx->query, uar_file, rx->ClientName, fname);
446          free(fname);
447          gui_save = ua->jcr->gui;
448          ua->jcr->gui = true;
449          db_list_sql_query(ua->jcr, ua->db, rx->query, prtit, ua, 1, HORZ_LIST);
450          ua->jcr->gui = gui_save;
451          done = false;
452          break;
453       case 2:                         /* enter a list of JobIds */
454          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
455             return 0;
456          }
457          pm_strcpy(rx->JobIds, ua->cmd);
458          break;
459       case 3:                         /* Enter an SQL list command */
460          if (!acl_access_ok(ua, Command_ACL, NT_("sqlquery"), 8)) {
461             bsendmsg(ua, _("SQL query not authorized.\n"));
462             return 0;
463          }
464          if (!get_cmd(ua, _("Enter SQL list command: "))) {
465             return 0;
466          }
467          gui_save = ua->jcr->gui;
468          ua->jcr->gui = true;
469          db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
470          ua->jcr->gui = gui_save;
471          done = false;
472          break;
473       case 4:                         /* Select the most recent backups */
474          bstrutime(date, sizeof(date), time(NULL));
475          if (!select_backups_before_date(ua, rx, date)) {
476             return 0;
477          }
478          break;
479       case 5:                         /* select backup at specified time */
480          if (!get_date(ua, date, sizeof(date))) {
481             return 0;
482          }
483          if (!select_backups_before_date(ua, rx, date)) {
484             return 0;
485          }
486          break;
487       case 6:                         /* Enter files */
488          bstrutime(date, sizeof(date), time(NULL));
489          if (!get_client_name(ua, rx)) {
490             return 0;
491          }
492          bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
493                         "containing a list of file names with paths, and terminate\n"
494                         "them with a blank line.\n"));
495          for ( ;; ) {
496             if (!get_cmd(ua, _("Enter full filename: "))) {
497                return 0;
498             }
499             len = strlen(ua->cmd);
500             if (len == 0) {
501                break;
502             }
503             insert_one_file_or_dir(ua, rx, date, false);
504          }
505          return 2;
506        case 7:                        /* enter files backed up before specified time */
507          if (!get_date(ua, date, sizeof(date))) {
508             return 0;
509          }
510          if (!get_client_name(ua, rx)) {
511             return 0;
512          }
513          bsendmsg(ua, _("Enter file names with paths, or < to enter a filename\n"
514                         "containing a list of file names with paths, and terminate\n"
515                         "them with a blank line.\n"));
516          for ( ;; ) {
517             if (!get_cmd(ua, _("Enter full filename: "))) {
518                return 0;
519             }
520             len = strlen(ua->cmd);
521             if (len == 0) {
522                break;
523             }
524             insert_one_file_or_dir(ua, rx, date, false);
525          }
526          return 2;
527
528       case 8:                         /* Find JobIds for current backup */
529          bstrutime(date, sizeof(date), time(NULL));
530          if (!select_backups_before_date(ua, rx, date)) {
531             return 0;
532          }
533          done = false;
534          break;
535
536       case 9:                         /* Find JobIds for give date */
537          if (!get_date(ua, date, sizeof(date))) {
538             return 0;
539          }
540          if (!select_backups_before_date(ua, rx, date)) {
541             return 0;
542          }
543          done = false;
544          break;
545
546       case 10:                        /* Enter directories */
547          if (*rx->JobIds != 0) {
548             bsendmsg(ua, _("You have already seleted the following JobIds: %s\n"),
549                rx->JobIds);
550          } else if (get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
551             if (*rx->JobIds != 0 && *ua->cmd) {
552                pm_strcat(rx->JobIds, ",");
553             }
554             pm_strcat(rx->JobIds, ua->cmd);
555          }
556          if (*rx->JobIds == 0 || *rx->JobIds == '.') {
557             return 0;                 /* nothing entered, return */
558          }
559          bstrutime(date, sizeof(date), time(NULL));
560          if (!get_client_name(ua, rx)) {
561             return 0;
562          }
563          bsendmsg(ua, _("Enter full directory names or start the name\n"
564                         "with a < to indicate it is a filename containing a list\n"
565                         "of directories and terminate them with a blank line.\n"));
566          for ( ;; ) {
567             if (!get_cmd(ua, _("Enter directory name: "))) {
568                return 0;
569             }
570             len = strlen(ua->cmd);
571             if (len == 0) {
572                break;
573             }
574             /* Add trailing slash to end of directory names */
575             if (ua->cmd[0] != '<' && ua->cmd[len-1] != '/') {
576                strcat(ua->cmd, "/");
577             }
578             insert_one_file_or_dir(ua, rx, date, true);
579          }
580          return 2;
581
582       case 11:                        /* Cancel or quit */
583          return 0;
584       }
585    }
586
587    if (*rx->JobIds == 0) {
588       bsendmsg(ua, _("No Jobs selected.\n"));
589       return 0;
590    }
591    if (strchr(rx->JobIds,',')) {
592       bsendmsg(ua, _("You have selected the following JobIds: %s\n"), rx->JobIds);
593    }
594    else {
595       bsendmsg(ua, _("You have selected the following JobId: %s\n"), rx->JobIds);
596    }
597
598
599    rx->TotalFiles = 0;
600    for (p=rx->JobIds; ; ) {
601       int stat = get_next_jobid_from_list(&p, &JobId);
602       if (stat < 0) {
603          bsendmsg(ua, _("Invalid JobId in list.\n"));
604          return 0;
605       }
606       if (stat == 0) {
607          break;
608       }
609       if (jr.JobId == JobId) {
610          continue;                    /* duplicate of last JobId */
611       }
612       memset(&jr, 0, sizeof(JOB_DBR));
613       jr.JobId = JobId;
614       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
615          char ed1[50];
616          bsendmsg(ua, _("Unable to get Job record for JobId=%s: ERR=%s\n"),
617             edit_int64(JobId, ed1), db_strerror(ua->db));
618          return 0;
619       }
620       if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
621          bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
622             jr.Name);
623          continue;
624       }
625       rx->TotalFiles += jr.JobFiles;
626    }
627    return 1;
628 }
629
630 /*
631  * Get date from user
632  */
633 static int get_date(UAContext *ua, char *date, int date_len)
634 {
635    bsendmsg(ua, _("The restored files will the most current backup\n"
636                   "BEFORE the date you specify below.\n\n"));
637    for ( ;; ) {
638       if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
639          return 0;
640       }
641       if (str_to_utime(ua->cmd) != 0) {
642          break;
643       }
644       bsendmsg(ua, _("Improper date format.\n"));
645    }
646    bstrncpy(date, ua->cmd, date_len);
647    return 1;
648 }
649
650 /*
651  * Insert a single file, or read a list of files from a file
652  */
653 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
654 {
655    FILE *ffd;
656    char file[5000];
657    char *p = ua->cmd;
658    int line = 0;
659
660    switch (*p) {
661    case '<':
662       p++;
663       if ((ffd = fopen(p, "rb")) == NULL) {
664          berrno be;
665          bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
666             p, be.strerror());
667          break;
668       }
669       while (fgets(file, sizeof(file), ffd)) {
670          line++;
671          if (dir) {
672             if (!insert_dir_into_findex_list(ua, rx, file, date)) {
673                bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
674             }
675          } else {
676             if (!insert_file_into_findex_list(ua, rx, file, date)) {
677                bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
678             }
679          }
680       }
681       fclose(ffd);
682       break;
683    case '?':
684       p++;
685       insert_table_into_findex_list(ua, rx, p);
686       break;
687    default:
688       if (dir) {
689          insert_dir_into_findex_list(ua, rx, ua->cmd, date);
690       } else {
691          insert_file_into_findex_list(ua, rx, ua->cmd, date);
692       }
693       break;
694    }
695 }
696
697 /*
698  * For a given file (path+filename), split into path and file, then
699  *   lookup the most recent backup in the catalog to get the JobId
700  *   and FileIndex, then insert them into the findex list.
701  */
702 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
703                                         char *date)
704 {
705    strip_trailing_newline(file);
706    split_path_and_filename(rx, file);
707    if (*rx->JobIds == 0) {
708       Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, 
709            rx->ClientName);
710    } else {
711       Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
712            rx->path, rx->fname, rx->ClientName);
713    }
714    rx->found = false;
715    /* Find and insert jobid and File Index */
716    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
717       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
718          rx->query, db_strerror(ua->db));
719    }
720    if (!rx->found) {
721       bsendmsg(ua, _("No database record found for: %s\n"), file);
722       return true;
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    strip_trailing_junk(dir);
735    if (*rx->JobIds == 0) {
736       bsendmsg(ua, _("No JobId specified cannot continue.\n"));
737       return false;
738    } else {
739       Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds, 
740            dir, rx->ClientName);
741    }
742    rx->found = false;
743    /* Find and insert jobid and File Index */
744    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
745       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
746          rx->query, db_strerror(ua->db));
747    }
748    if (!rx->found) {
749       bsendmsg(ua, _("No database record found for: %s\n"), dir);
750       return true;
751    }
752    return true;
753 }
754
755 /*
756  * Get the JobId and FileIndexes of all files in the specified table
757  */
758 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
759 {
760    strip_trailing_junk(table);
761    Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
762
763    rx->found = false;
764    /* Find and insert jobid and File Index */
765    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
766       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
767          rx->query, db_strerror(ua->db));
768    }
769    if (!rx->found) {
770       bsendmsg(ua, _("No table found: %s\n"), table);
771       return true;
772    }
773    return true;
774 }
775
776 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
777 {
778    char *p, *f;
779
780    /* Find path without the filename.
781     * I.e. everything after the last / is a "filename".
782     * OK, maybe it is a directory name, but we treat it like
783     * a filename. If we don't find a / then the whole name
784     * must be a path name (e.g. c:).
785     */
786    for (p=f=name; *p; p++) {
787       if (*p == '/') {
788          f = p;                       /* set pos of last slash */
789       }
790    }
791    if (*f == '/') {                   /* did we find a slash? */
792       f++;                            /* yes, point to filename */
793    } else {                           /* no, whole thing must be path name */
794       f = p;
795    }
796
797    /* If filename doesn't exist (i.e. root directory), we
798     * simply create a blank name consisting of a single
799     * space. This makes handling zero length filenames
800     * easier.
801     */
802    rx->fnl = p - f;
803    if (rx->fnl > 0) {
804       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
805       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
806       rx->fname[rx->fnl] = 0;
807    } else {
808       rx->fname[0] = 0;
809       rx->fnl = 0;
810    }
811
812    rx->pnl = f - name;
813    if (rx->pnl > 0) {
814       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
815       memcpy(rx->path, name, rx->pnl);
816       rx->path[rx->pnl] = 0;
817    } else {
818       rx->path[0] = 0;
819       rx->pnl = 0;
820    }
821
822    Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
823 }
824
825 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
826 {
827    TREE_CTX tree;
828    JobId_t JobId, last_JobId;
829    char *p;
830    bool OK = true;
831    char ed1[50];
832
833    memset(&tree, 0, sizeof(TREE_CTX));
834    /*
835     * Build the directory tree containing JobIds user selected
836     */
837    tree.root = new_tree(rx->TotalFiles);
838    tree.ua = ua;
839    tree.all = rx->all;
840    last_JobId = 0;
841    /*
842     * For display purposes, the same JobId, with different volumes may
843     * appear more than once, however, we only insert it once.
844     */
845    int items = 0;
846    p = rx->JobIds;
847    tree.FileEstimate = 0;
848    if (get_next_jobid_from_list(&p, &JobId) > 0) {
849       /* Use first JobId as estimate of the number of files to restore */
850       Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
851       if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
852          bsendmsg(ua, "%s\n", db_strerror(ua->db));
853       }
854       if (rx->found) {
855          /* Add about 25% more than this job for over estimate */
856          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
857          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
858       }
859    }
860    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
861       char ed1[50];
862
863       if (JobId == last_JobId) {
864          continue;                    /* eliminate duplicate JobIds */
865       }
866       last_JobId = JobId;
867       bsendmsg(ua, _("\nBuilding directory tree for JobId %s ...  "), 
868          edit_int64(JobId, ed1));
869       items++;
870       /*
871        * Find files for this JobId and insert them in the tree
872        */
873       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
874       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
875          bsendmsg(ua, "%s", db_strerror(ua->db));
876       }
877    }
878    if (tree.FileCount == 0) {
879       bsendmsg(ua, _("\nThere were no files inserted into the tree, so file selection\n"
880          "is not possible.Most likely your retention policy pruned the files\n"));
881       if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
882          OK = false;
883       } else {
884          last_JobId = 0;
885          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
886              if (JobId == last_JobId) {
887                 continue;                    /* eliminate duplicate JobIds */
888              }
889              add_findex_all(rx->bsr, JobId);
890           }
891           OK = true;
892       }
893    } else {
894       char ec1[50];
895       if (items==1) {
896          if (tree.all) {
897             bsendmsg(ua, _("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
898               edit_uint64_with_commas(tree.FileCount, ec1));
899          }
900          else {
901             bsendmsg(ua, _("\n1 Job, %s files inserted into the tree.\n"),
902               edit_uint64_with_commas(tree.FileCount, ec1));
903          }
904       }
905       else {
906          if (tree.all) {
907             bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
908               items, edit_uint64_with_commas(tree.FileCount, ec1));
909          }
910          else {
911             bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree.\n"),
912               items, edit_uint64_with_commas(tree.FileCount, ec1));
913          }
914       }
915
916       if (find_arg(ua, NT_("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->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 /* 
1096  * Return next JobId from comma separated list   
1097  *
1098  * Returns:
1099  *   1 if next JobId returned
1100  *   0 if no more JobIds are in list
1101  *  -1 there is an error
1102  */
1103 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1104 {
1105    char jobid[30];
1106    char *q = *p;
1107
1108    jobid[0] = 0;
1109    for (int i=0; i<(int)sizeof(jobid); i++) {
1110       if (*q == 0) {
1111          break;
1112       } else if (*q == ',') {
1113          q++;
1114          break;
1115       }
1116       jobid[i] = *q++;
1117       jobid[i+1] = 0;
1118    }
1119    if (jobid[0] == 0) {
1120       return 0;
1121    } else if (!is_a_number(jobid)) {
1122       return -1;                      /* error */
1123    }
1124    *p = q;
1125    *JobId = str_to_int64(jobid);
1126    return 1;
1127 }
1128
1129 static int count_handler(void *ctx, int num_fields, char **row)
1130 {
1131    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1132    rx->JobId = str_to_int64(row[0]);
1133    rx->found = true;
1134    return 0;
1135 }
1136
1137 /*
1138  * Callback handler to get JobId and FileIndex for files
1139  *   can insert more than one depending on the caller.
1140  */
1141 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1142 {
1143    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1144    rx->JobId = str_to_int64(row[0]);
1145    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1146    rx->found = true;
1147    rx->selected_files++;
1148    return 0;
1149 }
1150
1151 /*
1152  * Callback handler make list of JobIds
1153  */
1154 static int jobid_handler(void *ctx, int num_fields, char **row)
1155 {
1156    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1157
1158    if (strcmp(rx->last_jobid, row[0]) == 0) {
1159       return 0;                       /* duplicate id */
1160    }
1161    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1162    if (rx->JobIds[0] != 0) {
1163       pm_strcat(rx->JobIds, ",");
1164    }
1165    pm_strcat(rx->JobIds, row[0]);
1166    return 0;
1167 }
1168
1169
1170 /*
1171  * Callback handler to pickup last Full backup JobTDate
1172  */
1173 static int last_full_handler(void *ctx, int num_fields, char **row)
1174 {
1175    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1176
1177    rx->JobTDate = str_to_int64(row[1]);
1178    return 0;
1179 }
1180
1181 /*
1182  * Callback handler build FileSet name prompt list
1183  */
1184 static int fileset_handler(void *ctx, int num_fields, char **row)
1185 {
1186    /* row[0] = FileSet (name) */
1187    if (row[0]) {
1188       add_prompt((UAContext *)ctx, row[0]);
1189    }
1190    return 0;
1191 }
1192
1193 /*
1194  * Free names in the list
1195  */
1196 static void free_name_list(NAME_LIST *name_list)
1197 {
1198    for (int i=0; i < name_list->num_ids; i++) {
1199       free(name_list->name[i]);
1200    }
1201    if (name_list->name) {
1202       free(name_list->name);
1203       name_list->name = NULL;
1204    }
1205    name_list->max_ids = 0;
1206    name_list->num_ids = 0;
1207 }
1208
1209 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType) 
1210 {
1211    STORE *store;
1212
1213    if (rx.store) {
1214       Dmsg1(200, "Already have store=%s\n", rx.store->name());
1215       return;
1216    }
1217    /*
1218     * Try looking up Storage by name
1219     */
1220    LockRes();
1221    foreach_res(store, R_STORAGE) {
1222       if (strcmp(Storage, store->name()) == 0) {
1223          if (acl_access_ok(ua, Storage_ACL, store->name())) {
1224             rx.store = store;
1225          }
1226          break;
1227       }
1228    }
1229    UnlockRes();
1230
1231    if (rx.store) {
1232       /* Check if an explicit storage resource is given */
1233       store = NULL;
1234       int i = find_arg_with_value(ua, "storage");
1235       if (i > 0) {
1236          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1237          if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1238             store = NULL;
1239          }
1240       }
1241       if (store && (store != rx.store)) {
1242          bsendmsg(ua, _("Warning default storage overridden by \"%s\" on command line.\n"),
1243             store->name());
1244          rx.store = store;
1245          Dmsg1(200, "Set store=%s\n", rx.store->name());
1246       }
1247       return;
1248    }
1249
1250    /* If no storage resource, try to find one from MediaType */
1251    if (!rx.store) {
1252       LockRes();
1253       foreach_res(store, R_STORAGE) {
1254          if (strcmp(MediaType, store->media_type) == 0) {
1255             if (acl_access_ok(ua, Storage_ACL, store->name())) {
1256                rx.store = store;
1257                Dmsg1(200, "Set store=%s\n", rx.store->name());
1258                bsendmsg(ua, _("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1259                   Storage, store->name(), MediaType);
1260             }
1261             UnlockRes();
1262             return;
1263          }
1264       }
1265       UnlockRes();
1266       bsendmsg(ua, _("\nUnable to find Storage resource for\n"
1267          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1268    }
1269
1270    /* Take command line arg, or ask user if none */
1271    rx.store = get_storage_resource(ua, false /* don't use default */);
1272    Dmsg1(200, "Set store=%s\n", rx.store->name());
1273
1274 }