]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
252688bf83d3a87a40fc69ddb18e22b2a59bb56f
[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(400, "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 addcmd(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 rmcmd(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_("add"),        addcmd,       _("add file")},
444  { N_("count"),      countcmd,     _("count files")},
445  { N_("find"),       findcmd,      _("find files")},
446  { N_("ls"),         lscmd,        _("list current directory")},    
447  { N_("dir"),        lscmd,        _("list current directory")},    
448  { N_("help"),       helpcmd,      _("print help")},
449  { N_("cd"),         cdcmd,        _("change directory")},
450  { N_("pwd"),        pwdcmd,       _("print directory")},
451  { N_("rm"),         rmcmd,        _("remove a file")},
452  { N_("remove"),     rmcmd,        _("remove a file")},
453  { N_("done"),       quitcmd,      _("leave file selection mode")},
454  { N_("exit"),       quitcmd,      _("exit = done")},
455  { N_("?"),          helpcmd,      _("print help")},    
456              };
457 #define comsize (sizeof(commands)/sizeof(struct cmdstruct))
458
459
460 /*
461  * Enter a prompt mode where the user can select/deselect
462  *  files to be restored. This is sort of like a mini-shell
463  *  that allows "cd", "pwd", "add", "rm", ...
464  */
465 static void user_select_files(TREE_CTX *tree)
466 {
467    char cwd[2000];
468
469    bsendmsg(tree->ua, _( 
470       "You are now entering file selection mode where you add and\n"
471       "remove files to be restored. All files are initially added.\n"
472       "Enter done to leave this mode.\n\n"));
473    /*
474     * Enter interactive command handler allowing selection
475     *  of individual files.
476     */
477    tree->node = (TREE_NODE *)tree->root;
478    tree_getpath(tree->node, cwd, sizeof(cwd));
479    bsendmsg(tree->ua, _("cwd is: %s\n"), cwd);
480    for ( ;; ) {       
481       int found, len, stat, i;
482       if (!get_cmd(tree->ua, "$ ")) {
483          break;
484       }
485       parse_command_args(tree->ua);
486       if (tree->ua->argc == 0) {
487          return;
488       }
489
490       len = strlen(tree->ua->argk[0]);
491       found = 0;
492       for (i=0; i<(int)comsize; i++)       /* search for command */
493          if (strncasecmp(tree->ua->argk[0],  _(commands[i].key), len) == 0) {
494             stat = (*commands[i].func)(tree->ua, tree);   /* go execute command */
495             found = 1;
496             break;
497          }
498       if (!found) {
499          bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
500       }
501       if (!stat) {
502          break;
503       }
504    }
505 }
506
507 /*
508  * Create new FileIndex entry for BSR 
509  */
510 static RBSR_FINDEX *new_findex() 
511 {
512    RBSR_FINDEX *fi = (RBSR_FINDEX *)malloc(sizeof(RBSR_FINDEX));
513    memset(fi, 0, sizeof(RBSR_FINDEX));
514    return fi;
515 }
516
517 /* Free all BSR FileIndex entries */
518 static void free_findex(RBSR_FINDEX *fi)
519 {
520    if (fi) {
521       free_findex(fi->next);
522       free(fi);
523    }
524 }
525
526 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd) 
527 {
528    if (fi) {
529       if (fi->findex == fi->findex2) {
530          fprintf(fd, "FileIndex=%d\n", fi->findex);
531       } else {
532          fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
533       }
534       write_findex(ua, fi->next, fd);
535    }
536 }
537
538
539 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
540 {
541    if (fi) {
542       if (fi->findex == fi->findex2) {
543          bsendmsg(ua, "FileIndex=%d\n", fi->findex);
544       } else {
545          bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
546       }
547       print_findex(ua, fi->next);
548    }
549 }
550
551 /* Create a new bootstrap record */
552 static RBSR *new_bsr()
553 {
554    RBSR *bsr = (RBSR *)malloc(sizeof(RBSR));
555    memset(bsr, 0, sizeof(RBSR));
556    return bsr;
557 }
558
559 /* Free the entire BSR */
560 static void free_bsr(RBSR *bsr)
561 {
562    if (bsr) {
563       free_findex(bsr->fi);
564       free_bsr(bsr->next);
565       if (bsr->VolumeName) {
566          free(bsr->VolumeName);
567       }
568       free(bsr);
569    }
570 }
571
572 /*
573  * Complete the BSR by filling in the VolumeName and
574  *  VolSessionId and VolSessionTime using the JobId
575  */
576 static int complete_bsr(UAContext *ua, RBSR *bsr)
577 {
578    JOB_DBR jr;
579    char VolumeNames[1000];            /* ****FIXME**** */
580
581    if (bsr) {
582       memset(&jr, 0, sizeof(jr));
583       jr.JobId = bsr->JobId;
584       if (!db_get_job_record(ua->db, &jr)) {
585          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
586          return 0;
587       }
588       bsr->VolSessionId = jr.VolSessionId;
589       bsr->VolSessionTime = jr.VolSessionTime;
590       if (!db_get_job_volume_names(ua->db, bsr->JobId, VolumeNames)) {
591          bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
592          return 0;
593       }
594       bsr->VolumeName = bstrdup(VolumeNames);
595       return complete_bsr(ua, bsr->next);
596    }
597    return 1;
598 }
599
600 /*
601  * Write the bootstrap record to file
602  */
603 static int write_bsr_file(UAContext *ua, RBSR *bsr)
604 {
605    FILE *fd;
606    POOLMEM *fname = get_pool_memory(PM_MESSAGE);
607    int stat;
608
609    Mmsg(&fname, "%s/restore.bsr", working_directory);
610    fd = fopen(fname, "w+");
611    if (!fd) {
612       bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"), 
613          fname, strerror(errno));
614       free_pool_memory(fname);
615       return 0;
616    }
617    write_bsr(ua, bsr, fd);
618    stat = !ferror(fd);
619    fclose(fd);
620    bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
621    free_pool_memory(fname);
622    return stat;
623 }
624
625 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
626 {
627    if (bsr) {
628       if (bsr->VolumeName) {
629          fprintf(fd, "VolumeName=%s\n", bsr->VolumeName);
630       }
631       fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
632       fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
633       write_findex(ua, bsr->fi, fd);
634       write_bsr(ua, bsr->next, fd);
635    }
636 }
637
638 static void print_bsr(UAContext *ua, RBSR *bsr)
639 {
640    if (bsr) {
641       if (bsr->VolumeName) {
642          bsendmsg(ua, "VolumeName=%s\n", bsr->VolumeName);
643       }
644       bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
645       bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
646       print_findex(ua, bsr->fi);
647       print_bsr(ua, bsr->next);
648    }
649 }
650
651
652 /*
653  * Add a FileIndex to the list of BootStrap records.
654  *  Here we are only dealing with JobId's and the FileIndexes
655  *  associated with those JobIds.
656  */
657 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
658 {
659    RBSR *nbsr;
660    RBSR_FINDEX *fi, *lfi;
661
662    if (findex == 0) {
663       return;                         /* probably a dummy directory */
664    }
665    
666    if (!bsr->fi) {                    /* if no FI add one */
667       /* This is the first FileIndex item in the chain */
668       bsr->fi = new_findex();
669       bsr->JobId = JobId;
670       bsr->fi->findex = findex;
671       bsr->fi->findex2 = findex;
672       return;
673    }
674    /* Walk down list of bsrs until we find the JobId */
675    if (bsr->JobId != JobId) {
676       for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
677          if (nbsr->JobId == JobId) {
678             bsr = nbsr;
679             break;
680          }
681       }
682
683       if (!nbsr) {                    /* Must add new JobId */
684          /* Add new JobId at end of chain */
685          for (nbsr=bsr; nbsr->next; nbsr=nbsr->next) 
686             {  }
687          nbsr->next = new_bsr();
688          nbsr->next->JobId = JobId;
689          nbsr->next->fi = new_findex();
690          nbsr->next->fi->findex = findex;
691          nbsr->next->fi->findex2 = findex;
692          return;
693       }
694    }
695
696    /* 
697     * At this point, bsr points to bsr containing JobId,
698     *  and we are sure that there is at least one fi record.
699     */
700    lfi = fi = bsr->fi;
701    /* Check if this findex is smaller than first item */
702    if (findex < fi->findex) {
703       if ((findex+1) == fi->findex) {
704          fi->findex = findex;         /* extend down */
705          return;
706       }
707       fi = new_findex();              /* yes, insert before first item */
708       fi->findex = findex;
709       fi->findex2 = findex;
710       fi->next = lfi;
711       bsr->fi = fi;
712       return;
713    }
714    /* Walk down fi chain and find where to insert insert new FileIndex */
715    for ( ; fi; fi=fi->next) {
716       if (findex == (fi->findex2 + 1)) {  /* extend up */
717          RBSR_FINDEX *nfi;     
718          fi->findex2 = findex;
719          if (fi->next && ((findex+1) == fi->next->findex)) { 
720             nfi = fi->next;
721             fi->findex2 = nfi->findex2;
722             fi->next = nfi->next;
723             free(nfi);
724          }
725          return;
726       }
727       if (findex < fi->findex) {      /* add before */
728          if ((findex+1) == fi->findex) {
729             fi->findex = findex;
730             return;
731          }
732          break;
733       }
734       lfi = fi;
735    }
736    /* Add to last place found */
737    fi = new_findex();
738    fi->findex = findex;
739    fi->findex2 = findex;
740    fi->next = lfi->next;
741    lfi->next = fi;
742    return;
743 }
744
745 /*
746  * This callback routine is responsible for inserting the
747  *  items it gets into the directory tree. For each JobId selected
748  *  this routine is called once for each file. We do not allow
749  *  duplicate filenames, but instead keep the info from the most
750  *  recent file entered (i.e. the JobIds are assumed to be sorted)
751  */
752 static int insert_tree_handler(void *ctx, int num_fields, char **row)
753 {
754    TREE_CTX *tree = (TREE_CTX *)ctx;
755    char fname[2000];
756    TREE_NODE *node, *new_node;
757    int type;
758
759    strip_trailing_junk(row[1]);
760    if (*row[1] == 0) {
761       type = TN_DIR;
762    } else {
763       type = TN_FILE;
764    }
765    sprintf(fname, "%s%s", row[0], row[1]);
766    if (tree->avail_node) {
767       node = tree->avail_node;
768    } else {
769       node = new_tree_node(tree->root, type);
770       tree->avail_node = node;
771    }
772    Dmsg2(400, "FI=%d fname=%s\n", node->FileIndex, fname);
773    new_node = insert_tree_node(fname, node, tree->root, NULL);
774    /* Note, if node already exists, save new one for next time */
775    if (new_node != node) {
776       tree->avail_node = node;
777    } else {
778       tree->avail_node = NULL;
779    }
780    new_node->FileIndex = atoi(row[2]);
781    new_node->JobId = atoi(row[3]);
782    new_node->type = type;
783    new_node->extract = 1;             /* extract all by default */
784    tree->cnt++;
785    return 0;
786 }
787
788
789 /*
790  * Set extract to value passed. We recursively walk
791  *  down the tree setting all children if the 
792  *  node is a directory.
793  */
794 static void set_extract(TREE_NODE *node, int value)
795 {
796    TREE_NODE *n;
797
798    node->extract = value;
799    if (node->type != TN_FILE) {
800       for (n=node->child; n; n=n->sibling) {
801          set_extract(n, value);
802       }
803    }
804 }
805
806 static int addcmd(UAContext *ua, TREE_CTX *tree)
807 {
808    TREE_NODE *node;
809
810    if (ua->argc < 2)
811       return 1;
812    if (!tree->node->child) {     
813       return 1;
814    }
815    for (node = tree->node->child; node; node=node->sibling) {
816       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
817          set_extract(node, 1);
818       }
819    }
820    return 1;
821 }
822
823 static int countcmd(UAContext *ua, TREE_CTX *tree)
824 {
825    int total, extract;
826
827    total = extract = 0;
828    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
829       if (node->type != TN_NEWDIR) {
830          total++;
831          if (node->extract) {
832             extract++;
833          }
834       }
835    }
836    bsendmsg(ua, "%d total files. %d marked for extraction.\n", total, extract);
837    return 1;
838 }
839
840 static int findcmd(UAContext *ua, TREE_CTX *tree)
841 {
842    char cwd[2000];
843
844    if (ua->argc == 1) {
845       bsendmsg(ua, _("No file specification given.\n"));
846       return 0;
847    }
848    
849    for (int i=1; i < ua->argc; i++) {
850       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
851          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
852             tree_getpath(node, cwd, sizeof(cwd));
853             bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
854          }
855       }
856    }
857    return 1;
858 }
859
860
861
862 static int lscmd(UAContext *ua, TREE_CTX *tree)
863 {
864    TREE_NODE *node;
865
866    if (!tree->node->child) {     
867       return 1;
868    }
869    for (node = tree->node->child; node; node=node->sibling) {
870       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
871          bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
872             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
873       }
874    }
875    return 1;
876 }
877
878 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
879 {
880    unsigned int i;
881
882 /* usage(); */
883    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
884    for (i=0; i<comsize; i++) {
885       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
886    }
887    bsendmsg(ua, "\n");
888    return 1;
889 }
890
891 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
892 {
893    TREE_NODE *node;
894    char cwd[2000];
895
896    if (ua->argc != 2) {
897       return 1;
898    }
899    node = tree_cwd(ua->argk[1], tree->root, tree->node);
900    if (!node) {
901       bsendmsg(ua, _("Invalid path given.\n"));
902    } else {
903       tree->node = node;
904    }
905    tree_getpath(tree->node, cwd, sizeof(cwd));
906    bsendmsg(ua, _("cwd is: %s\n"), cwd);
907    return 1;
908 }
909
910 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
911 {
912    char cwd[2000];
913    tree_getpath(tree->node, cwd, sizeof(cwd));
914    bsendmsg(ua, _("cwd is: %s\n"), cwd);
915    return 1;
916 }
917
918
919 static int rmcmd(UAContext *ua, TREE_CTX *tree)
920 {
921    TREE_NODE *node;
922
923    if (ua->argc < 2)
924       return 1;
925    if (!tree->node->child) {     
926       return 1;
927    }
928    for (node = tree->node->child; node; node=node->sibling) {
929       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
930          set_extract(node, 0);
931       }
932    }
933    return 1;
934 }
935
936 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
937 {
938    return 0;
939 }