3 * Bacula Director -- User Agent Database restore Command
4 * Creates a bootstrap file for restoring files and
5 * starts the restore job.
7 * Tree handling routines split into ua_tree.c July MMIII.
8 * BSR (bootstrap record) handling routines split into
11 * Kern Sibbald, July MMII
17 Copyright (C) 2002-2003 Kern Sibbald and John Walker
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.
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.
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,
40 /* Imported functions */
41 extern int runcmd(UAContext *ua, char *cmd);
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;
51 /* Main structure for obtaining JobIds */
55 char ClientName[MAX_NAME_LENGTH];
56 char JobIds[200]; /* User entered string of JobIds */
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 */
68 #define MAX_ID_LIST_LEN 1000000
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);
87 int restorecmd(UAContext *ua, char *cmd)
91 JobId_t JobId, last_JobId;
97 JOB *restore_job = NULL;
100 uint32_t selected_files = 0;
104 i = find_arg_with_value(ua, "where");
113 memset(&tree, 0, sizeof(TREE_CTX));
114 memset(&name_list, 0, sizeof(name_list));
115 memset(&ji, 0, sizeof(ji));
117 /* Ensure there is at least one Restore Job */
119 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
120 if (job->JobType == JT_RESTORE) {
130 "No Restore Job Resource found. You must create at least\n"
131 "one before running this command.\n"));
136 * Request user to select JobIds by various different methods
137 * last 20 jobs, where File saved, most recent backup, ...
139 if (!user_select_jobids(ua, &ji)) {
144 * Build the directory tree containing JobIds user selected
146 tree.root = new_tree(ji.TotalFiles);
147 tree.root->fname = nofname;
149 query = get_pool_memory(PM_MESSAGE);
152 * For display purposes, the same JobId, with different volumes may
153 * appear more than once, however, we only insert it once.
156 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
158 if (JobId == last_JobId) {
159 continue; /* eliminate duplicate JobIds */
162 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
165 * Find files for this JobId and insert them in the tree
167 Mmsg(&query, uar_sel_files, JobId);
168 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
169 bsendmsg(ua, "%s", db_strerror(ua->db));
172 * Find the FileSets for this JobId and add to the name_list
174 Mmsg(&query, uar_mediatype, JobId);
175 if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
176 bsendmsg(ua, "%s", db_strerror(ua->db));
179 bsendmsg(ua, "%d item%s inserted into the tree and marked for extraction.\n",
180 items, items==1?"":"s");
181 free_pool_memory(query);
183 /* Check MediaType and select storage that corresponds */
184 get_storage_from_mediatype(ua, &name_list, &ji);
185 free_name_list(&name_list);
187 if (find_arg(ua, _("all")) < 0) {
188 /* Let the user select which files to restore */
189 user_select_files_from_tree(&tree);
193 * Walk down through the tree finding all files marked to be
194 * extracted making a bootstrap file.
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);
200 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
201 add_findex(bsr, node->JobId, node->FileIndex);
206 free_tree(tree.root); /* free the directory tree */
209 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
210 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
214 // print_bsr(ua, bsr);
215 write_bsr_file(ua, bsr);
216 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
218 bsendmsg(ua, _("No files selected to restore.\n"));
222 if (restore_jobs == 1) {
225 job = select_restore_job_resource(ua);
228 bsendmsg(ua, _("No Restore Job resource found!\n"));
232 /* If no client name specified yet, get it now */
233 if (!ji.ClientName[0]) {
235 memset(&cr, 0, sizeof(cr));
236 if (!get_client_dbr(ua, &cr)) {
239 bstrncpy(ji.ClientName, cr.Name, sizeof(ji.ClientName));
242 /* Build run command */
245 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
247 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
248 working_directory, where);
251 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
252 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
255 Dmsg1(400, "Submitting: %s\n", ua->cmd);
260 bsendmsg(ua, _("Restore command done.\n"));
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.
269 static int user_select_jobids(UAContext *ua, JOBIDS *ji)
272 char date[MAX_TIME_LENGTH];
279 "List last 20 Jobs run",
280 "List Jobs where a given File is saved",
281 "Enter list of JobIds to select",
282 "Enter SQL list command",
283 "Select the most recent backup for a client",
284 "Select backup for a client before a specified time",
295 switch (find_arg_keyword(ua, kw)) {
297 i = find_arg_with_value(ua, _("jobid"));
301 bstrncpy(ji->JobIds, ua->argv[i], sizeof(ji->JobIds));
304 case 1: /* current */
305 bstrutime(date, sizeof(date), time(NULL));
306 if (!select_backups_before_date(ua, ji, date)) {
312 i = find_arg_with_value(ua, _("before"));
316 if (str_to_utime(ua->argv[i]) == 0) {
317 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
320 bstrncpy(date, ua->argv[i], sizeof(date));
321 if (!select_backups_before_date(ua, ji, date)) {
331 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
332 "to be restored. You will be presented several methods\n"
333 "of specifying the JobIds. Then you will be allowed to\n"
334 "select which files from those JobIds are to be restored.\n\n"));
337 /* If choice not already made above, prompt */
339 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
340 for (int i=0; list[i]; i++) {
341 add_prompt(ua, list[i]);
344 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
347 case 0: /* list last 20 Jobs run */
348 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
351 case 1: /* list where a file is saved */
354 if (!get_cmd(ua, _("Enter Filename: "))) {
357 len = strlen(ua->cmd);
358 fname = (char *)malloc(len * 2 + 1);
359 db_escape_string(fname, ua->cmd, len);
360 query = get_pool_memory(PM_MESSAGE);
361 Mmsg(&query, uar_file, fname);
363 db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, HORZ_LIST);
364 free_pool_memory(query);
367 case 2: /* enter a list of JobIds */
368 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
371 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
373 case 3: /* Enter an SQL list command */
374 if (!get_cmd(ua, _("Enter SQL list command: "))) {
377 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
380 case 4: /* Select the most recent backups */
381 bstrutime(date, sizeof(date), time(NULL));
382 if (!select_backups_before_date(ua, ji, date)) {
386 case 5: /* select backup at specified time */
387 bsendmsg(ua, _("The restored files will the most current backup\n"
388 "BEFORE the date you specify below.\n\n"));
390 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
393 if (str_to_utime(ua->cmd) != 0) {
396 bsendmsg(ua, _("Improper date format.\n"));
398 bstrncpy(date, ua->cmd, sizeof(date));
399 if (!select_backups_before_date(ua, ji, date)) {
403 case 6: /* Cancel or quit */
408 if (*ji->JobIds == 0) {
409 bsendmsg(ua, _("No Jobs selected.\n"));
412 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
414 memset(&jr, 0, sizeof(JOB_DBR));
417 for (p=ji->JobIds; ; ) {
418 int stat = next_jobid_from_list(&p, &JobId);
420 bsendmsg(ua, _("Invalid JobId in list.\n"));
427 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
428 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
431 ji->TotalFiles += jr.JobFiles;
437 * This routine is used to get the current backup or a backup
438 * before the specified date.
440 static int select_backups_before_date(UAContext *ua, JOBIDS *ji, char *date)
446 char fileset_name[MAX_NAME_LENGTH];
449 query = get_pool_memory(PM_MESSAGE);
451 /* Create temp tables */
452 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
453 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
454 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
455 bsendmsg(ua, "%s\n", db_strerror(ua->db));
457 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
458 bsendmsg(ua, "%s\n", db_strerror(ua->db));
461 * Select Client from the Catalog
463 memset(&cr, 0, sizeof(cr));
464 if (!get_client_dbr(ua, &cr)) {
467 bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
472 Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
473 start_prompt(ua, _("The defined FileSet resources are:\n"));
474 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
475 bsendmsg(ua, "%s\n", db_strerror(ua->db));
477 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
478 fileset_name, sizeof(fileset_name)) < 0) {
481 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
482 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
483 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
484 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
485 "Continuing anyway.\n"));
489 /* Find JobId of last Full backup for this client, fileset */
490 Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSetId);
491 if (!db_sql_query(ua->db, query, NULL, NULL)) {
492 bsendmsg(ua, "%s\n", db_strerror(ua->db));
496 /* Find all Volumes used by that JobId */
497 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
498 bsendmsg(ua, "%s\n", db_strerror(ua->db));
501 /* Note, this is needed as I don't seem to get the callback
502 * from the call just above.
505 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
506 bsendmsg(ua, "%s\n", db_strerror(ua->db));
508 if (ji->JobTDate == 0) {
509 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
513 /* Now find all Incremental/Decremental Jobs after Full save */
514 Mmsg(&query, uar_inc_dec, edit_uint64(ji->JobTDate, ed1), date,
515 cr.ClientId, fsr.FileSetId);
516 if (!db_sql_query(ua->db, query, NULL, NULL)) {
517 bsendmsg(ua, "%s\n", db_strerror(ua->db));
520 /* Get the JobIds from that list */
522 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
523 bsendmsg(ua, "%s\n", db_strerror(ua->db));
526 if (ji->JobIds[0] != 0) {
527 /* Display a list of Jobs selected for this restore */
528 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
530 bsendmsg(ua, _("No jobs found.\n"));
536 free_pool_memory(query);
537 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
538 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
542 /* Return next JobId from comma separated list */
543 static int next_jobid_from_list(char **p, uint32_t *JobId)
549 for (int i=0; i<(int)sizeof(jobid); i++) {
550 if (*q == ',' || *q == 0) {
557 if (jobid[0] == 0 || !is_a_number(jobid)) {
561 *JobId = strtoul(jobid, NULL, 10);
566 * Callback handler make list of JobIds
568 static int jobid_handler(void *ctx, int num_fields, char **row)
570 JOBIDS *ji = (JOBIDS *)ctx;
572 /* Concatenate a JobId if it does not exceed array size */
573 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
574 if (ji->JobIds[0] != 0) {
575 strcat(ji->JobIds, ",");
577 strcat(ji->JobIds, row[0]);
584 * Callback handler to pickup last Full backup JobTDate
586 static int last_full_handler(void *ctx, int num_fields, char **row)
588 JOBIDS *ji = (JOBIDS *)ctx;
590 ji->JobTDate = strtoll(row[1], NULL, 10);
596 * Callback handler build fileset prompt list
598 static int fileset_handler(void *ctx, int num_fields, char **row)
600 char prompt[MAX_NAME_LENGTH+200];
602 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
603 add_prompt((UAContext *)ctx, prompt);
608 * Called here with each name to be added to the list. The name is
609 * added to the list if it is not already in the list.
611 * Used to make unique list of FileSets and MediaTypes
613 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
615 NAME_LIST *name = (NAME_LIST *)ctx;
617 if (name->num_ids == MAX_ID_LIST_LEN) {
620 if (name->num_ids == name->max_ids) {
621 if (name->max_ids == 0) {
622 name->max_ids = 1000;
623 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
625 name->max_ids = (name->max_ids * 3) / 2;
626 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
629 for (int i=0; i<name->num_ids; i++) {
630 if (strcmp(name->name[i], row[0]) == 0) {
631 return 0; /* already in list, return */
634 /* Add new name to list */
635 name->name[name->num_ids++] = bstrdup(row[0]);
641 * Print names in the list
643 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
645 for (int i=0; i < name_list->num_ids; i++) {
646 bsendmsg(ua, "%s\n", name_list->name[i]);
652 * Free names in the list
654 static void free_name_list(NAME_LIST *name_list)
656 for (int i=0; i < name_list->num_ids; i++) {
657 free(name_list->name[i]);
659 if (name_list->name) {
660 free(name_list->name);
662 name_list->max_ids = 0;
663 name_list->num_ids = 0;
666 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
668 char name[MAX_NAME_LENGTH];
671 if (name_list->num_ids > 1) {
672 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
673 "Restore is not possible. The MediaTypes used are:\n"));
674 print_name_list(ua, name_list);
675 ji->store = select_storage_resource(ua);
679 if (name_list->num_ids == 0) {
680 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
681 ji->store = select_storage_resource(ua);
685 start_prompt(ua, _("The defined Storage resources are:\n"));
687 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
688 if (strcmp(store->media_type, name_list->name[0]) == 0) {
689 add_prompt(ua, store->hdr.name);
693 do_prompt(ua, _("Storage"), _("Select Storage resource"), name, sizeof(name));
694 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
696 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
697 "MediaType %s, needed by the Jobs you selected.\n"
698 "You will be allowed to select a Storage device later.\n"),