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