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