1 /*
2 BAREOS® - Backup Archiving REcovery Open Sourced
3
4 Copyright (C) 2000-2012 Free Software Foundation Europe e.V.
5 Copyright (C) 2011-2016 Planets Communications B.V.
6 Copyright (C) 2013-2019 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, March MM
25 */
26 /**
27 * @file
28 * responsible for doing backup jobs
29 *
30 * Basic tasks done here:
31 * Open DB and create records for this job.
32 * Open Message Channel with Storage daemon to tell him a job will be
33 * starting. Open connection with File daemon and pass him commands to do the
34 * backup. When the File daemon finishes the job, update the DB.
35 */
36
37 #include "include/bareos.h"
38 #include "dird.h"
39 #include "dird/dird_globals.h"
40 #include "dird/backup.h"
41 #include "dird/fd_cmds.h"
42 #include "dird/getmsg.h"
43 #include "dird/inc_conf.h"
44 #include "dird/jcr_private.h"
45 #include "dird/job.h"
46 #include "dird/msgchan.h"
47 #include "dird/quota.h"
48 #include "dird/sd_cmds.h"
49 #include "ndmp/smc.h"
50 #include "dird/storage.h"
51 #include "include/auth_protocol_types.h"
52 #include "include/protocol_types.h"
53
54 #include "cats/sql.h"
55 #include "lib/bnet.h"
56 #include "lib/edit.h"
57 #include "lib/berrno.h"
58 #include "lib/util.h"
59
60 namespace directordaemon {
61
62 /* Commands sent to File daemon */
63 static char backupcmd[] = "backup FileIndex=%ld\n";
64 static char storaddrcmd[] = "storage address=%s port=%d ssl=%d\n";
65 static char passiveclientcmd[] = "passive client address=%s port=%d ssl=%d\n";
66
67 /* Responses received from File daemon */
68 static char OKbackup[] = "2000 OK backup\n";
69 static char OKstore[] = "2000 OK storage\n";
70 static char OKpassiveclient[] = "2000 OK passive client\n";
71 static char EndJob[] =
72 "2800 End Job TermCode=%d JobFiles=%u "
73 "ReadBytes=%llu JobBytes=%llu Errors=%u "
74 "VSS=%d Encrypt=%d\n";
75
ValidateClient(JobControlRecord * jcr)76 static inline bool ValidateClient(JobControlRecord* jcr)
77 {
78 switch (jcr->impl->res.client->Protocol) {
79 case APT_NATIVE:
80 return true;
81 default:
82 Jmsg(
83 jcr, M_FATAL, 0,
84 _("Client %s has illegal backup protocol %s for Native backup\n"),
85 jcr->impl->res.client->resource_name_,
86 AuthenticationProtocolTypeToString(jcr->impl->res.client->Protocol));
87 return false;
88 }
89 }
90
91 /**
92 * if both FD and SD have LanAddress set, use the storages' LanAddress to
93 * connect to.
94 */
StorageAddressToContact(ClientResource * client,StorageResource * store)95 char* StorageAddressToContact(ClientResource* client, StorageResource* store)
96 {
97 if (store->lanaddress && client->lanaddress) {
98 return store->lanaddress;
99 } else {
100 return store->address;
101 }
102 }
103
104 /**
105 * if both FD and SD have LanAddress set, use the clients' LanAddress to connect
106 * to.
107 *
108 */
ClientAddressToContact(ClientResource * client,StorageResource * store)109 char* ClientAddressToContact(ClientResource* client, StorageResource* store)
110 {
111 if (store->lanaddress && client->lanaddress) {
112 return client->lanaddress;
113 } else {
114 return client->address;
115 }
116 }
117
118 /**
119 * if both readstorage and writestorage have LanAddress set,
120 * use wstores' LanAddress to connect to.
121 *
122 */
StorageAddressToContact(StorageResource * read_storage,StorageResource * write_storage)123 char* StorageAddressToContact(StorageResource* read_storage,
124 StorageResource* write_storage)
125 {
126 if (read_storage->lanaddress && write_storage->lanaddress) {
127 return write_storage->lanaddress;
128 } else {
129 return write_storage->address;
130 }
131 }
132
ValidateStorage(JobControlRecord * jcr)133 static inline bool ValidateStorage(JobControlRecord* jcr)
134 {
135 StorageResource* store = nullptr;
136
137 foreach_alist (store, jcr->impl->res.write_storage_list) {
138 switch (store->Protocol) {
139 case APT_NATIVE:
140 continue;
141 default:
142 Jmsg(jcr, M_FATAL, 0,
143 _("Storage %s has illegal backup protocol %s for Native backup\n"),
144 store->resource_name_,
145 AuthenticationProtocolTypeToString(store->Protocol));
146 return false;
147 }
148 }
149
150 return true;
151 }
152
153 /*
154 * Called here before the job is run to do the job specific setup.
155 */
DoNativeBackupInit(JobControlRecord * jcr)156 bool DoNativeBackupInit(JobControlRecord* jcr)
157 {
158 FreeRstorage(jcr); /* we don't read so release */
159
160 if (!AllowDuplicateJob(jcr)) { return false; }
161
162 jcr->impl->jr.PoolId =
163 GetOrCreatePoolRecord(jcr, jcr->impl->res.pool->resource_name_);
164 if (jcr->impl->jr.PoolId == 0) { return false; }
165
166 /*
167 * If pool storage specified, use it instead of job storage
168 */
169 CopyWstorage(jcr, jcr->impl->res.pool->storage, _("Pool resource"));
170 if (!jcr->impl->res.write_storage_list) {
171 Jmsg(jcr, M_FATAL, 0,
172 _("No Storage specification found in Job or Pool.\n"));
173 return false;
174 }
175
176 /*
177 * Validate that we have a native client and storage(s).
178 */
179 if (!ValidateClient(jcr) || !ValidateStorage(jcr)) { return false; }
180
181 CreateClones(jcr); /* run any clone jobs */
182
183 return true;
184 }
185
186 /*
187 * Take all base jobs from job resource and find the last L_BASE jobid.
188 */
GetBaseJobids(JobControlRecord * jcr,db_list_ctx * jobids)189 static bool GetBaseJobids(JobControlRecord* jcr, db_list_ctx* jobids)
190 {
191 JobDbRecord jr;
192 JobResource* job = nullptr;
193 JobId_t id;
194 char str_jobid[50];
195
196 if (!jcr->impl->res.job->base) {
197 return false; /* no base job, stop accurate */
198 }
199
200 jr.StartTime = jcr->impl->jr.StartTime;
201
202 foreach_alist (job, jcr->impl->res.job->base) {
203 bstrncpy(jr.Name, job->resource_name_, sizeof(jr.Name));
204 jcr->db->GetBaseJobid(jcr, &jr, &id);
205
206 if (id) {
207 if (jobids->count) { PmStrcat(jobids->list, ","); }
208 PmStrcat(jobids->list, edit_uint64(id, str_jobid));
209 jobids->count++;
210 }
211 }
212
213 return jobids->count > 0;
214 }
215
216 /*
217 * Foreach files in currrent list, send "/path/fname\0LStat\0MD5\0Delta" to FD
218 * row[0]=Path, row[1]=Filename, row[2]=FileIndex
219 * row[3]=JobId row[4]=LStat row[5]=DeltaSeq row[6]=MD5
220 */
AccurateListHandler(void * ctx,int num_fields,char ** row)221 static int AccurateListHandler(void* ctx, int num_fields, char** row)
222 {
223 JobControlRecord* jcr = (JobControlRecord*)ctx;
224
225 if (JobCanceled(jcr)) { return 1; }
226
227 if (row[2][0] == '0') { /* discard when file_index == 0 */
228 return 0;
229 }
230
231 /* sending with checksum */
232 if (jcr->impl->use_accurate_chksum && num_fields == 9 &&
233 row[6][0] && /* skip checksum = '0' */
234 row[6][1]) {
235 jcr->file_bsock->fsend("%s%s%c%s%c%s%c%s", row[0], row[1], 0, row[4], 0,
236 row[6], 0, row[5]);
237 } else {
238 jcr->file_bsock->fsend("%s%s%c%s%c%c%s", row[0], row[1], 0, row[4], 0, 0,
239 row[5]);
240 }
241 return 0;
242 }
243
244 /* In this procedure, we check if the current fileset is using checksum
245 * FileSet-> Include-> Options-> Accurate/Verify/BaseJob=checksum
246 * This procedure uses jcr->HasBase, so it must be call after the initialization
247 */
IsChecksumNeededByFileset(JobControlRecord * jcr)248 static bool IsChecksumNeededByFileset(JobControlRecord* jcr)
249 {
250 IncludeExcludeItem* inc;
251 FileOptions* fopts;
252 FilesetResource* fs;
253 bool in_block = false;
254 bool have_basejob_option = false;
255
256 if (!jcr->impl->res.job || !jcr->impl->res.job->fileset) { return false; }
257
258 fs = jcr->impl->res.job->fileset;
259 for (std::size_t i = 0; i < fs->include_items.size(); i++) {
260 inc = fs->include_items[i];
261
262 for (std::size_t j = 0; j < inc->file_options_list.size(); j++) {
263 fopts = inc->file_options_list[j];
264
265 for (char* k = fopts->opts; *k; k++) { /* Try to find one request */
266 switch (*k) {
267 case 'V': /* verify */
268 in_block = jcr->is_JobType(JT_VERIFY); /* not used now */
269 break;
270 case 'J': /* Basejob keyword */
271 have_basejob_option = in_block = jcr->HasBase;
272 break;
273 case 'C': /* Accurate keyword */
274 in_block = !jcr->is_JobLevel(L_FULL);
275 break;
276 case ':': /* End of keyword */
277 in_block = false;
278 break;
279 case '5': /* MD5 */
280 case '1': /* SHA1 */
281 if (in_block) {
282 Dmsg0(50, "Checksum will be sent to FD\n");
283 return true;
284 }
285 break;
286 default:
287 break;
288 }
289 }
290 }
291 }
292
293 /* By default for BaseJobs, we send the checksum */
294 if (!have_basejob_option && jcr->HasBase) { return true; }
295
296 Dmsg0(50, "Checksum will be sent to FD\n");
297 return false;
298 }
299
300 /*
301 * Send current file list to FD
302 * DIR -> FD : accurate files=xxxx
303 * DIR -> FD : /path/to/file\0Lstat\0MD5\0Delta
304 * DIR -> FD : /path/to/dir/\0Lstat\0MD5\0Delta
305 * ...
306 * DIR -> FD : EOD
307 */
SendAccurateCurrentFiles(JobControlRecord * jcr)308 bool SendAccurateCurrentFiles(JobControlRecord* jcr)
309 {
310 PoolMem buf;
311 db_list_ctx jobids;
312 db_list_ctx nb;
313
314 /*
315 * In base level, no previous job is used and no restart incomplete jobs
316 */
317 if (jcr->IsCanceled() || jcr->is_JobLevel(L_BASE)) { return true; }
318
319 if (!jcr->accurate) { return true; }
320
321 if (jcr->is_JobLevel(L_FULL)) {
322 /*
323 * On Full mode, if no previous base job, no accurate things
324 */
325 if (GetBaseJobids(jcr, &jobids)) {
326 jcr->HasBase = true;
327 Jmsg(jcr, M_INFO, 0, _("Using BaseJobId(s): %s\n"), jobids.list);
328 } else {
329 return true;
330 }
331 } else {
332 /*
333 * For Incr/Diff level, we search for older jobs
334 */
335 jcr->db->AccurateGetJobids(jcr, &jcr->impl->jr, &jobids);
336
337 /*
338 * We are in Incr/Diff, but no Full to build the accurate list...
339 */
340 if (jobids.count == 0) {
341 Jmsg(jcr, M_FATAL, 0, _("Cannot find previous jobids.\n"));
342 return false; /* fail */
343 }
344 }
345
346 /*
347 * Don't send and store the checksum if fileset doesn't require it
348 */
349 jcr->impl->use_accurate_chksum = IsChecksumNeededByFileset(jcr);
350 if (jcr->JobId) { /* display the message only for real jobs */
351 Jmsg(jcr, M_INFO, 0, _("Sending Accurate information.\n"));
352 }
353
354 /*
355 * To be able to allocate the right size for htable
356 */
357 Mmsg(buf, "SELECT sum(JobFiles) FROM Job WHERE JobId IN (%s)", jobids.list);
358 jcr->db->SqlQuery(buf.c_str(), DbListHandler, &nb);
359 Dmsg2(200, "jobids=%s nb=%s\n", jobids.list, nb.list);
360 jcr->file_bsock->fsend("accurate files=%s\n", nb.list);
361
362 if (jcr->HasBase) {
363 jcr->nb_base_files = str_to_int64(nb.list);
364 if (!jcr->db->CreateBaseFileList(jcr, jobids.list)) {
365 Jmsg(jcr, M_FATAL, 0, "error in jcr->db->CreateBaseFileList:%s\n",
366 jcr->db->strerror());
367 return false;
368 }
369 if (!jcr->db->GetBaseFileList(jcr, jcr->impl->use_accurate_chksum,
370 AccurateListHandler, (void*)jcr)) {
371 Jmsg(jcr, M_FATAL, 0, "error in jcr->db->GetBaseFileList:%s\n",
372 jcr->db->strerror());
373 return false;
374 }
375 } else {
376 if (!jcr->db->OpenBatchConnection(jcr)) {
377 Jmsg0(jcr, M_FATAL, 0, "Can't get batch sql connection");
378 return false; /* Fail */
379 }
380
381 jcr->db_batch->GetFileList(
382 jcr, jobids.list, jcr->impl->use_accurate_chksum, false /* no delta */,
383 AccurateListHandler, (void*)jcr);
384 }
385
386 jcr->file_bsock->signal(BNET_EOD);
387 return true;
388 }
389
390 /*
391 * Do a backup of the specified FileSet
392 *
393 * Returns: false on failure
394 * true on success
395 */
DoNativeBackup(JobControlRecord * jcr)396 bool DoNativeBackup(JobControlRecord* jcr)
397 {
398 int status;
399 BareosSocket* fd = NULL;
400 BareosSocket* sd = NULL;
401 StorageResource* store = NULL;
402 ClientResource* client = NULL;
403 char ed1[100];
404 db_int64_ctx job;
405 PoolMem buf;
406
407 /* Print Job Start message */
408 Jmsg(jcr, M_INFO, 0, _("Start Backup JobId %s, Job=%s\n"),
409 edit_uint64(jcr->JobId, ed1), jcr->Job);
410
411 jcr->setJobStatus(JS_Running);
412 Dmsg2(100, "JobId=%d JobLevel=%c\n", jcr->impl->jr.JobId,
413 jcr->impl->jr.JobLevel);
414 if (!jcr->db->UpdateJobStartRecord(jcr, &jcr->impl->jr)) {
415 Jmsg(jcr, M_FATAL, 0, "%s", jcr->db->strerror());
416 return false;
417 }
418
419 if (CheckHardquotas(jcr)) {
420 Jmsg(jcr, M_FATAL, 0, _("Quota Exceeded. Job terminated.\n"));
421 return false;
422 }
423
424 if (CheckSoftquotas(jcr)) {
425 Dmsg0(10, "Quota exceeded\n");
426 Jmsg(jcr, M_FATAL, 0,
427 _("Soft Quota exceeded / Grace Time expired. Job terminated.\n"));
428 return false;
429 }
430
431 /*
432 * Open a message channel connection with the Storage
433 * daemon. This is to let him know that our client
434 * will be contacting him for a backup session.
435 */
436 Dmsg0(110, "Open connection with storage daemon\n");
437 jcr->setJobStatus(JS_WaitSD);
438
439 /*
440 * Start conversation with Storage daemon
441 */
442 if (!ConnectToStorageDaemon(jcr, 10, me->SDConnectTimeout, true)) {
443 return false;
444 }
445 sd = jcr->store_bsock;
446
447 /*
448 * Now start a job with the Storage daemon
449 */
450 if (!StartStorageDaemonJob(jcr, NULL, jcr->impl->res.write_storage_list)) {
451 return false;
452 }
453
454 /*
455 * When the client is not in passive mode we can put the SD in
456 * listen mode for the FD connection.
457 */
458 jcr->passive_client = jcr->impl->res.client->passive;
459 if (!jcr->passive_client) {
460 /*
461 * Start the job prior to starting the message thread below
462 * to avoid two threads from using the BareosSocket structure at
463 * the same time.
464 */
465 if (!sd->fsend("run")) { return false; }
466
467 /*
468 * Now start a Storage daemon message thread. Note,
469 * this thread is used to provide the catalog services
470 * for the backup job, including inserting the attributes
471 * into the catalog. See CatalogUpdate() in catreq.c
472 */
473 if (!StartStorageDaemonMessageThread(jcr)) { return false; }
474
475 Dmsg0(150, "Storage daemon connection OK\n");
476 }
477
478 jcr->setJobStatus(JS_WaitFD);
479 if (!ConnectToFileDaemon(jcr, 10, me->FDConnectTimeout, true)) {
480 goto bail_out;
481 }
482 Dmsg1(120, "jobid: %d: connected\n", jcr->JobId);
483 SendJobInfoToFileDaemon(jcr);
484 fd = jcr->file_bsock;
485
486 /*
487 * Check if the file daemon supports passive client mode.
488 */
489 if (jcr->passive_client && jcr->impl->FDVersion < FD_VERSION_51) {
490 Jmsg(jcr, M_FATAL, 0,
491 _("Client \"%s\" doesn't support passive client mode. "
492 "Please upgrade your client or disable compat mode.\n"),
493 jcr->impl->res.client->resource_name_);
494 goto close_fd;
495 }
496
497 jcr->setJobStatus(JS_Running);
498
499 if (!SendLevelCommand(jcr)) { goto bail_out; }
500
501 if (!SendIncludeList(jcr)) { goto bail_out; }
502
503 if (!SendExcludeList(jcr)) { goto bail_out; }
504
505 if (!SendPluginOptions(jcr)) { goto bail_out; }
506
507 if (!SendPreviousRestoreObjects(jcr)) { goto bail_out; }
508
509 if (!SendSecureEraseReqToFd(jcr)) {
510 Dmsg1(500, "Unexpected %s secure erase\n", "client");
511 }
512
513 if (jcr->impl->res.job->max_bandwidth > 0) {
514 jcr->max_bandwidth = jcr->impl->res.job->max_bandwidth;
515 } else if (jcr->impl->res.client->max_bandwidth > 0) {
516 jcr->max_bandwidth = jcr->impl->res.client->max_bandwidth;
517 }
518
519 if (jcr->max_bandwidth > 0) {
520 SendBwlimitToFd(jcr, jcr->Job); /* Old clients don't have this command */
521 }
522
523 client = jcr->impl->res.client;
524 store = jcr->impl->res.write_storage;
525 char* connection_target_address;
526
527 /*
528 * See if the client is a passive client or not.
529 */
530 if (!jcr->passive_client) {
531 /*
532 * Send Storage daemon address to the File daemon
533 */
534 if (store->SDDport == 0) { store->SDDport = store->SDport; }
535
536 /*
537 * TLS Requirement
538 */
539
540 TlsPolicy tls_policy;
541 if (jcr->impl->res.client->connection_successful_handshake_ !=
542 ClientConnectionHandshakeMode::kTlsFirst) {
543 tls_policy = store->GetPolicy();
544 } else {
545 tls_policy = store->IsTlsConfigured() ? TlsPolicy::kBnetTlsAuto
546 : TlsPolicy::kBnetTlsNone;
547 }
548
549 Dmsg1(200, "Tls Policy for active client is: %d\n", tls_policy);
550
551 connection_target_address = StorageAddressToContact(client, store);
552
553 fd->fsend(storaddrcmd, connection_target_address, store->SDDport,
554 tls_policy);
555 if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
556 Dmsg0(200, "Error from active client on storeaddrcmd\n");
557 goto bail_out;
558 }
559
560 } else { /* passive client */
561
562 TlsPolicy tls_policy;
563 if (jcr->impl->res.client->connection_successful_handshake_ !=
564 ClientConnectionHandshakeMode::kTlsFirst) {
565 tls_policy = client->GetPolicy();
566 } else {
567 tls_policy = client->IsTlsConfigured() ? TlsPolicy::kBnetTlsAuto
568 : TlsPolicy::kBnetTlsNone;
569 }
570 Dmsg1(200, "Tls Policy for passive client is: %d\n", tls_policy);
571
572 connection_target_address = ClientAddressToContact(client, store);
573
574 /*
575 * Tell the SD to connect to the FD.
576 */
577 sd->fsend(passiveclientcmd, connection_target_address, client->FDport,
578 tls_policy);
579 Bmicrosleep(2, 0);
580 if (!response(jcr, sd, OKpassiveclient, "Passive client", DISPLAY_ERROR)) {
581 goto bail_out;
582 }
583
584 /*
585 * Start the job prior to starting the message thread below
586 * to avoid two threads from using the BareosSocket structure at
587 * the same time.
588 */
589 if (!jcr->store_bsock->fsend("run")) { return false; }
590
591 /*
592 * Now start a Storage daemon message thread. Note,
593 * this thread is used to provide the catalog services
594 * for the backup job, including inserting the attributes
595 * into the catalog. See CatalogUpdate() in catreq.c
596 */
597 if (!StartStorageDaemonMessageThread(jcr)) { return false; }
598
599 Dmsg0(150, "Storage daemon connection OK\n");
600 } /* if (!jcr->passive_client) */
601
602 /*
603 * Declare the job started to start the MaxRunTime check
604 */
605 jcr->setJobStarted();
606
607 /*
608 * Send and run the RunBefore
609 */
610 if (!SendRunscriptsCommands(jcr)) { goto bail_out; }
611
612 /*
613 * We re-update the job start record so that the start
614 * time is set after the run before job. This avoids
615 * that any files created by the run before job will
616 * be saved twice. They will be backed up in the current
617 * job, but not in the next one unless they are changed.
618 * Without this, they will be backed up in this job and
619 * in the next job run because in that case, their date
620 * is after the start of this run.
621 */
622 jcr->start_time = time(NULL);
623 jcr->impl->jr.StartTime = jcr->start_time;
624 if (!jcr->db->UpdateJobStartRecord(jcr, &jcr->impl->jr)) {
625 Jmsg(jcr, M_FATAL, 0, "%s", jcr->db->strerror());
626 }
627
628 /*
629 * If backup is in accurate mode, we send the list of
630 * all files to FD.
631 */
632 if (!SendAccurateCurrentFiles(jcr)) { goto bail_out; /* error */ }
633
634 /*
635 * Send backup command
636 */
637 fd->fsend(backupcmd, jcr->JobFiles);
638 Dmsg1(100, ">filed: %s", fd->msg);
639 if (!response(jcr, fd, OKbackup, "Backup", DISPLAY_ERROR)) { goto bail_out; }
640
641 /*
642 * Pickup Job termination data
643 */
644 status = WaitForJobTermination(jcr);
645 jcr->db_batch->WriteBatchFileRecords(
646 jcr); /* used by bulk batch file insert */
647
648 if (jcr->HasBase && !jcr->db->CommitBaseFileAttributesRecord(jcr)) {
649 Jmsg(jcr, M_FATAL, 0, "%s", jcr->db->strerror());
650 }
651
652 /*
653 * Check softquotas after job did run.
654 * If quota is exceeded now, set the GraceTime.
655 */
656 CheckSoftquotas(jcr);
657
658 if (status == JS_Terminated) {
659 NativeBackupCleanup(jcr, status);
660 return true;
661 }
662
663 return false;
664
665 close_fd:
666 if (jcr->file_bsock) {
667 jcr->file_bsock->signal(BNET_TERMINATE);
668 jcr->file_bsock->close();
669 delete jcr->file_bsock;
670 jcr->file_bsock = NULL;
671 }
672
673 bail_out:
674 jcr->setJobStatus(JS_ErrorTerminated);
675 WaitForJobTermination(jcr, me->FDConnectTimeout);
676
677 return false;
678 }
679
680 /*
681 * Here we wait for the File daemon to signal termination,
682 * then we wait for the Storage daemon. When both are done,
683 * we return the job status.
684 *
685 * Also used by restore.c
686 */
WaitForJobTermination(JobControlRecord * jcr,int timeout)687 int WaitForJobTermination(JobControlRecord* jcr, int timeout)
688 {
689 int32_t n = 0;
690 BareosSocket* fd = jcr->file_bsock;
691 bool fd_ok = false;
692 uint32_t JobFiles, JobErrors;
693 uint32_t JobWarnings = 0;
694 uint64_t ReadBytes = 0;
695 uint64_t JobBytes = 0;
696 int VSS = 0;
697 int Encrypt = 0;
698 btimer_t* tid = NULL;
699
700 jcr->setJobStatus(JS_Running);
701
702 if (fd) {
703 if (timeout) {
704 tid = StartBsockTimer(fd, timeout); /* TODO: New timeout directive??? */
705 }
706
707 /*
708 * Wait for Client to terminate
709 */
710 while ((n = BgetDirmsg(fd)) >= 0) {
711 if (!fd_ok &&
712 sscanf(fd->msg, EndJob, &jcr->impl->FDJobStatus, &JobFiles,
713 &ReadBytes, &JobBytes, &JobErrors, &VSS, &Encrypt) == 7) {
714 fd_ok = true;
715 jcr->setJobStatus(jcr->impl->FDJobStatus);
716 Dmsg1(100, "FDStatus=%c\n", (char)jcr->JobStatus);
717 } else {
718 Jmsg(jcr, M_WARNING, 0, _("Unexpected Client Job message: %s\n"),
719 fd->msg);
720 }
721 if (JobCanceled(jcr)) { break; }
722 }
723 if (tid) { StopBsockTimer(tid); }
724
725 if (IsBnetError(fd)) {
726 int i = 0;
727 Jmsg(jcr, M_FATAL, 0, _("Network error with FD during %s: ERR=%s\n"),
728 job_type_to_str(jcr->getJobType()), fd->bstrerror());
729 while (i++ < 10 && jcr->impl->res.job->RescheduleIncompleteJobs &&
730 jcr->IsCanceled()) {
731 Bmicrosleep(3, 0);
732 }
733 }
734 fd->signal(BNET_TERMINATE); /* tell Client we are terminating */
735 }
736
737 /*
738 * Force cancel in SD if failing, but not for Incomplete jobs so that we let
739 * the SD despool.
740 */
741 Dmsg5(100, "cancel=%d fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", jcr->IsCanceled(),
742 fd_ok, jcr->impl->FDJobStatus, jcr->JobStatus,
743 jcr->impl->SDJobStatus);
744 if (jcr->IsCanceled() ||
745 (!jcr->impl->res.job->RescheduleIncompleteJobs && !fd_ok)) {
746 Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok,
747 jcr->impl->FDJobStatus, jcr->JobStatus, jcr->impl->SDJobStatus);
748 CancelStorageDaemonJob(jcr);
749 }
750
751 /*
752 * Note, the SD stores in jcr->JobFiles/ReadBytes/JobBytes/JobErrors
753 */
754 WaitForStorageDaemonTermination(jcr);
755
756 /*
757 * Return values from FD
758 */
759 if (fd_ok) {
760 jcr->JobFiles = JobFiles;
761 jcr->JobErrors += JobErrors; /* Keep total errors */
762 jcr->ReadBytes = ReadBytes;
763 jcr->JobBytes = JobBytes;
764 jcr->JobWarnings = JobWarnings;
765 jcr->impl->VSS = VSS;
766 jcr->impl->Encrypt = Encrypt;
767 } else {
768 Jmsg(jcr, M_FATAL, 0, _("No Job status returned from FD.\n"));
769 }
770
771 // Dmsg4(100, "fd_ok=%d FDJS=%d JS=%d SDJS=%d\n", fd_ok,
772 // jcr->impl_->FDJobStatus,
773 // jcr->JobStatus, jcr->impl_->SDJobStatus);
774
775 /*
776 * Return the first error status we find Dir, FD, or SD
777 */
778 if (!fd_ok || IsBnetError(fd)) { /* if fd not set, that use !fd_ok */
779 jcr->impl->FDJobStatus = JS_ErrorTerminated;
780 }
781 if (jcr->JobStatus != JS_Terminated) { return jcr->JobStatus; }
782 if (jcr->impl->FDJobStatus != JS_Terminated) {
783 return jcr->impl->FDJobStatus;
784 }
785 return jcr->impl->SDJobStatus;
786 }
787
788 /*
789 * Release resources allocated during backup.
790 */
NativeBackupCleanup(JobControlRecord * jcr,int TermCode)791 void NativeBackupCleanup(JobControlRecord* jcr, int TermCode)
792 {
793 const char* TermMsg;
794 char term_code[100];
795 int msg_type = M_INFO;
796 ClientDbRecord cr;
797
798 Dmsg2(100, "Enter backup_cleanup %d %c\n", TermCode, TermCode);
799
800 if (jcr->is_JobStatus(JS_Terminated) &&
801 (jcr->JobErrors || jcr->impl->SDErrors || jcr->JobWarnings)) {
802 TermCode = JS_Warnings;
803 }
804
805 UpdateJobEnd(jcr, TermCode);
806
807 if (!jcr->db->GetJobRecord(jcr, &jcr->impl->jr)) {
808 Jmsg(jcr, M_WARNING, 0,
809 _("Error getting Job record for Job report: ERR=%s"),
810 jcr->db->strerror());
811 jcr->setJobStatus(JS_ErrorTerminated);
812 }
813
814 bstrncpy(cr.Name, jcr->impl->res.client->resource_name_, sizeof(cr.Name));
815 if (!jcr->db->GetClientRecord(jcr, &cr)) {
816 Jmsg(jcr, M_WARNING, 0,
817 _("Error getting Client record for Job report: ERR=%s"),
818 jcr->db->strerror());
819 }
820
821 UpdateBootstrapFile(jcr);
822
823 switch (jcr->JobStatus) {
824 case JS_Terminated:
825 TermMsg = _("Backup OK");
826 break;
827 case JS_Incomplete:
828 TermMsg = _("Backup failed -- incomplete");
829 break;
830 case JS_Warnings:
831 TermMsg = _("Backup OK -- with warnings");
832 break;
833 case JS_FatalError:
834 case JS_ErrorTerminated:
835 TermMsg = _("*** Backup Error ***");
836 msg_type = M_ERROR; /* Generate error message */
837 if (jcr->store_bsock) {
838 jcr->store_bsock->signal(BNET_TERMINATE);
839 if (jcr->impl->SD_msg_chan_started) {
840 pthread_cancel(jcr->impl->SD_msg_chan);
841 }
842 }
843 break;
844 case JS_Canceled:
845 TermMsg = _("Backup Canceled");
846 if (jcr->store_bsock) {
847 jcr->store_bsock->signal(BNET_TERMINATE);
848 if (jcr->impl->SD_msg_chan_started) {
849 pthread_cancel(jcr->impl->SD_msg_chan);
850 }
851 }
852 break;
853 default:
854 TermMsg = term_code;
855 sprintf(term_code, _("Inappropriate term code: %c\n"), jcr->JobStatus);
856 break;
857 }
858
859 GenerateBackupSummary(jcr, &cr, msg_type, TermMsg);
860
861 Dmsg0(100, "Leave backup_cleanup()\n");
862 }
863
UpdateBootstrapFile(JobControlRecord * jcr)864 void UpdateBootstrapFile(JobControlRecord* jcr)
865 {
866 /*
867 * Now update the bootstrap file if any
868 */
869 if (jcr->IsTerminatedOk() && jcr->impl->jr.JobBytes &&
870 jcr->impl->res.job->WriteBootstrap) {
871 FILE* fd;
872 int VolCount;
873 int got_pipe = 0;
874 Bpipe* bpipe = NULL;
875 VolumeParameters* VolParams = NULL;
876 char edt[50], ed1[50], ed2[50];
877 POOLMEM* fname = GetPoolMemory(PM_FNAME);
878
879 fname = edit_job_codes(jcr, fname, jcr->impl->res.job->WriteBootstrap, "");
880 if (*fname == '|') {
881 got_pipe = 1;
882 bpipe = OpenBpipe(fname + 1, 0, "w"); /* skip first char "|" */
883 fd = bpipe ? bpipe->wfd : NULL;
884 } else {
885 /* ***FIXME*** handle BASE */
886 fd = fopen(fname, jcr->is_JobLevel(L_FULL) ? "w+b" : "a+b");
887 }
888 if (fd) {
889 VolCount = jcr->db->GetJobVolumeParameters(jcr, jcr->JobId, &VolParams);
890 if (VolCount == 0) {
891 Jmsg(jcr, M_ERROR, 0,
892 _("Could not get Job Volume Parameters to "
893 "update Bootstrap file. ERR=%s\n"),
894 jcr->db->strerror());
895 if (jcr->impl->SDJobFiles != 0) {
896 jcr->setJobStatus(JS_ErrorTerminated);
897 }
898 }
899 /* Start output with when and who wrote it */
900 bstrftimes(edt, sizeof(edt), time(NULL));
901 fprintf(fd, "# %s - %s - %s%s\n", edt, jcr->impl->jr.Job,
902 JobLevelToString(jcr->getJobLevel()), jcr->impl->since);
903 for (int i = 0; i < VolCount; i++) {
904 /* Write the record */
905 fprintf(fd, "Volume=\"%s\"\n", VolParams[i].VolumeName);
906 fprintf(fd, "MediaType=\"%s\"\n", VolParams[i].MediaType);
907 if (VolParams[i].Slot > 0) {
908 fprintf(fd, "Slot=%d\n", VolParams[i].Slot);
909 }
910 fprintf(fd, "VolSessionId=%u\n", jcr->VolSessionId);
911 fprintf(fd, "VolSessionTime=%u\n", jcr->VolSessionTime);
912 fprintf(fd, "VolAddr=%s-%s\n", edit_uint64(VolParams[i].StartAddr, ed1),
913 edit_uint64(VolParams[i].EndAddr, ed2));
914 fprintf(fd, "FileIndex=%d-%d\n", VolParams[i].FirstIndex,
915 VolParams[i].LastIndex);
916 }
917 if (VolParams) { free(VolParams); }
918 if (got_pipe) {
919 CloseBpipe(bpipe);
920 } else {
921 fclose(fd);
922 }
923 } else {
924 BErrNo be;
925 Jmsg(jcr, M_ERROR, 0,
926 _("Could not open WriteBootstrap file:\n"
927 "%s: ERR=%s\n"),
928 fname, be.bstrerror());
929 jcr->setJobStatus(JS_ErrorTerminated);
930 }
931 FreePoolMemory(fname);
932 }
933 }
934
935 /* clang-format off */
936
937 /*
938 * Generic function which generates a backup summary message.
939 * Used by:
940 * - NativeBackupCleanup e.g. normal backups
941 * - NativeVbackupCleanup e.g. virtual backups
942 * - NdmpBackupCleanup e.g. NDMP backups
943 */
GenerateBackupSummary(JobControlRecord * jcr,ClientDbRecord * cr,int msg_type,const char * TermMsg)944 void GenerateBackupSummary(JobControlRecord *jcr, ClientDbRecord *cr, int msg_type, const char *TermMsg)
945 {
946 char sdt[50], edt[50], schedt[50], gdt[50];
947 char ec1[30], ec2[30], ec3[30], ec4[30], ec5[30], compress[50];
948 char ec6[30], ec7[30], ec8[30], elapsed[50];
949 char fd_term_msg[100], sd_term_msg[100];
950 double kbps, compression;
951 utime_t RunTime;
952 MediaDbRecord mr;
953 PoolMem temp,
954 level_info,
955 statistics,
956 quota_info,
957 client_options,
958 daemon_status,
959 secure_erase_status,
960 compress_algo_list;
961
962 bstrftimes(schedt, sizeof(schedt), jcr->impl->jr.SchedTime);
963 bstrftimes(sdt, sizeof(sdt), jcr->impl->jr.StartTime);
964 bstrftimes(edt, sizeof(edt), jcr->impl->jr.EndTime);
965 RunTime = jcr->impl->jr.EndTime - jcr->impl->jr.StartTime;
966 bstrftimes(gdt, sizeof(gdt),
967 jcr->impl->res.client->GraceTime +
968 jcr->impl->res.client->SoftQuotaGracePeriod);
969
970 if (RunTime <= 0) {
971 kbps = 0;
972 } else {
973 kbps = ((double)jcr->impl->jr.JobBytes) / (1000.0 * (double)RunTime);
974 }
975
976 if (!jcr->db->GetJobVolumeNames(jcr, jcr->impl->jr.JobId, jcr->VolumeName)) {
977 /*
978 * Note, if the job has erred, most likely it did not write any
979 * tape, so suppress this "error" message since in that case
980 * it is normal. Or look at it the other way, only for a
981 * normal exit should we complain about this error.
982 */
983 if (jcr->IsTerminatedOk() && jcr->impl->jr.JobBytes) {
984 Jmsg(jcr, M_ERROR, 0, "%s", jcr->db->strerror());
985 }
986 jcr->VolumeName[0] = 0; /* none */
987 }
988
989 if (jcr->VolumeName[0]) {
990 /*
991 * Find last volume name. Multiple vols are separated by |
992 */
993 char *p = strrchr(jcr->VolumeName, '|');
994 if (p) {
995 p++; /* skip | */
996 } else {
997 p = jcr->VolumeName; /* no |, take full name */
998 }
999 bstrncpy(mr.VolumeName, p, sizeof(mr.VolumeName));
1000 if (!jcr->db->GetMediaRecord(jcr, &mr)) {
1001 Jmsg(jcr, M_WARNING, 0, _("Error getting Media record for Volume \"%s\": ERR=%s"),
1002 mr.VolumeName, jcr->db->strerror());
1003 }
1004 }
1005
1006 if (jcr->ReadBytes == 0) {
1007 bstrncpy(compress, "None", sizeof(compress));
1008 } else {
1009 compression = (double)100 - 100.0 * ((double)jcr->JobBytes / (double)jcr->ReadBytes);
1010 if (compression < 0.5) {
1011 bstrncpy(compress, "None", sizeof(compress));
1012 } else {
1013 Bsnprintf(compress, sizeof(compress), "%.1f %%", compression);
1014 FindUsedCompressalgos(&compress_algo_list, jcr);
1015 }
1016 }
1017
1018 JobstatusToAscii(jcr->impl->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
1019 JobstatusToAscii(jcr->impl->SDJobStatus, sd_term_msg, sizeof(sd_term_msg));
1020
1021 switch (jcr->getJobProtocol()) {
1022 case PT_NDMP_BAREOS:
1023 Mmsg(level_info, _(
1024 " Backup Level: %s%s\n"),
1025 JobLevelToString(jcr->getJobLevel()), jcr->impl->since);
1026 Mmsg(statistics, _(
1027 " NDMP Files Written: %s\n"
1028 " SD Files Written: %s\n"
1029 " NDMP Bytes Written: %s (%sB)\n"
1030 " SD Bytes Written: %s (%sB)\n"),
1031 edit_uint64_with_commas(jcr->impl->jr.JobFiles, ec1),
1032 edit_uint64_with_commas(jcr->impl->SDJobFiles, ec2),
1033 edit_uint64_with_commas(jcr->impl->jr.JobBytes, ec3),
1034 edit_uint64_with_suffix(jcr->impl->jr.JobBytes, ec4),
1035 edit_uint64_with_commas(jcr->impl->SDJobBytes, ec5),
1036 edit_uint64_with_suffix(jcr->impl->SDJobBytes, ec6));
1037 break;
1038 case PT_NDMP_NATIVE:
1039 Mmsg(level_info, _(
1040 " Backup Level: %s%s\n"),
1041 JobLevelToString(jcr->getJobLevel()), jcr->impl->since);
1042 Mmsg(statistics, _(
1043 " NDMP Files Written: %s\n"
1044 " NDMP Bytes Written: %s (%sB)\n"),
1045 edit_uint64_with_commas(jcr->impl->jr.JobFiles, ec1),
1046 edit_uint64_with_commas(jcr->impl->jr.JobBytes, ec3),
1047 edit_uint64_with_suffix(jcr->impl->jr.JobBytes, ec4));
1048 break;
1049 default:
1050 if (jcr->is_JobLevel(L_VIRTUAL_FULL)) {
1051 Mmsg(level_info, _(
1052 " Backup Level: Virtual Full\n"));
1053 Mmsg(statistics, _(
1054 " SD Files Written: %s\n"
1055 " SD Bytes Written: %s (%sB)\n"),
1056 edit_uint64_with_commas(jcr->impl->SDJobFiles, ec2),
1057 edit_uint64_with_commas(jcr->impl->SDJobBytes, ec5),
1058 edit_uint64_with_suffix(jcr->impl->SDJobBytes, ec6));
1059 } else {
1060 Mmsg(level_info, _(
1061 " Backup Level: %s%s\n"),
1062 JobLevelToString(jcr->getJobLevel()), jcr->impl->since);
1063 Mmsg(statistics, _(
1064 " FD Files Written: %s\n"
1065 " SD Files Written: %s\n"
1066 " FD Bytes Written: %s (%sB)\n"
1067 " SD Bytes Written: %s (%sB)\n"),
1068 edit_uint64_with_commas(jcr->impl->jr.JobFiles, ec1),
1069 edit_uint64_with_commas(jcr->impl->SDJobFiles, ec2),
1070 edit_uint64_with_commas(jcr->impl->jr.JobBytes, ec3),
1071 edit_uint64_with_suffix(jcr->impl->jr.JobBytes, ec4),
1072 edit_uint64_with_commas(jcr->impl->SDJobBytes, ec5),
1073 edit_uint64_with_suffix(jcr->impl->SDJobBytes, ec6));
1074 }
1075 break;
1076 }
1077
1078 if (jcr->impl->HasQuota) {
1079 if (jcr->impl->res.client->GraceTime != 0) {
1080 bstrftimes(gdt, sizeof(gdt), jcr->impl->res.client->GraceTime +
1081 jcr->impl->res.client->SoftQuotaGracePeriod);
1082 } else {
1083 bstrncpy(gdt, "Soft Quota not exceeded", sizeof(gdt));
1084 }
1085 Mmsg(quota_info, _(
1086 " Quota Used: %s (%sB)\n"
1087 " Burst Quota: %s (%sB)\n"
1088 " Soft Quota: %s (%sB)\n"
1089 " Hard Quota: %s (%sB)\n"
1090 " Grace Expiry Date: %s\n"),
1091 edit_uint64_with_commas(jcr->impl->jr.JobSumTotalBytes+jcr->impl->SDJobBytes, ec1),
1092 edit_uint64_with_suffix(jcr->impl->jr.JobSumTotalBytes+jcr->impl->SDJobBytes, ec2),
1093 edit_uint64_with_commas(jcr->impl->res.client->QuotaLimit, ec3),
1094 edit_uint64_with_suffix(jcr->impl->res.client->QuotaLimit, ec4),
1095 edit_uint64_with_commas(jcr->impl->res.client->SoftQuota, ec5),
1096 edit_uint64_with_suffix(jcr->impl->res.client->SoftQuota, ec6),
1097 edit_uint64_with_commas(jcr->impl->res.client->HardQuota, ec7),
1098 edit_uint64_with_suffix(jcr->impl->res.client->HardQuota, ec8),
1099 gdt);
1100 }
1101
1102 switch (jcr->getJobProtocol()) {
1103 case PT_NDMP_BAREOS:
1104 case PT_NDMP_NATIVE:
1105 break;
1106 default:
1107 if (jcr->is_JobLevel(L_VIRTUAL_FULL)) {
1108 Mmsg(daemon_status, _(
1109 " SD Errors: %d\n"
1110 " SD termination status: %s\n"
1111 " Accurate: %s\n"),
1112 jcr->impl->SDErrors,
1113 sd_term_msg,
1114 jcr->accurate ? _("yes") : _("no"));
1115 } else {
1116 if (jcr->HasBase) {
1117 Mmsg(client_options, _(
1118 " Software Compression: %s%s\n"
1119 " Base files/Used files: %lld/%lld (%.2f%%)\n"
1120 " VSS: %s\n"
1121 " Encryption: %s\n"
1122 " Accurate: %s\n"),
1123 compress,
1124 compress_algo_list.c_str(),
1125 jcr->nb_base_files,
1126 jcr->nb_base_files_used,
1127 jcr->nb_base_files_used * 100.0 / jcr->nb_base_files,
1128 jcr->impl->VSS ? _("yes") : _("no"),
1129 jcr->impl->Encrypt ? _("yes") : _("no"),
1130 jcr->accurate ? _("yes") : _("no"));
1131 } else {
1132 Mmsg(client_options, _(
1133 " Software Compression: %s%s\n"
1134 " VSS: %s\n"
1135 " Encryption: %s\n"
1136 " Accurate: %s\n"),
1137 compress,
1138 compress_algo_list.c_str(),
1139 jcr->impl->VSS ? _("yes") : _("no"),
1140 jcr->impl->Encrypt ? _("yes") : _("no"),
1141 jcr->accurate ? _("yes") : _("no"));
1142 }
1143
1144 Mmsg(daemon_status, _(
1145 " Non-fatal FD errors: %d\n"
1146 " SD Errors: %d\n"
1147 " FD termination status: %s\n"
1148 " SD termination status: %s\n"),
1149 jcr->JobErrors,
1150 jcr->impl->SDErrors,
1151 fd_term_msg,
1152 sd_term_msg);
1153
1154 if (me->secure_erase_cmdline) {
1155 Mmsg(temp," Dir Secure Erase Cmd: %s\n", me->secure_erase_cmdline);
1156 PmStrcat(secure_erase_status, temp.c_str());
1157 }
1158 if (!bstrcmp(jcr->impl->FDSecureEraseCmd, "*None*")) {
1159 Mmsg(temp, " FD Secure Erase Cmd: %s\n", jcr->impl->FDSecureEraseCmd);
1160 PmStrcat(secure_erase_status, temp.c_str());
1161 }
1162 if (!bstrcmp(jcr->impl->SDSecureEraseCmd, "*None*")) {
1163 Mmsg(temp, " SD Secure Erase Cmd: %s\n", jcr->impl->SDSecureEraseCmd);
1164 PmStrcat(secure_erase_status, temp.c_str());
1165 }
1166 }
1167 break;
1168 }
1169
1170 // Bmicrosleep(15, 0); /* for debugging SIGHUP */
1171
1172 Jmsg(jcr, msg_type, 0, _("%s %s %s (%s):\n"
1173 " Build OS: %s %s %s\n"
1174 " JobId: %d\n"
1175 " Job: %s\n"
1176 "%s"
1177 " Client: \"%s\" %s\n"
1178 " FileSet: \"%s\" %s\n"
1179 " Pool: \"%s\" (From %s)\n"
1180 " Catalog: \"%s\" (From %s)\n"
1181 " Storage: \"%s\" (From %s)\n"
1182 " Scheduled time: %s\n"
1183 " Start time: %s\n"
1184 " End time: %s\n"
1185 " Elapsed time: %s\n"
1186 " Priority: %d\n"
1187 "%s" /* FD/SD Statistics */
1188 "%s" /* Quota info */
1189 " Rate: %.1f KB/s\n"
1190 "%s" /* Client options */
1191 " Volume name(s): %s\n"
1192 " Volume Session Id: %d\n"
1193 " Volume Session Time: %d\n"
1194 " Last Volume Bytes: %s (%sB)\n"
1195 "%s" /* Daemon status info */
1196 "%s" /* SecureErase status */
1197 " Bareos binary info: %s\n"
1198 " Termination: %s\n\n"),
1199 BAREOS, my_name, kBareosVersionStrings.Full, kBareosVersionStrings.ShortDate,
1200 HOST_OS, DISTNAME, DISTVER,
1201 jcr->impl->jr.JobId,
1202 jcr->impl->jr.Job,
1203 level_info.c_str(),
1204 jcr->impl->res.client->resource_name_, cr->Uname,
1205 jcr->impl->res.fileset->resource_name_, jcr->impl->FSCreateTime,
1206 jcr->impl->res.pool->resource_name_, jcr->impl->res.pool_source,
1207 jcr->impl->res.catalog->resource_name_, jcr->impl->res.catalog_source,
1208 jcr->impl->res.write_storage->resource_name_, jcr->impl->res.wstore_source,
1209 schedt,
1210 sdt,
1211 edt,
1212 edit_utime(RunTime, elapsed, sizeof(elapsed)),
1213 jcr->JobPriority,
1214 statistics.c_str(),
1215 quota_info.c_str(),
1216 kbps,
1217 client_options.c_str(),
1218 jcr->VolumeName,
1219 jcr->VolSessionId,
1220 jcr->VolSessionTime,
1221 edit_uint64_with_commas(mr.VolBytes, ec7),
1222 edit_uint64_with_suffix(mr.VolBytes, ec8),
1223 daemon_status.c_str(),
1224 secure_erase_status.c_str(),
1225 kBareosVersionStrings.JoblogMessage,
1226 TermMsg);
1227
1228 /* clang-format on */
1229 }
1230 } /* namespace directordaemon */
1231