1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2002-2011 Free Software Foundation Europe e.V.
5 Copyright (C) 2011-2016 Planets Communications B.V.
6 Copyright (C) 2013-2020 Bareos GmbH & Co. KG
7
8 This program is Free Software; you can redistribute it and/or
9 modify it under the terms of version three of the GNU Affero General Public
10 License as published by the Free Software Foundation and included
11 in the file LICENSE.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Affero General Public License for more details.
17
18 You should have received a copy of the GNU Affero General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 02110-1301, USA.
22 */
23 /*
24 * Kern Sibbald, July MMII
25 * Tree handling routines split into ua_tree.c July MMIII.
26 * BootStrapRecord (bootstrap record) handling routines split into
27 * bsr.c July MMIII
28 */
29 /**
30 * @file
31 * User Agent Database restore Command
32 * Creates a bootstrap file for restoring files and
33 * starts the restore job.
34 */
35
36 #include "include/bareos.h"
37 #include "dird.h"
38 #include "dird/dird_globals.h"
39 #include "dird/jcr_private.h"
40 #include "dird/ua_db.h"
41 #include "dird/ua_input.h"
42 #include "dird/ua_select.h"
43 #include "dird/ua_tree.h"
44 #include "dird/ua_run.h"
45 #include "dird/bsr.h"
46 #include "lib/breg.h"
47 #include "lib/edit.h"
48 #include "lib/berrno.h"
49 #include "lib/parse_conf.h"
50 #include "lib/tree.h"
51 #include "include/make_unique.h"
52 #include "include/protocol_types.h"
53
54 namespace directordaemon {
55
56 /* Imported functions */
57 extern void PrintBsr(UaContext* ua, RestoreBootstrapRecord* bsr);
58
59
60 /* Forward referenced functions */
61 static int LastFullHandler(void* ctx, int num_fields, char** row);
62 static int JobidHandler(void* ctx, int num_fields, char** row);
63 static int UserSelectJobidsOrFiles(UaContext* ua, RestoreContext* rx);
64 static int FilesetHandler(void* ctx, int num_fields, char** row);
65 static void FreeNameList(NameList* name_list);
66 static bool SelectBackupsBeforeDate(UaContext* ua,
67 RestoreContext* rx,
68 char* date);
69 static bool BuildDirectoryTree(UaContext* ua, RestoreContext* rx);
70 static void free_rx(RestoreContext* rx);
71 static void SplitPathAndFilename(UaContext* ua,
72 RestoreContext* rx,
73 char* fname);
74 static int JobidFileindexHandler(void* ctx, int num_fields, char** row);
75 static bool InsertFileIntoFindexList(UaContext* ua,
76 RestoreContext* rx,
77 char* file,
78 char* date);
79 static bool InsertDirIntoFindexList(UaContext* ua,
80 RestoreContext* rx,
81 char* dir,
82 char* date);
83 static void InsertOneFileOrDir(UaContext* ua,
84 RestoreContext* rx,
85 char* date,
86 bool dir);
87 static bool GetClientName(UaContext* ua, RestoreContext* rx);
88 static bool GetRestoreClientName(UaContext* ua, RestoreContext& rx);
89 static bool get_date(UaContext* ua, char* date, int date_len);
90 static int RestoreCountHandler(void* ctx, int num_fields, char** row);
91 static bool InsertTableIntoFindexList(UaContext* ua,
92 RestoreContext* rx,
93 char* table);
94 static void GetAndDisplayBasejobs(UaContext* ua, RestoreContext* rx);
95
96 /**
97 * Restore files
98 */
RestoreCmd(UaContext * ua,const char * cmd)99 bool RestoreCmd(UaContext* ua, const char* cmd)
100 {
101 RestoreContext rx; /* restore context */
102 PoolMem buf;
103 JobResource* job;
104 int i;
105 JobControlRecord* jcr = ua->jcr;
106 char* escaped_bsr_name = NULL;
107 char* escaped_where_name = NULL;
108 char *strip_prefix, *add_prefix, *add_suffix, *regexp;
109 strip_prefix = add_prefix = add_suffix = regexp = NULL;
110
111 rx.path = GetPoolMemory(PM_FNAME);
112 rx.fname = GetPoolMemory(PM_FNAME);
113 rx.JobIds = GetPoolMemory(PM_FNAME);
114 rx.JobIds[0] = 0;
115 rx.BaseJobIds = GetPoolMemory(PM_FNAME);
116 rx.query = GetPoolMemory(PM_FNAME);
117 rx.bsr = std::make_unique<RestoreBootstrapRecord>();
118
119 i = FindArgWithValue(ua, "comment");
120 if (i >= 0) {
121 rx.comment = ua->argv[i];
122 if (!IsCommentLegal(ua, rx.comment)) { goto bail_out; }
123 }
124
125 i = FindArgWithValue(ua, "backupformat");
126 if (i >= 0) { rx.backup_format = ua->argv[i]; }
127
128 i = FindArgWithValue(ua, "where");
129 if (i >= 0) { rx.where = ua->argv[i]; }
130
131 i = FindArgWithValue(ua, "replace");
132 if (i >= 0) { rx.replace = ua->argv[i]; }
133
134 i = FindArgWithValue(ua, "pluginoptions");
135 if (i >= 0) { rx.plugin_options = ua->argv[i]; }
136
137 i = FindArgWithValue(ua, "strip_prefix");
138 if (i >= 0) { strip_prefix = ua->argv[i]; }
139
140 i = FindArgWithValue(ua, "add_prefix");
141 if (i >= 0) { add_prefix = ua->argv[i]; }
142
143 i = FindArgWithValue(ua, "add_suffix");
144 if (i >= 0) { add_suffix = ua->argv[i]; }
145
146 i = FindArgWithValue(ua, "regexwhere");
147 if (i >= 0) { rx.RegexWhere = ua->argv[i]; }
148
149 if (strip_prefix || add_suffix || add_prefix) {
150 int len = BregexpGetBuildWhereSize(strip_prefix, add_prefix, add_suffix);
151 regexp = (char*)malloc(len * sizeof(char));
152
153 bregexp_build_where(regexp, len, strip_prefix, add_prefix, add_suffix);
154 rx.RegexWhere = regexp;
155 }
156
157 /* TODO: add acl for regexwhere ? */
158
159 if (rx.RegexWhere) {
160 if (!ua->AclAccessOk(Where_ACL, rx.RegexWhere, true)) {
161 ua->ErrorMsg(_("\"RegexWhere\" specification not authorized.\n"));
162 goto bail_out;
163 }
164 }
165
166 if (rx.where) {
167 if (!ua->AclAccessOk(Where_ACL, rx.where, true)) {
168 ua->ErrorMsg(_("\"where\" specification not authorized.\n"));
169 goto bail_out;
170 }
171 }
172
173 if (!OpenClientDb(ua, true)) { goto bail_out; }
174
175 /* Ensure there is at least one Restore Job */
176 LockRes(my_config);
177 foreach_res (job, R_JOB) {
178 if (job->JobType == JT_RESTORE) {
179 if (!rx.restore_job) { rx.restore_job = job; }
180 rx.restore_jobs++;
181 }
182 }
183 UnlockRes(my_config);
184 if (!rx.restore_jobs) {
185 ua->ErrorMsg(
186 _("No Restore Job Resource found in %s.\n"
187 "You must create at least one before running this command.\n"),
188 my_config->get_base_config_path().c_str());
189 goto bail_out;
190 }
191
192 /*
193 * Request user to select JobIds or files by various different methods
194 * last 20 jobs, where File saved, most recent backup, ...
195 * In the end, a list of files are pumped into
196 * AddFindex()
197 */
198 switch (UserSelectJobidsOrFiles(ua, &rx)) {
199 case 0: /* error */
200 goto bail_out;
201 case 1: /* selected by jobid */
202 GetAndDisplayBasejobs(ua, &rx);
203 if (!BuildDirectoryTree(ua, &rx)) {
204 ua->SendMsg(_("Restore not done.\n"));
205 goto bail_out;
206 }
207 break;
208 case 2: /* selected by filename, no tree needed */
209 break;
210 }
211
212 if (rx.restore_jobs == 1) {
213 job = rx.restore_job;
214 } else {
215 job = get_restore_job(ua);
216 }
217 if (!job) { goto bail_out; }
218
219 /*
220 * When doing NDMP_NATIVE restores, we don't create any bootstrap file
221 * as we only send a namelist for restore. The storage handling is
222 * done by the NDMP state machine via robot and tape interface.
223 */
224 if (job->Protocol == PT_NDMP_NATIVE) {
225 ua->InfoMsg(
226 _("Skipping BootStrapRecord creation as we are doing NDMP_NATIVE "
227 "restore.\n"));
228
229 } else {
230 if (rx.bsr->JobId) {
231 char ed1[50];
232 if (!AddVolumeInformationToBsr(ua, rx.bsr.get())) {
233 ua->ErrorMsg(_(
234 "Unable to construct a valid BootStrapRecord. Cannot continue.\n"));
235 goto bail_out;
236 }
237 if (!(rx.selected_files = WriteBsrFile(ua, rx))) {
238 ua->WarningMsg(_("No files selected to be restored.\n"));
239 goto bail_out;
240 }
241 DisplayBsrInfo(ua, rx); /* display vols needed, etc */
242
243 if (rx.selected_files == 1) {
244 ua->InfoMsg(_("\n1 file selected to be restored.\n\n"));
245 } else {
246 ua->InfoMsg(_("\n%s files selected to be restored.\n\n"),
247 edit_uint64_with_commas(rx.selected_files, ed1));
248 }
249 } else {
250 ua->WarningMsg(_("No files selected to be restored.\n"));
251 goto bail_out;
252 }
253 }
254
255 if (!GetClientName(ua, &rx)) { goto bail_out; }
256 if (!rx.ClientName) {
257 ua->ErrorMsg(_("No Client resource found!\n"));
258 goto bail_out;
259 }
260 if (!GetRestoreClientName(ua, rx)) { goto bail_out; }
261
262 escaped_bsr_name = escape_filename(jcr->RestoreBootstrap);
263
264 Mmsg(ua->cmd,
265 "run job=\"%s\" client=\"%s\" restoreclient=\"%s\" storage=\"%s\""
266 " bootstrap=\"%s\" files=%u catalog=\"%s\"",
267 job->resource_name_, rx.ClientName, rx.RestoreClientName,
268 rx.store ? rx.store->resource_name_ : "",
269 escaped_bsr_name ? escaped_bsr_name : jcr->RestoreBootstrap,
270 rx.selected_files, ua->catalog->resource_name_);
271
272 /*
273 * Build run command
274 */
275 if (rx.backup_format) {
276 Mmsg(buf, " backupformat=%s", rx.backup_format);
277 PmStrcat(ua->cmd, buf);
278 }
279
280 PmStrcpy(buf, "");
281 if (rx.RegexWhere) {
282 escaped_where_name = escape_filename(rx.RegexWhere);
283 Mmsg(buf, " regexwhere=\"%s\"",
284 escaped_where_name ? escaped_where_name : rx.RegexWhere);
285
286 } else if (rx.where) {
287 escaped_where_name = escape_filename(rx.where);
288 Mmsg(buf, " where=\"%s\"",
289 escaped_where_name ? escaped_where_name : rx.where);
290 }
291 PmStrcat(ua->cmd, buf);
292
293 if (rx.replace) {
294 Mmsg(buf, " replace=%s", rx.replace);
295 PmStrcat(ua->cmd, buf);
296 }
297
298 if (rx.plugin_options) {
299 Mmsg(buf, " pluginoptions=%s", rx.plugin_options);
300 PmStrcat(ua->cmd, buf);
301 }
302
303 if (rx.comment) {
304 Mmsg(buf, " comment=\"%s\"", rx.comment);
305 PmStrcat(ua->cmd, buf);
306 }
307
308 if (escaped_bsr_name != NULL) { free(escaped_bsr_name); }
309
310 if (escaped_where_name != NULL) { free(escaped_where_name); }
311
312 if (regexp) { free(regexp); }
313
314 if (FindArg(ua, NT_("yes")) > 0) {
315 PmStrcat(ua->cmd, " yes"); /* pass it on to the run command */
316 }
317
318 Dmsg1(200, "Submitting: %s\n", ua->cmd);
319
320 /*
321 * Transfer jobids to jcr to for picking up restore objects
322 */
323 jcr->JobIds = rx.JobIds;
324 rx.JobIds = NULL;
325
326 ParseUaArgs(ua);
327 RunCmd(ua, ua->cmd);
328 free_rx(&rx);
329 GarbageCollectMemory(); /* release unused memory */
330 return true;
331
332 bail_out:
333 if (escaped_bsr_name != NULL) { free(escaped_bsr_name); }
334
335 if (escaped_where_name != NULL) { free(escaped_where_name); }
336
337 if (regexp) { free(regexp); }
338
339 free_rx(&rx);
340 GarbageCollectMemory(); /* release unused memory */
341 return false;
342 }
343
344 /**
345 * Fill the rx->BaseJobIds and display the list
346 */
GetAndDisplayBasejobs(UaContext * ua,RestoreContext * rx)347 static void GetAndDisplayBasejobs(UaContext* ua, RestoreContext* rx)
348 {
349 db_list_ctx jobids;
350
351 if (!ua->db->GetUsedBaseJobids(ua->jcr, rx->JobIds, &jobids)) {
352 ua->WarningMsg("%s", ua->db->strerror());
353 }
354
355 if (!jobids.empty()) {
356 PoolMem query(PM_MESSAGE);
357
358 ua->SendMsg(_("The restore will use the following job(s) as Base\n"));
359 ua->db->FillQuery(query, BareosDb::SQL_QUERY::uar_print_jobs,
360 jobids.GetAsString().c_str());
361 ua->db->ListSqlQuery(ua->jcr, query.c_str(), ua->send, HORZ_LIST, true);
362 }
363 PmStrcpy(rx->BaseJobIds, jobids.GetAsString().c_str());
364 }
365
free_rx(RestoreContext * rx)366 static void free_rx(RestoreContext* rx)
367 {
368 rx->bsr.reset(nullptr);
369
370 if (rx->ClientName) {
371 free(rx->ClientName);
372 rx->ClientName = NULL;
373 }
374
375 if (rx->RestoreClientName) {
376 free(rx->RestoreClientName);
377 rx->RestoreClientName = NULL;
378 }
379
380 FreeAndNullPoolMemory(rx->JobIds);
381 FreeAndNullPoolMemory(rx->BaseJobIds);
382 FreeAndNullPoolMemory(rx->fname);
383 FreeAndNullPoolMemory(rx->path);
384 FreeAndNullPoolMemory(rx->query);
385 FreeNameList(&rx->name_list);
386 }
387
HasValue(UaContext * ua,int i)388 static bool HasValue(UaContext* ua, int i)
389 {
390 if (!ua->argv[i]) {
391 ua->ErrorMsg(_("Missing value for keyword: %s\n"), ua->argk[i]);
392 return false;
393 }
394 return true;
395 }
396
397 /**
398 * This gets the client name from which the backup was made
399 */
GetClientName(UaContext * ua,RestoreContext * rx)400 static bool GetClientName(UaContext* ua, RestoreContext* rx)
401 {
402 int i;
403 ClientDbRecord cr;
404
405 /*
406 * If no client name specified yet, get it now
407 */
408 if (!rx->ClientName) {
409 /*
410 * Try command line argument
411 */
412 i = FindArgWithValue(ua, NT_("client"));
413 if (i < 0) { i = FindArgWithValue(ua, NT_("backupclient")); }
414 if (i >= 0) {
415 if (!IsNameValid(ua->argv[i], ua->errmsg)) {
416 ua->ErrorMsg("%s argument: %s", ua->argk[i], ua->errmsg);
417 return false;
418 }
419 bstrncpy(cr.Name, ua->argv[i], sizeof(cr.Name));
420 if (!ua->db->GetClientRecord(ua->jcr, &cr)) {
421 ua->ErrorMsg("invalid %s argument: %s\n", ua->argk[i], ua->argv[i]);
422 return false;
423 }
424 rx->ClientName = strdup(ua->argv[i]);
425 return true;
426 }
427 if (!GetClientDbr(ua, &cr)) { return false; }
428 rx->ClientName = strdup(cr.Name);
429 }
430
431 return true;
432 }
433
434 /**
435 * This is where we pick up a client name to restore to.
436 */
GetRestoreClientName(UaContext * ua,RestoreContext & rx)437 static bool GetRestoreClientName(UaContext* ua, RestoreContext& rx)
438 {
439 int i;
440
441 /*
442 * Try command line argument
443 */
444 i = FindArgWithValue(ua, NT_("restoreclient"));
445 if (i >= 0) {
446 if (!IsNameValid(ua->argv[i], ua->errmsg)) {
447 ua->ErrorMsg("%s argument: %s", ua->argk[i], ua->errmsg);
448 return false;
449 }
450 if (!ua->GetClientResWithName(ua->argv[i])) {
451 ua->ErrorMsg("invalid %s argument: %s\n", ua->argk[i], ua->argv[i]);
452 return false;
453 }
454 rx.RestoreClientName = strdup(ua->argv[i]);
455 return true;
456 }
457
458 rx.RestoreClientName = strdup(rx.ClientName);
459 return true;
460 }
461
462 /**
463 * The first step in the restore process is for the user to
464 * select a list of JobIds from which he will subsequently
465 * select which files are to be restored.
466 *
467 * Returns: 2 if filename list made
468 * 1 if jobid list made
469 * 0 on error
470 */
UserSelectJobidsOrFiles(UaContext * ua,RestoreContext * rx)471 static int UserSelectJobidsOrFiles(UaContext* ua, RestoreContext* rx)
472 {
473 const char* p;
474 char date[MAX_TIME_LENGTH];
475 bool have_date = false;
476 /* Include current second if using current time */
477 utime_t now = time(NULL) + 1;
478 JobId_t JobId;
479 bool done = false;
480 int i, j;
481 const char* list[]
482 = {_("List last 20 Jobs run"),
483 _("List Jobs where a given File is saved"),
484 _("Enter list of comma separated JobIds to select"),
485 _("Enter SQL list command"),
486 _("Select the most recent backup for a client"),
487 _("Select backup for a client before a specified time"),
488 _("Enter a list of files to restore"),
489 _("Enter a list of files to restore before a specified time"),
490 _("Find the JobIds of the most recent backup for a client"),
491 _("Find the JobIds for a backup for a client before a specified time"),
492 _("Enter a list of directories to restore for found JobIds"),
493 _("Select full restore to a specified Job date"),
494 _("Cancel"),
495 NULL};
496
497 const char* kw[] = { /*
498 * These keywords are handled in a for loop
499 */
500 "jobid", /* 0 */
501 "current", /* 1 */
502 "before", /* 2 */
503 "file", /* 3 */
504 "directory", /* 4 */
505 "select", /* 5 */
506 "pool", /* 6 */
507 "all", /* 7 */
508
509 /*
510 * The keyword below are handled by individual arg lookups
511 */
512 "client", /* 8 */
513 "storage", /* 9 */
514 "fileset", /* 10 */
515 "where", /* 11 */
516 "yes", /* 12 */
517 "bootstrap", /* 13 */
518 "done", /* 14 */
519 "strip_prefix", /* 15 */
520 "add_prefix", /* 16 */
521 "add_suffix", /* 17 */
522 "regexwhere", /* 18 */
523 "restoreclient", /* 19 */
524 "copies", /* 20 */
525 "comment", /* 21 */
526 "restorejob", /* 22 */
527 "replace", /* 23 */
528 "pluginoptions", /* 24 */
529 NULL};
530
531 rx->JobIds[0] = 0;
532
533 for (i = 1; i < ua->argc; i++) { /* loop through arguments */
534 bool found_kw = false;
535 for (j = 0; kw[j]; j++) { /* loop through keywords */
536 if (Bstrcasecmp(kw[j], ua->argk[i])) {
537 found_kw = true;
538 break;
539 }
540 }
541 if (!found_kw) {
542 ua->ErrorMsg(_("Unknown keyword: %s\n"), ua->argk[i]);
543 return 0;
544 }
545 /* Found keyword in kw[] list, process it */
546 switch (j) {
547 case 0: /* jobid */
548 if (!HasValue(ua, i)) { return 0; }
549 if (*rx->JobIds != 0) { PmStrcat(rx->JobIds, ","); }
550 PmStrcat(rx->JobIds, ua->argv[i]);
551 done = true;
552 break;
553 case 1: /* current */
554 /*
555 * Note, we add one second here just to include any job
556 * that may have finished within the current second,
557 * which happens a lot in scripting small jobs.
558 */
559 bstrutime(date, sizeof(date), now);
560 have_date = true;
561 break;
562 case 2: /* before */
563 if (have_date || !HasValue(ua, i)) { return 0; }
564 if (StrToUtime(ua->argv[i]) == 0) {
565 ua->ErrorMsg(_("Improper date format: %s\n"), ua->argv[i]);
566 return 0;
567 }
568 bstrncpy(date, ua->argv[i], sizeof(date));
569 have_date = true;
570 break;
571 case 3: /* file */
572 case 4: /* dir */
573 if (!HasValue(ua, i)) { return 0; }
574 if (!have_date) { bstrutime(date, sizeof(date), now); }
575 if (!GetClientName(ua, rx)) { return 0; }
576 PmStrcpy(ua->cmd, ua->argv[i]);
577 InsertOneFileOrDir(ua, rx, date, j == 4);
578 return 2;
579 case 5: /* select */
580 if (!have_date) { bstrutime(date, sizeof(date), now); }
581 if (!SelectBackupsBeforeDate(ua, rx, date)) { return 0; }
582 done = true;
583 break;
584 case 6: /* pool specified */
585 if (!HasValue(ua, i)) { return 0; }
586 rx->pool = ua->GetPoolResWithName(ua->argv[i]);
587 if (!rx->pool) {
588 ua->ErrorMsg(_("Error: Pool resource \"%s\" does not exist.\n"),
589 ua->argv[i]);
590 return 0;
591 }
592 break;
593 case 7: /* all specified */
594 rx->all = true;
595 break;
596 default:
597 /*
598 * All keywords 7 or greater are ignored or handled by a select prompt
599 */
600 break;
601 }
602 }
603
604 if (!done) {
605 ua->SendMsg(
606 _("\nFirst you select one or more JobIds that contain files\n"
607 "to be restored. You will be presented several methods\n"
608 "of specifying the JobIds. Then you will be allowed to\n"
609 "select which files from those JobIds are to be restored.\n\n"));
610 }
611
612 /* If choice not already made above, prompt */
613 for (; !done;) {
614 char* fname;
615 int len;
616 bool gui_save;
617 db_list_ctx jobids;
618
619 StartPrompt(ua,
620 _("To select the JobIds, you have the following choices:\n"));
621 for (int i = 0; list[i]; i++) { AddPrompt(ua, list[i]); }
622 done = true;
623 switch (DoPrompt(ua, "", _("Select item: "), NULL, 0)) {
624 case -1: /* error or cancel */
625 return 0;
626 case 0: /* list last 20 Jobs run */
627 if (!ua->AclAccessOk(Command_ACL, NT_("sqlquery"), true)) {
628 ua->ErrorMsg(_("SQL query not authorized.\n"));
629 return 0;
630 }
631 gui_save = ua->jcr->gui;
632 ua->jcr->gui = true;
633 ua->db->ListSqlQuery(ua->jcr, BareosDb::SQL_QUERY::uar_list_jobs,
634 ua->send, HORZ_LIST, true);
635 ua->jcr->gui = gui_save;
636 done = false;
637 break;
638 case 1: /* list where a file is saved */
639 if (!GetClientName(ua, rx)) { return 0; }
640 if (!GetCmd(ua, _("Enter Filename (no path):"))) { return 0; }
641 len = strlen(ua->cmd);
642 fname = (char*)malloc(len * 2 + 1);
643 ua->db->EscapeString(ua->jcr, fname, ua->cmd, len);
644 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_file,
645 rx->ClientName, fname);
646 free(fname);
647 gui_save = ua->jcr->gui;
648 ua->jcr->gui = true;
649 ua->db->ListSqlQuery(ua->jcr, rx->query, ua->send, HORZ_LIST, true);
650 ua->jcr->gui = gui_save;
651 done = false;
652 break;
653 case 2: /* enter a list of JobIds */
654 if (!GetCmd(ua, _("Enter JobId(s), comma separated, to restore: "))) {
655 return 0;
656 }
657 PmStrcpy(rx->JobIds, ua->cmd);
658 break;
659 case 3: /* Enter an SQL list command */
660 if (!ua->AclAccessOk(Command_ACL, NT_("sqlquery"), true)) {
661 ua->ErrorMsg(_("SQL query not authorized.\n"));
662 return 0;
663 }
664 if (!GetCmd(ua, _("Enter SQL list command: "))) { return 0; }
665 gui_save = ua->jcr->gui;
666 ua->jcr->gui = true;
667 ua->db->ListSqlQuery(ua->jcr, ua->cmd, ua->send, HORZ_LIST, true);
668 ua->jcr->gui = gui_save;
669 done = false;
670 break;
671 case 4: /* Select the most recent backups */
672 if (!have_date) { bstrutime(date, sizeof(date), now); }
673 if (!SelectBackupsBeforeDate(ua, rx, date)) { return 0; }
674 break;
675 case 5: /* select backup at specified time */
676 if (!have_date) {
677 if (!get_date(ua, date, sizeof(date))) { return 0; }
678 }
679 if (!SelectBackupsBeforeDate(ua, rx, date)) { return 0; }
680 break;
681 case 6: /* Enter files */
682 if (!have_date) { bstrutime(date, sizeof(date), now); }
683 if (!GetClientName(ua, rx)) { return 0; }
684 ua->SendMsg(
685 _("Enter file names with paths, or < to enter a filename\n"
686 "containing a list of file names with paths, and Terminate\n"
687 "them with a blank line.\n"));
688 for (;;) {
689 if (!GetCmd(ua, _("Enter full filename: "))) { return 0; }
690 len = strlen(ua->cmd);
691 if (len == 0) { break; }
692 InsertOneFileOrDir(ua, rx, date, false);
693 }
694 return 2;
695 case 7: /* enter files backed up before specified time */
696 if (!have_date) {
697 if (!get_date(ua, date, sizeof(date))) { return 0; }
698 }
699 if (!GetClientName(ua, rx)) { return 0; }
700 ua->SendMsg(
701 _("Enter file names with paths, or < to enter a filename\n"
702 "containing a list of file names with paths, and Terminate\n"
703 "them with a blank line.\n"));
704 for (;;) {
705 if (!GetCmd(ua, _("Enter full filename: "))) { return 0; }
706 len = strlen(ua->cmd);
707 if (len == 0) { break; }
708 InsertOneFileOrDir(ua, rx, date, false);
709 }
710 return 2;
711
712 case 8: /* Find JobIds for current backup */
713 if (!have_date) { bstrutime(date, sizeof(date), now); }
714 if (!SelectBackupsBeforeDate(ua, rx, date)) { return 0; }
715 done = false;
716 break;
717
718 case 9: /* Find JobIds for give date */
719 if (!have_date) {
720 if (!get_date(ua, date, sizeof(date))) { return 0; }
721 }
722 if (!SelectBackupsBeforeDate(ua, rx, date)) { return 0; }
723 done = false;
724 break;
725
726 case 10: /* Enter directories */
727 if (*rx->JobIds != 0) {
728 ua->SendMsg(_("You have already selected the following JobIds: %s\n"),
729 rx->JobIds);
730 } else if (GetCmd(ua,
731 _("Enter JobId(s), comma separated, to restore: "))) {
732 if (*rx->JobIds != 0 && *ua->cmd) { PmStrcat(rx->JobIds, ","); }
733 PmStrcat(rx->JobIds, ua->cmd);
734 }
735 if (*rx->JobIds == 0 || *rx->JobIds == '.') {
736 *rx->JobIds = 0;
737 return 0; /* nothing entered, return */
738 }
739 if (!have_date) { bstrutime(date, sizeof(date), now); }
740 if (!GetClientName(ua, rx)) { return 0; }
741 ua->SendMsg(
742 _("Enter full directory names or start the name\n"
743 "with a < to indicate it is a filename containing a list\n"
744 "of directories and Terminate them with a blank line.\n"));
745 for (;;) {
746 if (!GetCmd(ua, _("Enter directory name: "))) { return 0; }
747 len = strlen(ua->cmd);
748 if (len == 0) { break; }
749 /* Add trailing slash to end of directory names */
750 if (ua->cmd[0] != '<' && !IsPathSeparator(ua->cmd[len - 1])) {
751 strcat(ua->cmd, "/");
752 }
753 InsertOneFileOrDir(ua, rx, date, true);
754 }
755 return 2;
756
757 case 11: /* Choose a jobid and select jobs */
758 if (!GetCmd(ua, _("Enter JobId to get the state to restore: "))
759 || !IsAnInteger(ua->cmd)) {
760 return 0;
761 }
762 {
763 JobDbRecord jr;
764 jr.JobId = str_to_int64(ua->cmd);
765 if (!ua->db->GetJobRecord(ua->jcr, &jr)) {
766 ua->ErrorMsg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
767 ua->cmd, ua->db->strerror());
768 return 0;
769 }
770 ua->SendMsg(_("Selecting jobs to build the Full state at %s\n"),
771 jr.cStartTime);
772 jr.JobLevel = L_INCREMENTAL; /* Take Full+Diff+Incr */
773 if (!ua->db->AccurateGetJobids(ua->jcr, &jr, &jobids)) { return 0; }
774 }
775 PmStrcpy(rx->JobIds, jobids.GetAsString().c_str());
776 Dmsg1(30, "Item 12: jobids = %s\n", rx->JobIds);
777 break;
778 case 12: /* Cancel or quit */
779 return 0;
780 }
781 }
782
783 POOLMEM* JobIds = GetPoolMemory(PM_FNAME);
784 *JobIds = 0;
785 rx->TotalFiles = 0;
786 /*
787 * Find total number of files to be restored, and filter the JobId
788 * list to contain only ones permitted by the ACL conditions.
789 */
790 JobDbRecord jr;
791 for (p = rx->JobIds;;) {
792 char ed1[50];
793 int status = GetNextJobidFromList(&p, &JobId);
794 if (status < 0) {
795 ua->ErrorMsg(_("Invalid JobId in list.\n"));
796 FreePoolMemory(JobIds);
797 return 0;
798 }
799 if (status == 0) { break; }
800 if (jr.JobId == JobId) { continue; /* duplicate of last JobId */ }
801 jr = JobDbRecord{};
802 jr.JobId = JobId;
803 if (!ua->db->GetJobRecord(ua->jcr, &jr)) {
804 ua->ErrorMsg(_("Unable to get Job record for JobId=%s: ERR=%s\n"),
805 edit_int64(JobId, ed1), ua->db->strerror());
806 FreePoolMemory(JobIds);
807 return 0;
808 }
809 if (!ua->AclAccessOk(Job_ACL, jr.Name, true)) {
810 ua->ErrorMsg(
811 _("Access to JobId=%s (Job \"%s\") not authorized. Not selected.\n"),
812 edit_int64(JobId, ed1), jr.Name);
813 continue;
814 }
815 if (*JobIds != 0) { PmStrcat(JobIds, ","); }
816 PmStrcat(JobIds, edit_int64(JobId, ed1));
817 rx->TotalFiles += jr.JobFiles;
818 }
819 FreePoolMemory(rx->JobIds);
820 rx->JobIds = JobIds; /* Set ACL filtered list */
821 if (*rx->JobIds == 0) {
822 ua->WarningMsg(_("No Jobs selected.\n"));
823 return 0;
824 }
825
826 if (strchr(rx->JobIds, ',')) {
827 ua->InfoMsg(_("You have selected the following JobIds: %s\n"), rx->JobIds);
828 } else {
829 ua->InfoMsg(_("You have selected the following JobId: %s\n"), rx->JobIds);
830 }
831 return true;
832 }
833
834 /**
835 * Get date from user
836 */
get_date(UaContext * ua,char * date,int date_len)837 static bool get_date(UaContext* ua, char* date, int date_len)
838 {
839 ua->SendMsg(
840 _("The restored files will the most current backup\n"
841 "BEFORE the date you specify below.\n\n"));
842 for (;;) {
843 if (!GetCmd(ua, _("Enter date as YYYY-MM-DD HH:MM:SS :"))) { return false; }
844 if (StrToUtime(ua->cmd) != 0) { break; }
845 ua->ErrorMsg(_("Improper date format.\n"));
846 }
847 bstrncpy(date, ua->cmd, date_len);
848 return true;
849 }
850
851 /**
852 * Insert a single file, or read a list of files from a file
853 */
InsertOneFileOrDir(UaContext * ua,RestoreContext * rx,char * date,bool dir)854 static void InsertOneFileOrDir(UaContext* ua,
855 RestoreContext* rx,
856 char* date,
857 bool dir)
858 {
859 FILE* ffd;
860 char file[5000];
861 char* p = ua->cmd;
862 int line = 0;
863
864 switch (*p) {
865 case '<':
866 p++;
867 if ((ffd = fopen(p, "rb")) == NULL) {
868 BErrNo be;
869 ua->ErrorMsg(_("Cannot open file %s: ERR=%s\n"), p, be.bstrerror());
870 break;
871 }
872 while (fgets(file, sizeof(file), ffd)) {
873 line++;
874 if (dir) {
875 if (!InsertDirIntoFindexList(ua, rx, file, date)) {
876 ua->ErrorMsg(_("Error occurred on line %d of file \"%s\"\n"), line,
877 p);
878 }
879 } else {
880 if (!InsertFileIntoFindexList(ua, rx, file, date)) {
881 ua->ErrorMsg(_("Error occurred on line %d of file \"%s\"\n"), line,
882 p);
883 }
884 }
885 }
886 fclose(ffd);
887 break;
888 case '?':
889 p++;
890 InsertTableIntoFindexList(ua, rx, p);
891 break;
892 default:
893 if (dir) {
894 InsertDirIntoFindexList(ua, rx, ua->cmd, date);
895 } else {
896 InsertFileIntoFindexList(ua, rx, ua->cmd, date);
897 }
898 break;
899 }
900 }
901
902 /**
903 * For a given file (path+filename), split into path and file, then
904 * lookup the most recent backup in the catalog to get the JobId
905 * and FileIndex, then insert them into the findex list.
906 */
InsertFileIntoFindexList(UaContext * ua,RestoreContext * rx,char * file,char * date)907 static bool InsertFileIntoFindexList(UaContext* ua,
908 RestoreContext* rx,
909 char* file,
910 char* date)
911 {
912 StripTrailingNewline(file);
913 SplitPathAndFilename(ua, rx, file);
914
915 if (*rx->JobIds == 0) {
916 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_jobid_fileindex, date,
917 rx->path, rx->fname, rx->ClientName);
918 } else {
919 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_jobids_fileindex,
920 rx->JobIds, date, rx->path, rx->fname, rx->ClientName);
921 }
922
923 /*
924 * Find and insert jobid and File Index
925 */
926 rx->found = false;
927 if (!ua->db->SqlQuery(rx->query, JobidFileindexHandler, (void*)rx)) {
928 ua->ErrorMsg(_("Query failed: %s. ERR=%s\n"), rx->query,
929 ua->db->strerror());
930 }
931 if (!rx->found) {
932 ua->ErrorMsg(_("No database record found for: %s\n"), file);
933 return true;
934 }
935 return true;
936 }
937
938 /**
939 * For a given path lookup the most recent backup in the catalog
940 * to get the JobId and FileIndexes of all files in that directory.
941 */
InsertDirIntoFindexList(UaContext * ua,RestoreContext * rx,char * dir,char * date)942 static bool InsertDirIntoFindexList(UaContext* ua,
943 RestoreContext* rx,
944 char* dir,
945 char* date)
946 {
947 StripTrailingJunk(dir);
948
949 if (*rx->JobIds == 0) {
950 ua->ErrorMsg(_("No JobId specified cannot continue.\n"));
951 return false;
952 } else {
953 ua->db->FillQuery(rx->query,
954 BareosDb::SQL_QUERY::uar_jobid_fileindex_from_dir,
955 rx->JobIds, dir, rx->ClientName);
956 }
957
958 /*
959 * Find and insert jobid and File Index
960 */
961 rx->found = false;
962 if (!ua->db->SqlQuery(rx->query, JobidFileindexHandler, (void*)rx)) {
963 ua->ErrorMsg(_("Query failed: %s. ERR=%s\n"), rx->query,
964 ua->db->strerror());
965 }
966 if (!rx->found) {
967 ua->ErrorMsg(_("No database record found for: %s\n"), dir);
968 return true;
969 }
970 return true;
971 }
972
973 /**
974 * Get the JobId and FileIndexes of all files in the specified table
975 */
InsertTableIntoFindexList(UaContext * ua,RestoreContext * rx,char * table)976 static bool InsertTableIntoFindexList(UaContext* ua,
977 RestoreContext* rx,
978 char* table)
979 {
980 StripTrailingJunk(table);
981
982 ua->db->FillQuery(rx->query,
983 BareosDb::SQL_QUERY::uar_jobid_fileindex_from_table, table);
984
985 /*
986 * Find and insert jobid and File Index
987 */
988 rx->found = false;
989 if (!ua->db->SqlQuery(rx->query, JobidFileindexHandler, (void*)rx)) {
990 ua->ErrorMsg(_("Query failed: %s. ERR=%s\n"), rx->query,
991 ua->db->strerror());
992 }
993 if (!rx->found) {
994 ua->ErrorMsg(_("No table found: %s\n"), table);
995 return true;
996 }
997 return true;
998 }
999
SplitPathAndFilename(UaContext * ua,RestoreContext * rx,char * name)1000 static void SplitPathAndFilename(UaContext* ua, RestoreContext* rx, char* name)
1001 {
1002 char *p, *f;
1003
1004 /* Find path without the filename.
1005 * I.e. everything after the last / is a "filename".
1006 * OK, maybe it is a directory name, but we treat it like
1007 * a filename. If we don't find a / then the whole name
1008 * must be a path name (e.g. c:).
1009 */
1010 for (p = f = name; *p; p++) {
1011 if (IsPathSeparator(*p)) { f = p; /* set pos of last slash */ }
1012 }
1013 if (IsPathSeparator(*f)) { /* did we find a slash? */
1014 f++; /* yes, point to filename */
1015 } else { /* no, whole thing must be path name */
1016 f = p;
1017 }
1018
1019 /* If filename doesn't exist (i.e. root directory), we
1020 * simply create a blank name consisting of a single
1021 * space. This makes handling zero length filenames
1022 * easier.
1023 */
1024 rx->fnl = p - f;
1025 if (rx->fnl > 0) {
1026 rx->fname = CheckPoolMemorySize(rx->fname, 2 * (rx->fnl) + 1);
1027 ua->db->EscapeString(ua->jcr, rx->fname, f, rx->fnl);
1028 } else {
1029 rx->fname[0] = 0;
1030 rx->fnl = 0;
1031 }
1032
1033 rx->pnl = f - name;
1034 if (rx->pnl > 0) {
1035 rx->path = CheckPoolMemorySize(rx->path, 2 * (rx->pnl) + 1);
1036 ua->db->EscapeString(ua->jcr, rx->path, name, rx->pnl);
1037 } else {
1038 rx->path[0] = 0;
1039 rx->pnl = 0;
1040 }
1041
1042 Dmsg2(100, "split path=%s file=%s\n", rx->path, rx->fname);
1043 }
1044
AskForFileregex(UaContext * ua,RestoreContext * rx)1045 static bool AskForFileregex(UaContext* ua, RestoreContext* rx)
1046 {
1047 if (FindArg(ua, NT_("all")) >= 0) { /* if user enters all on command line */
1048 return true; /* select everything */
1049 }
1050 ua->SendMsg(
1051 _("\n\nFor one or more of the JobIds selected, no files were found,\n"
1052 "so file selection is not possible.\n"
1053 "Most likely your retention policy pruned the files.\n"));
1054 if (GetYesno(ua, _("\nDo you want to restore all the files? (yes|no): "))) {
1055 if (ua->pint32_val) { return true; }
1056
1057 while (GetCmd(
1058 ua, _("\nRegexp matching files to restore? (empty to abort): "))) {
1059 if (ua->cmd[0] == '\0') {
1060 break;
1061 } else {
1062 regex_t* fileregex_re{};
1063 int rc;
1064 char errmsg[500] = "";
1065
1066 fileregex_re = (regex_t*)malloc(sizeof(regex_t));
1067 rc = regcomp(fileregex_re, ua->cmd, REG_EXTENDED | REG_NOSUB);
1068 if (rc != 0) { regerror(rc, fileregex_re, errmsg, sizeof(errmsg)); }
1069 regfree(fileregex_re);
1070 free(fileregex_re);
1071 if (*errmsg) {
1072 ua->SendMsg(_("Regex compile error: %s\n"), errmsg);
1073 } else {
1074 rx->bsr->fileregex = strdup(ua->cmd);
1075 return true;
1076 }
1077 }
1078 }
1079 }
1080
1081 return false;
1082 }
1083
1084 /* Walk on the delta_list of a TREE_NODE item and insert all parts
1085 * TODO: Optimize for bootstrap creation, remove recursion
1086 * 6 -> 5 -> 4 -> 3 -> 2 -> 1 -> 0
1087 * should insert as
1088 * 0, 1, 2, 3, 4, 5, 6
1089 */
AddDeltaListFindex(RestoreContext * rx,struct delta_list * lst)1090 static void AddDeltaListFindex(RestoreContext* rx, struct delta_list* lst)
1091 {
1092 if (lst == NULL) { return; }
1093 if (lst->next) { AddDeltaListFindex(rx, lst->next); }
1094 AddFindex(rx->bsr.get(), lst->JobId, lst->FileIndex);
1095 }
1096
BuildDirectoryTree(UaContext * ua,RestoreContext * rx)1097 static bool BuildDirectoryTree(UaContext* ua, RestoreContext* rx)
1098 {
1099 TreeContext tree;
1100 JobId_t JobId, last_JobId;
1101 const char* p;
1102 bool OK = true;
1103 char ed1[50];
1104
1105 /*
1106 * Build the directory tree containing JobIds user selected
1107 */
1108 tree.root = new_tree(rx->TotalFiles);
1109 tree.ua = ua;
1110 tree.all = rx->all;
1111 last_JobId = 0;
1112
1113 /*
1114 * For display purposes, the same JobId, with different volumes may
1115 * appear more than once, however, we only insert it once.
1116 */
1117 p = rx->JobIds;
1118 tree.FileEstimate = 0;
1119
1120 if (GetNextJobidFromList(&p, &JobId) > 0) {
1121 /*
1122 * Use first JobId as estimate of the number of files to restore
1123 */
1124 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_count_files,
1125 edit_int64(JobId, ed1));
1126 if (!ua->db->SqlQuery(rx->query, RestoreCountHandler, (void*)rx)) {
1127 ua->ErrorMsg("%s\n", ua->db->strerror());
1128 }
1129 if (rx->found) {
1130 /*
1131 * Add about 25% more than this job for over estimate
1132 */
1133 tree.FileEstimate = rx->JobId + (rx->JobId >> 2);
1134 tree.DeltaCount = rx->JobId / 50; /* print 50 ticks */
1135 }
1136 }
1137
1138 ua->InfoMsg(_("\nBuilding directory tree for JobId(s) %s ... "), rx->JobIds);
1139
1140 ua->LogAuditEventInfoMsg(_("Building directory tree for JobId(s) %s"),
1141 rx->JobIds);
1142
1143 if (!ua->db->GetFileList(ua->jcr, rx->JobIds, false /* do not use md5 */,
1144 true /* get delta */, InsertTreeHandler,
1145 (void*)&tree)) {
1146 ua->ErrorMsg("%s", ua->db->strerror());
1147 }
1148
1149 if (*rx->BaseJobIds) {
1150 PmStrcat(rx->JobIds, ",");
1151 PmStrcat(rx->JobIds, rx->BaseJobIds);
1152 }
1153
1154 /*
1155 * At this point, the tree is built, so we can garbage collect
1156 * any memory released by the SQL engine that RedHat has
1157 * not returned to the OS :-(
1158 */
1159 GarbageCollectMemory();
1160
1161 /*
1162 * Look at the first JobId on the list (presumably the oldest) and
1163 * if it is marked purged, don't do the manual selection because
1164 * the Job was pruned, so the tree is incomplete.
1165 */
1166 if (tree.FileCount != 0) {
1167 /*
1168 * Find out if any Job is purged
1169 */
1170 Mmsg(rx->query, "SELECT SUM(PurgedFiles) FROM Job WHERE JobId IN (%s)",
1171 rx->JobIds);
1172 if (!ua->db->SqlQuery(rx->query, RestoreCountHandler, (void*)rx)) {
1173 ua->ErrorMsg("%s\n", ua->db->strerror());
1174 }
1175 /*
1176 * rx->JobId is the PurgedFiles flag
1177 */
1178 if (rx->found && rx->JobId > 0) {
1179 tree.FileCount = 0; /* set count to zero, no tree selection */
1180 }
1181 }
1182
1183 if (tree.FileCount == 0) {
1184 OK = AskForFileregex(ua, rx);
1185 if (OK) {
1186 last_JobId = 0;
1187 for (p = rx->JobIds; GetNextJobidFromList(&p, &JobId) > 0;) {
1188 if (JobId == last_JobId) { continue; /* eliminate duplicate JobIds */ }
1189 AddFindexAll(rx->bsr.get(), JobId);
1190 }
1191 }
1192 } else {
1193 char ec1[50];
1194 if (tree.all) {
1195 ua->InfoMsg(
1196 _("\n%s files inserted into the tree and marked for extraction.\n"),
1197 edit_uint64_with_commas(tree.FileCount, ec1));
1198 } else {
1199 ua->InfoMsg(_("\n%s files inserted into the tree.\n"),
1200 edit_uint64_with_commas(tree.FileCount, ec1));
1201 }
1202
1203 if (FindArg(ua, NT_("done")) < 0) {
1204 /*
1205 * Let the user interact in selecting which files to restore
1206 */
1207 OK = UserSelectFilesFromTree(&tree);
1208 }
1209
1210 /*
1211 * Walk down through the tree finding all files marked to be
1212 * extracted making a bootstrap file.
1213 */
1214 if (OK) {
1215 for (TREE_NODE* node = FirstTreeNode(tree.root); node;
1216 node = NextTreeNode(node)) {
1217 Dmsg2(400, "FI=%d node=0x%x\n", node->FileIndex, node);
1218 if (node->extract || node->extract_dir) {
1219 Dmsg3(400, "JobId=%lld type=%d FI=%d\n", (uint64_t)node->JobId,
1220 node->type, node->FileIndex);
1221 /* TODO: optimize bsr insertion when jobid are non sorted */
1222 AddDeltaListFindex(rx, node->delta_list);
1223 AddFindex(rx->bsr.get(), node->JobId, node->FileIndex);
1224 if (node->extract && node->type != TN_NEWDIR) {
1225 rx->selected_files++; /* count only saved files */
1226 }
1227 }
1228 }
1229 }
1230 }
1231
1232 /*
1233 * We keep the tree with selected restore files.
1234 * For NDMP restores its used in the DMA to know what to restore.
1235 * The tree is freed by the DMA when its done.
1236 */
1237 ua->jcr->impl->restore_tree_root = tree.root;
1238
1239 return OK;
1240 }
1241
1242 /**
1243 * This routine is used to get the current backup or a backup before the
1244 * specified date.
1245 */
SelectBackupsBeforeDate(UaContext * ua,RestoreContext * rx,char * date)1246 static bool SelectBackupsBeforeDate(UaContext* ua,
1247 RestoreContext* rx,
1248 char* date)
1249 {
1250 int i;
1251 ClientDbRecord cr;
1252 FileSetDbRecord fsr;
1253 bool ok = false;
1254 char ed1[50], ed2[50];
1255 char pool_select[MAX_NAME_LENGTH];
1256 char fileset_name[MAX_NAME_LENGTH];
1257
1258 /*
1259 * Create temp tables
1260 */
1261 ua->db->SqlQuery(BareosDb::SQL_QUERY::uar_del_temp);
1262 ua->db->SqlQuery(BareosDb::SQL_QUERY::uar_del_temp1);
1263
1264 if (!ua->db->SqlQuery(BareosDb::SQL_QUERY::uar_create_temp)) {
1265 ua->ErrorMsg("%s\n", ua->db->strerror());
1266 }
1267 if (!ua->db->SqlQuery(BareosDb::SQL_QUERY::uar_create_temp1)) {
1268 ua->ErrorMsg("%s\n", ua->db->strerror());
1269 }
1270 /*
1271 * Select Client from the Catalog
1272 */
1273 if (!GetClientDbr(ua, &cr)) { goto bail_out; }
1274 rx->ClientName = strdup(cr.Name);
1275
1276 /*
1277 * Get FileSet
1278 */
1279 i = FindArgWithValue(ua, "FileSet");
1280
1281 if (i >= 0 && IsNameValid(ua->argv[i], ua->errmsg)) {
1282 bstrncpy(fsr.FileSet, ua->argv[i], sizeof(fsr.FileSet));
1283 if (!ua->db->GetFilesetRecord(ua->jcr, &fsr)) {
1284 ua->ErrorMsg(_("Error getting FileSet \"%s\": ERR=%s\n"), fsr.FileSet,
1285 ua->db->strerror());
1286 i = -1;
1287 }
1288 } else if (i >= 0) { /* name is invalid */
1289 ua->ErrorMsg(_("FileSet argument: %s\n"), ua->errmsg);
1290 }
1291
1292 if (i < 0) { /* fileset not found */
1293 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_sel_fileset,
1294 edit_int64(cr.ClientId, ed1), ed1);
1295
1296 StartPrompt(ua, _("The defined FileSet resources are:\n"));
1297 if (!ua->db->SqlQuery(rx->query, FilesetHandler, (void*)ua)) {
1298 ua->ErrorMsg("%s\n", ua->db->strerror());
1299 }
1300 if (DoPrompt(ua, _("FileSet"), _("Select FileSet resource"), fileset_name,
1301 sizeof(fileset_name))
1302 < 0) {
1303 ua->ErrorMsg(_("No FileSet found for client \"%s\".\n"), cr.Name);
1304 goto bail_out;
1305 }
1306
1307 bstrncpy(fsr.FileSet, fileset_name, sizeof(fsr.FileSet));
1308 if (!ua->db->GetFilesetRecord(ua->jcr, &fsr)) {
1309 ua->WarningMsg(_("Error getting FileSet record: %s\n"),
1310 ua->db->strerror());
1311 ua->SendMsg(
1312 _("This probably means you modified the FileSet.\n"
1313 "Continuing anyway.\n"));
1314 }
1315 }
1316
1317 /*
1318 * If Pool specified, add PoolId specification
1319 */
1320 pool_select[0] = 0;
1321 if (rx->pool) {
1322 PoolDbRecord pr;
1323 bstrncpy(pr.Name, rx->pool->resource_name_, sizeof(pr.Name));
1324 if (ua->db->GetPoolRecord(ua->jcr, &pr)) {
1325 Bsnprintf(pool_select, sizeof(pool_select), "AND Media.PoolId=%s ",
1326 edit_int64(pr.PoolId, ed1));
1327 } else {
1328 ua->WarningMsg(_("Pool \"%s\" not found, using any pool.\n"), pr.Name);
1329 }
1330 }
1331
1332 /*
1333 * Find JobId of last Full backup for this client, fileset
1334 */
1335 if (pool_select[0]) {
1336 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_last_full,
1337 edit_int64(cr.ClientId, ed1), date, fsr.FileSet,
1338 pool_select);
1339
1340 if (!ua->db->SqlQuery(rx->query)) {
1341 ua->ErrorMsg("%s\n", ua->db->strerror());
1342 goto bail_out;
1343 }
1344 } else {
1345 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_last_full_no_pool,
1346 edit_int64(cr.ClientId, ed1), date, fsr.FileSet);
1347
1348 if (!ua->db->SqlQuery(rx->query)) {
1349 ua->ErrorMsg("%s\n", ua->db->strerror());
1350 goto bail_out;
1351 }
1352 }
1353
1354 /*
1355 * Find all Volumes used by that JobId
1356 */
1357 if (!ua->db->SqlQuery(BareosDb::SQL_QUERY::uar_full)) {
1358 ua->ErrorMsg("%s\n", ua->db->strerror());
1359 goto bail_out;
1360 }
1361
1362 /*
1363 * Note, this is needed because I don't seem to get the callback from the call
1364 * just above.
1365 */
1366 rx->JobTDate = 0;
1367 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_sel_all_temp1);
1368 if (!ua->db->SqlQuery(rx->query, LastFullHandler, (void*)rx)) {
1369 ua->WarningMsg("%s\n", ua->db->strerror());
1370 }
1371 if (rx->JobTDate == 0) {
1372 ua->ErrorMsg(_("No Full backup before %s found.\n"), date);
1373 goto bail_out;
1374 }
1375
1376 /*
1377 * Now find most recent Differential Job after Full save, if any
1378 */
1379 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_dif,
1380 edit_uint64(rx->JobTDate, ed1), date,
1381 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1382 if (!ua->db->SqlQuery(rx->query)) {
1383 ua->WarningMsg("%s\n", ua->db->strerror());
1384 }
1385
1386 /*
1387 * Now update JobTDate to look into Differential, if any
1388 */
1389 rx->JobTDate = 0;
1390 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_sel_all_temp);
1391 if (!ua->db->SqlQuery(rx->query, LastFullHandler, (void*)rx)) {
1392 ua->WarningMsg("%s\n", ua->db->strerror());
1393 }
1394 if (rx->JobTDate == 0) {
1395 ua->ErrorMsg(_("No Full backup before %s found.\n"), date);
1396 goto bail_out;
1397 }
1398
1399 /*
1400 * Now find all Incremental Jobs after Full/dif save
1401 */
1402 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_inc,
1403 edit_uint64(rx->JobTDate, ed1), date,
1404 edit_int64(cr.ClientId, ed2), fsr.FileSet, pool_select);
1405 if (!ua->db->SqlQuery(rx->query)) {
1406 ua->WarningMsg("%s\n", ua->db->strerror());
1407 }
1408
1409 /*
1410 * Get the JobIds from that list
1411 */
1412 rx->last_jobid[0] = rx->JobIds[0] = 0;
1413
1414 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_sel_jobid_temp);
1415 if (!ua->db->SqlQuery(rx->query, JobidHandler, (void*)rx)) {
1416 ua->WarningMsg("%s\n", ua->db->strerror());
1417 }
1418
1419 if (rx->JobIds[0] != 0) {
1420 if (FindArg(ua, NT_("copies")) > 0) {
1421 /*
1422 * Display a list of all copies
1423 */
1424 ua->db->ListCopiesRecords(ua->jcr, "", rx->JobIds, ua->send, HORZ_LIST);
1425
1426 if (FindArg(ua, NT_("yes")) > 0) {
1427 ua->pint32_val = 1;
1428 } else {
1429 GetYesno(ua,
1430 _("\nDo you want to restore from these copies? (yes|no): "));
1431 }
1432
1433 if (ua->pint32_val) {
1434 PoolMem JobIds(PM_FNAME);
1435
1436 /*
1437 * Change the list of jobs needed to do the restore to the copies of the
1438 * Job.
1439 */
1440 PmStrcpy(JobIds, rx->JobIds);
1441 rx->last_jobid[0] = rx->JobIds[0] = 0;
1442 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_sel_jobid_copies,
1443 JobIds.c_str());
1444 if (!ua->db->SqlQuery(rx->query, JobidHandler, (void*)rx)) {
1445 ua->WarningMsg("%s\n", ua->db->strerror());
1446 }
1447 }
1448 }
1449
1450 /*
1451 * Display a list of Jobs selected for this restore
1452 */
1453 ua->db->FillQuery(rx->query, BareosDb::SQL_QUERY::uar_list_jobs_by_idlist,
1454 rx->JobIds);
1455 ua->db->ListSqlQuery(ua->jcr, rx->query, ua->send, HORZ_LIST, true);
1456
1457 ok = true;
1458 } else {
1459 ua->WarningMsg(_("No jobs found.\n"));
1460 }
1461
1462 bail_out:
1463 ua->db->SqlQuery(BareosDb::SQL_QUERY::drop_deltabs);
1464 ua->db->SqlQuery(BareosDb::SQL_QUERY::uar_del_temp1);
1465
1466 return ok;
1467 }
1468
RestoreCountHandler(void * ctx,int num_fields,char ** row)1469 static int RestoreCountHandler(void* ctx, int num_fields, char** row)
1470 {
1471 RestoreContext* rx = (RestoreContext*)ctx;
1472 rx->JobId = str_to_int64(row[0]);
1473 rx->found = true;
1474 return 0;
1475 }
1476
1477 /**
1478 * Callback handler to get JobId and FileIndex for files
1479 * can insert more than one depending on the caller.
1480 */
JobidFileindexHandler(void * ctx,int num_fields,char ** row)1481 static int JobidFileindexHandler(void* ctx, int num_fields, char** row)
1482 {
1483 RestoreContext* rx = (RestoreContext*)ctx;
1484
1485 Dmsg2(200, "JobId=%s FileIndex=%s\n", row[0], row[1]);
1486 rx->JobId = str_to_int64(row[0]);
1487 AddFindex(rx->bsr.get(), rx->JobId, str_to_int64(row[1]));
1488 rx->found = true;
1489 rx->selected_files++;
1490
1491 JobidHandler(ctx, num_fields, row);
1492
1493 return 0;
1494 }
1495
1496 /**
1497 * Callback handler make list of JobIds
1498 */
JobidHandler(void * ctx,int num_fields,char ** row)1499 static int JobidHandler(void* ctx, int num_fields, char** row)
1500 {
1501 RestoreContext* rx = (RestoreContext*)ctx;
1502
1503 if (bstrcmp(rx->last_jobid, row[0])) { return 0; /* duplicate id */ }
1504 bstrncpy(rx->last_jobid, row[0], sizeof(rx->last_jobid));
1505 if (rx->JobIds[0] != 0) { PmStrcat(rx->JobIds, ","); }
1506 PmStrcat(rx->JobIds, row[0]);
1507 return 0;
1508 }
1509
1510 /**
1511 * Callback handler to pickup last Full backup JobTDate
1512 */
LastFullHandler(void * ctx,int num_fields,char ** row)1513 static int LastFullHandler(void* ctx, int num_fields, char** row)
1514 {
1515 RestoreContext* rx = (RestoreContext*)ctx;
1516
1517 rx->JobTDate = str_to_int64(row[1]);
1518 return 0;
1519 }
1520
1521 /**
1522 * Callback handler build FileSet name prompt list
1523 */
FilesetHandler(void * ctx,int num_fields,char ** row)1524 static int FilesetHandler(void* ctx, int num_fields, char** row)
1525 {
1526 /* row[0] = FileSet (name) */
1527 if (row[0]) { AddPrompt((UaContext*)ctx, row[0]); }
1528 return 0;
1529 }
1530
1531 /**
1532 * Free names in the list
1533 */
FreeNameList(NameList * name_list)1534 static void FreeNameList(NameList* name_list)
1535 {
1536 int i;
1537
1538 for (i = 0; i < name_list->num_ids; i++) { free(name_list->name[i]); }
1539 BfreeAndNull(name_list->name);
1540 name_list->max_ids = 0;
1541 name_list->num_ids = 0;
1542 }
1543
FindStorageResource(UaContext * ua,RestoreContext & rx,char * Storage,char * MediaType)1544 void FindStorageResource(UaContext* ua,
1545 RestoreContext& rx,
1546 char* Storage,
1547 char* MediaType)
1548 {
1549 StorageResource* store;
1550
1551 if (rx.store) {
1552 Dmsg1(200, "Already have store=%s\n", rx.store->resource_name_);
1553 return;
1554 }
1555 /*
1556 * Try looking up Storage by name
1557 */
1558 LockRes(my_config);
1559 foreach_res (store, R_STORAGE) {
1560 if (bstrcmp(Storage, store->resource_name_)) {
1561 if (ua->AclAccessOk(Storage_ACL, store->resource_name_)) {
1562 rx.store = store;
1563 }
1564 break;
1565 }
1566 }
1567 UnlockRes(my_config);
1568
1569 if (rx.store) {
1570 int i;
1571
1572 /*
1573 * Check if an explicit storage resource is given
1574 */
1575 store = NULL;
1576 i = FindArgWithValue(ua, "storage");
1577 if (i > 0) { store = ua->GetStoreResWithName(ua->argv[i]); }
1578 if (store && (store != rx.store)) {
1579 ua->InfoMsg(
1580 _("Warning default storage overridden by \"%s\" on command line.\n"),
1581 store->resource_name_);
1582 rx.store = store;
1583 Dmsg1(200, "Set store=%s\n", rx.store->resource_name_);
1584 }
1585 return;
1586 }
1587
1588 /*
1589 * If no storage resource, try to find one from MediaType
1590 */
1591 if (!rx.store) {
1592 LockRes(my_config);
1593 foreach_res (store, R_STORAGE) {
1594 if (bstrcmp(MediaType, store->media_type)) {
1595 if (ua->AclAccessOk(Storage_ACL, store->resource_name_)) {
1596 rx.store = store;
1597 Dmsg1(200, "Set store=%s\n", rx.store->resource_name_);
1598 if (Storage == NULL) {
1599 ua->WarningMsg(_("Using Storage \"%s\" from MediaType \"%s\".\n"),
1600 store->resource_name_, MediaType);
1601 } else {
1602 ua->WarningMsg(_("Storage \"%s\" not found, using Storage \"%s\" "
1603 "from MediaType \"%s\".\n"),
1604 Storage, store->resource_name_, MediaType);
1605 }
1606 }
1607 UnlockRes(my_config);
1608 return;
1609 }
1610 }
1611 UnlockRes(my_config);
1612 ua->WarningMsg(_("\nUnable to find Storage resource for\n"
1613 "MediaType \"%s\", needed by the Jobs you selected.\n"),
1614 MediaType);
1615 }
1616
1617 /*
1618 * Take command line arg, or ask user if none
1619 */
1620 rx.store = get_storage_resource(ua);
1621 if (rx.store) { Dmsg1(200, "Set store=%s\n", rx.store->resource_name_); }
1622 }
1623 } /* namespace directordaemon */
1624