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