1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2011-2016 Planets Communications B.V.
5 Copyright (C) 2013-2018 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 * Marco van Wieringen, May 2015
24 */
25 /**
26 * @file
27 * Backup specific NDMP Data Management Application (DMA) routines
28 */
29
30 #include "include/bareos.h"
31 #include "dird.h"
32 #include "dird/dird_globals.h"
33 #include "dird/job.h"
34 #include "dird/msgchan.h"
35 #include "dird/quota.h"
36 #include "dird/sd_cmds.h"
37 #include "dird/storage.h"
38 #include "lib/edit.h"
39
40 #if HAVE_NDMP
41 #include "dird/ndmp_dma_backup_common.h"
42 #include "dird/ndmp_dma_generic.h"
43
44 #define NDMP_NEED_ENV_KEYWORDS 1
45
46 #include "ndmp/ndmagents.h"
47 #include "ndmp_dma_priv.h"
48 #endif /* HAVE_NDMP */
49
50 namespace directordaemon {
51
52 #if HAVE_NDMP
53
54 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
55
56 /* Imported variables */
57
58 /* Forward referenced functions */
59
60
61 /**
62 * Extract any post backup statistics.
63 */
extract_post_backup_stats(JobControlRecord * jcr,char * filesystem,struct ndm_session * sess)64 static inline bool extract_post_backup_stats(JobControlRecord *jcr,
65 char *filesystem,
66 struct ndm_session *sess)
67 {
68 bool retval = true;
69 struct ndmmedia *media;
70 ndmp_backup_format_option *nbf_options;
71 struct ndm_env_entry *ndm_ee;
72
73 /*
74 * See if we know this backup format and get it options.
75 */
76 nbf_options = ndmp_lookup_backup_format_options(sess->control_acb->job.bu_type);
77
78 /*
79 * See if an error was raised during the backup session.
80 */
81 if (sess->error_raised) {
82 return false;
83 }
84
85 /*
86 * See if there is any media error.
87 */
88 for (media = sess->control_acb->job.result_media_tab.head; media; media = media->next) {
89 if (media->media_open_error ||
90 media->media_io_error ||
91 media->label_io_error ||
92 media->label_mismatch ||
93 media->fmark_error) {
94 retval = false;
95 }
96 }
97
98 /*
99 * Process the FHDB.
100 */
101 ProcessFhdb(&sess->control_acb->job.index_log);
102
103 /*
104 * Update the Job statistics from the NDMP statistics.
105 */
106 jcr->JobBytes += sess->control_acb->job.bytes_written;
107
108 /*
109 * After a successful backup we need to store all NDMP ENV variables
110 * for doing a successful restore operation.
111 */
112 ndm_ee = sess->control_acb->job.result_env_tab.head;
113 while (ndm_ee) {
114 if (!jcr->db->CreateNdmpEnvironmentString(jcr, &jcr->jr, ndm_ee->pval.name, ndm_ee->pval.value)) {
115 break;
116 }
117 ndm_ee = ndm_ee->next;
118 }
119
120 /*
121 * If we are doing a backup type that uses dumplevels save the last used dump level.
122 */
123 if (nbf_options && nbf_options->uses_level) {
124 jcr->db->UpdateNdmpLevelMapping(jcr, &jcr->jr, filesystem, sess->control_acb->job.bu_level);
125 }
126
127 return retval;
128 }
129
130 /**
131 * Setup a NDMP backup session.
132 */
DoNdmpBackupInit(JobControlRecord * jcr)133 bool DoNdmpBackupInit(JobControlRecord *jcr)
134 {
135 FreeRstorage(jcr); /* we don't read so release */
136
137 if (!AllowDuplicateJob(jcr)) {
138 return false;
139 }
140
141 jcr->jr.PoolId = GetOrCreatePoolRecord(jcr, jcr->res.pool->name());
142 if (jcr->jr.PoolId == 0) {
143 return false;
144 }
145
146 jcr->start_time = time(NULL);
147 jcr->jr.StartTime = jcr->start_time;
148 if (!jcr->db->UpdateJobStartRecord(jcr, &jcr->jr)) {
149 Jmsg(jcr, M_FATAL, 0, "%s", jcr->db->strerror());
150 return false;
151 }
152
153 /*
154 * If pool storage specified, use it instead of job storage
155 */
156 CopyWstorage(jcr, jcr->res.pool->storage, _("Pool resource"));
157
158 if (!jcr->res.write_storage_list) {
159 Jmsg(jcr, M_FATAL, 0, _("No Storage specification found in Job or Pool.\n"));
160 return false;
161 }
162
163 /*
164 * Validate the Job to have a NDMP client and NDMP storage.
165 */
166 if (!NdmpValidateClient(jcr)) {
167 return false;
168 }
169
170 if (!NdmpValidateStorage(jcr)) {
171 return false;
172 }
173
174 /*
175 * For now we only allow NDMP backups to bareos SD's
176 * so we need a paired storage definition.
177 */
178 if (!HasPairedStorage(jcr)) {
179 Jmsg(jcr, M_FATAL, 0,
180 _("Write storage doesn't point to storage definition with paired storage option.\n"));
181 return false;
182 }
183
184 return true;
185 }
186
187
188 /**
189 * Run a NDMP backup session.
190 */
DoNdmpBackup(JobControlRecord * jcr)191 bool DoNdmpBackup(JobControlRecord *jcr)
192 {
193 unsigned int cnt;
194 int i, status;
195 char ed1[100];
196 NIS *nis = NULL;
197 FilesetResource *fileset;
198 struct ndm_job_param ndmp_job;
199 struct ndm_session ndmp_sess;
200 bool session_initialized = false;
201 bool retval = false;
202 int NdmpLoglevel;
203
204 NdmpLoglevel = std::max(jcr->res.client->ndmp_loglevel, me->ndmp_loglevel);
205
206 /*
207 * Print Job Start message
208 */
209 Jmsg(jcr, M_INFO, 0, _("Start NDMP Backup JobId %s, Job=%s\n"),
210 edit_uint64(jcr->JobId, ed1), jcr->Job);
211
212 jcr->setJobStatus(JS_Running);
213 Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->jr.JobId, jcr->jr.JobLevel);
214 if (!jcr->db->UpdateJobStartRecord(jcr, &jcr->jr)) {
215 Jmsg(jcr, M_FATAL, 0, "%s", jcr->db->strerror());
216 return false;
217 }
218
219 if (CheckHardquotas(jcr)) {
220 Jmsg(jcr, M_FATAL, 0, "Quota Exceeded. Job terminated.");
221 return false;
222 }
223
224 if (CheckSoftquotas(jcr)) {
225 Dmsg0(10, "Quota exceeded\n");
226 Jmsg(jcr, M_FATAL, 0, "Soft Quota Exceeded / Grace Time expired. Job terminated.");
227 return false;
228 }
229
230 /*
231 * If we have a paired storage definition create a native connection
232 * to a Storage daemon and make it ready to receive a backup.
233 * The setup is more or less the same as for a normal non NDMP backup
234 * only the data doesn't come in from a FileDaemon but from a NDMP
235 * data mover which moves the data from the NDMP DATA AGENT to the NDMP
236 * TAPE AGENT.
237 */
238 if (jcr->res.write_storage->paired_storage) {
239 SetPairedStorage(jcr);
240
241 jcr->setJobStatus(JS_WaitSD);
242 if (!ConnectToStorageDaemon(jcr, 10, me->SDConnectTimeout, true)) {
243 return false;
244 }
245
246 /*
247 * Now start a job with the Storage daemon
248 */
249 if (!StartStorageDaemonJob(jcr, NULL, jcr->res.write_storage_list)) {
250 return false;
251 }
252
253 /*
254 * Start the job prior to starting the message thread below
255 * to avoid two threads from using the BareosSocket structure at
256 * the same time.
257 */
258 if (!jcr->store_bsock->fsend("run")) {
259 return false;
260 }
261
262 /*
263 * Now start a Storage daemon message thread. Note,
264 * this thread is used to provide the catalog services
265 * for the backup job.
266 */
267 if (!StartStorageDaemonMessageThread(jcr)) {
268 return false;
269 }
270 Dmsg0(150, "Storage daemon connection OK\n");
271 }
272
273 status = 0;
274
275 /*
276 * Initialize the ndmp backup job. We build the generic job only once
277 * and reuse the job definition for each separate sub-backup we perform as
278 * part of the whole job. We only free the env_table between every sub-backup.
279 */
280 if (!NdmpBuildClientJob(jcr, jcr->res.client, jcr->res.paired_read_write_storage, NDM_JOB_OP_BACKUP, &ndmp_job)) {
281 goto bail_out;
282 }
283
284 nis = (NIS *)malloc(sizeof(NIS));
285 memset(nis, 0, sizeof(NIS));
286
287 /*
288 * Loop over each include set of the fileset and fire off a NDMP backup of the included fileset.
289 */
290 cnt = 0;
291 fileset = jcr->res.fileset;
292
293
294 for (i = 0; i < fileset->num_includes; i++) {
295 int j;
296 char *item;
297 IncludeExcludeItem *ie = fileset->include_items[i];
298 PoolMem virtual_filename(PM_FNAME);
299
300 /*
301 * Loop over each file = entry of the fileset.
302 */
303 for (j = 0; j < ie->name_list.size(); j++) {
304 item = (char *)ie->name_list.get(j);
305
306 /*
307 * See if this is the first Backup run or not. For NDMP we can have multiple Backup
308 * runs as part of the same Job. When we are saving data to a Native Storage Daemon
309 * we let it know to expect a new backup session. It will generate a new authorization
310 * key so we wait for the nextrun_ready conditional variable to be raised by the msg_thread.
311 */
312 if (jcr->store_bsock && cnt > 0) {
313 jcr->store_bsock->fsend("nextrun");
314 P(mutex);
315 pthread_cond_wait(&jcr->nextrun_ready, &mutex);
316 V(mutex);
317 }
318
319 /*
320 * Perform the actual NDMP job.
321 * Initialize a new NDMP session
322 */
323 memset(&ndmp_sess, 0, sizeof(ndmp_sess));
324 ndmp_sess.conn_snooping = (me->ndmp_snooping) ? 1 : 0;
325 ndmp_sess.control_agent_enabled = 1;
326
327 ndmp_sess.param = (struct ndm_session_param *)malloc(sizeof(struct ndm_session_param));
328 memset(ndmp_sess.param, 0, sizeof(struct ndm_session_param));
329 ndmp_sess.param->log.deliver = NdmpLoghandler;
330 ndmp_sess.param->log_level = NativeToNdmpLoglevel(NdmpLoglevel, debug_level, nis);
331 nis->filesystem = item;
332 nis->FileIndex = cnt + 1;
333 nis->jcr = jcr;
334 nis->save_filehist = jcr->res.job->SaveFileHist;
335 nis->filehist_size = jcr->res.job->FileHistSize;
336
337 ndmp_sess.param->log.ctx = nis;
338 ndmp_sess.param->log_tag = bstrdup("DIR-NDMP");
339
340 /*
341 * Initialize the session structure.
342 */
343 if (ndma_session_initialize(&ndmp_sess)) {
344 goto cleanup;
345 }
346 session_initialized = true;
347
348 /*
349 * Copy the actual job to perform.
350 */
351 memcpy(&ndmp_sess.control_acb->job, &ndmp_job, sizeof(struct ndm_job_param));
352
353 /*
354 * We can use the same private pointer used in the logging with the JobControlRecord in
355 * the file index generation. We don't setup a index_log.deliver
356 * function as we catch the index information via callbacks.
357 */
358 ndmp_sess.control_acb->job.index_log.ctx = ndmp_sess.param->log.ctx;
359
360 if (!FillBackupEnvironment(jcr,
361 ie,
362 nis->filesystem,
363 &ndmp_sess.control_acb->job)) {
364 goto cleanup;
365 }
366
367 /*
368 * The full ndmp archive has a virtual filename, we need it to hardlink the individual
369 * file records to it. So we allocate it here once so its available during the whole
370 * NDMP session.
371 */
372 if (Bstrcasecmp(jcr->backup_format, "dump")) {
373 Mmsg(virtual_filename, "/@NDMP%s%%%d", nis->filesystem, jcr->DumpLevel);
374 } else {
375 Mmsg(virtual_filename, "/@NDMP%s", nis->filesystem);
376 }
377
378 if (nis->virtual_filename) {
379 free(nis->virtual_filename);
380 }
381 nis->virtual_filename = bstrdup(virtual_filename.c_str());
382
383 ndma_job_auto_adjust(&ndmp_sess.control_acb->job);
384 if (!NdmpValidateJob(jcr, &ndmp_sess.control_acb->job)) {
385 goto cleanup;
386 }
387
388 /*
389 * Commission the session for a run.
390 */
391 if (ndma_session_commission(&ndmp_sess)) {
392 goto cleanup;
393 }
394
395 /*
396 * Setup the DMA.
397 */
398 if (ndmca_connect_control_agent(&ndmp_sess)) {
399 goto cleanup;
400 }
401
402 ndmp_sess.conn_open = 1;
403 ndmp_sess.conn_authorized = 1;
404
405 RegisterCallbackHooks(&ndmp_sess.control_acb->job.index_log);
406
407 /*
408 * Let the DMA perform its magic.
409 */
410 if (ndmca_control_agent(&ndmp_sess) != 0) {
411 goto cleanup;
412 }
413
414 /*
415 * See if there were any errors during the backup.
416 */
417 jcr->jr.FileIndex = cnt + 1;
418 if (!extract_post_backup_stats(jcr, item, &ndmp_sess)) {
419 goto cleanup;
420 }
421
422 UnregisterCallbackHooks(&ndmp_sess.control_acb->job.index_log);
423
424 /*
425 * Reset the NDMP session states.
426 */
427 ndma_session_decommission(&ndmp_sess);
428
429 /*
430 * Cleanup the job after it has run.
431 */
432 ndma_destroy_env_list(&ndmp_sess.control_acb->job.env_tab);
433 ndma_destroy_env_list(&ndmp_sess.control_acb->job.result_env_tab);
434 ndma_destroy_nlist(&ndmp_sess.control_acb->job.nlist_tab);
435
436 /*
437 * Release any tape device name allocated.
438 */
439 if (ndmp_sess.control_acb->job.tape_device) {
440 free(ndmp_sess.control_acb->job.tape_device);
441 ndmp_sess.control_acb->job.tape_device = NULL;
442 }
443
444 /*
445 * Destroy the session.
446 */
447 ndma_session_destroy(&ndmp_sess);
448
449 /*
450 * Free the param block.
451 */
452 free(ndmp_sess.param->log_tag);
453 free(ndmp_sess.param);
454 ndmp_sess.param = NULL;
455
456 /*
457 * Reset the initialized state so we don't try to cleanup again.
458 */
459 session_initialized = false;
460
461 cnt++;
462 }
463 }
464
465 status = JS_Terminated;
466 retval = true;
467
468 /*
469 * Tell the storage daemon we are done.
470 */
471 if (jcr->store_bsock) {
472 jcr->store_bsock->fsend("finish");
473 WaitForStorageDaemonTermination(jcr);
474 jcr->db_batch->WriteBatchFileRecords(jcr); /* used by bulk batch file insert */
475 }
476
477 /*
478 * If we do incremental backups it can happen that the backup is empty if
479 * nothing changed but we always write a filestream. So we use the counter
480 * which counts the number of actual NDMP backup sessions we run to completion.
481 */
482 if (jcr->JobFiles < cnt) {
483 jcr->JobFiles = cnt;
484 }
485
486 /*
487 * Jump to the generic cleanup done for every Job.
488 */
489 goto ok_out;
490
491 cleanup:
492 /*
493 * Only need to cleanup when things are initialized.
494 */
495 if (session_initialized) {
496 ndma_destroy_env_list(&ndmp_sess.control_acb->job.env_tab);
497 ndma_destroy_env_list(&ndmp_sess.control_acb->job.result_env_tab);
498 ndma_destroy_nlist(&ndmp_sess.control_acb->job.nlist_tab);
499
500 if (ndmp_sess.control_acb->job.tape_device) {
501 free(ndmp_sess.control_acb->job.tape_device);
502 }
503
504 UnregisterCallbackHooks(&ndmp_sess.control_acb->job.index_log);
505
506 /*
507 * Destroy the session.
508 */
509 ndma_session_destroy(&ndmp_sess);
510 }
511
512 if (ndmp_sess.param) {
513 free(ndmp_sess.param->log_tag);
514 free(ndmp_sess.param);
515 }
516
517 bail_out:
518 /*
519 * Error handling of failed Job.
520 */
521 status = JS_ErrorTerminated;
522 jcr->setJobStatus(JS_ErrorTerminated);
523
524 if (jcr->store_bsock) {
525 CancelStorageDaemonJob(jcr);
526 WaitForStorageDaemonTermination(jcr);
527 }
528
529 ok_out:
530 if (nis) {
531 if (nis->virtual_filename) {
532 free(nis->virtual_filename);
533 }
534 free(nis);
535 }
536 FreePairedStorage(jcr);
537
538 if (status == JS_Terminated) {
539 NdmpBackupCleanup(jcr, status);
540 }
541
542 return retval;
543 }
544
545 #else
546 bool DoNdmpBackupInit(JobControlRecord *jcr)
547 {
548 Jmsg(jcr, M_FATAL, 0, _("NDMP protocol not supported\n"));
549 return false;
550 }
551
552 bool DoNdmpBackup(JobControlRecord *jcr)
553 {
554 Jmsg(jcr, M_FATAL, 0, _("NDMP protocol not supported\n"));
555 return false;
556 }
557
558 #endif /* HAVE_NDMP */
559 } /* namespace directordaemon */
560