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