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