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];
57 char JobIds[200]; /* User entered string of JobIds */
62 char **name; /* list of names */
63 int num_ids; /* ids stored */
64 int max_ids; /* size of array */
65 int num_del; /* number deleted */
66 int tot_ids; /* total to process */
69 #define MAX_ID_LIST_LEN 1000000
72 /* Forward referenced functions */
73 static int last_full_handler(void *ctx, int num_fields, char **row);
74 static int jobid_handler(void *ctx, int num_fields, char **row);
75 static int next_jobid_from_list(char **p, uint32_t *JobId);
76 static int user_select_jobids(UAContext *ua, JOBIDS *ji);
77 static int fileset_handler(void *ctx, int num_fields, char **row);
78 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
79 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
80 static void free_name_list(NAME_LIST *name_list);
81 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji);
82 static int select_backups_before_date(UAContext *ua, JOBIDS *ji, char *date);
88 int restorecmd(UAContext *ua, char *cmd)
92 JobId_t JobId, last_JobId;
98 JOB *restore_job = NULL;
101 uint32_t selected_files = 0;
105 i = find_arg_with_value(ua, "where");
114 memset(&tree, 0, sizeof(TREE_CTX));
115 memset(&name_list, 0, sizeof(name_list));
116 memset(&ji, 0, sizeof(ji));
118 /* Ensure there is at least one Restore Job */
120 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
121 if (job->JobType == JT_RESTORE) {
131 "No Restore Job Resource found. You must create at least\n"
132 "one before running this command.\n"));
137 * Request user to select JobIds by various different methods
138 * last 20 jobs, where File saved, most recent backup, ...
140 if (!user_select_jobids(ua, &ji)) {
145 * Build the directory tree containing JobIds user selected
147 tree.root = new_tree(ji.TotalFiles);
148 tree.root->fname = nofname;
150 query = get_pool_memory(PM_MESSAGE);
153 * For display purposes, the same JobId, with different volumes may
154 * appear more than once, however, we only insert it once.
157 for (p=ji.JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
159 if (JobId == last_JobId) {
160 continue; /* eliminate duplicate JobIds */
163 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
166 * Find files for this JobId and insert them in the tree
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));
173 * Find the FileSets for this JobId and add to the name_list
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));
180 bsendmsg(ua, "%d Job%s inserted into the tree and marked for extraction.\n",
181 items, items==1?"":"s");
182 free_pool_memory(query);
184 /* Check MediaType and select storage that corresponds */
185 get_storage_from_mediatype(ua, &name_list, &ji);
186 free_name_list(&name_list);
188 if (find_arg(ua, _("all")) < 0) {
189 /* Let the user select which files to restore */
190 user_select_files_from_tree(&tree);
194 * Walk down through the tree finding all files marked to be
195 * extracted making a bootstrap file.
198 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
199 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
201 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
202 add_findex(bsr, node->JobId, node->FileIndex);
207 free_tree(tree.root); /* free the directory tree */
210 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
211 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
215 // print_bsr(ua, bsr);
216 write_bsr_file(ua, bsr);
217 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
219 bsendmsg(ua, _("No files selected to restore.\n"));
223 if (restore_jobs == 1) {
226 job = select_restore_job_resource(ua);
229 bsendmsg(ua, _("No Restore Job resource found!\n"));
233 /* If no client name specified yet, get it now */
234 if (!ji.ClientName[0]) {
236 memset(&cr, 0, sizeof(cr));
237 if (!get_client_dbr(ua, &cr)) {
240 bstrncpy(ji.ClientName, cr.Name, sizeof(ji.ClientName));
243 /* Build run command */
246 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
248 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
249 working_directory, where);
252 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
253 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
257 Dmsg1(400, "Submitting: %s\n", ua->cmd);
261 bsendmsg(ua, _("Restore command done.\n"));
266 * The first step in the restore process is for the user to
267 * select a list of JobIds from which he will subsequently
268 * select which files are to be restored.
270 static int user_select_jobids(UAContext *ua, JOBIDS *ji)
273 char date[MAX_TIME_LENGTH];
280 "List last 20 Jobs run",
281 "List Jobs where a given File is saved",
282 "Enter list of JobIds to select",
283 "Enter SQL list command",
284 "Select the most recent backup for a client",
285 "Select backup for a client before a specified time",
296 switch (find_arg_keyword(ua, kw)) {
298 i = find_arg_with_value(ua, _("jobid"));
302 bstrncpy(ji->JobIds, ua->argv[i], sizeof(ji->JobIds));
305 case 1: /* current */
306 bstrutime(date, sizeof(date), time(NULL));
307 if (!select_backups_before_date(ua, ji, date)) {
313 i = find_arg_with_value(ua, _("before"));
317 if (str_to_utime(ua->argv[i]) == 0) {
318 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
321 bstrncpy(date, ua->argv[i], sizeof(date));
322 if (!select_backups_before_date(ua, ji, date)) {
332 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
333 "to be restored. You will be presented several methods\n"
334 "of specifying the JobIds. Then you will be allowed to\n"
335 "select which files from those JobIds are to be restored.\n\n"));
338 /* If choice not already made above, prompt */
340 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
341 for (int i=0; list[i]; i++) {
342 add_prompt(ua, list[i]);
345 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
348 case 0: /* list last 20 Jobs run */
349 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
352 case 1: /* list where a file is saved */
355 if (!get_cmd(ua, _("Enter Filename: "))) {
358 len = strlen(ua->cmd);
359 fname = (char *)malloc(len * 2 + 1);
360 db_escape_string(fname, ua->cmd, len);
361 query = get_pool_memory(PM_MESSAGE);
362 Mmsg(&query, uar_file, fname);
364 db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, HORZ_LIST);
365 free_pool_memory(query);
368 case 2: /* enter a list of JobIds */
369 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
372 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
374 case 3: /* Enter an SQL list command */
375 if (!get_cmd(ua, _("Enter SQL list command: "))) {
378 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
381 case 4: /* Select the most recent backups */
382 bstrutime(date, sizeof(date), time(NULL));
383 if (!select_backups_before_date(ua, ji, date)) {
387 case 5: /* select backup at specified time */
388 bsendmsg(ua, _("The restored files will the most current backup\n"
389 "BEFORE the date you specify below.\n\n"));
391 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
394 if (str_to_utime(ua->cmd) != 0) {
397 bsendmsg(ua, _("Improper date format.\n"));
399 bstrncpy(date, ua->cmd, sizeof(date));
400 if (!select_backups_before_date(ua, ji, date)) {
404 case 6: /* Cancel or quit */
409 if (*ji->JobIds == 0) {
410 bsendmsg(ua, _("No Jobs selected.\n"));
413 bsendmsg(ua, _("You have selected the following JobId%s: %s\n"),
414 strchr(ji->JobIds,',')?"s":"",ji->JobIds);
416 memset(&jr, 0, sizeof(JOB_DBR));
419 for (p=ji->JobIds; ; ) {
420 int stat = next_jobid_from_list(&p, &JobId);
422 bsendmsg(ua, _("Invalid JobId in list.\n"));
428 if (jr.JobId == JobId) {
429 continue; /* duplicate of last JobId */
432 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
433 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
436 ji->TotalFiles += jr.JobFiles;
442 * This routine is used to get the current backup or a backup
443 * before the specified date.
445 static int select_backups_before_date(UAContext *ua, JOBIDS *ji, char *date)
451 char fileset_name[MAX_NAME_LENGTH];
454 query = get_pool_memory(PM_MESSAGE);
456 /* Create temp tables */
457 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
458 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
459 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
460 bsendmsg(ua, "%s\n", db_strerror(ua->db));
462 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
463 bsendmsg(ua, "%s\n", db_strerror(ua->db));
466 * Select Client from the Catalog
468 memset(&cr, 0, sizeof(cr));
469 if (!get_client_dbr(ua, &cr)) {
472 bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
477 Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
478 start_prompt(ua, _("The defined FileSet resources are:\n"));
479 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
480 bsendmsg(ua, "%s\n", db_strerror(ua->db));
482 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
483 fileset_name, sizeof(fileset_name)) < 0) {
486 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
487 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
488 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
489 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
490 "Continuing anyway.\n"));
494 /* Find JobId of last Full backup for this client, fileset */
495 Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSetId);
496 if (!db_sql_query(ua->db, query, NULL, NULL)) {
497 bsendmsg(ua, "%s\n", db_strerror(ua->db));
501 /* Find all Volumes used by that JobId */
502 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
503 bsendmsg(ua, "%s\n", db_strerror(ua->db));
506 /* Note, this is needed as I don't seem to get the callback
507 * from the call just above.
510 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
511 bsendmsg(ua, "%s\n", db_strerror(ua->db));
513 if (ji->JobTDate == 0) {
514 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
518 /* Now find all Incremental/Decremental Jobs after Full save */
519 Mmsg(&query, uar_inc_dec, edit_uint64(ji->JobTDate, ed1), date,
520 cr.ClientId, fsr.FileSetId);
521 if (!db_sql_query(ua->db, query, NULL, NULL)) {
522 bsendmsg(ua, "%s\n", db_strerror(ua->db));
525 /* Get the JobIds from that list */
527 ji->last_jobid[0] = 0;
528 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
529 bsendmsg(ua, "%s\n", db_strerror(ua->db));
532 if (ji->JobIds[0] != 0) {
533 /* Display a list of Jobs selected for this restore */
534 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
536 bsendmsg(ua, _("No jobs found.\n"));
542 free_pool_memory(query);
543 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
544 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
548 /* Return next JobId from comma separated list */
549 static int next_jobid_from_list(char **p, uint32_t *JobId)
555 for (int i=0; i<(int)sizeof(jobid); i++) {
556 if (*q == ',' || *q == 0) {
563 if (jobid[0] == 0 || !is_a_number(jobid)) {
567 *JobId = strtoul(jobid, NULL, 10);
572 * Callback handler make list of JobIds
574 static int jobid_handler(void *ctx, int num_fields, char **row)
576 JOBIDS *ji = (JOBIDS *)ctx;
578 if (strcmp(ji->last_jobid, row[0]) == 0) {
579 return 0; /* duplicate id */
581 bstrncpy(ji->last_jobid, row[0], sizeof(ji->last_jobid));
582 /* Concatenate a JobId if it does not exceed array size */
583 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
584 if (ji->JobIds[0] != 0) {
585 strcat(ji->JobIds, ",");
587 strcat(ji->JobIds, row[0]);
594 * Callback handler to pickup last Full backup JobTDate
596 static int last_full_handler(void *ctx, int num_fields, char **row)
598 JOBIDS *ji = (JOBIDS *)ctx;
600 ji->JobTDate = strtoll(row[1], NULL, 10);
606 * Callback handler build fileset prompt list
608 static int fileset_handler(void *ctx, int num_fields, char **row)
610 char prompt[MAX_NAME_LENGTH+200];
612 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
613 add_prompt((UAContext *)ctx, prompt);
618 * Called here with each name to be added to the list. The name is
619 * added to the list if it is not already in the list.
621 * Used to make unique list of FileSets and MediaTypes
623 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
625 NAME_LIST *name = (NAME_LIST *)ctx;
627 if (name->num_ids == MAX_ID_LIST_LEN) {
630 if (name->num_ids == name->max_ids) {
631 if (name->max_ids == 0) {
632 name->max_ids = 1000;
633 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
635 name->max_ids = (name->max_ids * 3) / 2;
636 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
639 for (int i=0; i<name->num_ids; i++) {
640 if (strcmp(name->name[i], row[0]) == 0) {
641 return 0; /* already in list, return */
644 /* Add new name to list */
645 name->name[name->num_ids++] = bstrdup(row[0]);
651 * Print names in the list
653 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
655 for (int i=0; i < name_list->num_ids; i++) {
656 bsendmsg(ua, "%s\n", name_list->name[i]);
662 * Free names in the list
664 static void free_name_list(NAME_LIST *name_list)
666 for (int i=0; i < name_list->num_ids; i++) {
667 free(name_list->name[i]);
669 if (name_list->name) {
670 free(name_list->name);
672 name_list->max_ids = 0;
673 name_list->num_ids = 0;
676 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
678 char name[MAX_NAME_LENGTH];
681 if (name_list->num_ids > 1) {
682 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
683 "Restore is not possible. The MediaTypes used are:\n"));
684 print_name_list(ua, name_list);
685 ji->store = select_storage_resource(ua);
689 if (name_list->num_ids == 0) {
690 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
691 ji->store = select_storage_resource(ua);
695 start_prompt(ua, _("The defined Storage resources are:\n"));
697 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
698 if (strcmp(store->media_type, name_list->name[0]) == 0) {
699 add_prompt(ua, store->hdr.name);
703 do_prompt(ua, _("Storage"), _("Select Storage resource"), name, sizeof(name));
704 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
706 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
707 "MediaType %s, needed by the Jobs you selected.\n"
708 "You will be allowed to select a Storage device later.\n"),