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