]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
6cde0eb41b7578d6058c83e8856c0ffdbce65725
[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 //    ua->error_msg("Query=%s\n", rx->query);
775       return true;
776    }
777    return true;
778 }
779
780 /*
781  * For a given path lookup the most recent backup in the catalog
782  * to get the JobId and FileIndexes of all files in that directory.
783  */
784 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
785                                         char *date)
786 {
787    strip_trailing_junk(dir);
788    if (*rx->JobIds == 0) {
789       ua->error_msg(_("No JobId specified cannot continue.\n"));
790       return false;
791    } else {
792       Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds, 
793            dir, rx->ClientName);
794    }
795    rx->found = false;
796    /* Find and insert jobid and File Index */
797    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
798       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
799          rx->query, db_strerror(ua->db));
800    }
801    if (!rx->found) {
802       ua->error_msg(_("No database record found for: %s\n"), dir);
803       return true;
804    }
805    return true;
806 }
807
808 /*
809  * Get the JobId and FileIndexes of all files in the specified table
810  */
811 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
812 {
813    strip_trailing_junk(table);
814    Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
815
816    rx->found = false;
817    /* Find and insert jobid and File Index */
818    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
819       ua->error_msg(_("Query failed: %s. ERR=%s\n"),
820          rx->query, db_strerror(ua->db));
821    }
822    if (!rx->found) {
823       ua->error_msg(_("No table found: %s\n"), table);
824       return true;
825    }
826    return true;
827 }
828
829 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
830 {
831    char *p, *f;
832
833    /* Find path without the filename.
834     * I.e. everything after the last / is a "filename".
835     * OK, maybe it is a directory name, but we treat it like
836     * a filename. If we don't find a / then the whole name
837     * must be a path name (e.g. c:).
838     */
839    for (p=f=name; *p; p++) {
840       if (IsPathSeparator(*p)) {
841          f = p;                       /* set pos of last slash */
842       }
843    }
844    if (IsPathSeparator(*f)) {         /* did we find a slash? */
845       f++;                            /* yes, point to filename */
846    } else {                           /* no, whole thing must be path name */
847       f = p;
848    }
849
850    /* If filename doesn't exist (i.e. root directory), we
851     * simply create a blank name consisting of a single
852     * space. This makes handling zero length filenames
853     * easier.
854     */
855    rx->fnl = p - f;
856    if (rx->fnl > 0) {
857       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
858       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
859       rx->fname[rx->fnl] = 0;
860    } else {
861       rx->fname[0] = 0;
862       rx->fnl = 0;
863    }
864
865    rx->pnl = f - name;
866    if (rx->pnl > 0) {
867       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
868       memcpy(rx->path, name, rx->pnl);
869       rx->path[rx->pnl] = 0;
870    } else {
871       rx->path[0] = 0;
872       rx->pnl = 0;
873    }
874
875    Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
876 }
877
878 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
879 {
880    TREE_CTX tree;
881    JobId_t JobId, last_JobId;
882    char *p;
883    bool OK = true;
884    char ed1[50];
885
886    memset(&tree, 0, sizeof(TREE_CTX));
887    /*
888     * Build the directory tree containing JobIds user selected
889     */
890    tree.root = new_tree(rx->TotalFiles);
891    tree.ua = ua;
892    tree.all = rx->all;
893    last_JobId = 0;
894    /*
895     * For display purposes, the same JobId, with different volumes may
896     * appear more than once, however, we only insert it once.
897     */
898    int items = 0;
899    p = rx->JobIds;
900    tree.FileEstimate = 0;
901    if (get_next_jobid_from_list(&p, &JobId) > 0) {
902       /* Use first JobId as estimate of the number of files to restore */
903       Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
904       if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
905          ua->error_msg("%s\n", db_strerror(ua->db));
906       }
907       if (rx->found) {
908          /* Add about 25% more than this job for over estimate */
909          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
910          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
911       }
912    }
913    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
914       char ed1[50];
915
916       if (JobId == last_JobId) {
917          continue;                    /* eliminate duplicate JobIds */
918       }
919       last_JobId = JobId;
920       ua->info_msg(_("\nBuilding directory tree for JobId %s ...  "), 
921          edit_int64(JobId, ed1));
922       items++;
923       /*
924        * Find files for this JobId and insert them in the tree
925        */
926       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
927       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
928          ua->error_msg("%s", db_strerror(ua->db));
929       }
930    }
931    if (tree.FileCount == 0) {
932       ua->send_msg(_("\nThere were no files inserted into the tree, so file selection\n"
933          "is not possible.Most likely your retention policy pruned the files\n"));
934       if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
935          OK = false;
936       } else {
937          last_JobId = 0;
938          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
939              if (JobId == last_JobId) {
940                 continue;                    /* eliminate duplicate JobIds */
941              }
942              add_findex_all(rx->bsr, JobId);
943           }
944           OK = true;
945       }
946    } else {
947       char ec1[50];
948       if (items==1) {
949          if (tree.all) {
950             ua->info_msg(_("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
951               edit_uint64_with_commas(tree.FileCount, ec1));
952          }
953          else {
954             ua->info_msg(_("\n1 Job, %s files inserted into the tree.\n"),
955               edit_uint64_with_commas(tree.FileCount, ec1));
956          }
957       }
958       else {
959          if (tree.all) {
960             ua->info_msg(_("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
961               items, edit_uint64_with_commas(tree.FileCount, ec1));
962          }
963          else {
964             ua->info_msg(_("\n%d Jobs, %s files inserted into the tree.\n"),
965               items, edit_uint64_with_commas(tree.FileCount, ec1));
966          }
967       }
968
969       if (find_arg(ua, NT_("done")) < 0) {
970          /* Let the user interact in selecting which files to restore */
971          OK = user_select_files_from_tree(&tree);
972       }
973
974       /*
975        * Walk down through the tree finding all files marked to be
976        *  extracted making a bootstrap file.
977        */
978       if (OK) {
979          for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
980             Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
981             if (node->extract || node->extract_dir) {
982                Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
983                add_findex(rx->bsr, node->JobId, node->FileIndex);
984                if (node->extract && node->type != TN_NEWDIR) {
985                   rx->selected_files++;  /* count only saved files */
986                }
987             }
988          }
989       }
990    }
991
992    free_tree(tree.root);              /* free the directory tree */
993    return OK;
994 }
995
996
997 /*
998  * This routine is used to get the current backup or a backup
999  *   before the specified date.
1000  */
1001 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
1002 {
1003    bool ok = false;
1004    FILESET_DBR fsr;
1005    CLIENT_DBR cr;
1006    char fileset_name[MAX_NAME_LENGTH];
1007    char ed1[50], ed2[50];
1008    char pool_select[MAX_NAME_LENGTH];
1009    int i;
1010
1011
1012    /* Create temp tables */
1013    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1014    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1015    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
1016       ua->error_msg("%s\n", db_strerror(ua->db));
1017    }
1018    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
1019       ua->error_msg("%s\n", db_strerror(ua->db));
1020    }
1021    /*
1022     * Select Client from the Catalog
1023     */
1024    memset(&cr, 0, sizeof(cr));
1025    if (!get_client_dbr(ua, &cr)) {
1026       goto bail_out;
1027    }
1028    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1029
1030    /*
1031     * Get FileSet
1032     */
1033    memset(&fsr, 0, sizeof(fsr));
1034    i = find_arg_with_value(ua, "FileSet");
1035    if (i >= 0) {
1036       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1037       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1038          ua->error_msg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1039             db_strerror(ua->db));
1040          i = -1;
1041       }
1042    }
1043    if (i < 0) {                       /* fileset not found */
1044       edit_int64(cr.ClientId, ed1);
1045       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1046       start_prompt(ua, _("The defined FileSet resources are:\n"));
1047       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1048          ua->error_msg("%s\n", db_strerror(ua->db));
1049       }
1050       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1051                  fileset_name, sizeof(fileset_name)) < 0) {
1052          ua->error_msg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1053          goto bail_out;
1054       }
1055
1056       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1057       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1058          ua->warning_msg(_("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1059          ua->send_msg(_("This probably means you modified the FileSet.\n"
1060                      "Continuing anyway.\n"));
1061       }
1062    }
1063
1064    /* If Pool specified, add PoolId specification */
1065    pool_select[0] = 0;
1066    if (rx->pool) {
1067       POOL_DBR pr;
1068       memset(&pr, 0, sizeof(pr));
1069       bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1070       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1071          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
1072             edit_int64(pr.PoolId, ed1));
1073       } else {
1074          ua->warning_msg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1075       }
1076    }
1077
1078    /* Find JobId of last Full backup for this client, fileset */
1079    edit_int64(cr.ClientId, ed1);
1080    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1081          pool_select);
1082    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1083       ua->error_msg("%s\n", db_strerror(ua->db));
1084       goto bail_out;
1085    }
1086
1087    /* Find all Volumes used by that JobId */
1088    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1089       ua->error_msg("%s\n", db_strerror(ua->db));
1090       goto bail_out;
1091    }
1092    /* Note, this is needed because I don't seem to get the callback
1093     * from the call just above.
1094     */
1095    rx->JobTDate = 0;
1096    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1097       ua->warning_msg("%s\n", db_strerror(ua->db));
1098    }
1099    if (rx->JobTDate == 0) {
1100       ua->error_msg(_("No Full backup before %s found.\n"), date);
1101       goto bail_out;
1102    }
1103
1104    /* Now find most recent Differental Job after Full save, if any */
1105    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1106         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1107    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1108       ua->warning_msg("%s\n", db_strerror(ua->db));
1109    }
1110    /* Now update JobTDate to lock onto Differental, if any */
1111    rx->JobTDate = 0;
1112    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1113       ua->warning_msg("%s\n", db_strerror(ua->db));
1114    }
1115    if (rx->JobTDate == 0) {
1116       ua->error_msg(_("No Full backup before %s found.\n"), date);
1117       goto bail_out;
1118    }
1119
1120    /* Now find all Incremental Jobs after Full/dif save */
1121    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1122         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1123    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1124       ua->warning_msg("%s\n", db_strerror(ua->db));
1125    }
1126
1127    /* Get the JobIds from that list */
1128    rx->JobIds[0] = 0;
1129    rx->last_jobid[0] = 0;
1130    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1131       ua->warning_msg("%s\n", db_strerror(ua->db));
1132    }
1133
1134    if (rx->JobIds[0] != 0) {
1135       /* Display a list of Jobs selected for this restore */
1136       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1137       ok = true;
1138    } else {
1139       ua->warning_msg(_("No jobs found.\n"));
1140    }
1141
1142 bail_out:
1143    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1144    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1145    return ok;
1146 }
1147
1148
1149 /* 
1150  * Return next JobId from comma separated list   
1151  *
1152  * Returns:
1153  *   1 if next JobId returned
1154  *   0 if no more JobIds are in list
1155  *  -1 there is an error
1156  */
1157 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1158 {
1159    char jobid[30];
1160    char *q = *p;
1161
1162    jobid[0] = 0;
1163    for (int i=0; i<(int)sizeof(jobid); i++) {
1164       if (*q == 0) {
1165          break;
1166       } else if (*q == ',') {
1167          q++;
1168          break;
1169       }
1170       jobid[i] = *q++;
1171       jobid[i+1] = 0;
1172    }
1173    if (jobid[0] == 0) {
1174       return 0;
1175    } else if (!is_a_number(jobid)) {
1176       return -1;                      /* error */
1177    }
1178    *p = q;
1179    *JobId = str_to_int64(jobid);
1180    return 1;
1181 }
1182
1183 static int count_handler(void *ctx, int num_fields, char **row)
1184 {
1185    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1186    rx->JobId = str_to_int64(row[0]);
1187    rx->found = true;
1188    return 0;
1189 }
1190
1191 /*
1192  * Callback handler to get JobId and FileIndex for files
1193  *   can insert more than one depending on the caller.
1194  */
1195 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1196 {
1197    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1198    rx->JobId = str_to_int64(row[0]);
1199    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1200    rx->found = true;
1201    rx->selected_files++;
1202    return 0;
1203 }
1204
1205 /*
1206  * Callback handler make list of JobIds
1207  */
1208 static int jobid_handler(void *ctx, int num_fields, char **row)
1209 {
1210    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1211
1212    if (strcmp(rx->last_jobid, row[0]) == 0) {
1213       return 0;                       /* duplicate id */
1214    }
1215    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1216    if (rx->JobIds[0] != 0) {
1217       pm_strcat(rx->JobIds, ",");
1218    }
1219    pm_strcat(rx->JobIds, row[0]);
1220    return 0;
1221 }
1222
1223
1224 /*
1225  * Callback handler to pickup last Full backup JobTDate
1226  */
1227 static int last_full_handler(void *ctx, int num_fields, char **row)
1228 {
1229    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1230
1231    rx->JobTDate = str_to_int64(row[1]);
1232    return 0;
1233 }
1234
1235 /*
1236  * Callback handler build FileSet name prompt list
1237  */
1238 static int fileset_handler(void *ctx, int num_fields, char **row)
1239 {
1240    /* row[0] = FileSet (name) */
1241    if (row[0]) {
1242       add_prompt((UAContext *)ctx, row[0]);
1243    }
1244    return 0;
1245 }
1246
1247 /*
1248  * Free names in the list
1249  */
1250 static void free_name_list(NAME_LIST *name_list)
1251 {
1252    for (int i=0; i < name_list->num_ids; i++) {
1253       free(name_list->name[i]);
1254    }
1255    if (name_list->name) {
1256       free(name_list->name);
1257       name_list->name = NULL;
1258    }
1259    name_list->max_ids = 0;
1260    name_list->num_ids = 0;
1261 }
1262
1263 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType) 
1264 {
1265    STORE *store;
1266
1267    if (rx.store) {
1268       Dmsg1(200, "Already have store=%s\n", rx.store->name());
1269       return;
1270    }
1271    /*
1272     * Try looking up Storage by name
1273     */
1274    LockRes();
1275    foreach_res(store, R_STORAGE) {
1276       if (strcmp(Storage, store->name()) == 0) {
1277          if (acl_access_ok(ua, Storage_ACL, store->name())) {
1278             rx.store = store;
1279          }
1280          break;
1281       }
1282    }
1283    UnlockRes();
1284
1285    if (rx.store) {
1286       /* Check if an explicit storage resource is given */
1287       store = NULL;
1288       int i = find_arg_with_value(ua, "storage");
1289       if (i > 0) {
1290          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1291          if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1292             store = NULL;
1293          }
1294       }
1295       if (store && (store != rx.store)) {
1296          ua->info_msg(_("Warning default storage overridden by \"%s\" on command line.\n"),
1297             store->name());
1298          rx.store = store;
1299          Dmsg1(200, "Set store=%s\n", rx.store->name());
1300       }
1301       return;
1302    }
1303
1304    /* If no storage resource, try to find one from MediaType */
1305    if (!rx.store) {
1306       LockRes();
1307       foreach_res(store, R_STORAGE) {
1308          if (strcmp(MediaType, store->media_type) == 0) {
1309             if (acl_access_ok(ua, Storage_ACL, store->name())) {
1310                rx.store = store;
1311                Dmsg1(200, "Set store=%s\n", rx.store->name());
1312                ua->warning_msg(_("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1313                   Storage, store->name(), MediaType);
1314             }
1315             UnlockRes();
1316             return;
1317          }
1318       }
1319       UnlockRes();
1320       ua->warning_msg(_("\nUnable to find Storage resource for\n"
1321          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1322    }
1323
1324    /* Take command line arg, or ask user if none */
1325    rx.store = get_storage_resource(ua, false /* don't use default */);
1326    Dmsg1(200, "Set store=%s\n", rx.store->name());
1327
1328 }