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