]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Start applying new FSFE copyright.
[bacula/bacula] / bacula / src / dird / ua_restore.c
1 /*
2  *
3  *   Bacula Director -- User Agent Database restore Command
4  *      Creates a bootstrap file for restoring files and
5  *      starts the restore job.
6  *
7  *      Tree handling routines split into ua_tree.c July MMIII.
8  *      BSR (bootstrap record) handling routines split into
9  *        bsr.c July MMIII
10  *
11  *     Kern Sibbald, July MMII
12  *
13  *   Version $Id$
14  */
15 /*
16    Bacula® - The Network Backup Solution
17
18    Copyright (C) 2002-2006 Free Software Foundation Europe e.V.
19
20    The main author of Bacula is Kern Sibbald, with contributions from
21    many others, a complete list can be found in the file AUTHORS.
22    This program is Free Software; you can redistribute it and/or
23    modify it under the terms of version two of the GNU General Public
24    License as published by the Free Software Foundation plus additions
25    that are listed in the file LICENSE.
26
27    This program is distributed in the hope that it will be useful, but
28    WITHOUT ANY WARRANTY; without even the implied warranty of
29    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
30    General Public License for more details.
31
32    You should have received a copy of the GNU General Public License
33    along with this program; if not, write to the Free Software
34    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
35    02110-1301, USA.
36
37    Bacula® is a registered trademark ofJohn Walker.
38    The licensor of Bacula is the Free Software Foundation Europe
39    (FSFE), Fiduciary Program, Sumatrastrasse 25, 8006 Zürich,
40    Switzerland, email:ftf@fsfeurope.org.
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          bsendmsg(ua, _("Forbidden \"where\" specified.\n"));
99          goto bail_out;
100       }
101    }
102
103    if (!open_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       bsendmsg(ua, _(
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          bsendmsg(ua, _("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          bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
149          goto bail_out;
150       }
151       if (!(selected_files = write_bsr_file(ua, rx))) {
152          bsendmsg(ua, _("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          bsendmsg(ua, _("\n1 file selected to be restored.\n\n"));
161       }
162       else {
163          bsendmsg(ua, _("\n%s files selected to be restored.\n\n"), 
164             edit_uint64_with_commas(rx.selected_files, ed1));
165       }
166    } else {
167       bsendmsg(ua, _("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       bsendmsg(ua, _("No Restore Job 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          bsendmsg(ua, _("Forbidden \"where\" specified.\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       bsendmsg(ua, _("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          bsendmsg(ua, _("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             bsendmsg(ua, _("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             bsendmsg(ua, _("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             bsendmsg(ua, _("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       bsendmsg(ua, _("\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             bsendmsg(ua, _("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             bsendmsg(ua, _("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          bsendmsg(ua, _("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          bsendmsg(ua, _("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             bsendmsg(ua, _("You have already seleted 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          bsendmsg(ua, _("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] != '<' && 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    if (*rx->JobIds == 0) {
629       bsendmsg(ua, _("No Jobs selected.\n"));
630       return 0;
631    }
632    if (strchr(rx->JobIds,',')) {
633       bsendmsg(ua, _("You have selected the following JobIds: %s\n"), rx->JobIds);
634    }
635    else {
636       bsendmsg(ua, _("You have selected the following JobId: %s\n"), rx->JobIds);
637    }
638
639
640    rx->TotalFiles = 0;
641    for (p=rx->JobIds; ; ) {
642       int stat = get_next_jobid_from_list(&p, &JobId);
643       if (stat < 0) {
644          bsendmsg(ua, _("Invalid JobId in list.\n"));
645          return 0;
646       }
647       if (stat == 0) {
648          break;
649       }
650       if (jr.JobId == JobId) {
651          continue;                    /* duplicate of last JobId */
652       }
653       memset(&jr, 0, sizeof(JOB_DBR));
654       jr.JobId = JobId;
655       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
656          char ed1[50];
657          bsendmsg(ua, _("Unable to get Job record for JobId=%s: ERR=%s\n"),
658             edit_int64(JobId, ed1), db_strerror(ua->db));
659          return 0;
660       }
661       if (!acl_access_ok(ua, Job_ACL, jr.Name)) {
662          bsendmsg(ua, _("No authorization. Job \"%s\" not selected.\n"),
663             jr.Name);
664          continue;
665       }
666       rx->TotalFiles += jr.JobFiles;
667    }
668    return 1;
669 }
670
671 /*
672  * Get date from user
673  */
674 static int get_date(UAContext *ua, char *date, int date_len)
675 {
676    bsendmsg(ua, _("The restored files will the most current backup\n"
677                   "BEFORE the date you specify below.\n\n"));
678    for ( ;; ) {
679       if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
680          return 0;
681       }
682       if (str_to_utime(ua->cmd) != 0) {
683          break;
684       }
685       bsendmsg(ua, _("Improper date format.\n"));
686    }
687    bstrncpy(date, ua->cmd, date_len);
688    return 1;
689 }
690
691 /*
692  * Insert a single file, or read a list of files from a file
693  */
694 static void insert_one_file_or_dir(UAContext *ua, RESTORE_CTX *rx, char *date, bool dir)
695 {
696    FILE *ffd;
697    char file[5000];
698    char *p = ua->cmd;
699    int line = 0;
700
701    switch (*p) {
702    case '<':
703       p++;
704       if ((ffd = fopen(p, "rb")) == NULL) {
705          berrno be;
706          bsendmsg(ua, _("Cannot open file %s: ERR=%s\n"),
707             p, be.strerror());
708          break;
709       }
710       while (fgets(file, sizeof(file), ffd)) {
711          line++;
712          if (dir) {
713             if (!insert_dir_into_findex_list(ua, rx, file, date)) {
714                bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
715             }
716          } else {
717             if (!insert_file_into_findex_list(ua, rx, file, date)) {
718                bsendmsg(ua, _("Error occurred on line %d of %s\n"), line, p);
719             }
720          }
721       }
722       fclose(ffd);
723       break;
724    case '?':
725       p++;
726       insert_table_into_findex_list(ua, rx, p);
727       break;
728    default:
729       if (dir) {
730          insert_dir_into_findex_list(ua, rx, ua->cmd, date);
731       } else {
732          insert_file_into_findex_list(ua, rx, ua->cmd, date);
733       }
734       break;
735    }
736 }
737
738 /*
739  * For a given file (path+filename), split into path and file, then
740  *   lookup the most recent backup in the catalog to get the JobId
741  *   and FileIndex, then insert them into the findex list.
742  */
743 static bool insert_file_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *file,
744                                         char *date)
745 {
746    strip_trailing_newline(file);
747    split_path_and_filename(rx, file);
748    if (*rx->JobIds == 0) {
749       Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, 
750            rx->ClientName);
751    } else {
752       Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
753            rx->path, rx->fname, rx->ClientName);
754    }
755    rx->found = false;
756    /* Find and insert jobid and File Index */
757    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
758       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
759          rx->query, db_strerror(ua->db));
760    }
761    if (!rx->found) {
762       bsendmsg(ua, _("No database record found for: %s\n"), file);
763       return true;
764    }
765    return true;
766 }
767
768 /*
769  * For a given path lookup the most recent backup in the catalog
770  * to get the JobId and FileIndexes of all files in that directory.
771  */
772 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
773                                         char *date)
774 {
775    strip_trailing_junk(dir);
776    if (*rx->JobIds == 0) {
777       bsendmsg(ua, _("No JobId specified cannot continue.\n"));
778       return false;
779    } else {
780       Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds, 
781            dir, rx->ClientName);
782    }
783    rx->found = false;
784    /* Find and insert jobid and File Index */
785    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
786       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
787          rx->query, db_strerror(ua->db));
788    }
789    if (!rx->found) {
790       bsendmsg(ua, _("No database record found for: %s\n"), dir);
791       return true;
792    }
793    return true;
794 }
795
796 /*
797  * Get the JobId and FileIndexes of all files in the specified table
798  */
799 static bool insert_table_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *table)
800 {
801    strip_trailing_junk(table);
802    Mmsg(rx->query, uar_jobid_fileindex_from_table, table);
803
804    rx->found = false;
805    /* Find and insert jobid and File Index */
806    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
807       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
808          rx->query, db_strerror(ua->db));
809    }
810    if (!rx->found) {
811       bsendmsg(ua, _("No table found: %s\n"), table);
812       return true;
813    }
814    return true;
815 }
816
817 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
818 {
819    char *p, *f;
820
821    /* Find path without the filename.
822     * I.e. everything after the last / is a "filename".
823     * OK, maybe it is a directory name, but we treat it like
824     * a filename. If we don't find a / then the whole name
825     * must be a path name (e.g. c:).
826     */
827    for (p=f=name; *p; p++) {
828       if (*p == '/') {
829          f = p;                       /* set pos of last slash */
830       }
831    }
832    if (*f == '/') {                   /* did we find a slash? */
833       f++;                            /* yes, point to filename */
834    } else {                           /* no, whole thing must be path name */
835       f = p;
836    }
837
838    /* If filename doesn't exist (i.e. root directory), we
839     * simply create a blank name consisting of a single
840     * space. This makes handling zero length filenames
841     * easier.
842     */
843    rx->fnl = p - f;
844    if (rx->fnl > 0) {
845       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
846       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
847       rx->fname[rx->fnl] = 0;
848    } else {
849       rx->fname[0] = 0;
850       rx->fnl = 0;
851    }
852
853    rx->pnl = f - name;
854    if (rx->pnl > 0) {
855       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
856       memcpy(rx->path, name, rx->pnl);
857       rx->path[rx->pnl] = 0;
858    } else {
859       rx->path[0] = 0;
860       rx->pnl = 0;
861    }
862
863    Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
864 }
865
866 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
867 {
868    TREE_CTX tree;
869    JobId_t JobId, last_JobId;
870    char *p;
871    bool OK = true;
872    char ed1[50];
873
874    memset(&tree, 0, sizeof(TREE_CTX));
875    /*
876     * Build the directory tree containing JobIds user selected
877     */
878    tree.root = new_tree(rx->TotalFiles);
879    tree.ua = ua;
880    tree.all = rx->all;
881    last_JobId = 0;
882    /*
883     * For display purposes, the same JobId, with different volumes may
884     * appear more than once, however, we only insert it once.
885     */
886    int items = 0;
887    p = rx->JobIds;
888    tree.FileEstimate = 0;
889    if (get_next_jobid_from_list(&p, &JobId) > 0) {
890       /* Use first JobId as estimate of the number of files to restore */
891       Mmsg(rx->query, uar_count_files, edit_int64(JobId, ed1));
892       if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
893          bsendmsg(ua, "%s\n", db_strerror(ua->db));
894       }
895       if (rx->found) {
896          /* Add about 25% more than this job for over estimate */
897          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
898          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
899       }
900    }
901    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
902       char ed1[50];
903
904       if (JobId == last_JobId) {
905          continue;                    /* eliminate duplicate JobIds */
906       }
907       last_JobId = JobId;
908       bsendmsg(ua, _("\nBuilding directory tree for JobId %s ...  "), 
909          edit_int64(JobId, ed1));
910       items++;
911       /*
912        * Find files for this JobId and insert them in the tree
913        */
914       Mmsg(rx->query, uar_sel_files, edit_int64(JobId, ed1));
915       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
916          bsendmsg(ua, "%s", db_strerror(ua->db));
917       }
918    }
919    if (tree.FileCount == 0) {
920       bsendmsg(ua, _("\nThere were no files inserted into the tree, so file selection\n"
921          "is not possible.Most likely your retention policy pruned the files\n"));
922       if (!get_yesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
923          OK = false;
924       } else {
925          last_JobId = 0;
926          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
927              if (JobId == last_JobId) {
928                 continue;                    /* eliminate duplicate JobIds */
929              }
930              add_findex_all(rx->bsr, JobId);
931           }
932           OK = true;
933       }
934    } else {
935       char ec1[50];
936       if (items==1) {
937          if (tree.all) {
938             bsendmsg(ua, _("\n1 Job, %s files inserted into the tree and marked for extraction.\n"),
939               edit_uint64_with_commas(tree.FileCount, ec1));
940          }
941          else {
942             bsendmsg(ua, _("\n1 Job, %s files inserted into the tree.\n"),
943               edit_uint64_with_commas(tree.FileCount, ec1));
944          }
945       }
946       else {
947          if (tree.all) {
948             bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree and marked for extraction.\n"),
949               items, edit_uint64_with_commas(tree.FileCount, ec1));
950          }
951          else {
952             bsendmsg(ua, _("\n%d Jobs, %s files inserted into the tree.\n"),
953               items, edit_uint64_with_commas(tree.FileCount, ec1));
954          }
955       }
956
957       if (find_arg(ua, NT_("done")) < 0) {
958          /* Let the user interact in selecting which files to restore */
959          OK = user_select_files_from_tree(&tree);
960       }
961
962       /*
963        * Walk down through the tree finding all files marked to be
964        *  extracted making a bootstrap file.
965        */
966       if (OK) {
967          for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
968             Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
969             if (node->extract || node->extract_dir) {
970                Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
971                add_findex(rx->bsr, node->JobId, node->FileIndex);
972                if (node->extract && node->type != TN_NEWDIR) {
973                   rx->selected_files++;  /* count only saved files */
974                }
975             }
976          }
977       }
978    }
979
980    free_tree(tree.root);              /* free the directory tree */
981    return OK;
982 }
983
984
985 /*
986  * This routine is used to get the current backup or a backup
987  *   before the specified date.
988  */
989 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
990 {
991    bool ok = false;
992    FILESET_DBR fsr;
993    CLIENT_DBR cr;
994    char fileset_name[MAX_NAME_LENGTH];
995    char ed1[50], ed2[50];
996    char pool_select[MAX_NAME_LENGTH];
997    int i;
998
999
1000    /* Create temp tables */
1001    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1002    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1003    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
1004       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1005    }
1006    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
1007       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1008    }
1009    /*
1010     * Select Client from the Catalog
1011     */
1012    memset(&cr, 0, sizeof(cr));
1013    if (!get_client_dbr(ua, &cr)) {
1014       goto bail_out;
1015    }
1016    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
1017
1018    /*
1019     * Get FileSet
1020     */
1021    memset(&fsr, 0, sizeof(fsr));
1022    i = find_arg_with_value(ua, "FileSet");
1023    if (i >= 0) {
1024       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1025       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1026          bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1027             db_strerror(ua->db));
1028          i = -1;
1029       }
1030    }
1031    if (i < 0) {                       /* fileset not found */
1032       edit_int64(cr.ClientId, ed1);
1033       Mmsg(rx->query, uar_sel_fileset, ed1, ed1);
1034       start_prompt(ua, _("The defined FileSet resources are:\n"));
1035       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
1036          bsendmsg(ua, "%s\n", db_strerror(ua->db));
1037       }
1038       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
1039                  fileset_name, sizeof(fileset_name)) < 0) {
1040          goto bail_out;
1041       }
1042
1043       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1044       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1045          bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1046          bsendmsg(ua, _("This probably means you modified the FileSet.\n"
1047                      "Continuing anyway.\n"));
1048       }
1049    }
1050
1051    /* If Pool specified, add PoolId specification */
1052    pool_select[0] = 0;
1053    if (rx->pool) {
1054       POOL_DBR pr;
1055       memset(&pr, 0, sizeof(pr));
1056       bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1057       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1058          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
1059             edit_int64(pr.PoolId, ed1));
1060       } else {
1061          bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1062       }
1063    }
1064
1065    /* Find JobId of last Full backup for this client, fileset */
1066    edit_int64(cr.ClientId, ed1);
1067    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1068          pool_select);
1069    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1070       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1071       goto bail_out;
1072    }
1073
1074    /* Find all Volumes used by that JobId */
1075    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1076       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1077       goto bail_out;
1078    }
1079    /* Note, this is needed because I don't seem to get the callback
1080     * from the call just above.
1081     */
1082    rx->JobTDate = 0;
1083    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1084       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1085    }
1086    if (rx->JobTDate == 0) {
1087       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1088       goto bail_out;
1089    }
1090
1091    /* Now find most recent Differental Job after Full save, if any */
1092    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1093         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1094    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1095       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1096    }
1097    /* Now update JobTDate to lock onto Differental, if any */
1098    rx->JobTDate = 0;
1099    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1100       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1101    }
1102    if (rx->JobTDate == 0) {
1103       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1104       goto bail_out;
1105    }
1106
1107    /* Now find all Incremental Jobs after Full/dif save */
1108    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1109         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1110    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1111       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1112    }
1113
1114    /* Get the JobIds from that list */
1115    rx->JobIds[0] = 0;
1116    rx->last_jobid[0] = 0;
1117    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1118       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1119    }
1120
1121    if (rx->JobIds[0] != 0) {
1122       /* Display a list of Jobs selected for this restore */
1123       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1124       ok = true;
1125    } else {
1126       bsendmsg(ua, _("No jobs found.\n"));
1127    }
1128
1129 bail_out:
1130    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1131    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1132    return ok;
1133 }
1134
1135
1136 /* 
1137  * Return next JobId from comma separated list   
1138  *
1139  * Returns:
1140  *   1 if next JobId returned
1141  *   0 if no more JobIds are in list
1142  *  -1 there is an error
1143  */
1144 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1145 {
1146    char jobid[30];
1147    char *q = *p;
1148
1149    jobid[0] = 0;
1150    for (int i=0; i<(int)sizeof(jobid); i++) {
1151       if (*q == 0) {
1152          break;
1153       } else if (*q == ',') {
1154          q++;
1155          break;
1156       }
1157       jobid[i] = *q++;
1158       jobid[i+1] = 0;
1159    }
1160    if (jobid[0] == 0) {
1161       return 0;
1162    } else if (!is_a_number(jobid)) {
1163       return -1;                      /* error */
1164    }
1165    *p = q;
1166    *JobId = str_to_int64(jobid);
1167    return 1;
1168 }
1169
1170 static int count_handler(void *ctx, int num_fields, char **row)
1171 {
1172    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1173    rx->JobId = str_to_int64(row[0]);
1174    rx->found = true;
1175    return 0;
1176 }
1177
1178 /*
1179  * Callback handler to get JobId and FileIndex for files
1180  *   can insert more than one depending on the caller.
1181  */
1182 static int jobid_fileindex_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    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1187    rx->found = true;
1188    rx->selected_files++;
1189    return 0;
1190 }
1191
1192 /*
1193  * Callback handler make list of JobIds
1194  */
1195 static int jobid_handler(void *ctx, int num_fields, char **row)
1196 {
1197    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1198
1199    if (strcmp(rx->last_jobid, row[0]) == 0) {
1200       return 0;                       /* duplicate id */
1201    }
1202    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1203    if (rx->JobIds[0] != 0) {
1204       pm_strcat(rx->JobIds, ",");
1205    }
1206    pm_strcat(rx->JobIds, row[0]);
1207    return 0;
1208 }
1209
1210
1211 /*
1212  * Callback handler to pickup last Full backup JobTDate
1213  */
1214 static int last_full_handler(void *ctx, int num_fields, char **row)
1215 {
1216    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1217
1218    rx->JobTDate = str_to_int64(row[1]);
1219    return 0;
1220 }
1221
1222 /*
1223  * Callback handler build FileSet name prompt list
1224  */
1225 static int fileset_handler(void *ctx, int num_fields, char **row)
1226 {
1227    /* row[0] = FileSet (name) */
1228    if (row[0]) {
1229       add_prompt((UAContext *)ctx, row[0]);
1230    }
1231    return 0;
1232 }
1233
1234 /*
1235  * Free names in the list
1236  */
1237 static void free_name_list(NAME_LIST *name_list)
1238 {
1239    for (int i=0; i < name_list->num_ids; i++) {
1240       free(name_list->name[i]);
1241    }
1242    if (name_list->name) {
1243       free(name_list->name);
1244       name_list->name = NULL;
1245    }
1246    name_list->max_ids = 0;
1247    name_list->num_ids = 0;
1248 }
1249
1250 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType) 
1251 {
1252    STORE *store;
1253
1254    if (rx.store) {
1255       Dmsg1(200, "Already have store=%s\n", rx.store->name());
1256       return;
1257    }
1258    /*
1259     * Try looking up Storage by name
1260     */
1261    LockRes();
1262    foreach_res(store, R_STORAGE) {
1263       if (strcmp(Storage, store->name()) == 0) {
1264          if (acl_access_ok(ua, Storage_ACL, store->name())) {
1265             rx.store = store;
1266          }
1267          break;
1268       }
1269    }
1270    UnlockRes();
1271
1272    if (rx.store) {
1273       /* Check if an explicit storage resource is given */
1274       store = NULL;
1275       int i = find_arg_with_value(ua, "storage");
1276       if (i > 0) {
1277          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1278          if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1279             store = NULL;
1280          }
1281       }
1282       if (store && (store != rx.store)) {
1283          bsendmsg(ua, _("Warning default storage overridden by \"%s\" on command line.\n"),
1284             store->name());
1285          rx.store = store;
1286          Dmsg1(200, "Set store=%s\n", rx.store->name());
1287       }
1288       return;
1289    }
1290
1291    /* If no storage resource, try to find one from MediaType */
1292    if (!rx.store) {
1293       LockRes();
1294       foreach_res(store, R_STORAGE) {
1295          if (strcmp(MediaType, store->media_type) == 0) {
1296             if (acl_access_ok(ua, Storage_ACL, store->name())) {
1297                rx.store = store;
1298                Dmsg1(200, "Set store=%s\n", rx.store->name());
1299                bsendmsg(ua, _("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1300                   Storage, store->name(), MediaType);
1301             }
1302             UnlockRes();
1303             return;
1304          }
1305       }
1306       UnlockRes();
1307       bsendmsg(ua, _("\nUnable to find Storage resource for\n"
1308          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1309    }
1310
1311    /* Take command line arg, or ask user if none */
1312    rx.store = get_storage_resource(ua, false /* don't use default */);
1313    Dmsg1(200, "Set store=%s\n", rx.store->name());
1314
1315 }