]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
420931d5f190bee571778c2b16388c43427079ac
[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 of John 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_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       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 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          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] != '<' && !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    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 (IsPathSeparator(*p)) {
829          f = p;                       /* set pos of last slash */
830       }
831    }
832    if (IsPathSeparator(*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          bsendmsg(ua, _("No FileSet found for client \"%s\".\n"), cr.Name);
1041          goto bail_out;
1042       }
1043
1044       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1045       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
1046          bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
1047          bsendmsg(ua, _("This probably means you modified the FileSet.\n"
1048                      "Continuing anyway.\n"));
1049       }
1050    }
1051
1052    /* If Pool specified, add PoolId specification */
1053    pool_select[0] = 0;
1054    if (rx->pool) {
1055       POOL_DBR pr;
1056       memset(&pr, 0, sizeof(pr));
1057       bstrncpy(pr.Name, rx->pool->name(), sizeof(pr.Name));
1058       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
1059          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
1060             edit_int64(pr.PoolId, ed1));
1061       } else {
1062          bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1063       }
1064    }
1065
1066    /* Find JobId of last Full backup for this client, fileset */
1067    edit_int64(cr.ClientId, ed1);
1068    Mmsg(rx->query, uar_last_full, ed1, ed1, date, fsr.FileSet,
1069          pool_select);
1070    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1071       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1072       goto bail_out;
1073    }
1074
1075    /* Find all Volumes used by that JobId */
1076    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1077       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1078       goto bail_out;
1079    }
1080    /* Note, this is needed because I don't seem to get the callback
1081     * from the call just above.
1082     */
1083    rx->JobTDate = 0;
1084    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1085       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1086    }
1087    if (rx->JobTDate == 0) {
1088       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1089       goto bail_out;
1090    }
1091
1092    /* Now find most recent Differental Job after Full save, if any */
1093    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1094         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1095    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1096       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1097    }
1098    /* Now update JobTDate to lock onto Differental, if any */
1099    rx->JobTDate = 0;
1100    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1101       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1102    }
1103    if (rx->JobTDate == 0) {
1104       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1105       goto bail_out;
1106    }
1107
1108    /* Now find all Incremental Jobs after Full/dif save */
1109    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1110         edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1111    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1112       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1113    }
1114
1115    /* Get the JobIds from that list */
1116    rx->JobIds[0] = 0;
1117    rx->last_jobid[0] = 0;
1118    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1119       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1120    }
1121
1122    if (rx->JobIds[0] != 0) {
1123       /* Display a list of Jobs selected for this restore */
1124       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1125       ok = true;
1126    } else {
1127       bsendmsg(ua, _("No jobs found.\n"));
1128    }
1129
1130 bail_out:
1131    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1132    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1133    return ok;
1134 }
1135
1136
1137 /* 
1138  * Return next JobId from comma separated list   
1139  *
1140  * Returns:
1141  *   1 if next JobId returned
1142  *   0 if no more JobIds are in list
1143  *  -1 there is an error
1144  */
1145 int get_next_jobid_from_list(char **p, JobId_t *JobId)
1146 {
1147    char jobid[30];
1148    char *q = *p;
1149
1150    jobid[0] = 0;
1151    for (int i=0; i<(int)sizeof(jobid); i++) {
1152       if (*q == 0) {
1153          break;
1154       } else if (*q == ',') {
1155          q++;
1156          break;
1157       }
1158       jobid[i] = *q++;
1159       jobid[i+1] = 0;
1160    }
1161    if (jobid[0] == 0) {
1162       return 0;
1163    } else if (!is_a_number(jobid)) {
1164       return -1;                      /* error */
1165    }
1166    *p = q;
1167    *JobId = str_to_int64(jobid);
1168    return 1;
1169 }
1170
1171 static int count_handler(void *ctx, int num_fields, char **row)
1172 {
1173    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1174    rx->JobId = str_to_int64(row[0]);
1175    rx->found = true;
1176    return 0;
1177 }
1178
1179 /*
1180  * Callback handler to get JobId and FileIndex for files
1181  *   can insert more than one depending on the caller.
1182  */
1183 static int jobid_fileindex_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    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1188    rx->found = true;
1189    rx->selected_files++;
1190    return 0;
1191 }
1192
1193 /*
1194  * Callback handler make list of JobIds
1195  */
1196 static int jobid_handler(void *ctx, int num_fields, char **row)
1197 {
1198    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1199
1200    if (strcmp(rx->last_jobid, row[0]) == 0) {
1201       return 0;                       /* duplicate id */
1202    }
1203    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1204    if (rx->JobIds[0] != 0) {
1205       pm_strcat(rx->JobIds, ",");
1206    }
1207    pm_strcat(rx->JobIds, row[0]);
1208    return 0;
1209 }
1210
1211
1212 /*
1213  * Callback handler to pickup last Full backup JobTDate
1214  */
1215 static int last_full_handler(void *ctx, int num_fields, char **row)
1216 {
1217    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1218
1219    rx->JobTDate = str_to_int64(row[1]);
1220    return 0;
1221 }
1222
1223 /*
1224  * Callback handler build FileSet name prompt list
1225  */
1226 static int fileset_handler(void *ctx, int num_fields, char **row)
1227 {
1228    /* row[0] = FileSet (name) */
1229    if (row[0]) {
1230       add_prompt((UAContext *)ctx, row[0]);
1231    }
1232    return 0;
1233 }
1234
1235 /*
1236  * Free names in the list
1237  */
1238 static void free_name_list(NAME_LIST *name_list)
1239 {
1240    for (int i=0; i < name_list->num_ids; i++) {
1241       free(name_list->name[i]);
1242    }
1243    if (name_list->name) {
1244       free(name_list->name);
1245       name_list->name = NULL;
1246    }
1247    name_list->max_ids = 0;
1248    name_list->num_ids = 0;
1249 }
1250
1251 void find_storage_resource(UAContext *ua, RESTORE_CTX &rx, char *Storage, char *MediaType) 
1252 {
1253    STORE *store;
1254
1255    if (rx.store) {
1256       Dmsg1(200, "Already have store=%s\n", rx.store->name());
1257       return;
1258    }
1259    /*
1260     * Try looking up Storage by name
1261     */
1262    LockRes();
1263    foreach_res(store, R_STORAGE) {
1264       if (strcmp(Storage, store->name()) == 0) {
1265          if (acl_access_ok(ua, Storage_ACL, store->name())) {
1266             rx.store = store;
1267          }
1268          break;
1269       }
1270    }
1271    UnlockRes();
1272
1273    if (rx.store) {
1274       /* Check if an explicit storage resource is given */
1275       store = NULL;
1276       int i = find_arg_with_value(ua, "storage");
1277       if (i > 0) {
1278          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1279          if (store && !acl_access_ok(ua, Storage_ACL, store->name())) {
1280             store = NULL;
1281          }
1282       }
1283       if (store && (store != rx.store)) {
1284          bsendmsg(ua, _("Warning default storage overridden by \"%s\" on command line.\n"),
1285             store->name());
1286          rx.store = store;
1287          Dmsg1(200, "Set store=%s\n", rx.store->name());
1288       }
1289       return;
1290    }
1291
1292    /* If no storage resource, try to find one from MediaType */
1293    if (!rx.store) {
1294       LockRes();
1295       foreach_res(store, R_STORAGE) {
1296          if (strcmp(MediaType, store->media_type) == 0) {
1297             if (acl_access_ok(ua, Storage_ACL, store->name())) {
1298                rx.store = store;
1299                Dmsg1(200, "Set store=%s\n", rx.store->name());
1300                bsendmsg(ua, _("Storage \"%s\" not found, using Storage \"%s\" from MediaType \"%s\".\n"),
1301                   Storage, store->name(), MediaType);
1302             }
1303             UnlockRes();
1304             return;
1305          }
1306       }
1307       UnlockRes();
1308       bsendmsg(ua, _("\nUnable to find Storage resource for\n"
1309          "MediaType \"%s\", needed by the Jobs you selected.\n"), MediaType);
1310    }
1311
1312    /* Take command line arg, or ask user if none */
1313    rx.store = get_storage_resource(ua, false /* don't use default */);
1314    Dmsg1(200, "Set store=%s\n", rx.store->name());
1315
1316 }