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