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