]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Clean up old structs in dird + remove Slot invalidation code
[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
5  *
6  *     Kern Sibbald, July MMII
7  *
8  *   Version $Id$
9  */
10
11 /*
12    Copyright (C) 2002-2003 Kern Sibbald and John Walker
13
14    This program is free software; you can redistribute it and/or
15    modify it under the terms of the GNU General Public License as
16    published by the Free Software Foundation; either version 2 of
17    the License, or (at your option) any later version.
18
19    This program is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22    General Public License for more details.
23
24    You should have received a copy of the GNU General Public
25    License along with this program; if not, write to the Free
26    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27    MA 02111-1307, USA.
28
29  */
30
31 #include "bacula.h"
32 #include "dird.h"
33 #include <fnmatch.h>
34 #include "findlib/find.h"
35
36
37
38 /* Imported functions */
39 extern int runcmd(UAContext *ua, char *cmd);
40
41 /* Imported variables */
42 extern char *uar_list_jobs,     *uar_file,        *uar_sel_files;
43 extern char *uar_del_temp,      *uar_del_temp1,   *uar_create_temp;
44 extern char *uar_create_temp1,  *uar_last_full,   *uar_full;
45 extern char *uar_inc,           *uar_list_temp,   *uar_sel_jobid_temp;
46 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
47
48
49 /* Context for insert_tree_handler() */
50 struct TREE_CTX {
51    TREE_ROOT *root;                   /* root */
52    TREE_NODE *node;                   /* current node */
53    TREE_NODE *avail_node;             /* unused node last insert */
54    int cnt;                           /* count for user feedback */
55    UAContext *ua;
56 };
57
58 /* Main structure for obtaining JobIds */
59 struct JOBIDS {
60    utime_t JobTDate;
61    uint32_t TotalFiles;
62    char ClientName[MAX_NAME_LENGTH];
63    char JobIds[200];                  /* User entered string of JobIds */
64    STORE  *store;
65 };
66
67
68 /* FileIndex entry in restore bootstrap record */
69 struct RBSR_FINDEX {
70    RBSR_FINDEX *next;
71    int32_t findex;
72    int32_t findex2;
73 };
74
75 /* 
76  * Restore bootstrap record -- not the real one, but useful here   
77  *  The restore bsr is a chain of BSR records (linked by next).
78  *  Each BSR represents a single JobId, and within it, it
79  *    contains a linked list of file indexes for that JobId.
80  *    The complete_bsr() routine, will then add all the volumes
81  *    on which the Job is stored to the BSR.
82  */
83 struct RBSR {
84    RBSR *next;                        /* next JobId */
85    uint32_t JobId;                    /* JobId this bsr */
86    uint32_t VolSessionId;                   
87    uint32_t VolSessionTime;
88    int      VolCount;                 /* Volume parameter count */
89    VOL_PARAMS *VolParams;             /* Volume, start/end file/blocks */
90    RBSR_FINDEX *fi;                   /* File indexes this JobId */
91 };
92
93 struct NAME_LIST {
94    char **name;                       /* list of names */
95    int num_ids;                       /* ids stored */
96    int max_ids;                       /* size of array */
97    int num_del;                       /* number deleted */
98    int tot_ids;                       /* total to process */
99 };
100
101 #define MAX_ID_LIST_LEN 1000000
102
103
104 /* Forward referenced functions */
105 static RBSR *new_bsr();
106 static void free_bsr(RBSR *bsr);
107 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd);
108 static int  write_bsr_file(UAContext *ua, RBSR *bsr);
109 static void print_bsr(UAContext *ua, RBSR *bsr);
110 static int  complete_bsr(UAContext *ua, RBSR *bsr);
111 static int insert_tree_handler(void *ctx, int num_fields, char **row);
112 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex);
113 static int last_full_handler(void *ctx, int num_fields, char **row);
114 static int jobid_handler(void *ctx, int num_fields, char **row);
115 static int next_jobid_from_list(char **p, uint32_t *JobId);
116 static int user_select_jobids(UAContext *ua, JOBIDS *ji);
117 static void user_select_files(TREE_CTX *tree);
118 static int fileset_handler(void *ctx, int num_fields, char **row);
119 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
120 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
121 static void free_name_list(NAME_LIST *name_list);
122 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji);
123 static RBSR *sort_bsr(RBSR *bsr);
124
125
126 /*
127  *   Restore files
128  *
129  */
130 int restorecmd(UAContext *ua, char *cmd)
131 {
132    POOLMEM *query;
133    TREE_CTX tree;
134    JobId_t JobId, last_JobId;
135    char *p;
136    RBSR *bsr;
137    char *nofname = "";
138    JOBIDS ji;
139    JOB *job = NULL;
140    JOB *restore_job = NULL;
141    int restore_jobs = 0;
142    NAME_LIST name_list;
143    uint32_t selected_files = 0;
144    char *where = NULL;
145    int i;
146
147    i = find_arg_with_value(ua, "where");
148    if (i >= 0) {
149       where = ua->argv[i];
150    }
151
152    if (!open_db(ua)) {
153       return 0;
154    }
155
156    memset(&tree, 0, sizeof(TREE_CTX));
157    memset(&name_list, 0, sizeof(name_list));
158    memset(&ji, 0, sizeof(ji));
159
160    /* Ensure there is at least one Restore Job */
161    LockRes();
162    while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
163       if (job->JobType == JT_RESTORE) {
164          if (!restore_job) {
165             restore_job = job;
166          }
167          restore_jobs++;
168       }
169    }
170    UnlockRes();
171    if (!restore_jobs) {
172       bsendmsg(ua, _(
173          "No Restore Job Resource found. You must create at least\n"
174          "one before running this command.\n"));
175       return 0;
176    }
177
178    /* 
179     * Request user to select JobIds by various different methods
180     *  last 20 jobs, where File saved, most recent backup, ...
181     */
182    if (!user_select_jobids(ua, &ji)) {
183       return 0;
184    }
185
186    /* 
187     * Build the directory tree containing JobIds user selected
188     */
189    tree.root = new_tree(ji.TotalFiles);
190    tree.root->fname = nofname;
191    tree.ua = ua;
192    query = get_pool_memory(PM_MESSAGE);
193    last_JobId = 0;
194    /*
195     * For display purposes, the same JobId, with different volumes may
196     * appear more than once, however, we only insert it once.
197     */
198    int items = 0;
199    for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
200
201       if (JobId == last_JobId) {             
202          continue;                    /* eliminate duplicate JobIds */
203       }
204       last_JobId = JobId;
205       bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
206       items++;
207       /*
208        * Find files for this JobId and insert them in the tree
209        */
210       Mmsg(&query, uar_sel_files, JobId);
211       if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
212          bsendmsg(ua, "%s", db_strerror(ua->db));
213       }
214       /*
215        * Find the FileSets for this JobId and add to the name_list
216        */
217       Mmsg(&query, uar_mediatype, JobId);
218       if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
219          bsendmsg(ua, "%s", db_strerror(ua->db));
220       }
221
222    }
223    bsendmsg(ua, "%d items inserted into the tree and marked for extraction.\n", items);
224    free_pool_memory(query);
225
226    /* Check MediaType and select storage that corresponds */
227    get_storage_from_mediatype(ua, &name_list, &ji);
228    free_name_list(&name_list);
229
230    /* Let the user select which files to restore */
231    user_select_files(&tree);
232
233    /*
234     * Walk down through the tree finding all files marked to be 
235     *  extracted making a bootstrap file.
236     */
237    bsr = new_bsr();
238    for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
239       Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
240       if (node->extract) {
241          Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
242          add_findex(bsr, node->JobId, node->FileIndex);
243          selected_files++;
244       }
245    }
246
247    free_tree(tree.root);              /* free the directory tree */
248
249    if (bsr->JobId) {
250       if (!complete_bsr(ua, bsr)) {   /* find Vol, SessId, SessTime from JobIds */
251          bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
252          free_bsr(bsr);
253          return 0;
254       }
255 //    print_bsr(ua, bsr);
256       write_bsr_file(ua, bsr);
257       bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
258    } else {
259       bsendmsg(ua, _("No files selected to restore.\n"));
260    }
261    free_bsr(bsr);
262
263    if (restore_jobs == 1) {
264       job = restore_job;
265    } else {
266       job = select_restore_job_resource(ua);
267    }
268    if (!job) {
269       bsendmsg(ua, _("No Restore Job resource found!\n"));
270       return 0;
271    }
272
273    /* If no client name specified yet, get it now */
274    if (!ji.ClientName[0]) {
275       CLIENT_DBR cr;
276       memset(&cr, 0, sizeof(cr));
277       if (!get_client_dbr(ua, &cr)) {
278          return 0;
279       }
280       bstrncpy(ji.ClientName, cr.Name, sizeof(ji.ClientName));
281    }
282
283    /* Build run command */
284    if (where) {
285       Mmsg(&ua->cmd, 
286           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
287           " where=\"%s\"",
288           job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
289           working_directory, where);
290    } else {
291       Mmsg(&ua->cmd, 
292           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
293           job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
294           working_directory);
295    }
296    Dmsg1(400, "Submitting: %s\n", ua->cmd);
297    
298    parse_ua_args(ua);
299    runcmd(ua, ua->cmd);
300
301    bsendmsg(ua, _("Restore command done.\n"));
302    return 1;
303 }
304
305 /*
306  * The first step in the restore process is for the user to 
307  *  select a list of JobIds from which he will subsequently
308  *  select which files are to be restored.
309  */
310 static int user_select_jobids(UAContext *ua, JOBIDS *ji)
311 {
312    char fileset_name[MAX_NAME_LENGTH];
313    char *p, ed1[50];
314    FILESET_DBR fsr;
315    CLIENT_DBR cr;
316    JobId_t JobId;
317    JOB_DBR jr;
318    POOLMEM *query;
319    int done = 0;
320    char *list[] = { 
321       "List last 20 Jobs run",
322       "List Jobs where a given File is saved",
323       "Enter list of JobIds to select",
324       "Enter SQL list command", 
325       "Select the most recent backup for a client",
326       "Cancel",
327       NULL };
328
329    bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
330                   "to be restored. You will be presented several methods\n"
331                   "of specifying the JobIds. Then you will be allowed to\n"
332                   "select which files from those JobIds are to be restored.\n\n"));
333
334    for ( ; !done; ) {
335       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
336       for (int i=0; list[i]; i++) {
337          add_prompt(ua, list[i]);
338       }
339       done = 1;
340       switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
341       case -1:                        /* error */
342          return 0;
343       case 0:                         /* list last 20 Jobs run */
344          db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, 0);
345          done = 0;
346          break;
347       case 1:                         /* list where a file is saved */
348          char *fname;
349          int len;
350          if (!get_cmd(ua, _("Enter Filename: "))) {
351             return 0;
352          }
353          len = strlen(ua->cmd);
354          fname = (char *)malloc(len * 2 + 1);
355          db_escape_string(fname, ua->cmd, len);
356          query = get_pool_memory(PM_MESSAGE);
357          Mmsg(&query, uar_file, fname);
358          free(fname);
359          db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, 0);
360          free_pool_memory(query);
361          done = 0;
362          break;
363       case 2:                         /* enter a list of JobIds */
364          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
365             return 0;
366          }
367          bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
368          break;
369       case 3:                         /* Enter an SQL list command */
370          if (!get_cmd(ua, _("Enter SQL list command: "))) {
371             return 0;
372          }
373          db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, 0);
374          done = 0;
375          break;
376       case 4:                         /* Select the most recent backups */
377          query = get_pool_memory(PM_MESSAGE);
378          db_sql_query(ua->db, uar_del_temp, NULL, NULL);
379          db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
380          if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
381             bsendmsg(ua, "%s\n", db_strerror(ua->db));
382          }
383          if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
384             bsendmsg(ua, "%s\n", db_strerror(ua->db));
385          }
386          /*
387           * Select Client from the Catalog
388           */
389          memset(&cr, 0, sizeof(cr));
390          if (!get_client_dbr(ua, &cr)) {
391             free_pool_memory(query);
392             db_sql_query(ua->db, uar_del_temp, NULL, NULL);
393             db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
394             return 0;
395          }
396          bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
397
398          /*
399           * Select FileSet 
400           */
401          Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
402          start_prompt(ua, _("The defined FileSet resources are:\n"));
403          if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
404             bsendmsg(ua, "%s\n", db_strerror(ua->db));
405          }
406          if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"), 
407                        fileset_name, sizeof(fileset_name)) < 0) {
408             free_pool_memory(query);
409             db_sql_query(ua->db, uar_del_temp, NULL, NULL);
410             db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
411             return 0;
412          }
413          fsr.FileSetId = atoi(fileset_name);  /* Id is first part of name */
414          if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
415             bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
416             bsendmsg(ua, _("This probably means you modified the FileSet.\n"
417                            "Continuing anyway.\n"));
418          }
419
420          /* Find JobId of last Full backup for this client, fileset */
421          Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, fsr.FileSetId);
422          if (!db_sql_query(ua->db, query, NULL, NULL)) {
423             bsendmsg(ua, "%s\n", db_strerror(ua->db));
424          }
425          /* Find all Volumes used by that JobId */
426          if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
427             bsendmsg(ua, "%s\n", db_strerror(ua->db));
428          }
429          /* Note, this is needed as I don't seem to get the callback
430           * from the call just above.
431           */
432          if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
433             bsendmsg(ua, "%s\n", db_strerror(ua->db));
434          }
435          /* Now find all Incremental Jobs */
436          Mmsg(&query, uar_inc, edit_uint64(ji->JobTDate, ed1), cr.ClientId, fsr.FileSetId);
437          if (!db_sql_query(ua->db, query, NULL, NULL)) {
438             bsendmsg(ua, "%s\n", db_strerror(ua->db));
439          }
440          free_pool_memory(query);
441          db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, 0);
442
443          if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
444             bsendmsg(ua, "%s\n", db_strerror(ua->db));
445          }
446          db_sql_query(ua->db, uar_del_temp, NULL, NULL);
447          db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
448          break;
449       case 5:
450          return 0;
451       }
452    }
453
454    if (*ji->JobIds == 0) {
455       bsendmsg(ua, _("No Jobs selected.\n"));
456       return 0;
457    }
458    bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
459
460    memset(&jr, 0, sizeof(JOB_DBR));
461
462    ji->TotalFiles = 0;
463    for (p=ji->JobIds; ; ) {
464       int stat = next_jobid_from_list(&p, &JobId);
465       if (stat < 0) {
466          bsendmsg(ua, _("Invalid JobId in list.\n"));
467          return 0;
468       }
469       if (stat == 0) {
470          break;
471       }
472       jr.JobId = JobId;
473       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
474          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
475          return 0;
476       }
477       ji->TotalFiles += jr.JobFiles;
478    }
479    return 1;
480 }
481
482 static int next_jobid_from_list(char **p, uint32_t *JobId)
483 {
484    char jobid[30];
485    int i;
486    char *q = *p;
487
488    jobid[0] = 0;
489    for (i=0; i<(int)sizeof(jobid); i++) {
490       if (*q == ',' || *q == 0) {
491          q++;
492          break;
493       }
494       jobid[i] = *q++;
495       jobid[i+1] = 0;
496    }
497    if (jobid[0] == 0 || !is_a_number(jobid)) {
498       return 0;
499    }
500    *p = q;
501    *JobId = strtoul(jobid, NULL, 10);
502    return 1;
503 }
504
505 /*
506  * Callback handler make list of JobIds
507  */
508 static int jobid_handler(void *ctx, int num_fields, char **row)
509 {
510    JOBIDS *ji = (JOBIDS *)ctx;
511
512    if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
513       if (ji->JobIds[0] != 0) {
514          strcat(ji->JobIds, ",");
515       }
516       strcat(ji->JobIds, row[0]);
517    }
518
519    return 0;
520 }
521
522
523 /*
524  * Callback handler to pickup last Full backup JobId and ClientId
525  */
526 static int last_full_handler(void *ctx, int num_fields, char **row)
527 {
528    JOBIDS *ji = (JOBIDS *)ctx;
529
530    ji->JobTDate = strtoll(row[1], NULL, 10);
531
532    return 0;
533 }
534
535 /*
536  * Callback handler build fileset prompt list
537  */
538 static int fileset_handler(void *ctx, int num_fields, char **row)
539 {
540    char prompt[MAX_NAME_LENGTH+200];
541
542    snprintf(prompt, sizeof(prompt), "%s  %s  %s", row[0], row[1], row[2]);
543    add_prompt((UAContext *)ctx, prompt);
544    return 0;
545 }
546
547 /* Forward referenced commands */
548
549 static int markcmd(UAContext *ua, TREE_CTX *tree);
550 static int countcmd(UAContext *ua, TREE_CTX *tree);
551 static int findcmd(UAContext *ua, TREE_CTX *tree);
552 static int lscmd(UAContext *ua, TREE_CTX *tree);
553 static int dircmd(UAContext *ua, TREE_CTX *tree);
554 static int helpcmd(UAContext *ua, TREE_CTX *tree);
555 static int cdcmd(UAContext *ua, TREE_CTX *tree);
556 static int pwdcmd(UAContext *ua, TREE_CTX *tree);
557 static int unmarkcmd(UAContext *ua, TREE_CTX *tree);
558 static int quitcmd(UAContext *ua, TREE_CTX *tree);
559
560
561 struct cmdstruct { char *key; int (*func)(UAContext *ua, TREE_CTX *tree); char *help; }; 
562 static struct cmdstruct commands[] = {
563  { N_("mark"),       markcmd,      _("mark file for restoration")},
564  { N_("unmark"),     unmarkcmd,    _("unmark file for restoration")},
565  { N_("cd"),         cdcmd,        _("change current directory")},
566  { N_("pwd"),        pwdcmd,       _("print current working directory")},
567  { N_("ls"),         lscmd,        _("list current directory")},    
568  { N_("dir"),        dircmd,       _("list current directory")},    
569  { N_("count"),      countcmd,     _("count marked files")},
570  { N_("find"),       findcmd,      _("find files")},
571  { N_("done"),       quitcmd,      _("leave file selection mode")},
572  { N_("exit"),       quitcmd,      _("exit = done")},
573  { N_("help"),       helpcmd,      _("print help")},
574  { N_("?"),          helpcmd,      _("print help")},    
575              };
576 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
577
578
579 /*
580  * Enter a prompt mode where the user can select/deselect
581  *  files to be restored. This is sort of like a mini-shell
582  *  that allows "cd", "pwd", "add", "rm", ...
583  */
584 static void user_select_files(TREE_CTX *tree)
585 {
586    char cwd[2000];
587
588    bsendmsg(tree->ua, _( 
589       "\nYou are now entering file selection mode where you add and\n"
590       "remove files to be restored. All files are initially added.\n"
591       "Enter \"done\" to leave this mode.\n\n"));
592    /*
593     * Enter interactive command handler allowing selection
594     *  of individual files.
595     */
596    tree->node = (TREE_NODE *)tree->root;
597    tree_getpath(tree->node, cwd, sizeof(cwd));
598    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
599    for ( ;; ) {       
600       int found, len, stat, i;
601       if (!get_cmd(tree->ua, "$ ")) {
602          break;
603       }
604       parse_ua_args(tree->ua);
605       if (tree->ua->argc == 0) {
606          return;
607       }
608
609       len = strlen(tree->ua->argk[0]);
610       found = 0;
611       stat = 0;
612       for (i=0; i<(int)comsize; i++)       /* search for command */
613          if (strncasecmp(tree->ua->argk[0],  _(commands[i].key), len) == 0) {
614             stat = (*commands[i].func)(tree->ua, tree);   /* go execute command */
615             found = 1;
616             break;
617          }
618       if (!found) {
619          bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
620          continue;
621       }
622       if (!stat) {
623          break;
624       }
625    }
626 }
627
628 /*
629  * Create new FileIndex entry for BSR 
630  */
631 static RBSR_FINDEX *new_findex() 
632 {
633    RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
634    memset(fi, 0, sizeof(RBSR_FINDEX));
635    return fi;
636 }
637
638 /* Free all BSR FileIndex entries */
639 static void free_findex(RBSR_FINDEX *fi)
640 {
641    if (fi) {
642       free_findex(fi->next);
643       free(fi);
644    }
645 }
646
647 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd) 
648 {
649    if (fi) {
650       if (fi->findex == fi->findex2) {
651          fprintf(fd, "FileIndex=%d\n", fi->findex);
652       } else {
653          fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
654       }
655       write_findex(ua, fi->next, fd);
656    }
657 }
658
659
660 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
661 {
662    if (fi) {
663       if (fi->findex == fi->findex2) {
664          bsendmsg(ua, "FileIndex=%d\n", fi->findex);
665       } else {
666          bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
667       }
668       print_findex(ua, fi->next);
669    }
670 }
671
672 /* Create a new bootstrap record */
673 static RBSR *new_bsr()
674 {
675    RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
676    memset(bsr, 0, sizeof(RBSR));
677    return bsr;
678 }
679
680 /* Free the entire BSR */
681 static void free_bsr(RBSR *bsr)
682 {
683    if (bsr) {
684       free_findex(bsr->fi);
685       free_bsr(bsr->next);
686       if (bsr->VolParams) {
687          free(bsr->VolParams);
688       }
689       free(bsr);
690    }
691 }
692
693 /*
694  * Complete the BSR by filling in the VolumeName and
695  *  VolSessionId and VolSessionTime using the JobId
696  */
697 static int complete_bsr(UAContext *ua, RBSR *bsr)
698 {
699    JOB_DBR jr;
700
701    if (bsr) {
702       memset(&jr, 0, sizeof(jr));
703       jr.JobId = bsr->JobId;
704       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
705          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
706          return 0;
707       }
708       bsr->VolSessionId = jr.VolSessionId;
709       bsr->VolSessionTime = jr.VolSessionTime;
710       if ((bsr->VolCount=db_get_job_volume_parameters(ua->jcr, ua->db, bsr->JobId, 
711            &(bsr->VolParams))) == 0) {
712          bsendmsg(ua, _("Unable to get Job Volume Parameters. ERR=%s\n"), db_strerror(ua->db));
713          if (bsr->VolParams) {
714             free(bsr->VolParams);
715             bsr->VolParams = NULL;
716          }
717          return 0;
718       }
719       return complete_bsr(ua, bsr->next);
720    }
721    return 1;
722 }
723
724 /*
725  * Write the bootstrap record to file
726  */
727 static int write_bsr_file(UAContext *ua, RBSR *bsr)
728 {
729    FILE *fd;
730    POOLMEM *fname = get_pool_memory(PM_MESSAGE);
731    int stat;
732
733    Mmsg(&fname, "%s/restore.bsr", working_directory);
734    fd = fopen(fname, "w+");
735    if (!fd) {
736       bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"), 
737          fname, strerror(errno));
738       free_pool_memory(fname);
739       return 0;
740    }
741    /* Sort the bsr chain */
742    bsr = sort_bsr(bsr);
743    /* Write them to file */
744    write_bsr(ua, bsr, fd);
745    stat = !ferror(fd);
746    fclose(fd);
747    bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
748
749    /* Tell the user what he will need to mount */
750    bsendmsg(ua, _("\nThe restore job will require the following Volumes:\n"));
751    /* Create Unique list of Volumes using prompt list */
752    start_prompt(ua, "");
753    for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
754       for (int i=0; i < nbsr->VolCount; i++) {
755          add_prompt(ua, nbsr->VolParams[i].VolumeName);
756       }
757    }
758    for (int i=0; i < ua->num_prompts; i++) {
759       bsendmsg(ua, "   %s\n", ua->prompt[i]);
760       free(ua->prompt[i]);
761    }
762    ua->num_prompts = 0;
763    bsendmsg(ua, "\n");
764    free_pool_memory(fname);
765    return stat;
766 }
767
768 /*
769  * First sort the bsr chain, then sort the VolParams   
770  */
771 static RBSR *sort_bsr(RBSR *bsr)
772 {
773    if (!bsr) {
774       return bsr;
775    }
776    /* ****FIXME**** sort the bsr chain */
777    for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
778    }
779    return bsr;
780 }
781
782 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
783 {
784    if (bsr) {
785       for (int i=0; i < bsr->VolCount; i++) {
786          fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
787          fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
788          fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
789          fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile, 
790                  bsr->VolParams[i].EndFile);
791          fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
792                  bsr->VolParams[i].EndBlock);
793          write_findex(ua, bsr->fi, fd);
794       }
795       write_bsr(ua, bsr->next, fd);
796    }
797 }
798
799 static void print_bsr(UAContext *ua, RBSR *bsr)
800 {
801    if (bsr) {
802       for (int i=0; i < bsr->VolCount; i++) {
803          bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
804          bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
805          bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
806          bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile, 
807                   bsr->VolParams[i].EndFile);
808          bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
809                   bsr->VolParams[i].EndBlock);
810          print_findex(ua, bsr->fi);
811       }
812       print_bsr(ua, bsr->next);
813    }
814 }
815
816
817 /*
818  * Add a FileIndex to the list of BootStrap records.
819  *  Here we are only dealing with JobId's and the FileIndexes
820  *  associated with those JobIds.
821  */
822 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
823 {
824    RBSR *nbsr;
825    RBSR_FINDEX *fi, *lfi;
826
827    if (findex == 0) {
828       return;                         /* probably a dummy directory */
829    }
830    
831    if (!bsr->fi) {                    /* if no FI add one */
832       /* This is the first FileIndex item in the chain */
833       bsr->fi = new_findex();
834       bsr->JobId = JobId;
835       bsr->fi->findex = findex;
836       bsr->fi->findex2 = findex;
837       return;
838    }
839    /* Walk down list of bsrs until we find the JobId */
840    if (bsr->JobId != JobId) {
841       for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
842          if (nbsr->JobId == JobId) {
843             bsr = nbsr;
844             break;
845          }
846       }
847
848       if (!nbsr) {                    /* Must add new JobId */
849          /* Add new JobId at end of chain */
850          for (nbsr=bsr; nbsr->next; nbsr=nbsr->next) 
851             {  }
852          nbsr->next = new_bsr();
853          nbsr->next->JobId = JobId;
854          nbsr->next->fi = new_findex();
855          nbsr->next->fi->findex = findex;
856          nbsr->next->fi->findex2 = findex;
857          return;
858       }
859    }
860
861    /* 
862     * At this point, bsr points to bsr containing JobId,
863     *  and we are sure that there is at least one fi record.
864     */
865    lfi = fi = bsr->fi;
866    /* Check if this findex is smaller than first item */
867    if (findex < fi->findex) {
868       if ((findex+1) == fi->findex) {
869          fi->findex = findex;         /* extend down */
870          return;
871       }
872       fi = new_findex();              /* yes, insert before first item */
873       fi->findex = findex;
874       fi->findex2 = findex;
875       fi->next = lfi;
876       bsr->fi = fi;
877       return;
878    }
879    /* Walk down fi chain and find where to insert insert new FileIndex */
880    for ( ; fi; fi=fi->next) {
881       if (findex == (fi->findex2 + 1)) {  /* extend up */
882          RBSR_FINDEX *nfi;     
883          fi->findex2 = findex;
884          if (fi->next && ((findex+1) == fi->next->findex)) { 
885             nfi = fi->next;
886             fi->findex2 = nfi->findex2;
887             fi->next = nfi->next;
888             free(nfi);
889          }
890          return;
891       }
892       if (findex < fi->findex) {      /* add before */
893          if ((findex+1) == fi->findex) {
894             fi->findex = findex;
895             return;
896          }
897          break;
898       }
899       lfi = fi;
900    }
901    /* Add to last place found */
902    fi = new_findex();
903    fi->findex = findex;
904    fi->findex2 = findex;
905    fi->next = lfi->next;
906    lfi->next = fi;
907    return;
908 }
909
910 /*
911  * This callback routine is responsible for inserting the
912  *  items it gets into the directory tree. For each JobId selected
913  *  this routine is called once for each file. We do not allow
914  *  duplicate filenames, but instead keep the info from the most
915  *  recent file entered (i.e. the JobIds are assumed to be sorted)
916  */
917 static int insert_tree_handler(void *ctx, int num_fields, char **row)
918 {
919    TREE_CTX *tree = (TREE_CTX *)ctx;
920    char fname[2000];
921    TREE_NODE *node, *new_node;
922    int type;
923
924    strip_trailing_junk(row[1]);
925    if (*row[1] == 0) {
926       if (*row[0] != '/') {           /* Must be Win32 directory */
927          type = TN_DIR_NLS;
928       } else {
929          type = TN_DIR;
930       }
931    } else {
932       type = TN_FILE;
933    }
934    sprintf(fname, "%s%s", row[0], row[1]);
935    if (tree->avail_node) {
936       node = tree->avail_node;
937    } else {
938       node = new_tree_node(tree->root, type);
939       tree->avail_node = node;
940    }
941    Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
942    new_node = insert_tree_node(fname, node, tree->root, NULL);
943    /* Note, if node already exists, save new one for next time */
944    if (new_node != node) {
945       tree->avail_node = node;
946    } else {
947       tree->avail_node = NULL;
948    }
949    new_node->FileIndex = atoi(row[2]);
950    new_node->JobId = atoi(row[3]);
951    new_node->type = type;
952    new_node->extract = 1;             /* extract all by default */
953    tree->cnt++;
954    return 0;
955 }
956
957
958 /*
959  * Set extract to value passed. We recursively walk
960  *  down the tree setting all children if the 
961  *  node is a directory.
962  */
963 static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
964 {
965    TREE_NODE *n;
966    FILE_DBR fdbr;
967    struct stat statp;
968
969    node->extract = value;
970    /* For a non-file (i.e. directory), we see all the children */
971    if (node->type != TN_FILE) {
972       for (n=node->child; n; n=n->sibling) {
973          set_extract(ua, n, tree, value);
974       }
975    } else if (value) {
976       char cwd[2000];
977       /* Ordinary file, we get the full path, look up the
978        * attributes, decode them, and if we are hard linked to
979        * a file that was saved, we must load that file too.
980        */
981       tree_getpath(node, cwd, sizeof(cwd));
982       fdbr.FileId = 0;
983       fdbr.JobId = node->JobId;
984       if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
985          uint32_t LinkFI;
986          decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
987          /*
988           * If we point to a hard linked file, traverse the tree to
989           * find that file, and mark it for restoration as well. It
990           * must have the Link we just obtained and the same JobId.
991           */
992          if (LinkFI) {
993             for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
994                if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
995                   n->extract = 1;
996                   break;
997                }
998             }
999          }
1000       }
1001    }
1002 }
1003
1004 static int markcmd(UAContext *ua, TREE_CTX *tree)
1005 {
1006    TREE_NODE *node;
1007
1008    if (ua->argc < 2)
1009       return 1;
1010    if (!tree->node->child) {     
1011       return 1;
1012    }
1013    for (node = tree->node->child; node; node=node->sibling) {
1014       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1015          set_extract(ua, node, tree, 1);
1016       }
1017    }
1018    return 1;
1019 }
1020
1021 static int countcmd(UAContext *ua, TREE_CTX *tree)
1022 {
1023    int total, extract;
1024
1025    total = extract = 0;
1026    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1027       if (node->type != TN_NEWDIR) {
1028          total++;
1029          if (node->extract) {
1030             extract++;
1031          }
1032       }
1033    }
1034    bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
1035    return 1;
1036 }
1037
1038 static int findcmd(UAContext *ua, TREE_CTX *tree)
1039 {
1040    char cwd[2000];
1041
1042    if (ua->argc == 1) {
1043       bsendmsg(ua, _("No file specification given.\n"));
1044       return 0;
1045    }
1046    
1047    for (int i=1; i < ua->argc; i++) {
1048       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1049          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
1050             tree_getpath(node, cwd, sizeof(cwd));
1051             bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
1052          }
1053       }
1054    }
1055    return 1;
1056 }
1057
1058
1059
1060 static int lscmd(UAContext *ua, TREE_CTX *tree)
1061 {
1062    TREE_NODE *node;
1063
1064    if (!tree->node->child) {     
1065       return 1;
1066    }
1067    for (node = tree->node->child; node; node=node->sibling) {
1068       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1069          bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1070             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1071       }
1072    }
1073    return 1;
1074 }
1075
1076 extern char *getuser(uid_t uid);
1077 extern char *getgroup(gid_t gid);
1078
1079 /*
1080  * This is actually the long form used for "dir"
1081  */
1082 static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
1083 {
1084    char *p, *f;
1085    char ec1[30];
1086    int n;
1087
1088    p = encode_mode(statp->st_mode, buf);
1089    n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
1090    p += n;
1091    n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1092    p += n;
1093    n = sprintf(p, "%8.8s  ", edit_uint64(statp->st_size, ec1));
1094    p += n;
1095    p = encode_time(statp->st_ctime, p);
1096    *p++ = ' ';
1097    if (extract) {
1098       *p++ = '*';
1099    } else {
1100       *p++ = ' ';
1101    }
1102    for (f=fname; *f; )
1103       *p++ = *f++;
1104    *p = 0;
1105 }
1106
1107
1108 /*
1109  * Like ls command, but give more detail on each file
1110  */
1111 static int dircmd(UAContext *ua, TREE_CTX *tree)
1112 {
1113    TREE_NODE *node;
1114    FILE_DBR fdbr;
1115    struct stat statp;
1116    char buf[1000];
1117    char cwd[1100];
1118
1119    if (!tree->node->child) {     
1120       return 1;
1121    }
1122    for (node = tree->node->child; node; node=node->sibling) {
1123       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1124          tree_getpath(node, cwd, sizeof(cwd));
1125          fdbr.FileId = 0;
1126          fdbr.JobId = node->JobId;
1127          if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1128             uint32_t LinkFI;
1129             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1130             ls_output(buf, cwd, node->extract, &statp);
1131             bsendmsg(ua, "%s\n", buf);
1132          } else {
1133             /* Something went wrong getting attributes -- print name */
1134             bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1135                (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1136          }
1137       }
1138    }
1139    return 1;
1140 }
1141
1142
1143 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
1144 {
1145    unsigned int i;
1146
1147 /* usage(); */
1148    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
1149    for (i=0; i<comsize; i++) {
1150       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1151    }
1152    bsendmsg(ua, "\n");
1153    return 1;
1154 }
1155
1156 /*
1157  * Change directories.  Note, if the user specifies x: and it fails,
1158  *   we assume it is a Win32 absolute cd rather than relative and
1159  *   try a second time with /x: ...  Win32 kludge.
1160  */
1161 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
1162 {
1163    TREE_NODE *node;
1164    char cwd[2000];
1165
1166    if (ua->argc != 2) {
1167       return 1;
1168    }
1169    node = tree_cwd(ua->argk[1], tree->root, tree->node);
1170    if (!node) {
1171       /* Try once more if Win32 drive -- make absolute */
1172       if (ua->argk[1][1] == ':') {  /* win32 drive */
1173          strcpy(cwd, "/");
1174          strcat(cwd, ua->argk[1]);
1175          node = tree_cwd(cwd, tree->root, tree->node);
1176       }
1177       if (!node) {
1178          bsendmsg(ua, _("Invalid path given.\n"));
1179       } else {
1180          tree->node = node;
1181       }
1182    } else {
1183       tree->node = node;
1184    }
1185    tree_getpath(tree->node, cwd, sizeof(cwd));
1186    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1187    return 1;
1188 }
1189
1190 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
1191 {
1192    char cwd[2000];
1193    tree_getpath(tree->node, cwd, sizeof(cwd));
1194    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1195    return 1;
1196 }
1197
1198
1199 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1200 {
1201    TREE_NODE *node;
1202
1203    if (ua->argc < 2)
1204       return 1;
1205    if (!tree->node->child) {     
1206       return 1;
1207    }
1208    for (node = tree->node->child; node; node=node->sibling) {
1209       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1210          set_extract(ua, node, tree, 0);
1211       }
1212    }
1213    return 1;
1214 }
1215
1216 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
1217 {
1218    return 0;
1219 }
1220
1221
1222 /*
1223  * Called here with each name to be added to the list. The name is
1224  *   added to the list if it is not already in the list.
1225  */
1226 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1227 {
1228    NAME_LIST *name = (NAME_LIST *)ctx;
1229
1230    if (name->num_ids == MAX_ID_LIST_LEN) {  
1231       return 1;
1232    }
1233    if (name->num_ids == name->max_ids) {
1234       if (name->max_ids == 0) {
1235          name->max_ids = 1000;
1236          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1237       } else {
1238          name->max_ids = (name->max_ids * 3) / 2;
1239          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1240       }
1241    }
1242    for (int i=0; i<name->num_ids; i++) {
1243       if (strcmp(name->name[i], row[0]) == 0) {
1244          return 0;                    /* already in list, return */
1245       }
1246    }
1247    /* Add new name to list */
1248    name->name[name->num_ids++] = bstrdup(row[0]);
1249    return 0;
1250 }
1251
1252
1253 /*
1254  * Print names in the list
1255  */
1256 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1257
1258    int i;
1259
1260    for (i=0; i < name_list->num_ids; i++) {
1261       bsendmsg(ua, "%s\n", name_list->name[i]);
1262    }
1263 }
1264
1265
1266 /*
1267  * Free names in the list
1268  */
1269 static void free_name_list(NAME_LIST *name_list)
1270
1271    int i;
1272
1273    for (i=0; i < name_list->num_ids; i++) {
1274       free(name_list->name[i]);
1275    }
1276    if (name_list->name) {
1277       free(name_list->name);
1278    }
1279    name_list->max_ids = 0;
1280    name_list->num_ids = 0;
1281 }
1282
1283 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
1284 {
1285    char name[MAX_NAME_LENGTH];
1286    STORE *store = NULL;
1287
1288    if (name_list->num_ids > 1) {
1289       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1290          "Restore is not possible. The MediaTypes used are:\n"));
1291       print_name_list(ua, name_list);
1292       ji->store = select_storage_resource(ua);
1293       return;
1294    }
1295
1296    if (name_list->num_ids == 0) {
1297       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1298       ji->store = select_storage_resource(ua);
1299       return;
1300    }
1301
1302    start_prompt(ua, _("The defined Storage resources are:\n"));
1303    LockRes();
1304    while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1305       if (strcmp(store->media_type, name_list->name[0]) == 0) {
1306          add_prompt(ua, store->hdr.name);
1307       }
1308    }
1309    UnlockRes();
1310    do_prompt(ua, _("Storage"),  _("Select Storage resource"), name, sizeof(name));
1311    ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1312    if (!ji->store) {
1313       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1314          "MediaType %s, needed by the Jobs you selected.\n"
1315          "You will be allowed to select a Storage device later.\n"),
1316          name_list->name[0]); 
1317    }
1318 }