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