]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
7071a0b748ecf35b404fdfc7e8e1df10569357d8
[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    POOLMEM *VolumeNames;
579
580    if (bsr) {
581       VolumeNames = get_pool_memory(PM_MESSAGE);
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          free_pool_memory(VolumeNames);
593          return 0;
594       }
595       bsr->VolumeName = bstrdup(VolumeNames);
596       free_pool_memory(VolumeNames);
597       return complete_bsr(ua, bsr->next);
598    }
599    return 1;
600 }
601
602 /*
603  * Write the bootstrap record to file
604  */
605 static int write_bsr_file(UAContext *ua, RBSR *bsr)
606 {
607    FILE *fd;
608    POOLMEM *fname = get_pool_memory(PM_MESSAGE);
609    int stat;
610
611    Mmsg(&fname, "%s/restore.bsr", working_directory);
612    fd = fopen(fname, "w+");
613    if (!fd) {
614       bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"), 
615          fname, strerror(errno));
616       free_pool_memory(fname);
617       return 0;
618    }
619    write_bsr(ua, bsr, fd);
620    stat = !ferror(fd);
621    fclose(fd);
622    bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
623    free_pool_memory(fname);
624    return stat;
625 }
626
627 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
628 {
629    if (bsr) {
630       if (bsr->VolumeName) {
631          fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
632       }
633       fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
634       fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
635       write_findex(ua, bsr->fi, fd);
636       write_bsr(ua, bsr->next, fd);
637    }
638 }
639
640 static void print_bsr(UAContext *ua, RBSR *bsr)
641 {
642    if (bsr) {
643       if (bsr->VolumeName) {
644          bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
645       }
646       bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
647       bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
648       print_findex(ua, bsr->fi);
649       print_bsr(ua, bsr->next);
650    }
651 }
652
653
654 /*
655  * Add a FileIndex to the list of BootStrap records.
656  *  Here we are only dealing with JobId's and the FileIndexes
657  *  associated with those JobIds.
658  */
659 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
660 {
661    RBSR *nbsr;
662    RBSR_FINDEX *fi, *lfi;
663
664    if (findex == 0) {
665       return;                         /* probably a dummy directory */
666    }
667    
668    if (!bsr->fi) {                    /* if no FI add one */
669       /* This is the first FileIndex item in the chain */
670       bsr->fi = new_findex();
671       bsr->JobId = JobId;
672       bsr->fi->findex = findex;
673       bsr->fi->findex2 = findex;
674       return;
675    }
676    /* Walk down list of bsrs until we find the JobId */
677    if (bsr->JobId != JobId) {
678       for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
679          if (nbsr->JobId == JobId) {
680             bsr = nbsr;
681             break;
682          }
683       }
684
685       if (!nbsr) {                    /* Must add new JobId */
686          /* Add new JobId at end of chain */
687          for (nbsr=bsr; nbsr->next; nbsr=nbsr->next) 
688             {  }
689          nbsr->next = new_bsr();
690          nbsr->next->JobId = JobId;
691          nbsr->next->fi = new_findex();
692          nbsr->next->fi->findex = findex;
693          nbsr->next->fi->findex2 = findex;
694          return;
695       }
696    }
697
698    /* 
699     * At this point, bsr points to bsr containing JobId,
700     *  and we are sure that there is at least one fi record.
701     */
702    lfi = fi = bsr->fi;
703    /* Check if this findex is smaller than first item */
704    if (findex < fi->findex) {
705       if ((findex+1) == fi->findex) {
706          fi->findex = findex;         /* extend down */
707          return;
708       }
709       fi = new_findex();              /* yes, insert before first item */
710       fi->findex = findex;
711       fi->findex2 = findex;
712       fi->next = lfi;
713       bsr->fi = fi;
714       return;
715    }
716    /* Walk down fi chain and find where to insert insert new FileIndex */
717    for ( ; fi; fi=fi->next) {
718       if (findex == (fi->findex2 + 1)) {  /* extend up */
719          RBSR_FINDEX *nfi;     
720          fi->findex2 = findex;
721          if (fi->next && ((findex+1) == fi->next->findex)) { 
722             nfi = fi->next;
723             fi->findex2 = nfi->findex2;
724             fi->next = nfi->next;
725             free(nfi);
726          }
727          return;
728       }
729       if (findex < fi->findex) {      /* add before */
730          if ((findex+1) == fi->findex) {
731             fi->findex = findex;
732             return;
733          }
734          break;
735       }
736       lfi = fi;
737    }
738    /* Add to last place found */
739    fi = new_findex();
740    fi->findex = findex;
741    fi->findex2 = findex;
742    fi->next = lfi->next;
743    lfi->next = fi;
744    return;
745 }
746
747 /*
748  * This callback routine is responsible for inserting the
749  *  items it gets into the directory tree. For each JobId selected
750  *  this routine is called once for each file. We do not allow
751  *  duplicate filenames, but instead keep the info from the most
752  *  recent file entered (i.e. the JobIds are assumed to be sorted)
753  */
754 static int insert_tree_handler(void *ctx, int num_fields, char **row)
755 {
756    TREE_CTX *tree = (TREE_CTX *)ctx;
757    char fname[2000];
758    TREE_NODE *node, *new_node;
759    int type;
760
761    strip_trailing_junk(row[1]);
762    if (*row[1] == 0) {
763       type = TN_DIR;
764    } else {
765       type = TN_FILE;
766    }
767    sprintf(fname, "%s%s", row[0], row[1]);
768    if (tree->avail_node) {
769       node = tree->avail_node;
770    } else {
771       node = new_tree_node(tree->root, type);
772       tree->avail_node = node;
773    }
774    Dmsg2(400, "FI=%d fname=%s\n", node->FileIndex, fname);
775    new_node = insert_tree_node(fname, node, tree->root, NULL);
776    /* Note, if node already exists, save new one for next time */
777    if (new_node != node) {
778       tree->avail_node = node;
779    } else {
780       tree->avail_node = NULL;
781    }
782    new_node->FileIndex = atoi(row[2]);
783    new_node->JobId = atoi(row[3]);
784    new_node->type = type;
785    new_node->extract = 1;             /* extract all by default */
786    tree->cnt++;
787    return 0;
788 }
789
790
791 /*
792  * Set extract to value passed. We recursively walk
793  *  down the tree setting all children if the 
794  *  node is a directory.
795  */
796 static void set_extract(TREE_NODE *node, int value)
797 {
798    TREE_NODE *n;
799
800    node->extract = value;
801    if (node->type != TN_FILE) {
802       for (n=node->child; n; n=n->sibling) {
803          set_extract(n, value);
804       }
805    }
806 }
807
808 static int markcmd(UAContext *ua, TREE_CTX *tree)
809 {
810    TREE_NODE *node;
811
812    if (ua->argc < 2)
813       return 1;
814    if (!tree->node->child) {     
815       return 1;
816    }
817    for (node = tree->node->child; node; node=node->sibling) {
818       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
819          set_extract(node, 1);
820       }
821    }
822    return 1;
823 }
824
825 static int countcmd(UAContext *ua, TREE_CTX *tree)
826 {
827    int total, extract;
828
829    total = extract = 0;
830    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
831       if (node->type != TN_NEWDIR) {
832          total++;
833          if (node->extract) {
834             extract++;
835          }
836       }
837    }
838    bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
839    return 1;
840 }
841
842 static int findcmd(UAContext *ua, TREE_CTX *tree)
843 {
844    char cwd[2000];
845
846    if (ua->argc == 1) {
847       bsendmsg(ua, _("No file specification given.\n"));
848       return 0;
849    }
850    
851    for (int i=1; i < ua->argc; i++) {
852       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
853          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
854             tree_getpath(node, cwd, sizeof(cwd));
855             bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
856          }
857       }
858    }
859    return 1;
860 }
861
862
863
864 static int lscmd(UAContext *ua, TREE_CTX *tree)
865 {
866    TREE_NODE *node;
867
868    if (!tree->node->child) {     
869       return 1;
870    }
871    for (node = tree->node->child; node; node=node->sibling) {
872       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
873          bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
874             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
875       }
876    }
877    return 1;
878 }
879
880 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
881 {
882    unsigned int i;
883
884 /* usage(); */
885    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
886    for (i=0; i<comsize; i++) {
887       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
888    }
889    bsendmsg(ua, "\n");
890    return 1;
891 }
892
893 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
894 {
895    TREE_NODE *node;
896    char cwd[2000];
897
898    if (ua->argc != 2) {
899       return 1;
900    }
901    node = tree_cwd(ua->argk[1], tree->root, tree->node);
902    if (!node) {
903       bsendmsg(ua, _("Invalid path given.\n"));
904    } else {
905       tree->node = node;
906    }
907    tree_getpath(tree->node, cwd, sizeof(cwd));
908    bsendmsg(ua, _("cwd is: %s\n"), cwd);
909    return 1;
910 }
911
912 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
913 {
914    char cwd[2000];
915    tree_getpath(tree->node, cwd, sizeof(cwd));
916    bsendmsg(ua, _("cwd is: %s\n"), cwd);
917    return 1;
918 }
919
920
921 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
922 {
923    TREE_NODE *node;
924
925    if (ua->argc < 2)
926       return 1;
927    if (!tree->node->child) {     
928       return 1;
929    }
930    for (node = tree->node->child; node; node=node->sibling) {
931       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
932          set_extract(node, 0);
933       }
934    }
935    return 1;
936 }
937
938 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
939 {
940    return 0;
941 }