]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
Fix tree relcd + add rate
[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       stat = 0;
558       for (i=0; i<(int)comsize; i++)       /* search for command */
559          if (strncasecmp(tree->ua->argk[0],  _(commands[i].key), len) == 0) {
560             stat = (*commands[i].func)(tree->ua, tree);   /* go execute command */
561             found = 1;
562             break;
563          }
564       if (!found) {
565          bsendmsg(tree->ua, _("Illegal command. Enter \"done\" to exit.\n"));
566          continue;
567       }
568       if (!stat) {
569          break;
570       }
571    }
572 }
573
574 /*
575  * Create new FileIndex entry for BSR 
576  */
577 static RBSR_FINDEX *new_findex() 
578 {
579    RBSR_FINDEX *fi = (RBSR_FINDEX *)bmalloc(sizeof(RBSR_FINDEX));
580    memset(fi, 0, sizeof(RBSR_FINDEX));
581    return fi;
582 }
583
584 /* Free all BSR FileIndex entries */
585 static void free_findex(RBSR_FINDEX *fi)
586 {
587    if (fi) {
588       free_findex(fi->next);
589       free(fi);
590    }
591 }
592
593 static void write_findex(UAContext *ua, RBSR_FINDEX *fi, FILE *fd) 
594 {
595    if (fi) {
596       if (fi->findex == fi->findex2) {
597          fprintf(fd, "FileIndex=%d\n", fi->findex);
598       } else {
599          fprintf(fd, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
600       }
601       write_findex(ua, fi->next, fd);
602    }
603 }
604
605
606 static void print_findex(UAContext *ua, RBSR_FINDEX *fi)
607 {
608    if (fi) {
609       if (fi->findex == fi->findex2) {
610          bsendmsg(ua, "FileIndex=%d\n", fi->findex);
611       } else {
612          bsendmsg(ua, "FileIndex=%d-%d\n", fi->findex, fi->findex2);
613       }
614       print_findex(ua, fi->next);
615    }
616 }
617
618 /* Create a new bootstrap record */
619 static RBSR *new_bsr()
620 {
621    RBSR *bsr = (RBSR *)bmalloc(sizeof(RBSR));
622    memset(bsr, 0, sizeof(RBSR));
623    return bsr;
624 }
625
626 /* Free the entire BSR */
627 static void free_bsr(RBSR *bsr)
628 {
629    if (bsr) {
630       free_findex(bsr->fi);
631       free_bsr(bsr->next);
632       if (bsr->VolumeName) {
633          free(bsr->VolumeName);
634       }
635       free(bsr);
636    }
637 }
638
639 /*
640  * Complete the BSR by filling in the VolumeName and
641  *  VolSessionId and VolSessionTime using the JobId
642  */
643 static int complete_bsr(UAContext *ua, RBSR *bsr)
644 {
645    JOB_DBR jr;
646    POOLMEM *VolumeNames;
647
648    if (bsr) {
649       VolumeNames = get_pool_memory(PM_MESSAGE);
650       memset(&jr, 0, sizeof(jr));
651       jr.JobId = bsr->JobId;
652       if (!db_get_job_record(ua->db, &jr)) {
653          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
654          return 0;
655       }
656       bsr->VolSessionId = jr.VolSessionId;
657       bsr->VolSessionTime = jr.VolSessionTime;
658       if (!db_get_job_volume_names(ua->db, bsr->JobId, &VolumeNames)) {
659          bsendmsg(ua, _("Unable to get Job Volumes. ERR=%s\n"), db_strerror(ua->db));
660          free_pool_memory(VolumeNames);
661          return 0;
662       }
663       bsr->VolumeName = bstrdup(VolumeNames);
664       free_pool_memory(VolumeNames);
665       return complete_bsr(ua, bsr->next);
666    }
667    return 1;
668 }
669
670 /*
671  * Write the bootstrap record to file
672  */
673 static int write_bsr_file(UAContext *ua, RBSR *bsr)
674 {
675    FILE *fd;
676    POOLMEM *fname = get_pool_memory(PM_MESSAGE);
677    int stat;
678
679    Mmsg(&fname, "%s/restore.bsr", working_directory);
680    fd = fopen(fname, "w+");
681    if (!fd) {
682       bsendmsg(ua, _("Unable to create bootstrap file %s. ERR=%s\n"), 
683          fname, strerror(errno));
684       free_pool_memory(fname);
685       return 0;
686    }
687    write_bsr(ua, bsr, fd);
688    stat = !ferror(fd);
689    fclose(fd);
690 // bsendmsg(ua, _("Bootstrap records written to %s\n"), fname);
691    free_pool_memory(fname);
692    return stat;
693 }
694
695 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
696 {
697    if (bsr) {
698       if (bsr->VolumeName) {
699          fprintf(fd, "Volume=\"%s\"\n", bsr->VolumeName);
700       }
701       fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
702       fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
703       write_findex(ua, bsr->fi, fd);
704       write_bsr(ua, bsr->next, fd);
705    }
706 }
707
708 static void print_bsr(UAContext *ua, RBSR *bsr)
709 {
710    if (bsr) {
711       if (bsr->VolumeName) {
712          bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolumeName);
713       }
714       bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
715       bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
716       print_findex(ua, bsr->fi);
717       print_bsr(ua, bsr->next);
718    }
719 }
720
721
722 /*
723  * Add a FileIndex to the list of BootStrap records.
724  *  Here we are only dealing with JobId's and the FileIndexes
725  *  associated with those JobIds.
726  */
727 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
728 {
729    RBSR *nbsr;
730    RBSR_FINDEX *fi, *lfi;
731
732    if (findex == 0) {
733       return;                         /* probably a dummy directory */
734    }
735    
736    if (!bsr->fi) {                    /* if no FI add one */
737       /* This is the first FileIndex item in the chain */
738       bsr->fi = new_findex();
739       bsr->JobId = JobId;
740       bsr->fi->findex = findex;
741       bsr->fi->findex2 = findex;
742       return;
743    }
744    /* Walk down list of bsrs until we find the JobId */
745    if (bsr->JobId != JobId) {
746       for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
747          if (nbsr->JobId == JobId) {
748             bsr = nbsr;
749             break;
750          }
751       }
752
753       if (!nbsr) {                    /* Must add new JobId */
754          /* Add new JobId at end of chain */
755          for (nbsr=bsr; nbsr->next; nbsr=nbsr->next) 
756             {  }
757          nbsr->next = new_bsr();
758          nbsr->next->JobId = JobId;
759          nbsr->next->fi = new_findex();
760          nbsr->next->fi->findex = findex;
761          nbsr->next->fi->findex2 = findex;
762          return;
763       }
764    }
765
766    /* 
767     * At this point, bsr points to bsr containing JobId,
768     *  and we are sure that there is at least one fi record.
769     */
770    lfi = fi = bsr->fi;
771    /* Check if this findex is smaller than first item */
772    if (findex < fi->findex) {
773       if ((findex+1) == fi->findex) {
774          fi->findex = findex;         /* extend down */
775          return;
776       }
777       fi = new_findex();              /* yes, insert before first item */
778       fi->findex = findex;
779       fi->findex2 = findex;
780       fi->next = lfi;
781       bsr->fi = fi;
782       return;
783    }
784    /* Walk down fi chain and find where to insert insert new FileIndex */
785    for ( ; fi; fi=fi->next) {
786       if (findex == (fi->findex2 + 1)) {  /* extend up */
787          RBSR_FINDEX *nfi;     
788          fi->findex2 = findex;
789          if (fi->next && ((findex+1) == fi->next->findex)) { 
790             nfi = fi->next;
791             fi->findex2 = nfi->findex2;
792             fi->next = nfi->next;
793             free(nfi);
794          }
795          return;
796       }
797       if (findex < fi->findex) {      /* add before */
798          if ((findex+1) == fi->findex) {
799             fi->findex = findex;
800             return;
801          }
802          break;
803       }
804       lfi = fi;
805    }
806    /* Add to last place found */
807    fi = new_findex();
808    fi->findex = findex;
809    fi->findex2 = findex;
810    fi->next = lfi->next;
811    lfi->next = fi;
812    return;
813 }
814
815 /*
816  * This callback routine is responsible for inserting the
817  *  items it gets into the directory tree. For each JobId selected
818  *  this routine is called once for each file. We do not allow
819  *  duplicate filenames, but instead keep the info from the most
820  *  recent file entered (i.e. the JobIds are assumed to be sorted)
821  */
822 static int insert_tree_handler(void *ctx, int num_fields, char **row)
823 {
824    TREE_CTX *tree = (TREE_CTX *)ctx;
825    char fname[2000];
826    TREE_NODE *node, *new_node;
827    int type;
828
829    strip_trailing_junk(row[1]);
830    if (*row[1] == 0) {
831       type = TN_DIR;
832    } else {
833       type = TN_FILE;
834    }
835    sprintf(fname, "%s%s", row[0], row[1]);
836    if (tree->avail_node) {
837       node = tree->avail_node;
838    } else {
839       node = new_tree_node(tree->root, type);
840       tree->avail_node = node;
841    }
842    Dmsg2(400, "FI=%d fname=%s\n", node->FileIndex, fname);
843    new_node = insert_tree_node(fname, node, tree->root, NULL);
844    /* Note, if node already exists, save new one for next time */
845    if (new_node != node) {
846       tree->avail_node = node;
847    } else {
848       tree->avail_node = NULL;
849    }
850    new_node->FileIndex = atoi(row[2]);
851    new_node->JobId = atoi(row[3]);
852    new_node->type = type;
853    new_node->extract = 1;             /* extract all by default */
854    tree->cnt++;
855    return 0;
856 }
857
858
859 /*
860  * Set extract to value passed. We recursively walk
861  *  down the tree setting all children if the 
862  *  node is a directory.
863  */
864 static void set_extract(TREE_NODE *node, int value)
865 {
866    TREE_NODE *n;
867
868    node->extract = value;
869    if (node->type != TN_FILE) {
870       for (n=node->child; n; n=n->sibling) {
871          set_extract(n, value);
872       }
873    }
874 }
875
876 static int markcmd(UAContext *ua, TREE_CTX *tree)
877 {
878    TREE_NODE *node;
879
880    if (ua->argc < 2)
881       return 1;
882    if (!tree->node->child) {     
883       return 1;
884    }
885    for (node = tree->node->child; node; node=node->sibling) {
886       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
887          set_extract(node, 1);
888       }
889    }
890    return 1;
891 }
892
893 static int countcmd(UAContext *ua, TREE_CTX *tree)
894 {
895    int total, extract;
896
897    total = extract = 0;
898    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
899       if (node->type != TN_NEWDIR) {
900          total++;
901          if (node->extract) {
902             extract++;
903          }
904       }
905    }
906    bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
907    return 1;
908 }
909
910 static int findcmd(UAContext *ua, TREE_CTX *tree)
911 {
912    char cwd[2000];
913
914    if (ua->argc == 1) {
915       bsendmsg(ua, _("No file specification given.\n"));
916       return 0;
917    }
918    
919    for (int i=1; i < ua->argc; i++) {
920       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
921          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
922             tree_getpath(node, cwd, sizeof(cwd));
923             bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
924          }
925       }
926    }
927    return 1;
928 }
929
930
931
932 static int lscmd(UAContext *ua, TREE_CTX *tree)
933 {
934    TREE_NODE *node;
935
936    if (!tree->node->child) {     
937       return 1;
938    }
939    for (node = tree->node->child; node; node=node->sibling) {
940       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
941          bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
942             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
943       }
944    }
945    return 1;
946 }
947
948 extern char *getuser(uid_t uid);
949 extern char *getgroup(gid_t gid);
950
951 static void ls_output(char *buf, char *fname, struct stat *statp)
952 {
953    char *p, *f;
954    int n;
955
956    p = encode_mode(statp->st_mode, buf);
957    n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
958    p += n;
959    n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
960    p += n;
961    n = sprintf(p, "%8ld  ", statp->st_size);
962    p += n;
963    p = encode_time(statp->st_ctime, p);
964    *p++ = ' ';
965    *p++ = ' ';
966    for (f=fname; *f; )
967       *p++ = *f++;
968    *p = 0;
969 }
970
971
972 /*
973  * Like ls command, but give more detail on each file
974  */
975 static int dircmd(UAContext *ua, TREE_CTX *tree)
976 {
977    TREE_NODE *node;
978    FILE_DBR fdbr;
979    struct stat statp;
980    char buf[1000];
981    char cwd[1100];
982
983    if (!tree->node->child) {     
984       return 1;
985    }
986    for (node = tree->node->child; node; node=node->sibling) {
987       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
988          tree_getpath(node, cwd, sizeof(cwd));
989          fdbr.FileId = 0;
990          fdbr.JobId = node->JobId;
991          if (db_get_file_attributes_record(ua->db, cwd, &fdbr)) {
992             decode_stat(fdbr.LStat, &statp); /* decode stat pkt */
993             ls_output(buf, cwd, &statp);
994             bsendmsg(ua, "%s\n", buf);
995          }
996       }
997    }
998    return 1;
999 }
1000
1001
1002 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
1003 {
1004    unsigned int i;
1005
1006 /* usage(); */
1007    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
1008    for (i=0; i<comsize; i++) {
1009       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1010    }
1011    bsendmsg(ua, "\n");
1012    return 1;
1013 }
1014
1015 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
1016 {
1017    TREE_NODE *node;
1018    char cwd[2000];
1019
1020    if (ua->argc != 2) {
1021       return 1;
1022    }
1023    node = tree_cwd(ua->argk[1], tree->root, tree->node);
1024    if (!node) {
1025       bsendmsg(ua, _("Invalid path given.\n"));
1026    } else {
1027       tree->node = node;
1028    }
1029    tree_getpath(tree->node, cwd, sizeof(cwd));
1030    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1031    return 1;
1032 }
1033
1034 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
1035 {
1036    char cwd[2000];
1037    tree_getpath(tree->node, cwd, sizeof(cwd));
1038    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1039    return 1;
1040 }
1041
1042
1043 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1044 {
1045    TREE_NODE *node;
1046
1047    if (ua->argc < 2)
1048       return 1;
1049    if (!tree->node->child) {     
1050       return 1;
1051    }
1052    for (node = tree->node->child; node; node=node->sibling) {
1053       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1054          set_extract(node, 0);
1055       }
1056    }
1057    return 1;
1058 }
1059
1060 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
1061 {
1062    return 0;
1063 }
1064
1065
1066 /*
1067  * Called here with each name to be added to the list. The name is
1068  *   added to the list if it is not already in the list.
1069  */
1070 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1071 {
1072    NAME_LIST *name = (NAME_LIST *)ctx;
1073
1074    if (name->num_ids == MAX_ID_LIST_LEN) {  
1075       return 1;
1076    }
1077    if (name->num_ids == name->max_ids) {
1078       if (name->max_ids == 0) {
1079          name->max_ids = 1000;
1080          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1081       } else {
1082          name->max_ids = (name->max_ids * 3) / 2;
1083          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1084       }
1085    }
1086    for (int i=0; i<name->num_ids; i++) {
1087       if (strcmp(name->name[i], row[0]) == 0) {
1088          return 0;                    /* already in list, return */
1089       }
1090    }
1091    /* Add new name to list */
1092    name->name[name->num_ids++] = bstrdup(row[0]);
1093    return 0;
1094 }
1095
1096
1097 /*
1098  * Print names in the list
1099  */
1100 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1101
1102    int i;
1103
1104    for (i=0; i < name_list->num_ids; i++) {
1105       bsendmsg(ua, "%s\n", name_list->name[i]);
1106    }
1107 }
1108
1109
1110 /*
1111  * Free names in the list
1112  */
1113 static void free_name_list(NAME_LIST *name_list)
1114
1115    int i;
1116
1117    for (i=0; i < name_list->num_ids; i++) {
1118       free(name_list->name[i]);
1119    }
1120    free(name_list->name);
1121    name_list->max_ids = 0;
1122    name_list->num_ids = 0;
1123 }
1124
1125 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JobIds *ji)
1126 {
1127    char name[MAX_NAME_LENGTH];
1128    STORE *store = NULL;
1129
1130    if (name_list->num_ids > 1) {
1131       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1132          "Restore is not possible. The MediaTypes used are:\n"));
1133       print_name_list(ua, name_list);
1134       ji->store = select_storage_resource(ua);
1135       return;
1136    }
1137
1138    start_prompt(ua, _("The defined Storage resources are:\n"));
1139    LockRes();
1140    while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1141       if (strcmp(store->hdr.name, name_list->name[0]) == 0) {
1142          add_prompt(ua, store->hdr.name);
1143       }
1144    }
1145    UnlockRes();
1146    do_prompt(ua, _("Select Storage resource"), name);
1147    ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1148    if (!ji->store) {
1149       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1150          "MediaType %s, needed by the Jobs you selected.\n"
1151          "You will be allowed to select a Storage device later.\n"),
1152          name_list->name[0]); 
1153    }
1154 }