]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
0f7491531f02c39a13b7e44eaeeb41f2bccf34a0
[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];
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 int comp_vol_params(const void *v1, const void *v2)
767 {
768    VOL_PARAMS *vol1 = (VOL_PARAMS *)v1;
769    VOL_PARAMS *vol2 = (VOL_PARAMS *)v2;
770
771    if (vol1->FirstIndex < vol2->FirstIndex) {
772       return -1;
773    } else if (vol1->FirstIndex > vol2->FirstIndex) {
774       return 1;
775    } else {
776       return 0;
777    }
778 }
779
780 /*
781  * First sort the bsr chain, then sort the VolParams   
782  */
783 static RBSR *sort_bsr(RBSR *bsr)
784 {
785    if (!bsr) {
786       return bsr;
787    }
788    /* ****FIXME**** sort the bsr chain */
789    /* Sort the VolParams for each bsr */
790    for (RBSR *nbsr=bsr; nbsr; nbsr=nbsr->next) {
791       if (nbsr->VolCount > 1) {
792          Dmsg1(100, "VolCount=%d\n", nbsr->VolCount);
793          qsort((void *)nbsr->VolParams, nbsr->VolCount, sizeof(VOL_PARAMS), 
794                comp_vol_params);
795       }
796    }
797    return bsr;
798 }
799
800 static void write_bsr(UAContext *ua, RBSR *bsr, FILE *fd)
801 {
802    if (bsr) {
803       for (int i=0; i < bsr->VolCount; i++) {
804          fprintf(fd, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
805          fprintf(fd, "VolSessionId=%u\n", bsr->VolSessionId);
806          fprintf(fd, "VolSessionTime=%u\n", bsr->VolSessionTime);
807          fprintf(fd, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile, 
808                  bsr->VolParams[i].EndFile);
809          fprintf(fd, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
810                  bsr->VolParams[i].EndBlock);
811          write_findex(ua, bsr->fi, fd);
812       }
813       write_bsr(ua, bsr->next, fd);
814    }
815 }
816
817 static void print_bsr(UAContext *ua, RBSR *bsr)
818 {
819    if (bsr) {
820       for (int i=0; i < bsr->VolCount; i++) {
821          bsendmsg(ua, "Volume=\"%s\"\n", bsr->VolParams[i].VolumeName);
822          bsendmsg(ua, "VolSessionId=%u\n", bsr->VolSessionId);
823          bsendmsg(ua, "VolSessionTime=%u\n", bsr->VolSessionTime);
824          bsendmsg(ua, "VolFile=%u-%u\n", bsr->VolParams[i].StartFile, 
825                   bsr->VolParams[i].EndFile);
826          bsendmsg(ua, "VolBlock=%u-%u\n", bsr->VolParams[i].StartBlock,
827                   bsr->VolParams[i].EndBlock);
828          print_findex(ua, bsr->fi);
829       }
830       print_bsr(ua, bsr->next);
831    }
832 }
833
834
835 /*
836  * Add a FileIndex to the list of BootStrap records.
837  *  Here we are only dealing with JobId's and the FileIndexes
838  *  associated with those JobIds.
839  */
840 static void add_findex(RBSR *bsr, uint32_t JobId, int32_t findex)
841 {
842    RBSR *nbsr;
843    RBSR_FINDEX *fi, *lfi;
844
845    if (findex == 0) {
846       return;                         /* probably a dummy directory */
847    }
848    
849    if (!bsr->fi) {                    /* if no FI add one */
850       /* This is the first FileIndex item in the chain */
851       bsr->fi = new_findex();
852       bsr->JobId = JobId;
853       bsr->fi->findex = findex;
854       bsr->fi->findex2 = findex;
855       return;
856    }
857    /* Walk down list of bsrs until we find the JobId */
858    if (bsr->JobId != JobId) {
859       for (nbsr=bsr->next; nbsr; nbsr=nbsr->next) {
860          if (nbsr->JobId == JobId) {
861             bsr = nbsr;
862             break;
863          }
864       }
865
866       if (!nbsr) {                    /* Must add new JobId */
867          /* Add new JobId at end of chain */
868          for (nbsr=bsr; nbsr->next; nbsr=nbsr->next) 
869             {  }
870          nbsr->next = new_bsr();
871          nbsr->next->JobId = JobId;
872          nbsr->next->fi = new_findex();
873          nbsr->next->fi->findex = findex;
874          nbsr->next->fi->findex2 = findex;
875          return;
876       }
877    }
878
879    /* 
880     * At this point, bsr points to bsr containing JobId,
881     *  and we are sure that there is at least one fi record.
882     */
883    lfi = fi = bsr->fi;
884    /* Check if this findex is smaller than first item */
885    if (findex < fi->findex) {
886       if ((findex+1) == fi->findex) {
887          fi->findex = findex;         /* extend down */
888          return;
889       }
890       fi = new_findex();              /* yes, insert before first item */
891       fi->findex = findex;
892       fi->findex2 = findex;
893       fi->next = lfi;
894       bsr->fi = fi;
895       return;
896    }
897    /* Walk down fi chain and find where to insert insert new FileIndex */
898    for ( ; fi; fi=fi->next) {
899       if (findex == (fi->findex2 + 1)) {  /* extend up */
900          RBSR_FINDEX *nfi;     
901          fi->findex2 = findex;
902          if (fi->next && ((findex+1) == fi->next->findex)) { 
903             nfi = fi->next;
904             fi->findex2 = nfi->findex2;
905             fi->next = nfi->next;
906             free(nfi);
907          }
908          return;
909       }
910       if (findex < fi->findex) {      /* add before */
911          if ((findex+1) == fi->findex) {
912             fi->findex = findex;
913             return;
914          }
915          break;
916       }
917       lfi = fi;
918    }
919    /* Add to last place found */
920    fi = new_findex();
921    fi->findex = findex;
922    fi->findex2 = findex;
923    fi->next = lfi->next;
924    lfi->next = fi;
925    return;
926 }
927
928 /*
929  * This callback routine is responsible for inserting the
930  *  items it gets into the directory tree. For each JobId selected
931  *  this routine is called once for each file. We do not allow
932  *  duplicate filenames, but instead keep the info from the most
933  *  recent file entered (i.e. the JobIds are assumed to be sorted)
934  */
935 static int insert_tree_handler(void *ctx, int num_fields, char **row)
936 {
937    TREE_CTX *tree = (TREE_CTX *)ctx;
938    char fname[2000];
939    TREE_NODE *node, *new_node;
940    int type;
941
942    strip_trailing_junk(row[1]);
943    if (*row[1] == 0) {
944       if (*row[0] != '/') {           /* Must be Win32 directory */
945          type = TN_DIR_NLS;
946       } else {
947          type = TN_DIR;
948       }
949    } else {
950       type = TN_FILE;
951    }
952    sprintf(fname, "%s%s", row[0], row[1]);
953    if (tree->avail_node) {
954       node = tree->avail_node;
955    } else {
956       node = new_tree_node(tree->root, type);
957       tree->avail_node = node;
958    }
959    Dmsg3(200, "FI=%d type=%d fname=%s\n", node->FileIndex, type, fname);
960    new_node = insert_tree_node(fname, node, tree->root, NULL);
961    /* Note, if node already exists, save new one for next time */
962    if (new_node != node) {
963       tree->avail_node = node;
964    } else {
965       tree->avail_node = NULL;
966    }
967    new_node->FileIndex = atoi(row[2]);
968    new_node->JobId = atoi(row[3]);
969    new_node->type = type;
970    new_node->extract = 1;             /* extract all by default */
971    tree->cnt++;
972    return 0;
973 }
974
975
976 /*
977  * Set extract to value passed. We recursively walk
978  *  down the tree setting all children if the 
979  *  node is a directory.
980  */
981 static void set_extract(UAContext *ua, TREE_NODE *node, TREE_CTX *tree, int value)
982 {
983    TREE_NODE *n;
984    FILE_DBR fdbr;
985    struct stat statp;
986
987    node->extract = value;
988    /* For a non-file (i.e. directory), we see all the children */
989    if (node->type != TN_FILE) {
990       for (n=node->child; n; n=n->sibling) {
991          set_extract(ua, n, tree, value);
992       }
993    } else if (value) {
994       char cwd[2000];
995       /* Ordinary file, we get the full path, look up the
996        * attributes, decode them, and if we are hard linked to
997        * a file that was saved, we must load that file too.
998        */
999       tree_getpath(node, cwd, sizeof(cwd));
1000       fdbr.FileId = 0;
1001       fdbr.JobId = node->JobId;
1002       if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1003          uint32_t LinkFI;
1004          decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1005          /*
1006           * If we point to a hard linked file, traverse the tree to
1007           * find that file, and mark it for restoration as well. It
1008           * must have the Link we just obtained and the same JobId.
1009           */
1010          if (LinkFI) {
1011             for (n=first_tree_node(tree->root); n; n=next_tree_node(n)) {
1012                if (n->FileIndex == LinkFI && n->JobId == node->JobId) {
1013                   n->extract = 1;
1014                   break;
1015                }
1016             }
1017          }
1018       }
1019    }
1020 }
1021
1022 static int markcmd(UAContext *ua, TREE_CTX *tree)
1023 {
1024    TREE_NODE *node;
1025
1026    if (ua->argc < 2)
1027       return 1;
1028    if (!tree->node->child) {     
1029       return 1;
1030    }
1031    for (node = tree->node->child; node; node=node->sibling) {
1032       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1033          set_extract(ua, node, tree, 1);
1034       }
1035    }
1036    return 1;
1037 }
1038
1039 static int countcmd(UAContext *ua, TREE_CTX *tree)
1040 {
1041    int total, extract;
1042
1043    total = extract = 0;
1044    for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1045       if (node->type != TN_NEWDIR) {
1046          total++;
1047          if (node->extract) {
1048             extract++;
1049          }
1050       }
1051    }
1052    bsendmsg(ua, "%d total files. %d marked for restoration.\n", total, extract);
1053    return 1;
1054 }
1055
1056 static int findcmd(UAContext *ua, TREE_CTX *tree)
1057 {
1058    char cwd[2000];
1059
1060    if (ua->argc == 1) {
1061       bsendmsg(ua, _("No file specification given.\n"));
1062       return 0;
1063    }
1064    
1065    for (int i=1; i < ua->argc; i++) {
1066       for (TREE_NODE *node=first_tree_node(tree->root); node; node=next_tree_node(node)) {
1067          if (fnmatch(ua->argk[i], node->fname, 0) == 0) {
1068             tree_getpath(node, cwd, sizeof(cwd));
1069             bsendmsg(ua, "%s%s\n", node->extract?"*":"", cwd);
1070          }
1071       }
1072    }
1073    return 1;
1074 }
1075
1076
1077
1078 static int lscmd(UAContext *ua, TREE_CTX *tree)
1079 {
1080    TREE_NODE *node;
1081
1082    if (!tree->node->child) {     
1083       return 1;
1084    }
1085    for (node = tree->node->child; node; node=node->sibling) {
1086       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1087          bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1088             (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1089       }
1090    }
1091    return 1;
1092 }
1093
1094 extern char *getuser(uid_t uid);
1095 extern char *getgroup(gid_t gid);
1096
1097 /*
1098  * This is actually the long form used for "dir"
1099  */
1100 static void ls_output(char *buf, char *fname, int extract, struct stat *statp)
1101 {
1102    char *p, *f;
1103    char ec1[30];
1104    int n;
1105
1106    p = encode_mode(statp->st_mode, buf);
1107    n = sprintf(p, "  %2d ", (uint32_t)statp->st_nlink);
1108    p += n;
1109    n = sprintf(p, "%-8.8s %-8.8s", getuser(statp->st_uid), getgroup(statp->st_gid));
1110    p += n;
1111    n = sprintf(p, "%8.8s  ", edit_uint64(statp->st_size, ec1));
1112    p += n;
1113    p = encode_time(statp->st_ctime, p);
1114    *p++ = ' ';
1115    if (extract) {
1116       *p++ = '*';
1117    } else {
1118       *p++ = ' ';
1119    }
1120    for (f=fname; *f; )
1121       *p++ = *f++;
1122    *p = 0;
1123 }
1124
1125
1126 /*
1127  * Like ls command, but give more detail on each file
1128  */
1129 static int dircmd(UAContext *ua, TREE_CTX *tree)
1130 {
1131    TREE_NODE *node;
1132    FILE_DBR fdbr;
1133    struct stat statp;
1134    char buf[1000];
1135    char cwd[1100];
1136
1137    if (!tree->node->child) {     
1138       return 1;
1139    }
1140    for (node = tree->node->child; node; node=node->sibling) {
1141       if (ua->argc == 1 || fnmatch(ua->argk[1], node->fname, 0) == 0) {
1142          tree_getpath(node, cwd, sizeof(cwd));
1143          fdbr.FileId = 0;
1144          fdbr.JobId = node->JobId;
1145          if (db_get_file_attributes_record(ua->jcr, ua->db, cwd, &fdbr)) {
1146             uint32_t LinkFI;
1147             decode_stat(fdbr.LStat, &statp, &LinkFI); /* decode stat pkt */
1148             ls_output(buf, cwd, node->extract, &statp);
1149             bsendmsg(ua, "%s\n", buf);
1150          } else {
1151             /* Something went wrong getting attributes -- print name */
1152             bsendmsg(ua, "%s%s%s\n", node->extract?"*":"", node->fname,
1153                (node->type==TN_DIR||node->type==TN_NEWDIR)?"/":"");
1154          }
1155       }
1156    }
1157    return 1;
1158 }
1159
1160
1161 static int helpcmd(UAContext *ua, TREE_CTX *tree) 
1162 {
1163    unsigned int i;
1164
1165 /* usage(); */
1166    bsendmsg(ua, _("  Command    Description\n  =======    ===========\n"));
1167    for (i=0; i<comsize; i++) {
1168       bsendmsg(ua, _("  %-10s %s\n"), _(commands[i].key), _(commands[i].help));
1169    }
1170    bsendmsg(ua, "\n");
1171    return 1;
1172 }
1173
1174 /*
1175  * Change directories.  Note, if the user specifies x: and it fails,
1176  *   we assume it is a Win32 absolute cd rather than relative and
1177  *   try a second time with /x: ...  Win32 kludge.
1178  */
1179 static int cdcmd(UAContext *ua, TREE_CTX *tree) 
1180 {
1181    TREE_NODE *node;
1182    char cwd[2000];
1183
1184    if (ua->argc != 2) {
1185       return 1;
1186    }
1187    node = tree_cwd(ua->argk[1], tree->root, tree->node);
1188    if (!node) {
1189       /* Try once more if Win32 drive -- make absolute */
1190       if (ua->argk[1][1] == ':') {  /* win32 drive */
1191          strcpy(cwd, "/");
1192          strcat(cwd, ua->argk[1]);
1193          node = tree_cwd(cwd, tree->root, tree->node);
1194       }
1195       if (!node) {
1196          bsendmsg(ua, _("Invalid path given.\n"));
1197       } else {
1198          tree->node = node;
1199       }
1200    } else {
1201       tree->node = node;
1202    }
1203    tree_getpath(tree->node, cwd, sizeof(cwd));
1204    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1205    return 1;
1206 }
1207
1208 static int pwdcmd(UAContext *ua, TREE_CTX *tree) 
1209 {
1210    char cwd[2000];
1211    tree_getpath(tree->node, cwd, sizeof(cwd));
1212    bsendmsg(ua, _("cwd is: %s\n"), cwd);
1213    return 1;
1214 }
1215
1216
1217 static int unmarkcmd(UAContext *ua, TREE_CTX *tree)
1218 {
1219    TREE_NODE *node;
1220
1221    if (ua->argc < 2)
1222       return 1;
1223    if (!tree->node->child) {     
1224       return 1;
1225    }
1226    for (node = tree->node->child; node; node=node->sibling) {
1227       if (fnmatch(ua->argk[1], node->fname, 0) == 0) {
1228          set_extract(ua, node, tree, 0);
1229       }
1230    }
1231    return 1;
1232 }
1233
1234 static int quitcmd(UAContext *ua, TREE_CTX *tree) 
1235 {
1236    return 0;
1237 }
1238
1239
1240 /*
1241  * Called here with each name to be added to the list. The name is
1242  *   added to the list if it is not already in the list.
1243  */
1244 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
1245 {
1246    NAME_LIST *name = (NAME_LIST *)ctx;
1247
1248    if (name->num_ids == MAX_ID_LIST_LEN) {  
1249       return 1;
1250    }
1251    if (name->num_ids == name->max_ids) {
1252       if (name->max_ids == 0) {
1253          name->max_ids = 1000;
1254          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
1255       } else {
1256          name->max_ids = (name->max_ids * 3) / 2;
1257          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
1258       }
1259    }
1260    for (int i=0; i<name->num_ids; i++) {
1261       if (strcmp(name->name[i], row[0]) == 0) {
1262          return 0;                    /* already in list, return */
1263       }
1264    }
1265    /* Add new name to list */
1266    name->name[name->num_ids++] = bstrdup(row[0]);
1267    return 0;
1268 }
1269
1270
1271 /*
1272  * Print names in the list
1273  */
1274 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
1275
1276    int i;
1277
1278    for (i=0; i < name_list->num_ids; i++) {
1279       bsendmsg(ua, "%s\n", name_list->name[i]);
1280    }
1281 }
1282
1283
1284 /*
1285  * Free names in the list
1286  */
1287 static void free_name_list(NAME_LIST *name_list)
1288
1289    int i;
1290
1291    for (i=0; i < name_list->num_ids; i++) {
1292       free(name_list->name[i]);
1293    }
1294    if (name_list->name) {
1295       free(name_list->name);
1296    }
1297    name_list->max_ids = 0;
1298    name_list->num_ids = 0;
1299 }
1300
1301 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
1302 {
1303    char name[MAX_NAME_LENGTH];
1304    STORE *store = NULL;
1305
1306    if (name_list->num_ids > 1) {
1307       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
1308          "Restore is not possible. The MediaTypes used are:\n"));
1309       print_name_list(ua, name_list);
1310       ji->store = select_storage_resource(ua);
1311       return;
1312    }
1313
1314    if (name_list->num_ids == 0) {
1315       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
1316       ji->store = select_storage_resource(ua);
1317       return;
1318    }
1319
1320    start_prompt(ua, _("The defined Storage resources are:\n"));
1321    LockRes();
1322    while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
1323       if (strcmp(store->media_type, name_list->name[0]) == 0) {
1324          add_prompt(ua, store->hdr.name);
1325       }
1326    }
1327    UnlockRes();
1328    do_prompt(ua, _("Select Storage resource"), name, sizeof(name));
1329    ji->store = (STORE *)GetResWithName(R_STORAGE, name);
1330    if (!ji->store) {
1331       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
1332          "MediaType %s, needed by the Jobs you selected.\n"
1333          "You will be allowed to select a Storage device later.\n"),
1334          name_list->name[0]); 
1335    }
1336 }