1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2011-2015 Planets Communications B.V.
5    Copyright (C) 2013-2019 Bareos GmbH & Co. KG
6 
7    This program is Free Software; you can redistribute it and/or
8    modify it under the terms of version three of the GNU Affero General Public
9    License as published by the Free Software Foundation and included
10    in the file LICENSE.
11 
12    This program is distributed in the hope that it will be useful, but
13    WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15    Affero General Public License for more details.
16 
17    You should have received a copy of the GNU Affero General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20    02110-1301, USA.
21 */
22 /*
23  * Restore specific NDMP Data Management Application (DMA) routines
24  * for NDMP_NATIVE restores
25  *
26  * extracted and reorganized from ndmp_dma_restore.c
27  * Philipp Storz, April 2017
28  */
29 
30 #include "include/bareos.h"
31 #include "dird.h"
32 #include "dird/dird_globals.h"
33 #include "dird/storage.h"
34 #include "lib/edit.h"
35 #include "lib/tree.h"
36 
37 #if HAVE_NDMP
38 
39 #define NDMP_NEED_ENV_KEYWORDS 1
40 
41 #include "ndmp/ndmagents.h"
42 #include "dird/ndmp_dma_storage.h"
43 #include "ndmp_dma_priv.h"
44 #include "dird/jcr_private.h"
45 #include "dird/ndmp_dma_restore_common.h"
46 #include "dird/ndmp_dma_generic.h"
47 
48 namespace directordaemon {
49 
50 /*
51  * Fill the NDMP restore environment table with the data for the data agent to
52  * act on.
53  */
fill_restore_environment_ndmp_native(JobControlRecord * jcr,int32_t current_fi,struct ndm_job_param * job)54 static inline bool fill_restore_environment_ndmp_native(
55     JobControlRecord* jcr,
56     int32_t current_fi,
57     struct ndm_job_param* job)
58 {
59   ndmp9_pval pv;
60   PoolMem tape_device;
61   PoolMem destination_path;
62   ndmp_backup_format_option* nbf_options;
63 
64   /*
65    * See if we know this backup format and get it options.
66    */
67   nbf_options = ndmp_lookup_backup_format_options(job->bu_type);
68 
69 
70   /*
71    * Selected JobIds are stored in jcr->JobIds, comma separated
72    * We use the first jobid to get the environment string
73    */
74 
75   JobId_t JobId{str_to_uint32(jcr->JobIds)};
76   if (JobId <= 0) {
77     Jmsg(jcr, M_FATAL, 0, "Impossible JobId: %d", JobId);
78     return false;
79   }
80 
81   if (!jcr->db->GetNdmpEnvironmentString(JobId, NdmpEnvHandler,
82                                          &job->env_tab)) {
83     Jmsg(jcr, M_FATAL, 0,
84          _("Could not load NDMP environment. Cannot continue without one.\n"));
85     return false;
86   }
87 
88   /* try to extract ndmp_filesystem from environment */
89   char* ndmp_filesystem = nullptr;
90   for (struct ndm_env_entry* entry = job->env_tab.head; entry;
91        entry = entry->next) {
92     if (bstrcmp(entry->pval.name, ndmp_env_keywords[NDMP_ENV_KW_FILESYSTEM])) {
93       ndmp_filesystem = entry->pval.value;
94       break;
95     }
96   }
97 
98   if (!ndmp_filesystem) {
99     Jmsg(jcr, M_FATAL, 0, _("No %s in NDMP environment. Cannot continue.\n"),
100          ndmp_env_keywords[NDMP_ENV_KW_FILESYSTEM]);
101     return false;
102   }
103 
104   /*
105    * See where to restore the data.
106    */
107   char* restore_prefix = nullptr;
108   if (jcr->where) {
109     restore_prefix = jcr->where;
110   } else {
111     restore_prefix = jcr->impl->res.job->RestoreWhere;
112   }
113 
114   if (!restore_prefix) { return false; }
115 
116   /*
117    * Tell the data engine where to restore.
118    */
119   if (nbf_options && nbf_options->restore_prefix_relative) {
120     switch (*restore_prefix) {
121       case '^':
122         /*
123          * Use the restore_prefix as an absolute restore prefix.
124          * We skip the leading ^ that is the trigger for absolute restores.
125          */
126         PmStrcpy(destination_path, restore_prefix + 1);
127         break;
128       default:
129         /*
130          * Use the restore_prefix as an relative restore prefix.
131          */
132         if (strlen(restore_prefix) == 1 && *restore_prefix == '/') {
133           PmStrcpy(destination_path, ndmp_filesystem);
134         } else {
135           PmStrcpy(destination_path, ndmp_filesystem);
136           PmStrcat(destination_path, restore_prefix);
137         }
138     }
139   } else {
140     if (strlen(restore_prefix) == 1 && *restore_prefix == '/') {
141       /*
142        * Use the original pathname as restore prefix.
143        */
144       PmStrcpy(destination_path, ndmp_filesystem);
145     } else {
146       /*
147        * Use the restore_prefix as an absolute restore prefix.
148        */
149       PmStrcpy(destination_path, restore_prefix);
150     }
151   }
152 
153   pv.name = ndmp_env_keywords[NDMP_ENV_KW_PREFIX];
154   pv.value = ndmp_filesystem;
155   ndma_store_env_list(&job->env_tab, &pv);
156 
157   if (ndmp_filesystem && SetFilesToRestoreNdmpNative(jcr, job, current_fi,
158                                                      destination_path.c_str(),
159                                                      ndmp_filesystem) == 0) {
160     Jmsg(jcr, M_INFO, 0, _("No files selected for restore\n"));
161     return false;
162   }
163   return true;
164 }
165 
166 /*
167  * See in the tree with selected files what files were selected to be restored.
168  */
SetFilesToRestoreNdmpNative(JobControlRecord * jcr,struct ndm_job_param * job,int32_t FileIndex,const char * restore_prefix,const char * ndmp_filesystem)169 int SetFilesToRestoreNdmpNative(JobControlRecord* jcr,
170                                 struct ndm_job_param* job,
171                                 int32_t FileIndex,
172                                 const char* restore_prefix,
173                                 const char* ndmp_filesystem)
174 {
175   int len;
176   int cnt = 0;
177   TREE_NODE *node, *parent;
178   PoolMem restore_pathname, tmp;
179 
180   node = FirstTreeNode(jcr->impl->restore_tree_root);
181   while (node) {
182     /*
183      * node->extract_dir  means that only the directory should be selected for
184      * extraction itself, the subdirs and subfiles are not automaticaly marked
185      * for extraction ( i.e. set node->extract)
186      *
187      * We can use this to select a directory for DDAR.
188      *
189      * If "DIRECT = Y" AND "RECURSIVE = Y" are set, directories in the namelist
190      * will be recursively extracted.
191      *
192      * Restoring a whole directory using this mechanism is much more efficient
193      * than creating an namelist entry for every single file and directory below
194      * the selected one.
195      */
196     if (node->extract_dir || node->extract) {
197       PmStrcpy(restore_pathname, node->fname);
198       /*
199        * Walk up the parent until we hit the head of the list.
200        */
201       for (parent = node->parent; parent; parent = parent->parent) {
202         PmStrcpy(tmp, restore_pathname.c_str());
203         Mmsg(restore_pathname, "%s/%s", parent->fname, tmp.c_str());
204       }
205       /*
206        * only add nodes that have valid DAR info i.e. fhinfo is not
207        * NDMP9_INVALID_U_QUAD
208        */
209       if (node->fhinfo != NDMP9_INVALID_U_QUAD) {
210         /*
211          * See if we need to strip the prefix from the filename.
212          */
213         len = 0;
214         if (ndmp_filesystem &&
215             bstrncmp(restore_pathname.c_str(), ndmp_filesystem,
216                      strlen(ndmp_filesystem))) {
217           len = strlen(ndmp_filesystem);
218         }
219 
220         Jmsg(jcr, M_INFO, 0,
221              _("Namelist add: node:%llu, info:%llu, name:\"%s\" \n"),
222              node->fhnode, node->fhinfo, restore_pathname.c_str());
223 
224         AddToNamelist(job, restore_pathname.c_str() + len, restore_prefix,
225                       (char*)"", (char*)"", node->fhnode, node->fhinfo);
226 
227         cnt++;
228 
229       } else {
230         Jmsg(jcr, M_INFO, 0,
231              _("not added node \"%s\" to namelist because "
232                "of missing fhinfo: node:%llu info:%llu\n"),
233              restore_pathname.c_str(), node->fhnode, node->fhinfo);
234       }
235     }
236     node = NextTreeNode(node);
237   }
238   return cnt;
239 }
240 
241 /**
242  * Execute native NDMP restore.
243  */
DoNdmpNativeRestore(JobControlRecord * jcr)244 static bool DoNdmpNativeRestore(JobControlRecord* jcr)
245 {
246   NIS* nis = NULL;
247   int32_t current_fi = 0;
248   struct ndm_session ndmp_sess;
249   struct ndm_job_param ndmp_job;
250   bool session_initialized = false;
251   bool retval = false;
252   int NdmpLoglevel;
253   char mediabuf[100];
254   slot_number_t ndmp_slot;
255   StorageResource* store = NULL;
256 
257   store = jcr->impl->res.read_storage;
258 
259   memset(&ndmp_sess, 0, sizeof(ndmp_sess));
260 
261   nis = (NIS*)malloc(sizeof(NIS));
262   memset(nis, 0, sizeof(NIS));
263 
264   NdmpLoglevel =
265       std::max(jcr->impl->res.client->ndmp_loglevel, me->ndmp_loglevel);
266 
267   if (!NdmpBuildClientAndStorageJob(jcr, store, jcr->impl->res.client,
268                                     true, /* init_tape */
269                                     true, /* init_robot */
270                                     NDM_JOB_OP_EXTRACT, &ndmp_job)) {
271     goto cleanup;
272   }
273 
274 
275   if (!ndmp_native_setup_robot_and_tape_for_native_backup_job(jcr, store,
276                                                               ndmp_job)) {
277     Jmsg(jcr, M_ERROR, 0,
278          _("ndmp_native_setup_robot_and_tape_for_native_backup_job failed\n"));
279     goto cleanup;
280   }
281 
282 
283   /*
284    * Get media from database and put into ndmmmedia table
285    */
286 
287   GetNdmmediaInfoFromDatabase(&ndmp_job.media_tab, jcr);
288 
289   for (ndmmedia* media = ndmp_job.media_tab.head; media; media = media->next) {
290     ndmmedia_to_str(media, mediabuf);
291     Jmsg(jcr, M_INFO, 0, _("Media: %s\n"), mediabuf);
292   }
293 
294   for (ndmmedia* media = ndmp_job.media_tab.head; media; media = media->next) {
295     Jmsg(jcr, M_INFO, 0, _("Logical slot for volume %s is %d\n"), media->label,
296          media->slot_addr);
297     ndmp_slot = GetElementAddressByBareosSlotNumber(
298         &store->runtime_storage_status->storage_mapping,
299         slot_type_t::kSlotTypeStorage, media->slot_addr);
300     media->slot_addr = ndmp_slot;
301     Jmsg(jcr, M_INFO, 0, _("Physical(NDMP) slot for volume %s is %d\n"),
302          media->label, media->slot_addr);
303     Jmsg(jcr, M_INFO, 0, _("Media Index of volume %s is %d\n"), media->label,
304          media->index);
305   }
306 
307 
308   if (!NdmpValidateJob(jcr, &ndmp_job)) {
309     Jmsg(jcr, M_ERROR, 0, _("ERROR in ndmp_validate_job\n"));
310     goto cleanup_ndmp;
311   }
312 
313   /*
314    * session initialize
315    */
316   ndmp_sess.param =
317       (struct ndm_session_param*)malloc(sizeof(struct ndm_session_param));
318   memset(ndmp_sess.param, 0, sizeof(struct ndm_session_param));
319   ndmp_sess.param->log.deliver = NdmpLoghandler;
320   ndmp_sess.param->log_level =
321       NativeToNdmpLoglevel(NdmpLoglevel, debug_level, nis);
322   nis->jcr = jcr;
323   ndmp_sess.param->log.ctx = nis;
324   ndmp_sess.param->log_tag = strdup("DIR-NDMP");
325 
326   ndmp_sess.conn_snooping = (me->ndmp_snooping) ? 1 : 0;
327   ndmp_sess.control_agent_enabled = 1;
328 
329   ndmp_sess.dump_media_info = 1;  // for debugging
330 
331   jcr->setJobStatus(JS_Running);
332 
333   /*
334    * Initialize the session structure.
335    */
336   if (ndma_session_initialize(&ndmp_sess)) { goto cleanup_ndmp; }
337   session_initialized = true;
338 
339   ndmca_media_calculate_offsets(&ndmp_sess);
340 
341   /*
342    * copy our prepared ndmp_job into the session
343    */
344   memcpy(&ndmp_sess.control_acb->job, &ndmp_job, sizeof(struct ndm_job_param));
345 
346 
347   if (!fill_restore_environment_ndmp_native(jcr, current_fi,
348                                             &ndmp_sess.control_acb->job)) {
349     Jmsg(jcr, M_ERROR, 0, _("ERROR in fill_restore_environment\n"));
350     goto cleanup_ndmp;
351   }
352 
353   /*
354    * Commission the session for a run.
355    */
356   if (ndma_session_commission(&ndmp_sess)) {
357     Jmsg(jcr, M_ERROR, 0, _("ERROR in ndma_session_commission\n"));
358     goto cleanup_ndmp;
359   }
360 
361   /*
362    * Setup the DMA.
363    */
364   if (ndmca_connect_control_agent(&ndmp_sess)) {
365     Jmsg(jcr, M_ERROR, 0, _("ERROR in ndmca_connect_control_agent\n"));
366     goto cleanup_ndmp;
367   }
368 
369   ndmp_sess.conn_open = 1;
370   ndmp_sess.conn_authorized = 1;
371 
372   /*
373    * Let the DMA perform its magic.
374    */
375   if (ndmca_control_agent(&ndmp_sess) != 0) {
376     Jmsg(jcr, M_ERROR, 0, _("ERROR in ndmca_control_agent\n"));
377     goto cleanup_ndmp;
378   }
379 
380   if (!unreserve_ndmp_tapedevice_for_job(store, jcr)) {
381     Jmsg(jcr, M_ERROR, 0, "could not free ndmp tape device %s from job %d",
382          ndmp_job.tape_device, jcr->JobId);
383   }
384 
385   /*
386    * See if there were any errors during the restore.
387    */
388   if (!ExtractPostRestoreStats(jcr, &ndmp_sess)) {
389     Jmsg(jcr, M_ERROR, 0, _("ERROR in ExtractPostRestoreStats\n"));
390     goto cleanup_ndmp;
391   }
392 
393   /*
394    * Reset the NDMP session states.
395    */
396   ndma_session_decommission(&ndmp_sess);
397 
398   /*
399    * Cleanup the job after it has run.
400    */
401   ndma_destroy_env_list(&ndmp_sess.control_acb->job.env_tab);
402   ndma_destroy_env_list(&ndmp_sess.control_acb->job.result_env_tab);
403   ndma_destroy_nlist(&ndmp_sess.control_acb->job.nlist_tab);
404 
405   /*
406    * Destroy the session.
407    */
408   ndma_session_destroy(&ndmp_sess);
409 
410   /*
411    * Free the param block.
412    */
413   free(ndmp_sess.param->log_tag);
414   free(ndmp_sess.param);
415   ndmp_sess.param = NULL;
416 
417   /*
418    * Reset the initialized state so we don't try to cleanup again.
419    */
420   session_initialized = false;
421 
422   /*
423    * Jump to the generic cleanup done for every Job.
424    */
425   retval = true;
426   goto cleanup;
427 
428 cleanup_ndmp:
429   if (!unreserve_ndmp_tapedevice_for_job(store, jcr)) {
430     Jmsg(jcr, M_ERROR, 0, "could not free ndmp tape device %s from job %d",
431          ndmp_job.tape_device, jcr->JobId);
432   }
433   /*
434    * Only need to cleanup when things are initialized.
435    */
436   if (session_initialized) {
437     ndma_destroy_env_list(&ndmp_sess.control_acb->job.env_tab);
438     ndma_destroy_env_list(&ndmp_sess.control_acb->job.result_env_tab);
439     ndma_destroy_nlist(&ndmp_sess.control_acb->job.nlist_tab);
440 
441     /*
442      * Destroy the session.
443      */
444     ndma_session_destroy(&ndmp_sess);
445   }
446 
447   if (ndmp_sess.param) {
448     if (ndmp_sess.param->log_tag) { free(ndmp_sess.param->log_tag); }
449     free(ndmp_sess.param);
450   }
451 cleanup:
452   free(nis);
453 
454   FreeTree(jcr->impl->restore_tree_root);
455   jcr->impl->restore_tree_root = NULL;
456   return retval;
457 }
458 
459 /*
460  * Run a NDMP restore session.
461  */
DoNdmpRestoreNdmpNative(JobControlRecord * jcr)462 bool DoNdmpRestoreNdmpNative(JobControlRecord* jcr)
463 {
464   int status;
465 
466   jcr->impl->jr.JobLevel = L_FULL; /* Full restore */
467   if (!jcr->db->UpdateJobStartRecord(jcr, &jcr->impl->jr)) {
468     Jmsg(jcr, M_FATAL, 0, "%s", jcr->db->strerror());
469     goto bail_out;
470   }
471   Dmsg0(20, "Updated job start record\n");
472 
473   Dmsg1(20, "RestoreJobId=%d\n", jcr->impl->res.job->RestoreJobId);
474 
475   /*
476    * Validate the Job to have a NDMP client.
477    */
478   if (!NdmpValidateClient(jcr)) { return false; }
479 
480   /*
481    * Print Job Start message
482    */
483   Jmsg(jcr, M_INFO, 0, _("Start Restore Job %s\n"), jcr->Job);
484 
485   if (!DoNdmpNativeRestore(jcr)) { goto bail_out; }
486 
487   status = JS_Terminated;
488   NdmpRestoreCleanup(jcr, status);
489   return true;
490 
491 bail_out:
492   return false;
493 }
494 
495 } /* namespace directordaemon */
496 #endif /* HAVE_NDMP */
497