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 or Files to be restored */
55 char ClientName[MAX_NAME_LENGTH];
57 char JobIds[200]; /* User entered string of JobIds */
61 uint32_t selected_files;
67 char **name; /* list of names */
68 int num_ids; /* ids stored */
69 int max_ids; /* size of array */
70 int num_del; /* number deleted */
71 int tot_ids; /* total to process */
74 #define MAX_ID_LIST_LEN 1000000
77 /* Forward referenced functions */
78 static int last_full_handler(void *ctx, int num_fields, char **row);
79 static int jobid_handler(void *ctx, int num_fields, char **row);
80 static int next_jobid_from_list(char **p, uint32_t *JobId);
81 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx);
82 static int fileset_handler(void *ctx, int num_fields, char **row);
83 static void print_name_list(UAContext *ua, NAME_LIST *name_list);
84 static int unique_name_list_handler(void *ctx, int num_fields, char **row);
85 static void free_name_list(NAME_LIST *name_list);
86 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx);
87 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date);
88 static void build_directory_tree(UAContext *ua, RESTORE_CTX *rx);
94 int restorecmd(UAContext *ua, char *cmd)
96 RESTORE_CTX rx; /* restore context */
100 memset(&rx, 0, sizeof(rx));
102 i = find_arg_with_value(ua, "where");
104 rx.where = ua->argv[i];
111 /* Ensure there is at least one Restore Job */
113 while ( (job = (JOB *)GetNextRes(R_JOB, (RES *)job)) ) {
114 if (job->JobType == JT_RESTORE) {
115 if (!rx.restore_job) {
116 rx.restore_job = job;
122 if (!rx.restore_jobs) {
124 "No Restore Job Resource found. You must create at least\n"
125 "one before running this command.\n"));
131 * Request user to select JobIds or files by various different methods
132 * last 20 jobs, where File saved, most recent backup, ...
133 * In the end, a list of files are pumped into
136 switch (user_select_jobids_or_files(ua, &rx)) {
139 return 0; /* error */
140 case 1: /* select by jobid */
141 build_directory_tree(ua, &rx);
148 if (!complete_bsr(ua, rx.bsr)) { /* find Vol, SessId, SessTime from JobIds */
149 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
153 // print_bsr(ua, rx.bsr);
154 write_bsr_file(ua, rx.bsr);
155 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), rx.selected_files);
157 bsendmsg(ua, _("No files selected to restore.\n"));
161 if (rx.restore_jobs == 1) {
162 job = rx.restore_job;
164 job = select_restore_job_resource(ua);
167 bsendmsg(ua, _("No Restore Job resource found!\n"));
171 /* If no client name specified yet, get it now */
172 if (!rx.ClientName[0]) {
174 memset(&cr, 0, sizeof(cr));
175 if (!get_client_dbr(ua, &cr)) {
178 bstrncpy(rx.ClientName, cr.Name, sizeof(rx.ClientName));
181 /* Build run command */
184 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
186 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
187 working_directory, rx.where);
190 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
191 job->hdr.name, rx.ClientName, rx.store?rx.store->hdr.name:"",
195 Dmsg1(400, "Submitting: %s\n", ua->cmd);
199 bsendmsg(ua, _("Restore command done.\n"));
204 * The first step in the restore process is for the user to
205 * select a list of JobIds from which he will subsequently
206 * select which files are to be restored.
208 static int user_select_jobids_or_files(UAContext *ua, RESTORE_CTX *rx)
211 char date[MAX_TIME_LENGTH];
218 "List last 20 Jobs run",
219 "List Jobs where a given File is saved",
220 "Enter list of JobIds to select",
221 "Enter SQL list command",
222 "Select the most recent backup for a client",
223 "Select backup for a client before a specified time",
224 "Enter a list of files to restore",
235 switch (find_arg_keyword(ua, kw)) {
237 i = find_arg_with_value(ua, _("jobid"));
241 bstrncpy(rx->JobIds, ua->argv[i], sizeof(rx->JobIds));
244 case 1: /* current */
245 bstrutime(date, sizeof(date), time(NULL));
246 if (!select_backups_before_date(ua, rx, date)) {
252 i = find_arg_with_value(ua, _("before"));
256 if (str_to_utime(ua->argv[i]) == 0) {
257 bsendmsg(ua, _("Improper date format: %s\n"), ua->argv[i]);
260 bstrncpy(date, ua->argv[i], sizeof(date));
261 if (!select_backups_before_date(ua, rx, date)) {
271 bsendmsg(ua, _("\nFirst you select one or more JobIds that contain files\n"
272 "to be restored. You will be presented several methods\n"
273 "of specifying the JobIds. Then you will be allowed to\n"
274 "select which files from those JobIds are to be restored.\n\n"));
277 /* If choice not already made above, prompt */
279 start_prompt(ua, _("To select the JobIds, you have the following choices:\n"));
280 for (int i=0; list[i]; i++) {
281 add_prompt(ua, list[i]);
284 switch (do_prompt(ua, "", _("Select item: "), NULL, 0)) {
287 case 0: /* list last 20 Jobs run */
288 db_list_sql_query(ua->jcr, ua->db, uar_list_jobs, prtit, ua, 1, HORZ_LIST);
291 case 1: /* list where a file is saved */
294 if (!get_cmd(ua, _("Enter Filename: "))) {
297 len = strlen(ua->cmd);
298 fname = (char *)malloc(len * 2 + 1);
299 db_escape_string(fname, ua->cmd, len);
300 query = get_pool_memory(PM_MESSAGE);
301 Mmsg(&query, uar_file, fname);
303 db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, HORZ_LIST);
304 free_pool_memory(query);
307 case 2: /* enter a list of JobIds */
308 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
311 bstrncpy(rx->JobIds, ua->cmd, sizeof(rx->JobIds));
313 case 3: /* Enter an SQL list command */
314 if (!get_cmd(ua, _("Enter SQL list command: "))) {
317 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
320 case 4: /* Select the most recent backups */
321 bstrutime(date, sizeof(date), time(NULL));
322 if (!select_backups_before_date(ua, rx, date)) {
326 case 5: /* select backup at specified time */
327 bsendmsg(ua, _("The restored files will the most current backup\n"
328 "BEFORE the date you specify below.\n\n"));
330 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
333 if (str_to_utime(ua->cmd) != 0) {
336 bsendmsg(ua, _("Improper date format.\n"));
338 bstrncpy(date, ua->cmd, sizeof(date));
339 if (!select_backups_before_date(ua, rx, date)) {
343 case 6: /* Enter files */
344 bsendmsg(ua, "Not yet implemented\n");
347 case 7: /* Cancel or quit */
352 if (*rx->JobIds == 0) {
353 bsendmsg(ua, _("No Jobs selected.\n"));
356 bsendmsg(ua, _("You have selected the following JobId%s: %s\n"),
357 strchr(rx->JobIds,',')?"s":"",rx->JobIds);
359 memset(&jr, 0, sizeof(JOB_DBR));
362 for (p=rx->JobIds; ; ) {
363 int stat = next_jobid_from_list(&p, &JobId);
365 bsendmsg(ua, _("Invalid JobId in list.\n"));
371 if (jr.JobId == JobId) {
372 continue; /* duplicate of last JobId */
375 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
376 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
379 rx->TotalFiles += jr.JobFiles;
384 static void build_directory_tree(UAContext *ua, RESTORE_CTX *rx)
388 JobId_t JobId, last_JobId;
393 memset(&tree, 0, sizeof(TREE_CTX));
394 memset(&name_list, 0, sizeof(name_list));
396 * Build the directory tree containing JobIds user selected
398 tree.root = new_tree(rx->TotalFiles);
399 tree.root->fname = nofname;
401 query = get_pool_memory(PM_MESSAGE);
404 * For display purposes, the same JobId, with different volumes may
405 * appear more than once, however, we only insert it once.
408 for (p=rx->JobIds; next_jobid_from_list(&p, &JobId) > 0; ) {
410 if (JobId == last_JobId) {
411 continue; /* eliminate duplicate JobIds */
414 bsendmsg(ua, _("Building directory tree for JobId %u ...\n"), JobId);
417 * Find files for this JobId and insert them in the tree
419 Mmsg(&query, uar_sel_files, JobId);
420 if (!db_sql_query(ua->db, query, insert_tree_handler, (void *)&tree)) {
421 bsendmsg(ua, "%s", db_strerror(ua->db));
424 * Find the FileSets for this JobId and add to the name_list
426 Mmsg(&query, uar_mediatype, JobId);
427 if (!db_sql_query(ua->db, query, unique_name_list_handler, (void *)&name_list)) {
428 bsendmsg(ua, "%s", db_strerror(ua->db));
431 bsendmsg(ua, "%d Job%s inserted into the tree and marked for extraction.\n",
432 items, items==1?"":"s");
433 free_pool_memory(query);
435 /* Check MediaType and select storage that corresponds */
436 get_storage_from_mediatype(ua, &name_list, rx);
437 free_name_list(&name_list);
439 if (find_arg(ua, _("all")) < 0) {
440 /* Let the user select which files to restore */
441 user_select_files_from_tree(&tree);
445 * Walk down through the tree finding all files marked to be
446 * extracted making a bootstrap file.
448 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
449 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
451 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
452 add_findex(rx->bsr, node->JobId, node->FileIndex);
453 rx->selected_files++;
457 free_tree(tree.root); /* free the directory tree */
462 * This routine is used to get the current backup or a backup
463 * before the specified date.
465 static int select_backups_before_date(UAContext *ua, RESTORE_CTX *rx, char *date)
471 char fileset_name[MAX_NAME_LENGTH];
474 query = get_pool_memory(PM_MESSAGE);
476 /* Create temp tables */
477 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
478 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
479 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
480 bsendmsg(ua, "%s\n", db_strerror(ua->db));
482 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
483 bsendmsg(ua, "%s\n", db_strerror(ua->db));
486 * Select Client from the Catalog
488 memset(&cr, 0, sizeof(cr));
489 if (!get_client_dbr(ua, &cr)) {
492 bstrncpy(rx->ClientName, cr.Name, sizeof(rx->ClientName));
497 Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
498 start_prompt(ua, _("The defined FileSet resources are:\n"));
499 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
500 bsendmsg(ua, "%s\n", db_strerror(ua->db));
502 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
503 fileset_name, sizeof(fileset_name)) < 0) {
506 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
507 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
508 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
509 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
510 "Continuing anyway.\n"));
514 /* Find JobId of last Full backup for this client, fileset */
515 Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSetId);
516 if (!db_sql_query(ua->db, query, NULL, NULL)) {
517 bsendmsg(ua, "%s\n", db_strerror(ua->db));
521 /* Find all Volumes used by that JobId */
522 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
523 bsendmsg(ua, "%s\n", db_strerror(ua->db));
526 /* Note, this is needed as I don't seem to get the callback
527 * from the call just above.
530 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)rx)) {
531 bsendmsg(ua, "%s\n", db_strerror(ua->db));
533 if (rx->JobTDate == 0) {
534 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
538 /* Now find all Incremental/Decremental Jobs after Full save */
539 Mmsg(&query, uar_inc_dec, edit_uint64(rx->JobTDate, ed1), date,
540 cr.ClientId, fsr.FileSetId);
541 if (!db_sql_query(ua->db, query, NULL, NULL)) {
542 bsendmsg(ua, "%s\n", db_strerror(ua->db));
545 /* Get the JobIds from that list */
547 rx->last_jobid[0] = 0;
548 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)rx)) {
549 bsendmsg(ua, "%s\n", db_strerror(ua->db));
552 if (rx->JobIds[0] != 0) {
553 /* Display a list of Jobs selected for this restore */
554 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
556 bsendmsg(ua, _("No jobs found.\n"));
562 free_pool_memory(query);
563 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
564 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
568 /* Return next JobId from comma separated list */
569 static int next_jobid_from_list(char **p, uint32_t *JobId)
575 for (int i=0; i<(int)sizeof(jobid); i++) {
576 if (*q == ',' || *q == 0) {
583 if (jobid[0] == 0 || !is_a_number(jobid)) {
587 *JobId = strtoul(jobid, NULL, 10);
592 * Callback handler make list of JobIds
594 static int jobid_handler(void *ctx, int num_fields, char **row)
596 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
598 if (strcmp(rx->last_jobid, row[0]) == 0) {
599 return 0; /* duplicate id */
601 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
602 /* Concatenate a JobId if it does not exceed array size */
603 if (strlen(rx->JobIds)+strlen(row[0])+2 < sizeof(rx->JobIds)) {
604 if (rx->JobIds[0] != 0) {
605 strcat(rx->JobIds, ",");
607 strcat(rx->JobIds, row[0]);
614 * Callback handler to pickup last Full backup JobTDate
616 static int last_full_handler(void *ctx, int num_fields, char **row)
618 RESTORE_CTX *rx = (RESTORE_CTX *)ctx;
620 rx->JobTDate = strtoll(row[1], NULL, 10);
626 * Callback handler build fileset prompt list
628 static int fileset_handler(void *ctx, int num_fields, char **row)
630 char prompt[MAX_NAME_LENGTH+200];
632 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
633 add_prompt((UAContext *)ctx, prompt);
638 * Called here with each name to be added to the list. The name is
639 * added to the list if it is not already in the list.
641 * Used to make unique list of FileSets and MediaTypes
643 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
645 NAME_LIST *name = (NAME_LIST *)ctx;
647 if (name->num_ids == MAX_ID_LIST_LEN) {
650 if (name->num_ids == name->max_ids) {
651 if (name->max_ids == 0) {
652 name->max_ids = 1000;
653 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
655 name->max_ids = (name->max_ids * 3) / 2;
656 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
659 for (int i=0; i<name->num_ids; i++) {
660 if (strcmp(name->name[i], row[0]) == 0) {
661 return 0; /* already in list, return */
664 /* Add new name to list */
665 name->name[name->num_ids++] = bstrdup(row[0]);
671 * Print names in the list
673 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
675 for (int i=0; i < name_list->num_ids; i++) {
676 bsendmsg(ua, "%s\n", name_list->name[i]);
682 * Free names in the list
684 static void free_name_list(NAME_LIST *name_list)
686 for (int i=0; i < name_list->num_ids; i++) {
687 free(name_list->name[i]);
689 if (name_list->name) {
690 free(name_list->name);
692 name_list->max_ids = 0;
693 name_list->num_ids = 0;
696 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, RESTORE_CTX *rx)
698 char name[MAX_NAME_LENGTH];
701 if (name_list->num_ids > 1) {
702 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
703 "Restore is not possible. The MediaTypes used are:\n"));
704 print_name_list(ua, name_list);
705 rx->store = select_storage_resource(ua);
709 if (name_list->num_ids == 0) {
710 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
711 rx->store = select_storage_resource(ua);
715 start_prompt(ua, _("The defined Storage resources are:\n"));
717 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
718 if (strcmp(store->media_type, name_list->name[0]) == 0) {
719 add_prompt(ua, store->hdr.name);
723 do_prompt(ua, _("Storage"), _("Select Storage resource"), name, sizeof(name));
724 rx->store = (STORE *)GetResWithName(R_STORAGE, name);
726 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
727 "MediaType %s, needed by the Jobs you selected.\n"
728 "You will be allowed to select a Storage device later.\n"),