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, *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);
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));
180 bsendmsg(ua, "%d item%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 /* Let the user select which files to restore */
189 user_select_files_from_tree(&tree);
192 * Walk down through the tree finding all files marked to be
193 * extracted making a bootstrap file.
196 for (TREE_NODE *node=first_tree_node(tree.root); node; node=next_tree_node(node)) {
197 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
199 Dmsg2(400, "type=%d FI=%d\n", node->type, node->FileIndex);
200 add_findex(bsr, node->JobId, node->FileIndex);
205 free_tree(tree.root); /* free the directory tree */
208 if (!complete_bsr(ua, bsr)) { /* find Vol, SessId, SessTime from JobIds */
209 bsendmsg(ua, _("Unable to construct a valid BSR. Cannot continue.\n"));
213 // print_bsr(ua, bsr);
214 write_bsr_file(ua, bsr);
215 bsendmsg(ua, _("\n%u files selected to restore.\n\n"), selected_files);
217 bsendmsg(ua, _("No files selected to restore.\n"));
221 if (restore_jobs == 1) {
224 job = select_restore_job_resource(ua);
227 bsendmsg(ua, _("No Restore Job resource found!\n"));
231 /* If no client name specified yet, get it now */
232 if (!ji.ClientName[0]) {
234 memset(&cr, 0, sizeof(cr));
235 if (!get_client_dbr(ua, &cr)) {
238 bstrncpy(ji.ClientName, cr.Name, sizeof(ji.ClientName));
241 /* Build run command */
244 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\""
246 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
247 working_directory, where);
250 "run job=\"%s\" client=\"%s\" storage=\"%s\" bootstrap=\"%s/restore.bsr\"",
251 job->hdr.name, ji.ClientName, ji.store?ji.store->hdr.name:"",
254 Dmsg1(400, "Submitting: %s\n", ua->cmd);
259 bsendmsg(ua, _("Restore command done.\n"));
264 * The first step in the restore process is for the user to
265 * select a list of JobIds from which he will subsequently
266 * select which files are to be restored.
268 static int user_select_jobids(UAContext *ua, JOBIDS *ji)
270 char fileset_name[MAX_NAME_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",
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 query = get_pool_memory(PM_MESSAGE);
336 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
337 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
338 if (!db_sql_query(ua->db, uar_create_temp, NULL, NULL)) {
339 bsendmsg(ua, "%s\n", db_strerror(ua->db));
341 if (!db_sql_query(ua->db, uar_create_temp1, NULL, NULL)) {
342 bsendmsg(ua, "%s\n", db_strerror(ua->db));
345 * Select Client from the Catalog
347 memset(&cr, 0, sizeof(cr));
348 if (!get_client_dbr(ua, &cr)) {
349 free_pool_memory(query);
350 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
351 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
354 bstrncpy(ji->ClientName, cr.Name, sizeof(ji->ClientName));
359 Mmsg(&query, uar_sel_fileset, cr.ClientId, cr.ClientId);
360 start_prompt(ua, _("The defined FileSet resources are:\n"));
361 if (!db_sql_query(ua->db, query, fileset_handler, (void *)ua)) {
362 bsendmsg(ua, "%s\n", db_strerror(ua->db));
364 if (do_prompt(ua, _("FileSet"), _("Select FileSet resource"),
365 fileset_name, sizeof(fileset_name)) < 0) {
366 free_pool_memory(query);
367 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
368 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
371 fsr.FileSetId = atoi(fileset_name); /* Id is first part of name */
372 if (!db_get_fileset_record(ua->jcr, ua->db, &fsr)) {
373 bsendmsg(ua, _("Error getting FileSet record: %s\n"), db_strerror(ua->db));
374 bsendmsg(ua, _("This probably means you modified the FileSet.\n"
375 "Continuing anyway.\n"));
378 /* Find JobId of last Full backup for this client, fileset */
379 Mmsg(&query, uar_last_full, cr.ClientId, cr.ClientId, fsr.FileSetId);
380 if (!db_sql_query(ua->db, query, NULL, NULL)) {
381 bsendmsg(ua, "%s\n", db_strerror(ua->db));
383 /* Find all Volumes used by that JobId */
384 if (!db_sql_query(ua->db, uar_full, NULL,NULL)) {
385 bsendmsg(ua, "%s\n", db_strerror(ua->db));
387 /* Note, this is needed as I don't seem to get the callback
388 * from the call just above.
390 if (!db_sql_query(ua->db, uar_sel_all_temp1, last_full_handler, (void *)ji)) {
391 bsendmsg(ua, "%s\n", db_strerror(ua->db));
393 /* Now find all Incremental Jobs */
394 Mmsg(&query, uar_inc, edit_uint64(ji->JobTDate, ed1), cr.ClientId, fsr.FileSetId);
395 if (!db_sql_query(ua->db, query, NULL, NULL)) {
396 bsendmsg(ua, "%s\n", db_strerror(ua->db));
398 free_pool_memory(query);
399 db_list_sql_query(ua->jcr, ua->db, uar_list_temp, prtit, ua, 1, HORZ_LIST);
401 if (!db_sql_query(ua->db, uar_sel_jobid_temp, jobid_handler, (void *)ji)) {
402 bsendmsg(ua, "%s\n", db_strerror(ua->db));
404 db_sql_query(ua->db, uar_del_temp, NULL, NULL);
405 db_sql_query(ua->db, uar_del_temp1, NULL, NULL);
412 if (*ji->JobIds == 0) {
413 bsendmsg(ua, _("No Jobs selected.\n"));
416 bsendmsg(ua, _("You have selected the following JobId: %s\n"), ji->JobIds);
418 memset(&jr, 0, sizeof(JOB_DBR));
421 for (p=ji->JobIds; ; ) {
422 int stat = next_jobid_from_list(&p, &JobId);
424 bsendmsg(ua, _("Invalid JobId in list.\n"));
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;
440 static int next_jobid_from_list(char **p, uint32_t *JobId)
447 for (i=0; i<(int)sizeof(jobid); i++) {
448 if (*q == ',' || *q == 0) {
455 if (jobid[0] == 0 || !is_a_number(jobid)) {
459 *JobId = strtoul(jobid, NULL, 10);
464 * Callback handler make list of JobIds
466 static int jobid_handler(void *ctx, int num_fields, char **row)
468 JOBIDS *ji = (JOBIDS *)ctx;
470 if (strlen(ji->JobIds)+strlen(row[0])+2 < sizeof(ji->JobIds)) {
471 if (ji->JobIds[0] != 0) {
472 strcat(ji->JobIds, ",");
474 strcat(ji->JobIds, row[0]);
482 * Callback handler to pickup last Full backup JobId and ClientId
484 static int last_full_handler(void *ctx, int num_fields, char **row)
486 JOBIDS *ji = (JOBIDS *)ctx;
488 ji->JobTDate = strtoll(row[1], NULL, 10);
494 * Callback handler build fileset prompt list
496 static int fileset_handler(void *ctx, int num_fields, char **row)
498 char prompt[MAX_NAME_LENGTH+200];
500 snprintf(prompt, sizeof(prompt), "%s %s %s", row[0], row[1], row[2]);
501 add_prompt((UAContext *)ctx, prompt);
506 * Called here with each name to be added to the list. The name is
507 * added to the list if it is not already in the list.
509 * Used to make unique list of FileSets and MediaTypes
511 static int unique_name_list_handler(void *ctx, int num_fields, char **row)
513 NAME_LIST *name = (NAME_LIST *)ctx;
515 if (name->num_ids == MAX_ID_LIST_LEN) {
518 if (name->num_ids == name->max_ids) {
519 if (name->max_ids == 0) {
520 name->max_ids = 1000;
521 name->name = (char **)bmalloc(sizeof(char *) * name->max_ids);
523 name->max_ids = (name->max_ids * 3) / 2;
524 name->name = (char **)brealloc(name->name, sizeof(char *) * name->max_ids);
527 for (int i=0; i<name->num_ids; i++) {
528 if (strcmp(name->name[i], row[0]) == 0) {
529 return 0; /* already in list, return */
532 /* Add new name to list */
533 name->name[name->num_ids++] = bstrdup(row[0]);
539 * Print names in the list
541 static void print_name_list(UAContext *ua, NAME_LIST *name_list)
545 for (i=0; i < name_list->num_ids; i++) {
546 bsendmsg(ua, "%s\n", name_list->name[i]);
552 * Free names in the list
554 static void free_name_list(NAME_LIST *name_list)
558 for (i=0; i < name_list->num_ids; i++) {
559 free(name_list->name[i]);
561 if (name_list->name) {
562 free(name_list->name);
564 name_list->max_ids = 0;
565 name_list->num_ids = 0;
568 static void get_storage_from_mediatype(UAContext *ua, NAME_LIST *name_list, JOBIDS *ji)
570 char name[MAX_NAME_LENGTH];
573 if (name_list->num_ids > 1) {
574 bsendmsg(ua, _("Warning, the JobIds that you selected refer to more than one MediaType.\n"
575 "Restore is not possible. The MediaTypes used are:\n"));
576 print_name_list(ua, name_list);
577 ji->store = select_storage_resource(ua);
581 if (name_list->num_ids == 0) {
582 bsendmsg(ua, _("No MediaType found for your JobIds.\n"));
583 ji->store = select_storage_resource(ua);
587 start_prompt(ua, _("The defined Storage resources are:\n"));
589 while ((store = (STORE *)GetNextRes(R_STORAGE, (RES *)store))) {
590 if (strcmp(store->media_type, name_list->name[0]) == 0) {
591 add_prompt(ua, store->hdr.name);
595 do_prompt(ua, _("Storage"), _("Select Storage resource"), name, sizeof(name));
596 ji->store = (STORE *)GetResWithName(R_STORAGE, name);
598 bsendmsg(ua, _("\nWarning. Unable to find Storage resource for\n"
599 "MediaType %s, needed by the Jobs you selected.\n"
600 "You will be allowed to select a Storage device later.\n"),