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