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