]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
3639758865c9ad85d3b85b42f159bcd43cb76975
[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    strip_trailing_junk(file);
691    split_path_and_filename(rx, file);
692    if (*rx->JobIds == 0) {
693       Mmsg(rx->query, uar_jobid_fileindex, date, rx->path, rx->fname, 
694            rx->ClientName);
695    } else {
696       Mmsg(rx->query, uar_jobids_fileindex, rx->JobIds, date,
697            rx->path, rx->fname, rx->ClientName);
698    }
699    rx->found = false;
700    /* Find and insert jobid and File Index */
701    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
702       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
703          rx->query, db_strerror(ua->db));
704    }
705    if (!rx->found) {
706       bsendmsg(ua, _("No database record found for: %s\n"), file);
707       return true;
708    }
709    /*
710     * Find the MediaTypes for this JobId and add to the name_list
711     */
712    Mmsg(rx->query, uar_mediatype, rx->JobId);
713    if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
714       bsendmsg(ua, "%s", db_strerror(ua->db));
715       return false;
716    }
717    return true;
718 }
719
720 /*
721  * For a given path lookup the most recent backup in the catalog
722  * to get the JobId and FileIndexes of all files in that directory.
723  */
724 static bool insert_dir_into_findex_list(UAContext *ua, RESTORE_CTX *rx, char *dir,
725                                         char *date)
726 {
727    strip_trailing_junk(dir);
728    if (*rx->JobIds == 0) {
729       bsendmsg(ua, _("No JobId specified cannot continue.\n"));
730       return false;
731    } else {
732       Mmsg(rx->query, uar_jobid_fileindex_from_dir, rx->JobIds, 
733            dir, rx->ClientName);
734    }
735    rx->found = false;
736    /* Find and insert jobid and File Index */
737    if (!db_sql_query(ua->db, rx->query, jobid_fileindex_handler, (void *)rx)) {
738       bsendmsg(ua, _("Query failed: %s. ERR=%s\n"),
739          rx->query, db_strerror(ua->db));
740    }
741    if (!rx->found) {
742       bsendmsg(ua, _("No database record found for: %s\n"), dir);
743       return true;
744    }
745    /*
746     * Find the MediaTypes for this JobId and add to the name_list
747     */
748    Mmsg(rx->query, uar_mediatype, rx->JobId);
749    if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
750       bsendmsg(ua, "%s", db_strerror(ua->db));
751       return false;
752    }
753    return true;
754 }
755
756
757 static void split_path_and_filename(RESTORE_CTX *rx, char *name)
758 {
759    char *p, *f;
760
761    /* Find path without the filename.
762     * I.e. everything after the last / is a "filename".
763     * OK, maybe it is a directory name, but we treat it like
764     * a filename. If we don't find a / then the whole name
765     * must be a path name (e.g. c:).
766     */
767    for (p=f=name; *p; p++) {
768       if (*p == '/') {
769          f = p;                       /* set pos of last slash */
770       }
771    }
772    if (*f == '/') {                   /* did we find a slash? */
773       f++;                            /* yes, point to filename */
774    } else {                           /* no, whole thing must be path name */
775       f = p;
776    }
777
778    /* If filename doesn't exist (i.e. root directory), we
779     * simply create a blank name consisting of a single
780     * space. This makes handling zero length filenames
781     * easier.
782     */
783    rx->fnl = p - f;
784    if (rx->fnl > 0) {
785       rx->fname = check_pool_memory_size(rx->fname, rx->fnl+1);
786       memcpy(rx->fname, f, rx->fnl);    /* copy filename */
787       rx->fname[rx->fnl] = 0;
788    } else {
789       rx->fname[0] = 0;
790       rx->fnl = 0;
791    }
792
793    rx->pnl = f - name;
794    if (rx->pnl > 0) {
795       rx->path = check_pool_memory_size(rx->path, rx->pnl+1);
796       memcpy(rx->path, name, rx->pnl);
797       rx->path[rx->pnl] = 0;
798    } else {
799       rx->path[0] = 0;
800       rx->pnl = 0;
801    }
802
803    Dmsg2(100, "sllit path=%s file=%s\n", rx->path, rx->fname);
804 }
805
806 static bool build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
807 {
808    TREE_CTX tree;
809    JobId_t JobId, last_JobId;
810    char *p;
811    bool OK = true;
812
813    memset(&tree, 0, sizeof(TREE_CTX));
814    /*
815     * Build the directory tree containing JobIds user selected
816     */
817    tree.root = new_tree(rx->TotalFiles);
818    tree.ua = ua;
819    tree.all = rx->all;
820    last_JobId = 0;
821    /*
822     * For display purposes, the same JobId, with different volumes may
823     * appear more than once, however, we only insert it once.
824     */
825    int items = 0;
826    p = rx->JobIds;
827    tree.FileEstimate = 0;
828    if (get_next_jobid_from_list(&p, &JobId) > 0) {
829       /* Use first JobId as estimate of the number of files to restore */
830       Mmsg(rx->query, uar_count_files, JobId);
831       if (!db_sql_query(ua->db, rx->query, count_handler, (void *)rx)) {
832          bsendmsg(ua, "%s\n", db_strerror(ua->db));
833       }
834       if (rx->found) {
835          /* Add about 25% more than this job for over estimate */
836          tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
837          tree.DeltaCount = rx->JobId/50; /* print 50 ticks */
838       }
839    }
840    for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
841       char ed1[50];
842
843       if (JobId == last_JobId) {
844          continue;                    /* eliminate duplicate JobIds */
845       }
846       last_JobId = JobId;
847       bsendmsg(ua, _("\nBuilding directory tree for JobId %s ...  "), 
848          edit_int64(JobId, ed1));
849       items++;
850       /*
851        * Find files for this JobId and insert them in the tree
852        */
853       Mmsg(rx->query, uar_sel_files, JobId);
854       if (!db_sql_query(ua->db, rx->query, insert_tree_handler, (void *)&tree)) {
855          bsendmsg(ua, "%s", db_strerror(ua->db));
856       }
857       /*
858        * Find the MediaTypes for this JobId and add to the name_list
859        */
860       Mmsg(rx->query, uar_mediatype, JobId);
861       if (!db_sql_query(ua->db, rx->query, unique_name_list_handler, (void *)&rx->name_list)) {
862          bsendmsg(ua, "%s", db_strerror(ua->db));
863       }
864    }
865    if (tree.FileCount == 0) {
866       bsendmsg(ua, "\nThere were no files inserted into the tree, so file selection\n"
867          "is not possible.\n");
868       if (!get_yesno(ua, _("Do you want to restore all the files? (yes|no): "))) {
869          OK = false;
870       } else {
871          last_JobId = 0;
872          for (p=rx->JobIds; get_next_jobid_from_list(&p, &JobId) > 0; ) {
873              if (JobId == last_JobId) {
874                 continue;                    /* eliminate duplicate JobIds */
875              }
876              add_findex_all(rx->bsr, JobId);
877           }
878           OK = true;
879       }
880    } else {
881       char ec1[50];
882       bsendmsg(ua, "\n%d Job%s, %s files inserted into the tree%s.\n",
883          items, items==1?"":"s", edit_uint64_with_commas(tree.FileCount, ec1),
884          tree.all?" and marked for extraction":"");
885
886       /* Check MediaType and select storage that corresponds */
887       get_storage_from_mediatype(ua, &rx->name_list, rx);
888
889       if (find_arg(ua, _("done")) < 0) {
890          /* Let the user interact in selecting which files to restore */
891          OK = user_select_files_from_tree(&tree);
892       }
893
894       /*
895        * Walk down through the tree finding all files marked to be
896        *  extracted making a bootstrap file.
897        */
898       if (OK) {
899          for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
900             Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
901             if (node->extract || node->extract_dir) {
902                Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
903                add_findex(rx->bsr, node->JobId, node->FileIndex);
904                if (node->extract && node->type != TN_NEWDIR) {
905                   rx->selected_files++;  /* count only saved files */
906                }
907             }
908          }
909       }
910    }
911
912    free_tree(tree.root);              /* free the directory tree */
913    return OK;
914 }
915
916
917 /*
918  * This routine is used to get the current backup or a backup
919  *   before the specified date.
920  */
921 static bool select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
922 {
923    bool ok = false;
924    FILESET_DBR fsr;
925    CLIENT_DBR cr;
926    char fileset_name[MAX_NAME_LENGTH];
927    char ed1[50];
928    char pool_select[MAX_NAME_LENGTH];
929    int i;
930
931
932    /* Create temp tables */
933    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
934    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
935    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
936       bsendmsg(ua, "%s\n", db_strerror(ua->db));
937    }
938    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
939       bsendmsg(ua, "%s\n", db_strerror(ua->db));
940    }
941    /*
942     * Select Client from the Catalog
943     */
944    memset(&cr, 0, sizeof(cr));
945    if (!get_client_dbr(ua, &cr)) {
946       goto bail_out;
947    }
948    bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
949
950    /*
951     * Get FileSet
952     */
953    memset(&fsr, 0, sizeof(fsr));
954    i = find_arg_with_value(ua, "FileSet");
955    if (i >= 0) {
956       bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
957       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
958          bsendmsg(ua, _("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
959             db_strerror(ua->db));
960          i = -1;
961       }
962    }
963    if (i < 0) {                       /* fileset not found */
964       Mmsg(rx->query, uar_sel_fileset, cr.ClientId, cr.ClientId);
965       start_prompt(ua, _("The defined FileSet resources are:\n"));
966       if (!db_sql_query(ua->db, rx->query, fileset_handler, (void *)ua)) {
967          bsendmsg(ua, "%s\n", db_strerror(ua->db));
968       }
969       if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
970                  fileset_name, sizeof(fileset_name)) < 0) {
971          goto bail_out;
972       }
973
974       bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
975       if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
976          bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
977          bsendmsg(ua, _("This probably means you modified the FileSet.\n"
978                      "Continuing anyway.\n"));
979       }
980    }
981
982    /* If Pool specified, add PoolId specification */
983    pool_select[0] = 0;
984    if (rx->pool) {
985       POOL_DBR pr;
986       char ed1[50];
987       memset(&pr, 0, sizeof(pr));
988       bstrncpy(pr.Name, rx->pool->hdr.name, sizeof(pr.Name));
989       if (db_get_pool_record(ua->jcr, ua->db, &pr)) {
990          bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ", 
991             edit_int64(pr.PoolId, ed1));
992       } else {
993          bsendmsg(ua, _("Pool \"%s\" not found, using any pool.\n"), pr.Name);
994       }
995    }
996
997    /* Find JobId of last Full backup for this client, fileset */
998    Mmsg(rx->query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSet,
999          pool_select);
1000    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1001       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1002       goto bail_out;
1003    }
1004
1005    /* Find all Volumes used by that JobId */
1006    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
1007       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1008       goto bail_out;
1009    }
1010    /* Note, this is needed because I don't seem to get the callback
1011     * from the call just above.
1012     */
1013    rx->JobTDate = 0;
1014    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
1015       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1016    }
1017    if (rx->JobTDate == 0) {
1018       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1019       goto bail_out;
1020    }
1021
1022    /* Now find most recent Differental Job after Full save, if any */
1023    Mmsg(rx->query, uar_dif, edit_uint64(rx->JobTDate, ed1), date,
1024         cr.ClientId, fsr.FileSet, pool_select);
1025    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1026       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1027    }
1028    /* Now update JobTDate to lock onto Differental, if any */
1029    rx->JobTDate = 0;
1030    if (!db_sql_query(ua->db, uar_sel_all_temp, last_full_handler, (void *)rx)) {
1031       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1032    }
1033    if (rx->JobTDate == 0) {
1034       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
1035       goto bail_out;
1036    }
1037
1038    /* Now find all Incremental Jobs after Full/dif save */
1039    Mmsg(rx->query, uar_inc, edit_uint64(rx->JobTDate, ed1), date,
1040         cr.ClientId, fsr.FileSet, pool_select);
1041    if (!db_sql_query(ua->db, rx->query, NULL, NULL)) {
1042       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1043    }
1044
1045    /* Get the JobIds from that list */
1046    rx->JobIds[0] = 0;
1047    rx->last_jobid[0] = 0;
1048    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
1049       bsendmsg(ua, "%s\n", db_strerror(ua->db));
1050    }
1051
1052    if (rx->JobIds[0] != 0) {
1053       /* Display a list of Jobs selected for this restore */
1054       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
1055       ok = true;
1056    } else {
1057       bsendmsg(ua, _("No jobs found.\n"));
1058    }
1059
1060 bail_out:
1061    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
1062    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
1063    return ok;
1064 }
1065
1066
1067 /* Return next JobId from comma separated list */
1068 static int get_next_jobid_from_list(char **p, uint32_t *JobId)
1069 {
1070    char jobid[30];
1071    char *q = *p;
1072
1073    jobid[0] = 0;
1074    for (int i=0; i<(int)sizeof(jobid); i++) {
1075       if (*q == 0) {
1076          break;
1077       } else if (*q == ',') {
1078          q++;
1079          break;
1080       }
1081       jobid[i] = *q++;
1082       jobid[i+1] = 0;
1083    }
1084    if (jobid[0] == 0) {
1085       return 0;
1086    } else if (!is_a_number(jobid)) {
1087       return -1;                      /* error */
1088    }
1089    *p = q;
1090    *JobId = str_to_int64(jobid);
1091    return 1;
1092 }
1093
1094 static int count_handler(void *ctx, int num_fields, char **row)
1095 {
1096    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1097    rx->JobId = str_to_int64(row[0]);
1098    rx->found = true;
1099    return 0;
1100 }
1101
1102 /*
1103  * Callback handler to get JobId and FileIndex for files
1104  *   can insert more than one depending on the caller.
1105  */
1106 static int jobid_fileindex_handler(void *ctx, int num_fields, char **row)
1107 {
1108    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1109    rx->JobId = str_to_int64(row[0]);
1110    add_findex(rx->bsr, rx->JobId, str_to_int64(row[1]));
1111    rx->found = true;
1112    rx->selected_files++;
1113    return 0;
1114 }
1115
1116 /*
1117  * Callback handler make list of JobIds
1118  */
1119 static int jobid_handler(void *ctx, int num_fields, char **row)
1120 {
1121    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1122
1123    if (strcmp(rx->last_jobid, row[0]) == 0) {
1124       return 0;                       /* duplicate id */
1125    }
1126    bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1127    if (rx->JobIds[0] != 0) {
1128       pm_strcat(rx->JobIds, ",");
1129    }
1130    pm_strcat(rx->JobIds, row[0]);
1131    return 0;
1132 }
1133
1134
1135 /*
1136  * Callback handler to pickup last Full backup JobTDate
1137  */
1138 static int last_full_handler(void *ctx, int num_fields, char **row)
1139 {
1140    RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
1141
1142    rx->JobTDate = str_to_int64(row[1]);
1143    return 0;
1144 }
1145
1146 /*
1147  * Callback handler build FileSet name prompt list
1148  */
1149 static int fileset_handler(void *ctx, int num_fields, char **row)
1150 {
1151    /* row[0] = FileSet (name) */
1152    if (row[0]) {
1153       add_prompt((UAContext *)ctx, row[0]);
1154    }
1155    return 0;
1156 }
1157
1158 /*
1159  * Called here with each name to be added to the list. The name is
1160  *   added to the list if it is not already in the list.
1161  *
1162  * Used to make unique list of FileSets and MediaTypes
1163  */
1164 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1165 {
1166    NAME_LIST *name = (NAME_LIST *)ctx;
1167
1168    if (name->num_ids == MAX_ID_LIST_LEN) {
1169       return 1;
1170    }
1171    if (name->num_ids == name->max_ids) {
1172       if (name->max_ids == 0) {
1173          name->max_ids = 1000;
1174          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1175       } else {
1176          name->max_ids = (name->max_ids * 3) / 2;
1177          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1178       }
1179    }
1180    for (int i=0; i<name->num_ids; i++) {
1181       if (strcmp(name->name[i], row[0]) == 0) {
1182          return 0;                    /* already in list, return */
1183       }
1184    }
1185    /* Add new name to list */
1186    name->name[name->num_ids++] = bstrdup(row[0]);
1187    return 0;
1188 }
1189
1190
1191 /*
1192  * Print names in the list
1193  */
1194 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1195 {
1196    for (int i=0; i < name_list->num_ids; i++) {
1197       bsendmsg(ua, "%s\n", name_list->name[i]);
1198    }
1199 }
1200
1201
1202 /*
1203  * Free names in the list
1204  */
1205 static void free_name_list(NAME_LIST *name_list)
1206 {
1207    for (int i=0; i < name_list->num_ids; i++) {
1208       free(name_list->name[i]);
1209    }
1210    if (name_list->name) {
1211       free(name_list->name);
1212       name_list->name = NULL;
1213    }
1214    name_list->max_ids = 0;
1215    name_list->num_ids = 0;
1216 }
1217
1218 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
1219 {
1220    STORE *store;
1221
1222    if (name_list->num_ids > 1) {
1223       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1224          "Restore is not possible. The MediaTypes used are:\n"));
1225       print_name_list(ua, name_list);
1226       rx->store = select_storage_resource(ua);
1227       return;
1228    }
1229
1230    if (name_list->num_ids == 0) {
1231       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1232       rx->store = select_storage_resource(ua);
1233       return;
1234    }
1235    if (rx->store) {
1236       return;
1237    }
1238    /*
1239     * We have a single MediaType, look it up in our Storage resource
1240     */
1241    LockRes();
1242    foreach_res(store, R_STORAGE) {
1243       if (strcmp(name_list->name[0], store->media_type) == 0) {
1244          if (acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1245             rx->store = store;
1246          }
1247          break;
1248       }
1249    }
1250    UnlockRes();
1251
1252    if (rx->store) {
1253       /* Check if an explicit storage resource is given */
1254       store = NULL;
1255       int i = find_arg_with_value(ua, "storage");
1256       if (i > 0) {
1257          store = (STORE *)GetResWithName(R_STORAGE, ua->argv[i]);
1258          if (store && !acl_access_ok(ua, Storage_ACL, store->hdr.name)) {
1259             store = NULL;
1260          }
1261       }
1262       if (store && (store != rx->store)) {
1263          bsendmsg(ua, _("Warning default storage overridden by %s on command line.\n"),
1264             store->hdr.name);
1265          rx->store = store;
1266       }
1267       return;
1268    }
1269
1270    /* Take command line arg, or ask user if none */
1271    rx->store = get_storage_resource(ua, false /* don't use default */);
1272
1273    if (!rx->store) {
1274       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1275          "MediaType \"%s\", needed by the Jobs you selected.\n"
1276          "You will be allowed to select a Storage device later.\n"),
1277          name_list->name[0]);
1278    }
1279 }