]> git.sur5r.net Git - bacula/bacula/blob - bacula/src/dird/ua_restore.c
New var.c file + implement multiple simultaneous jobs
[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 and
5  *      starts the restore job.
6  *
7  *      Tree handling routines split into ua_tree.c July MMIII.
8  *      BSR (bootstrap record) handling routines split into
9  *        bsr.c July MMIII
10  *
11  *     Kern Sibbald, July MMII
12  *
13  *   Version $Id$
14  */
15
16 /*
17    Copyright (C) 2002-2003 Kern Sibbald and John Walker
18
19    This program is free software; you can redistribute it and/or
20    modify it under the terms of the GNU General Public License as
21    published by the Free Software Foundation; either version 2 of
22    the License, or (at your option) any later version.
23
24    This program is distributed in the hope that it will be useful,
25    but WITHOUT ANY WARRANTY; without even the implied warranty of
26    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27    General Public License for more details.
28
29    You should have received a copy of the GNU General Public
30    License along with this program; if not, write to the Free
31    Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
32    MA 02111-1307, USA.
33
34  */
35
36 #include "bacula.h"
37 #include "dird.h"
38
39
40 /* Imported functions */
41 extern int runcmd(UAContext *ua, char *cmd);
42
43 /* Imported variables */
44 extern char *uar_list_jobs,     *uar_file,        *uar_sel_files;
45 extern char *uar_del_temp,      *uar_del_temp1,   *uar_create_temp;
46 extern char *uar_create_temp1,  *uar_last_full,   *uar_full;
47 extern char *uar_inc_dec,       *uar_list_temp,   *uar_sel_jobid_temp;
48 extern char *uar_sel_all_temp1, *uar_sel_fileset, *uar_mediatype;
49
50
51 /* Main structure for obtaining JobIds */
52 struct JOBIDS {
53    utime_t JobTDate;
54    uint32_t TotalFiles;
55    char ClientName[MAX_NAME_LENGTH];
56    char JobIds[200];                  /* User entered string of JobIds */
57    STORE  *store;
58 };
59
60 struct NAME_LIST {
61    char **name;                       /* list of names */
62    int num_ids;                       /* ids stored */
63    int max_ids;                       /* size of array */
64    int num_del;                       /* number deleted */
65    int tot_ids;                       /* total to process */
66 };
67
68 #define MAX_ID_LIST_LEN 1000000
69
70
71 /* Forward referenced functions */
72 static int last_full_handler(void *ctx, int num_fields, char **row);
73 static int jobid_handler(void *ctx, int num_fields, char **row);
74 static int next_jobid_from_list(char **p, uint32_t *JobId);
75 static int user_select_jobids(UAContext *ua, JOBIDS *ji);
76 static int fileset_handler(void *ctx, int num_fields, char **row);
77 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
78 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
79 static void free_name_list(NAME_LIST *name_list);
80 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji);
81 static int select_backups_before_date(UAContext *ua, JOBIDS *ji, char *date);
82
83
84 /*
85  *   Restore files
86  *
87  */
88 int restorecmd(UAContext *ua, char *cmd)
89 {
90    POOLMEM *query;
91    TREE_CTX tree;
92    JobId_t JobId, last_JobId;
93    char *p;
94    RBSR *bsr;
95    char *nofname = "";
96    JOBIDS ji;
97    JOB *job = NULL;
98    JOB *restore_job = NULL;
99    int restore_jobs = 0;
100    NAME_LIST name_list;
101    uint32_t selected_files = 0;
102    char *where = NULL;
103    int i;
104
105    i = find_arg_with_value(ua, "where");
106    if (i >= 0) {
107       where = ua->argv[i];
108    }
109
110    if (!open_db(ua)) {
111       return 0;
112    }
113
114    memset(&tree, 0, sizeof(TREE_CTX));
115    memset(&name_list, 0, sizeof(name_list));
116    memset(&ji, 0, sizeof(ji));
117
118    /* Ensure there is at least one Restore Job */
119    LockRes();
120    while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
121       if (job->JobType == JT_RESTORE) {
122          if (!restore_job) {
123             restore_job = job;
124          }
125          restore_jobs++;
126       }
127    }
128    UnlockRes();
129    if (!restore_jobs) {
130       bsendmsg(ua, _(
131          "No Restore Job Resource found. You must create at least\n"
132          "one before running this command.\n"));
133       return 0;
134    }
135
136    /* 
137     * Request user to select JobIds by various different methods
138     *  last 20 jobs, where File saved, most recent backup, ...
139     */
140    if (!user_select_jobids(ua, &ji)) {
141       return 0;
142    }
143
144    /* 
145     * Build the directory tree containing JobIds user selected
146     */
147    tree.root = new_tree(ji.TotalFiles);
148    tree.root->fname = nofname;
149    tree.ua = ua;
150    query = get_pool_memory(PM_MESSAGE);
151    last_JobId = 0;
152    /*
153     * For display purposes, the same JobId, with different volumes may
154     * appear more than once, however, we only insert it once.
155     */
156    int items = 0;
157    for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
158
159       if (JobId == last_JobId) {             
160          continue;                    /* eliminate duplicate JobIds */
161       }
162       last_JobId = JobId;
163       bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
164       items++;
165       /*
166        * Find files for this JobId and insert them in the tree
167        */
168       Mmsg(&query, uar_sel_files, JobId);
169       if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
170          bsendmsg(ua, "%s", db_strerror(ua->db));
171       }
172       /*
173        * Find the FileSets for this JobId and add to the name_list
174        */
175       Mmsg(&query, uar_mediatype, JobId);
176       if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
177          bsendmsg(ua, "%s", db_strerror(ua->db));
178       }
179
180    }
181    bsendmsg(ua, "%d item%s inserted into the tree and marked for extraction.\n", 
182       items, items==1?"":"s");
183    free_pool_memory(query);
184
185    /* Check MediaType and select storage that corresponds */
186    get_storage_from_mediatype(ua, &name_list, &ji);
187    free_name_list(&name_list);
188
189    /* Let the user select which files to restore */
190    user_select_files_from_tree(&tree);
191
192    /*
193     * Walk down through the tree finding all files marked to be 
194     *  extracted making a bootstrap file.
195     */
196    bsr = new_bsr();
197    for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
198       Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
199       if (node->extract) {
200          Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
201          add_findex(bsr, node->JobId, node->FileIndex);
202          selected_files++;
203       }
204    }
205
206    free_tree(tree.root);              /* free the directory tree */
207
208    if (bsr->JobId) {
209       if (!complete_bsr(ua, bsr)) {   /* find Vol, SessId, SessTime from JobIds */
210          bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
211          free_bsr(bsr);
212          return 0;
213       }
214 //    print_bsr(ua, bsr);
215       write_bsr_file(ua, bsr);
216       bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
217    } else {
218       bsendmsg(ua, _("No files selected to restore.\n"));
219    }
220    free_bsr(bsr);
221
222    if (restore_jobs == 1) {
223       job = restore_job;
224    } else {
225       job = select_restore_job_resource(ua);
226    }
227    if (!job) {
228       bsendmsg(ua, _("No Restore Job resource found!\n"));
229       return 0;
230    }
231
232    /* If no client name specified yet, get it now */
233    if (!ji.ClientName[0]) {
234       CLIENT_DBR cr;
235       memset(&cr, 0, sizeof(cr));
236       if (!get_client_dbr(ua, &cr)) {
237          return 0;
238       }
239       bstrncpy(ji.ClientName, cr.Name, sizeof(ji.ClientName));
240    }
241
242    /* Build run command */
243    if (where) {
244       Mmsg(&ua->cmd, 
245           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
246           " where=\"%s\"",
247           job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
248           working_directory, where);
249    } else {
250       Mmsg(&ua->cmd, 
251           "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
252           job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
253           working_directory);
254    }
255    Dmsg1(400, "Submitting: %s\n", ua->cmd);
256    
257    parse_ua_args(ua);
258    runcmd(ua, ua->cmd);
259
260    bsendmsg(ua, _("Restore command done.\n"));
261    return 1;
262 }
263
264 /*
265  * The first step in the restore process is for the user to 
266  *  select a list of JobIds from which he will subsequently
267  *  select which files are to be restored.
268  */
269 static int user_select_jobids(UAContext *ua, JOBIDS *ji)
270 {
271    char *p;
272    char date[MAX_TIME_LENGTH];
273    JobId_t JobId;
274    JOB_DBR jr;
275    POOLMEM *query;
276    int done = 0;
277    char *list[] = { 
278       "List last 20 Jobs run",
279       "List Jobs where a given File is saved",
280       "Enter list of JobIds to select",
281       "Enter SQL list command", 
282       "Select the most recent backup for a client",
283       "Select backup for a client before a specified time",
284       "Cancel",
285       NULL };
286
287    bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
288                   "to be restored. You will be presented several methods\n"
289                   "of specifying the JobIds. Then you will be allowed to\n"
290                   "select which files from those JobIds are to be restored.\n\n"));
291
292    for ( ; !done; ) {
293       start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
294       for (int i=0; list[i]; i++) {
295          add_prompt(ua, list[i]);
296       }
297       done = 1;
298       switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
299       case -1:                        /* error */
300          return 0;
301       case 0:                         /* list last 20 Jobs run */
302          db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
303          done = 0;
304          break;
305       case 1:                         /* list where a file is saved */
306          char *fname;
307          int len;
308          if (!get_cmd(ua, _("Enter Filename: "))) {
309             return 0;
310          }
311          len = strlen(ua->cmd);
312          fname = (char *)malloc(len * 2 + 1);
313          db_escape_string(fname, ua->cmd, len);
314          query = get_pool_memory(PM_MESSAGE);
315          Mmsg(&query, uar_file, fname);
316          free(fname);
317          db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, HORZ_LIST);
318          free_pool_memory(query);
319          done = 0;
320          break;
321       case 2:                         /* enter a list of JobIds */
322          if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
323             return 0;
324          }
325          bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
326          break;
327       case 3:                         /* Enter an SQL list command */
328          if (!get_cmd(ua, _("Enter SQL list command: "))) {
329             return 0;
330          }
331          db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
332          done = 0;
333          break;
334       case 4:                         /* Select the most recent backups */
335          bstrutime(date, sizeof(date), time(NULL));
336          if (!select_backups_before_date(ua, ji, date)) {
337             return 0;
338          }
339          break;
340       case 5:                         /* select backup at specified time */
341          bsendmsg(ua, _("The restored files will the most current backup\n"
342                         "BEFORE the date you specify below.\n\n"));
343          for ( ;; ) {
344             if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
345                return 0;
346             }
347             if (str_to_utime(ua->cmd) != 0) {
348                break;
349             }
350             bsendmsg(ua, _("Improper date format.\n"));
351          }              
352          bstrncpy(date, ua->cmd, sizeof(date));
353          if (!select_backups_before_date(ua, ji, date)) {
354             return 0;
355          }
356
357       case 6:                         /* Cancel or quit */
358          return 0;
359       }
360    }
361
362    if (*ji->JobIds == 0) {
363       bsendmsg(ua, _("No Jobs selected.\n"));
364       return 0;
365    }
366    bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
367
368    memset(&jr, 0, sizeof(JOB_DBR));
369
370    ji->TotalFiles = 0;
371    for (p=ji->JobIds; ; ) {
372       int stat = next_jobid_from_list(&p, &JobId);
373       if (stat < 0) {
374          bsendmsg(ua, _("Invalid JobId in list.\n"));
375          return 0;
376       }
377       if (stat == 0) {
378          break;
379       }
380       jr.JobId = JobId;
381       if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
382          bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
383          return 0;
384       }
385       ji->TotalFiles += jr.JobFiles;
386    }
387    return 1;
388 }
389
390 /*
391  * This routine is used to get the current backup or a backup
392  *   before the specified date.
393  */
394 static int select_backups_before_date(UAContext *ua, JOBIDS *ji, char *date)
395 {
396    int stat = 0;
397    POOLMEM *query;
398    FILESET_DBR fsr;
399    CLIENT_DBR cr;
400    char fileset_name[MAX_NAME_LENGTH];
401    char ed1[50];
402
403    query = get_pool_memory(PM_MESSAGE);
404
405    /* Create temp tables */
406    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
407    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
408    if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
409       bsendmsg(ua, "%s\n", db_strerror(ua->db));
410    }
411    if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
412       bsendmsg(ua, "%s\n", db_strerror(ua->db));
413    }
414    /*
415     * Select Client from the Catalog
416     */
417    memset(&cr, 0, sizeof(cr));
418    if (!get_client_dbr(ua, &cr)) {
419       goto bail_out;
420    }
421    bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
422
423    /*
424     * Select FileSet 
425     */
426    Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
427    start_prompt(ua, _("The defined FileSet resources are:\n"));
428    if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
429       bsendmsg(ua, "%s\n", db_strerror(ua->db));
430    }
431    if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"), 
432                  fileset_name, sizeof(fileset_name)) < 0) {
433       goto bail_out;
434    }
435    fsr.FileSetId = atoi(fileset_name);  /* Id is first part of name */
436    if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
437       bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
438       bsendmsg(ua, _("This probably means you modified the FileSet.\n"
439                      "Continuing anyway.\n"));
440    }
441
442
443    /* Find JobId of last Full backup for this client, fileset */
444    Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSetId);
445    if (!db_sql_query(ua->db, query, NULL, NULL)) {
446       bsendmsg(ua, "%s\n", db_strerror(ua->db));
447       goto bail_out;
448    }
449
450    /* Find all Volumes used by that JobId */
451    if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
452       bsendmsg(ua, "%s\n", db_strerror(ua->db));
453       goto bail_out;
454    }
455    /* Note, this is needed as I don't seem to get the callback
456     * from the call just above.
457     */
458    ji->JobTDate = 0;
459    if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
460       bsendmsg(ua, "%s\n", db_strerror(ua->db));
461    }
462    if (ji->JobTDate == 0) {
463       bsendmsg(ua, _("No Full backup before %s found.\n"), date);
464       goto bail_out;
465    }
466
467    /* Now find all Incremental/Decremental Jobs after Full save */
468    Mmsg(&query, uar_inc_dec, edit_uint64(ji->JobTDate, ed1), date,
469         cr.ClientId, fsr.FileSetId);
470    if (!db_sql_query(ua->db, query, NULL, NULL)) {
471       bsendmsg(ua, "%s\n", db_strerror(ua->db));
472    }
473
474    /* Get the JobIds from that list */
475    ji->JobIds[0] = 0;
476    if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
477       bsendmsg(ua, "%s\n", db_strerror(ua->db));
478    }
479
480    if (ji->JobIds[0] != 0) {
481       /* Display a list of Jobs selected for this restore */
482       db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
483    } else {
484       bsendmsg(ua, _("No jobs found.\n")); 
485    }
486
487    stat = 1;
488  
489 bail_out:
490    free_pool_memory(query);
491    db_sql_query(ua->db, uar_del_temp, NULL, NULL);
492    db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
493    return stat;
494 }
495
496
497 static int next_jobid_from_list(char **p, uint32_t *JobId)
498 {
499    char jobid[30];
500    int i;
501    char *q = *p;
502
503    jobid[0] = 0;
504    for (i=0; i<(int)sizeof(jobid); i++) {
505       if (*q == ',' || *q == 0) {
506          q++;
507          break;
508       }
509       jobid[i] = *q++;
510       jobid[i+1] = 0;
511    }
512    if (jobid[0] == 0 || !is_a_number(jobid)) {
513       return 0;
514    }
515    *p = q;
516    *JobId = strtoul(jobid, NULL, 10);
517    return 1;
518 }
519
520 /*
521  * Callback handler make list of JobIds
522  */
523 static int jobid_handler(void *ctx, int num_fields, char **row)
524 {
525    JOBIDS *ji = (JOBIDS *)ctx;
526
527    if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
528       if (ji->JobIds[0] != 0) {
529          strcat(ji->JobIds, ",");
530       }
531       strcat(ji->JobIds, row[0]);
532    }
533
534    return 0;
535 }
536
537
538 /*
539  * Callback handler to pickup last Full backup JobTDate
540  */
541 static int last_full_handler(void *ctx, int num_fields, char **row)
542 {
543    JOBIDS *ji = (JOBIDS *)ctx;
544
545    ji->JobTDate = strtoll(row[1], NULL, 10);
546
547    return 0;
548 }
549
550 /*
551  * Callback handler build fileset prompt list
552  */
553 static int fileset_handler(void *ctx, int num_fields, char **row)
554 {
555    char prompt[MAX_NAME_LENGTH+200];
556
557    snprintf(prompt, sizeof(prompt), "%s  %s  %s", row[0], row[1], row[2]);
558    add_prompt((UAContext *)ctx, prompt);
559    return 0;
560 }
561
562 /*
563  * Called here with each name to be added to the list. The name is
564  *   added to the list if it is not already in the list.
565  *
566  * Used to make unique list of FileSets and MediaTypes
567  */
568 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
569 {
570    NAME_LIST *name = (NAME_LIST *)ctx;
571
572    if (name->num_ids == MAX_ID_LIST_LEN) {  
573       return 1;
574    }
575    if (name->num_ids == name->max_ids) {
576       if (name->max_ids == 0) {
577          name->max_ids = 1000;
578          name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
579       } else {
580          name->max_ids = (name->max_ids * 3) / 2;
581          name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
582       }
583    }
584    for (int i=0; i<name->num_ids; i++) {
585       if (strcmp(name->name[i], row[0]) == 0) {
586          return 0;                    /* already in list, return */
587       }
588    }
589    /* Add new name to list */
590    name->name[name->num_ids++] = bstrdup(row[0]);
591    return 0;
592 }
593
594
595 /*
596  * Print names in the list
597  */
598 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
599
600    int i;
601
602    for (i=0; i < name_list->num_ids; i++) {
603       bsendmsg(ua, "%s\n", name_list->name[i]);
604    }
605 }
606
607
608 /*
609  * Free names in the list
610  */
611 static void free_name_list(NAME_LIST *name_list)
612
613    int i;
614
615    for (i=0; i < name_list->num_ids; i++) {
616       free(name_list->name[i]);
617    }
618    if (name_list->name) {
619       free(name_list->name);
620    }
621    name_list->max_ids = 0;
622    name_list->num_ids = 0;
623 }
624
625 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
626 {
627    char name[MAX_NAME_LENGTH];
628    STORE *store = NULL;
629
630    if (name_list->num_ids > 1) {
631       bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
632          "Restore is not possible. The MediaTypes used are:\n"));
633       print_name_list(ua, name_list);
634       ji->store = select_storage_resource(ua);
635       return;
636    }
637
638    if (name_list->num_ids == 0) {
639       bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
640       ji->store = select_storage_resource(ua);
641       return;
642    }
643
644    start_prompt(ua, _("The defined Storage resources are:\n"));
645    LockRes();
646    while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
647       if (strcmp(store->media_type, name_list->name[0]) == 0) {
648          add_prompt(ua, store->hdr.name);
649       }
650    }
651    UnlockRes();
652    do_prompt(ua, _("Storage"),  _("Select Storage resource"), name, sizeof(name));
653    ji->store = (STORE *)GetResWithName(R_STORAGE, name);
654    if (!ji->store) {
655       bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
656          "MediaType %s, needed by the Jobs you selected.\n"
657          "You will be allowed to select a Storage device later.\n"),
658          name_list->name[0]); 
659    }
660 }