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