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