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);
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));
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);
185 /* Check MediaType and select storage that corresponds */
186 get_storage_from_mediatype(ua, &name_list, &ji);
187 free_name_list(&name_list);
189 /* Let the user select which files to restore */
190 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];
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",
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"));
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]);
298 switch (do_prompt(ua, "", _("Select item: "), NULL, 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);
305 case 1: /* list where a file is saved */
308 if (!get_cmd(ua, _("Enter Filename: "))) {
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);
317 db_list_sql_query(ua->jcr, ua->db, query, prtit, ua, 1, HORZ_LIST);
318 free_pool_memory(query);
321 case 2: /* enter a list of JobIds */
322 if (!get_cmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
325 bstrncpy(ji->JobIds, ua->cmd, sizeof(ji->JobIds));
327 case 3: /* Enter an SQL list command */
328 if (!get_cmd(ua, _("Enter SQL list command: "))) {
331 db_list_sql_query(ua->jcr, ua->db, ua->cmd, prtit, ua, 1, HORZ_LIST);
334 case 4: /* Select the most recent backups */
335 bstrutime(date, sizeof(date), time(NULL));
336 if (!select_backups_before_date(ua, ji, date)) {
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"));
344 if (!get_cmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) {
347 if (str_to_utime(ua->cmd) != 0) {
350 bsendmsg(ua, _("Improper date format.\n"));
352 bstrncpy(date, ua->cmd, sizeof(date));
353 if (!select_backups_before_date(ua, ji, date)) {
357 case 6: /* Cancel or quit */
362 if (*ji->JobIds == 0) {
363 bsendmsg(ua, _("No Jobs selected.\n"));
366 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
368 memset(&jr, 0, sizeof(JOB_DBR));
371 for (p=ji->JobIds; ; ) {
372 int stat = next_jobid_from_list(&p, &JobId);
374 bsendmsg(ua, _("Invalid JobId in list.\n"));
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));
385 ji->TotalFiles += jr.JobFiles;
391 * This routine is used to get the current backup or a backup
392 * before the specified date.
394 static int select_backups_before_date(UAContext *ua, JOBIDS *ji, char *date)
400 char fileset_name[MAX_NAME_LENGTH];
403 query = get_pool_memory(PM_MESSAGE);
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));
411 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
412 bsendmsg(ua, "%s\n", db_strerror(ua->db));
415 * Select Client from the Catalog
417 memset(&cr, 0, sizeof(cr));
418 if (!get_client_dbr(ua, &cr)) {
421 bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
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));
431 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
432 fileset_name, sizeof(fileset_name)) < 0) {
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"));
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));
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));
455 /* Note, this is needed as I don't seem to get the callback
456 * from the call just above.
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));
462 if (ji->JobTDate == 0) {
463 bsendmsg(ua, _("No Full backup before %s found.\n"), date);
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));
474 /* Get the JobIds from that list */
476 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
477 bsendmsg(ua, "%s\n", db_strerror(ua->db));
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);
484 bsendmsg(ua, _("No jobs found.\n"));
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);
497 static int next_jobid_from_list(char **p, uint32_t *JobId)
504 for (i=0; i<(int)sizeof(jobid); i++) {
505 if (*q == ',' || *q == 0) {
512 if (jobid[0] == 0 || !is_a_number(jobid)) {
516 *JobId = strtoul(jobid, NULL, 10);
521 * Callback handler make list of JobIds
523 static int jobid_handler(void *ctx, int num_fields, char **row)
525 JOBIDS *ji = (JOBIDS *)ctx;
527 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
528 if (ji->JobIds[0] != 0) {
529 strcat(ji->JobIds, ",");
531 strcat(ji->JobIds, row[0]);
539 * Callback handler to pickup last Full backup JobTDate
541 static int last_full_handler(void *ctx, int num_fields, char **row)
543 JOBIDS *ji = (JOBIDS *)ctx;
545 ji->JobTDate = strtoll(row[1], NULL, 10);
551 * Callback handler build fileset prompt list
553 static int fileset_handler(void *ctx, int num_fields, char **row)
555 char prompt[MAX_NAME_LENGTH+200];
557 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
558 add_prompt((UAContext *)ctx, prompt);
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.
566 * Used to make unique list of FileSets and MediaTypes
568 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
570 NAME_LIST *name = (NAME_LIST *)ctx;
572 if (name->num_ids == MAX_ID_LIST_LEN) {
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);
580 name->max_ids = (name->max_ids * 3) / 2;
581 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
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 */
589 /* Add new name to list */
590 name->name[name->num_ids++] = bstrdup(row[0]);
596 * Print names in the list
598 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
602 for (i=0; i < name_list->num_ids; i++) {
603 bsendmsg(ua, "%s\n", name_list->name[i]);
609 * Free names in the list
611 static void free_name_list(NAME_LIST *name_list)
615 for (i=0; i < name_list->num_ids; i++) {
616 free(name_list->name[i]);
618 if (name_list->name) {
619 free(name_list->name);
621 name_list->max_ids = 0;
622 name_list->num_ids = 0;
625 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
627 char name[MAX_NAME_LENGTH];
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);
638 if (name_list->num_ids == 0) {
639 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
640 ji->store = select_storage_resource(ua);
644 start_prompt(ua, _("The defined Storage resources are:\n"));
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);
652 do_prompt(ua, _("Storage"), _("Select Storage resource"), name, sizeof(name));
653 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
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"),