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