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\n"), ji->JobIds);
415 memset(&jr, 0, sizeof(JOB_DBR));
418 for (p=ji->JobIds; ; ) {
419 int stat = next_jobid_from_list(&p, &JobId);
421 bsendmsg(ua, _("Invalid JobId in list.\n"));
427 if (jr.JobId == JobId) {
428 continue; /* duplicate of last JobId */
431 if (!db_get_job_record(ua->jcr, ua->db, &jr)) {
432 bsendmsg(ua, _("Unable to get Job record. ERR=%s\n"), db_strerror(ua->db));
435 ji->TotalFiles += jr.JobFiles;
441 * This routine is used to get the current backup or a backup
442 * before the specified date.
444 static int select_backups_before_date(UAContext *ua, JOBIDS *ji, char *date)
450 char fileset_name[MAX_NAME_LENGTH];
453 query = get_pool_memory(PM_MESSAGE);
455 /* Create temp tables */
456 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
457 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
458 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
459 bsendmsg(ua, "%s\n", db_strerror(ua->db));
461 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
462 bsendmsg(ua, "%s\n", db_strerror(ua->db));
465 * Select Client from the Catalog
467 memset(&cr, 0, sizeof(cr));
468 if (!get_client_dbr(ua, &cr)) {
471 bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
476 Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
477 start_prompt(ua, _("The defined FileSet resources are:\n"));
478 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
479 bsendmsg(ua, "%s\n", db_strerror(ua->db));
481 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
482 fileset_name, sizeof(fileset_name)) < 0) {
485 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
486 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
487 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
488 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
489 "Continuing anyway.\n"));
493 /* Find JobId of last Full backup for this client, fileset */
494 Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, date, fsr.FileSetId);
495 if (!db_sql_query(ua->db, query, NULL, NULL)) {
496 bsendmsg(ua, "%s\n", db_strerror(ua->db));
500 /* Find all Volumes used by that JobId */
501 if (!db_sql_query(ua->db, uar_full, NULL, NULL)) {
502 bsendmsg(ua, "%s\n", db_strerror(ua->db));
505 /* Note, this is needed as I don't seem to get the callback
506 * from the call just above.
509 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
510 bsendmsg(ua, "%s\n", db_strerror(ua->db));
512 if (ji->JobTDate == 0) {
513 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
517 /* Now find all Incremental/Decremental Jobs after Full save */
518 Mmsg(&query, uar_inc_dec, edit_uint64(ji->JobTDate, ed1), date,
519 cr.ClientId, fsr.FileSetId);
520 if (!db_sql_query(ua->db, query, NULL, NULL)) {
521 bsendmsg(ua, "%s\n", db_strerror(ua->db));
524 /* Get the JobIds from that list */
526 ji->last_jobid[0] = 0;
527 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
528 bsendmsg(ua, "%s\n", db_strerror(ua->db));
531 if (ji->JobIds[0] != 0) {
532 /* Display a list of Jobs selected for this restore */
533 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
535 bsendmsg(ua, _("No jobs found.\n"));
541 free_pool_memory(query);
542 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
543 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
547 /* Return next JobId from comma separated list */
548 static int next_jobid_from_list(char **p, uint32_t *JobId)
554 for (int i=0; i<(int)sizeof(jobid); i++) {
555 if (*q == ',' || *q == 0) {
562 if (jobid[0] == 0 || !is_a_number(jobid)) {
566 *JobId = strtoul(jobid, NULL, 10);
571 * Callback handler make list of JobIds
573 static int jobid_handler(void *ctx, int num_fields, char **row)
575 JOBIDS *ji = (JOBIDS *)ctx;
577 if (strcmp(ji->last_jobid, row[0]) == 0) {
578 return 0; /* duplicate id */
580 bstrncpy(ji->last_jobid, row[0], sizeof(ji->last_jobid));
581 /* Concatenate a JobId if it does not exceed array size */
582 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
583 if (ji->JobIds[0] != 0) {
584 strcat(ji->JobIds, ",");
586 strcat(ji->JobIds, row[0]);
593 * Callback handler to pickup last Full backup JobTDate
595 static int last_full_handler(void *ctx, int num_fields, char **row)
597 JOBIDS *ji = (JOBIDS *)ctx;
599 ji->JobTDate = strtoll(row[1], NULL, 10);
605 * Callback handler build fileset prompt list
607 static int fileset_handler(void *ctx, int num_fields, char **row)
609 char prompt[MAX_NAME_LENGTH+200];
611 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
612 add_prompt((UAContext *)ctx, prompt);
617 * Called here with each name to be added to the list. The name is
618 * added to the list if it is not already in the list.
620 * Used to make unique list of FileSets and MediaTypes
622 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
624 NAME_LIST *name = (NAME_LIST *)ctx;
626 if (name->num_ids == MAX_ID_LIST_LEN) {
629 if (name->num_ids == name->max_ids) {
630 if (name->max_ids == 0) {
631 name->max_ids = 1000;
632 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
634 name->max_ids = (name->max_ids * 3) / 2;
635 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
638 for (int i=0; i<name->num_ids; i++) {
639 if (strcmp(name->name[i], row[0]) == 0) {
640 return 0; /* already in list, return */
643 /* Add new name to list */
644 name->name[name->num_ids++] = bstrdup(row[0]);
650 * Print names in the list
652 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
654 for (int i=0; i < name_list->num_ids; i++) {
655 bsendmsg(ua, "%s\n", name_list->name[i]);
661 * Free names in the list
663 static void free_name_list(NAME_LIST *name_list)
665 for (int i=0; i < name_list->num_ids; i++) {
666 free(name_list->name[i]);
668 if (name_list->name) {
669 free(name_list->name);
671 name_list->max_ids = 0;
672 name_list->num_ids = 0;
675 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
677 char name[MAX_NAME_LENGTH];
680 if (name_list->num_ids > 1) {
681 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
682 "Restore is not possible. The MediaTypes used are:\n"));
683 print_name_list(ua, name_list);
684 ji->store = select_storage_resource(ua);
688 if (name_list->num_ids == 0) {
689 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
690 ji->store = select_storage_resource(ua);
694 start_prompt(ua, _("The defined Storage resources are:\n"));
696 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
697 if (strcmp(store->media_type, name_list->name[0]) == 0) {
698 add_prompt(ua, store->hdr.name);
702 do_prompt(ua, _("Storage"), _("Select Storage resource"), name, sizeof(name));
703 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
705 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
706 "MediaType %s, needed by the Jobs you selected.\n"
707 "You will be allowed to select a Storage device later.\n"),