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