1 /*
2    BAREOS® - Backup Archiving REcovery Open Sourced
3 
4    Copyright (C) 2000-2011 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, October MM
25  */
26 /**
27  * @file
28  * responsible for running file verification
29  *
30  * Basic tasks done here:
31  *    * Open DB
32  *    * Open connection with File daemon and pass him commands to do the verify.
33  *    * When the File daemon sends the attributes, compare them to what is in
34  * the DB.
35  */
36 
37 #include "include/bareos.h"
38 #include "dird.h"
39 #include "dird/dird_globals.h"
40 #include "findlib/find.h"
41 #include "dird/backup.h"
42 #include "dird/fd_cmds.h"
43 #include "dird/getmsg.h"
44 #include "dird/jcr_private.h"
45 #include "dird/job.h"
46 #include "dird/msgchan.h"
47 #include "dird/sd_cmds.h"
48 #include "dird/storage.h"
49 #include "dird/verify.h"
50 #include "lib/berrno.h"
51 #include "lib/bnet.h"
52 #include "lib/edit.h"
53 #include "lib/util.h"
54 
55 namespace directordaemon {
56 
57 /* Commands sent to File daemon */
58 static char verifycmd[] = "verify level=%s\n";
59 static char storaddrcmd[] =
60     "storage address=%s port=%d ssl=%d Authorization=%s\n";
61 static char passiveclientcmd[] = "passive client address=%s port=%d ssl=%d\n";
62 
63 /* Responses received from File daemon */
64 static char OKverify[] = "2000 OK verify\n";
65 static char OKstore[] = "2000 OK storage\n";
66 static char OKpassiveclient[] = "2000 OK passive client\n";
67 
68 /* Forward referenced functions */
69 static void PrtFname(JobControlRecord* jcr);
70 static int MissingHandler(void* ctx, int num_fields, char** row);
71 
72 /**
73  * Called here before the job is run to do the job
74  *   specific setup.
75  */
DoVerifyInit(JobControlRecord * jcr)76 bool DoVerifyInit(JobControlRecord* jcr)
77 {
78   int JobLevel;
79 
80   if (!AllowDuplicateJob(jcr)) { return false; }
81 
82   JobLevel = jcr->getJobLevel();
83   switch (JobLevel) {
84     case L_VERIFY_INIT:
85     case L_VERIFY_CATALOG:
86     case L_VERIFY_DISK_TO_CATALOG:
87       FreeRstorage(jcr);
88       FreeWstorage(jcr);
89       break;
90     case L_VERIFY_VOLUME_TO_CATALOG:
91       FreeWstorage(jcr);
92       break;
93     case L_VERIFY_DATA:
94       break;
95     default:
96       Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), JobLevel,
97             JobLevel);
98       return false;
99   }
100   return true;
101 }
102 
103 /**
104  * Do a verification of the specified files against the Catalog
105  *
106  *  Returns:  false on failure
107  *            true  on success
108  */
DoVerify(JobControlRecord * jcr)109 bool DoVerify(JobControlRecord* jcr)
110 {
111   int JobLevel;
112   const char* level;
113   BareosSocket* fd = NULL;
114   BareosSocket* sd = NULL;
115   int status;
116   char ed1[100];
117   JobDbRecord jr;
118   JobId_t verify_jobid = 0;
119   const char* Name;
120 
121   FreeWstorage(jcr); /* we don't write */
122 
123   new (&jcr->impl->previous_jr)
124       JobDbRecord();  // placement new instead of memset
125 
126   /*
127    * Find JobId of last job that ran. Note, we do this when
128    *   the job actually starts running, not at schedule time,
129    *   so that we find the last job that terminated before
130    *   this job runs rather than before it is scheduled. This
131    *   permits scheduling a Backup and Verify at the same time,
132    *   but with the Verify at a lower priority.
133    *
134    *   For VERIFY_CATALOG we want the JobId of the last INIT.
135    *   For VERIFY_VOLUME_TO_CATALOG, we want the JobId of the
136    *       last backup Job.
137    */
138   JobLevel = jcr->getJobLevel();
139   switch (JobLevel) {
140     case L_VERIFY_CATALOG:
141     case L_VERIFY_VOLUME_TO_CATALOG:
142     case L_VERIFY_DISK_TO_CATALOG:
143       jr = jcr->impl->jr;
144       if (jcr->impl->res.verify_job &&
145           (JobLevel == L_VERIFY_VOLUME_TO_CATALOG ||
146            JobLevel == L_VERIFY_DISK_TO_CATALOG)) {
147         Name = jcr->impl->res.verify_job->resource_name_;
148       } else {
149         Name = NULL;
150       }
151       Dmsg1(100, "find last jobid for: %s\n", NPRT(Name));
152 
153       /*
154        * See if user supplied a jobid= as run argument or from menu
155        */
156       if (jcr->impl->VerifyJobId) {
157         verify_jobid = jcr->impl->VerifyJobId;
158         Dmsg1(100, "Supplied jobid=%d\n", verify_jobid);
159 
160       } else {
161         if (!jcr->db->FindLastJobid(jcr, Name, &jr)) {
162           if (JobLevel == L_VERIFY_CATALOG) {
163             Jmsg(jcr, M_FATAL, 0,
164                  _("Unable to find JobId of previous InitCatalog Job.\n"
165                    "Please run a Verify with Level=InitCatalog before\n"
166                    "running the current Job.\n"));
167           } else {
168             Jmsg(jcr, M_FATAL, 0,
169                  _("Unable to find JobId of previous Job for this client.\n"));
170           }
171           return false;
172         }
173         verify_jobid = jr.JobId;
174       }
175       Dmsg1(100, "Last full jobid=%d\n", verify_jobid);
176 
177       /*
178        * Now get the job record for the previous backup that interests
179        *   us. We use the verify_jobid that we found above.
180        */
181       jcr->impl->previous_jr.JobId = verify_jobid;
182       if (!jcr->db->GetJobRecord(jcr, &jcr->impl->previous_jr)) {
183         Jmsg(jcr, M_FATAL, 0,
184              _("Could not get job record for previous Job. ERR=%s"),
185              jcr->db->strerror());
186         return false;
187       }
188       if (!(jcr->impl->previous_jr.JobStatus == JS_Terminated ||
189             jcr->impl->previous_jr.JobStatus == JS_Warnings)) {
190         Jmsg(jcr, M_FATAL, 0,
191              _("Last Job %d did not Terminate normally. JobStatus=%c\n"),
192              verify_jobid, jcr->impl->previous_jr.JobStatus);
193         return false;
194       }
195       Jmsg(jcr, M_INFO, 0, _("Verifying against JobId=%d Job=%s\n"),
196            jcr->impl->previous_jr.JobId, jcr->impl->previous_jr.Job);
197   }
198 
199   /*
200    * If we are verifying a Volume, we need the Storage
201    *   daemon, so open a connection, otherwise, just
202    *   create a dummy authorization key (passed to
203    *   File daemon but not used).
204    */
205   switch (JobLevel) {
206     case L_VERIFY_VOLUME_TO_CATALOG:
207       /*
208        * Note: negative status is an error, zero status, means
209        *  no files were backed up, so skip calling SD and
210        *  client.
211        */
212       status = CreateRestoreBootstrapFile(jcr);
213       if (status < 0) { /* error */
214         return false;
215       } else if (status == 0) {            /* No files, nothing to do */
216         VerifyCleanup(jcr, JS_Terminated); /* clean up */
217         return true;                       /* get out */
218       }
219 
220       if (jcr->impl->res.verify_job) {
221         jcr->impl->res.fileset = jcr->impl->res.verify_job->fileset;
222       }
223       break;
224     default:
225       jcr->sd_auth_key = strdup("dummy"); /* dummy Storage daemon key */
226       break;
227   }
228 
229   Dmsg2(100, "ClientId=%u JobLevel=%c\n", jcr->impl->previous_jr.ClientId,
230         JobLevel);
231 
232   if (!jcr->db->UpdateJobStartRecord(jcr, &jcr->impl->jr)) {
233     Jmsg(jcr, M_FATAL, 0, "%s", jcr->db->strerror());
234     return false;
235   }
236 
237   /*
238    * Print Job Start message
239    */
240   Jmsg(jcr, M_INFO, 0, _("Start Verify JobId=%s Level=%s Job=%s\n"),
241        edit_uint64(jcr->JobId, ed1), JobLevelToString(JobLevel), jcr->Job);
242 
243   switch (JobLevel) {
244     case L_VERIFY_VOLUME_TO_CATALOG:
245       /*
246        * Start conversation with Storage daemon
247        */
248       jcr->setJobStatus(JS_Blocked);
249       if (!ConnectToStorageDaemon(jcr, 10, me->SDConnectTimeout, true)) {
250         return false;
251       }
252       sd = jcr->store_bsock;
253 
254       /*
255        * Now start a job with the Storage daemon
256        */
257       if (!StartStorageDaemonJob(jcr, jcr->impl->res.read_storage_list, NULL,
258                                  /* send_bsr */ true)) {
259         return false;
260       }
261 
262       jcr->passive_client = jcr->impl->res.client->passive;
263       if (!jcr->passive_client) {
264         /*
265          * Start the Job in the SD.
266          */
267         if (!sd->fsend("run")) { return false; }
268 
269         /*
270          * Now start a Storage daemon message thread
271          */
272         if (!StartStorageDaemonMessageThread(jcr)) { return false; }
273         Dmsg0(50, "Storage daemon connection OK\n");
274       }
275 
276       /*
277        * OK, now connect to the File daemon and ask him for the files.
278        */
279       jcr->setJobStatus(JS_Blocked);
280       if (!ConnectToFileDaemon(jcr, 10, me->FDConnectTimeout, true)) {
281         goto bail_out;
282       }
283       SendJobInfoToFileDaemon(jcr);
284       fd = jcr->file_bsock;
285 
286       /*
287        * Check if the file daemon supports passive client mode.
288        */
289       if (jcr->passive_client && jcr->impl->FDVersion < FD_VERSION_51) {
290         Jmsg(jcr, M_FATAL, 0,
291              _("Client \"%s\" doesn't support passive client mode. "
292                "Please upgrade your client or disable compat mode.\n"),
293              jcr->impl->res.client->resource_name_);
294         goto bail_out;
295       }
296       break;
297     default:
298       /*
299        * OK, now connect to the File daemon and ask him for the files.
300        */
301       jcr->setJobStatus(JS_Blocked);
302       if (!ConnectToFileDaemon(jcr, 10, me->FDConnectTimeout, true)) {
303         goto bail_out;
304       }
305       SendJobInfoToFileDaemon(jcr);
306       fd = jcr->file_bsock;
307       break;
308   }
309 
310   jcr->setJobStatus(JS_Running);
311 
312   Dmsg0(30, ">filed: Send include list\n");
313   if (!SendIncludeList(jcr)) { goto bail_out; }
314 
315   Dmsg0(30, ">filed: Send exclude list\n");
316   if (!SendExcludeList(jcr)) { goto bail_out; }
317 
318   /*
319    * Send Level command to File daemon, as well as the Storage address if
320    * appropriate.
321    */
322   switch (JobLevel) {
323     case L_VERIFY_INIT:
324       level = "init";
325       break;
326     case L_VERIFY_CATALOG:
327       level = "catalog";
328       break;
329     case L_VERIFY_VOLUME_TO_CATALOG:
330       if (!jcr->RestoreBootstrap) {
331         Jmsg0(jcr, M_FATAL, 0, _("Deprecated feature ... use bootstrap.\n"));
332         goto bail_out;
333       }
334 
335       if (!jcr->passive_client) {
336         StorageResource* store = jcr->impl->res.read_storage;
337 
338         /*
339          * Send Storage daemon address to the File daemon
340          */
341         if (store->SDDport == 0) { store->SDDport = store->SDport; }
342 
343         TlsPolicy tls_policy;
344         if (jcr->impl->res.client->connection_successful_handshake_ !=
345             ClientConnectionHandshakeMode::kTlsFirst) {
346           tls_policy = store->GetPolicy();
347         } else {
348           tls_policy = store->IsTlsConfigured() ? TlsPolicy::kBnetTlsAuto
349                                                 : TlsPolicy::kBnetTlsNone;
350         }
351 
352         Dmsg1(200, "Tls Policy for active client is: %d\n", tls_policy);
353 
354         fd->fsend(storaddrcmd, store->address, store->SDDport, tls_policy,
355                   jcr->sd_auth_key);
356         if (!response(jcr, fd, OKstore, "Storage", DISPLAY_ERROR)) {
357           goto bail_out;
358         }
359       } else {
360         ClientResource* client = jcr->impl->res.client;
361 
362         TlsPolicy tls_policy;
363         if (jcr->impl->res.client->connection_successful_handshake_ !=
364             ClientConnectionHandshakeMode::kTlsFirst) {
365           tls_policy = client->GetPolicy();
366         } else {
367           tls_policy = client->IsTlsConfigured() ? TlsPolicy::kBnetTlsAuto
368                                                  : TlsPolicy::kBnetTlsNone;
369         }
370 
371         Dmsg1(200, "Tls Policy for passive client is: %d\n", tls_policy);
372 
373         /*
374          * Tell the SD to connect to the FD.
375          */
376         sd->fsend(passiveclientcmd, client->address, client->FDport,
377                   tls_policy);
378         Bmicrosleep(2, 0);
379         if (!response(jcr, sd, OKpassiveclient, "Passive client",
380                       DISPLAY_ERROR)) {
381           goto bail_out;
382         }
383 
384         /*
385          * Start the Job in the SD.
386          */
387         if (!sd->fsend("run")) { goto bail_out; }
388 
389         /*
390          * Now start a Storage daemon message thread
391          */
392         if (!StartStorageDaemonMessageThread(jcr)) { goto bail_out; }
393         Dmsg0(50, "Storage daemon connection OK\n");
394       }
395 
396       level = "volume";
397       break;
398     case L_VERIFY_DATA:
399       level = "data";
400       break;
401     case L_VERIFY_DISK_TO_CATALOG:
402       level = "disk_to_catalog";
403       break;
404     default:
405       Jmsg2(jcr, M_FATAL, 0, _("Unimplemented Verify level %d(%c)\n"), JobLevel,
406             JobLevel);
407       goto bail_out;
408   }
409 
410   if (!SendRunscriptsCommands(jcr)) { goto bail_out; }
411 
412   /*
413    * Send verify command/level to File daemon
414    */
415   fd->fsend(verifycmd, level);
416   if (!response(jcr, fd, OKverify, "Verify", DISPLAY_ERROR)) { goto bail_out; }
417 
418   /*
419    * Now get data back from File daemon and
420    *  compare it to the catalog or store it in the
421    *  catalog depending on the run type.
422    */
423   switch (JobLevel) {
424     case L_VERIFY_CATALOG:
425       /*
426        * Verify from catalog
427        */
428       Dmsg0(10, "Verify level=catalog\n");
429       jcr->impl->sd_msg_thread_done =
430           true; /* no SD msg thread, so it is done */
431       jcr->impl->SDJobStatus = JS_Terminated;
432       GetAttributesAndCompareToCatalog(jcr, jcr->impl->previous_jr.JobId);
433       break;
434     case L_VERIFY_VOLUME_TO_CATALOG:
435       /*
436        * Verify Volume to catalog entries
437        */
438       Dmsg0(10, "Verify level=volume\n");
439       GetAttributesAndCompareToCatalog(jcr, jcr->impl->previous_jr.JobId);
440       break;
441     case L_VERIFY_DISK_TO_CATALOG:
442       /*
443        * Verify Disk attributes to catalog
444        */
445       Dmsg0(10, "Verify level=disk_to_catalog\n");
446       jcr->impl->sd_msg_thread_done =
447           true; /* no SD msg thread, so it is done */
448       jcr->impl->SDJobStatus = JS_Terminated;
449       GetAttributesAndCompareToCatalog(jcr, jcr->impl->previous_jr.JobId);
450       break;
451     case L_VERIFY_INIT:
452       /*
453        * Build catalog
454        */
455       Dmsg0(10, "Verify level=init\n");
456       jcr->impl->sd_msg_thread_done =
457           true; /* no SD msg thread, so it is done */
458       jcr->impl->SDJobStatus = JS_Terminated;
459       GetAttributesAndPutInCatalog(jcr);
460       jcr->db->EndTransaction(jcr); /* Terminate any open transaction */
461       jcr->db_batch->WriteBatchFileRecords(jcr);
462       break;
463     default:
464       Jmsg1(jcr, M_FATAL, 0, _("Unimplemented verify level %d\n"), JobLevel);
465       goto bail_out;
466   }
467 
468   status = WaitForJobTermination(jcr);
469   VerifyCleanup(jcr, status);
470   return true;
471 
472 bail_out:
473   if (jcr->file_bsock) {
474     jcr->file_bsock->signal(BNET_TERMINATE);
475     jcr->file_bsock->close();
476     delete jcr->file_bsock;
477     jcr->file_bsock = NULL;
478   }
479 
480   return false;
481 }
482 
483 /**
484  * Release resources allocated during verify.
485  */
VerifyCleanup(JobControlRecord * jcr,int TermCode)486 void VerifyCleanup(JobControlRecord* jcr, int TermCode)
487 {
488   int JobLevel;
489   char sdt[50], edt[50];
490   char ec1[30], ec2[30];
491   char term_code[100], fd_term_msg[100], sd_term_msg[100];
492   const char* TermMsg;
493   int msg_type;
494   const char* Name;
495 
496   // Dmsg1(100, "Enter VerifyCleanup() TermCod=%d\n", TermCode);
497 
498   JobLevel = jcr->getJobLevel();
499   Dmsg3(900, "JobLevel=%c Expected=%u JobFiles=%u\n", JobLevel,
500         jcr->impl->ExpectedFiles, jcr->JobFiles);
501   if (JobLevel == L_VERIFY_VOLUME_TO_CATALOG &&
502       jcr->impl->ExpectedFiles != jcr->JobFiles) {
503     TermCode = JS_ErrorTerminated;
504   }
505 
506   UpdateJobEnd(jcr, TermCode);
507 
508   if (JobCanceled(jcr)) { CancelStorageDaemonJob(jcr); }
509 
510   if (jcr->impl->unlink_bsr && jcr->RestoreBootstrap) {
511     SecureErase(jcr, jcr->RestoreBootstrap);
512     jcr->impl->unlink_bsr = false;
513   }
514 
515   msg_type = M_INFO; /* By default INFO message */
516   switch (TermCode) {
517     case JS_Terminated:
518       TermMsg = _("Verify OK");
519       break;
520     case JS_FatalError:
521     case JS_ErrorTerminated:
522       TermMsg = _("*** Verify Error ***");
523       msg_type = M_ERROR; /* Generate error message */
524       break;
525     case JS_Error:
526       TermMsg = _("Verify warnings");
527       break;
528     case JS_Canceled:
529       TermMsg = _("Verify Canceled");
530       break;
531     case JS_Differences:
532       TermMsg = _("Verify Differences");
533       break;
534     default:
535       TermMsg = term_code;
536       Bsnprintf(term_code, sizeof(term_code),
537                 _("Inappropriate term code: %d %c\n"), TermCode, TermCode);
538       break;
539   }
540   bstrftimes(sdt, sizeof(sdt), jcr->impl->jr.StartTime);
541   bstrftimes(edt, sizeof(edt), jcr->impl->jr.EndTime);
542   if (jcr->impl->res.verify_job) {
543     Name = jcr->impl->res.verify_job->resource_name_;
544   } else {
545     Name = "";
546   }
547 
548   JobstatusToAscii(jcr->impl->FDJobStatus, fd_term_msg, sizeof(fd_term_msg));
549   switch (JobLevel) {
550     case L_VERIFY_VOLUME_TO_CATALOG:
551       JobstatusToAscii(jcr->impl->SDJobStatus, sd_term_msg,
552                        sizeof(sd_term_msg));
553       Jmsg(jcr, msg_type, 0,
554            _("%s %s %s (%s):\n"
555              "  Build OS:               %s %s %s\n"
556              "  JobId:                  %d\n"
557              "  Job:                    %s\n"
558              "  FileSet:                %s\n"
559              "  Verify Level:           %s\n"
560              "  Client:                 %s\n"
561              "  Verify JobId:           %d\n"
562              "  Verify Job:             %s\n"
563              "  Start time:             %s\n"
564              "  End time:               %s\n"
565              "  Files Expected:         %s\n"
566              "  Files Examined:         %s\n"
567              "  Non-fatal FD errors:    %d\n"
568              "  FD termination status:  %s\n"
569              "  SD termination status:  %s\n"
570              "  Bareos binary info:     %s\n"
571              "  Termination:            %s\n\n"),
572            BAREOS, my_name, kBareosVersionStrings.Full,
573            kBareosVersionStrings.ShortDate, HOST_OS, DISTNAME, DISTVER,
574            jcr->impl->jr.JobId, jcr->impl->jr.Job,
575            jcr->impl->res.fileset->resource_name_, JobLevelToString(JobLevel),
576            jcr->impl->res.client->resource_name_, jcr->impl->previous_jr.JobId,
577            Name, sdt, edt,
578            edit_uint64_with_commas(jcr->impl->ExpectedFiles, ec1),
579            edit_uint64_with_commas(jcr->JobFiles, ec2), jcr->JobErrors,
580            fd_term_msg, sd_term_msg, kBareosVersionStrings.JoblogMessage,
581            TermMsg);
582       break;
583     default:
584       Jmsg(jcr, msg_type, 0,
585            _("%s %s %s (%s):\n"
586              "  Build:                  %s %s %s\n"
587              "  JobId:                  %d\n"
588              "  Job:                    %s\n"
589              "  FileSet:                %s\n"
590              "  Verify Level:           %s\n"
591              "  Client:                 %s\n"
592              "  Verify JobId:           %d\n"
593              "  Verify Job:             %s\n"
594              "  Start time:             %s\n"
595              "  End time:               %s\n"
596              "  Files Examined:         %s\n"
597              "  Non-fatal FD errors:    %d\n"
598              "  FD termination status:  %s\n"
599              "  Bareos binary info:     %s\n"
600              "  Termination:            %s\n\n"),
601            BAREOS, my_name, kBareosVersionStrings.Full,
602            kBareosVersionStrings.ShortDate, HOST_OS, DISTNAME, DISTVER,
603            jcr->impl->jr.JobId, jcr->impl->jr.Job,
604            jcr->impl->res.fileset->resource_name_, JobLevelToString(JobLevel),
605            jcr->impl->res.client->resource_name_, jcr->impl->previous_jr.JobId,
606            Name, sdt, edt, edit_uint64_with_commas(jcr->JobFiles, ec1),
607            jcr->JobErrors, fd_term_msg, kBareosVersionStrings.JoblogMessage,
608            TermMsg);
609       break;
610   }
611 
612   Dmsg0(100, "Leave VerifyCleanup()\n");
613 }
614 
615 /**
616  * This routine is called only during a Verify
617  */
GetAttributesAndCompareToCatalog(JobControlRecord * jcr,JobId_t JobId)618 void GetAttributesAndCompareToCatalog(JobControlRecord* jcr, JobId_t JobId)
619 {
620   BareosSocket* fd;
621   int n, len;
622   FileDbRecord fdbr;
623   struct stat statf; /* file stat */
624   struct stat statc; /* catalog stat */
625   PoolMem buf(PM_MESSAGE);
626   POOLMEM* fname = GetPoolMemory(PM_FNAME);
627   int do_Digest = CRYPTO_DIGEST_NONE;
628   int32_t file_index = 0;
629 
630   fd = jcr->file_bsock;
631   fdbr.JobId = JobId;
632   jcr->impl->FileIndex = 0;
633 
634   Dmsg0(20, "dir: waiting to receive file attributes\n");
635   /*
636    * Get Attributes and Signature from File daemon
637    * We expect:
638    *   FileIndex
639    *   Stream
640    *   Options or Digest (MD5/SHA1)
641    *   Filename
642    *   Attributes
643    *   Link name  ???
644    */
645   while ((n = BgetDirmsg(fd)) >= 0 && !JobCanceled(jcr)) {
646     int stream;
647     char *attr, *p, *fn;
648     PoolMem Opts_Digest(PM_MESSAGE); /* Verify Opts or MD5/SHA1 digest */
649 
650     if (JobCanceled(jcr)) { goto bail_out; }
651     fname = CheckPoolMemorySize(fname, fd->message_length);
652     jcr->impl->fname =
653         CheckPoolMemorySize(jcr->impl->fname, fd->message_length);
654     Dmsg1(200, "Atts+Digest=%s\n", fd->msg);
655     if ((len = sscanf(fd->msg, "%ld %d %100s", &file_index, &stream, fname)) !=
656         3) {
657       Jmsg3(jcr, M_FATAL, 0,
658             _("dird<filed: bad attributes, expected 3 fields got %d\n"
659               " mslen=%d msg=%s\n"),
660             len, fd->message_length, fd->msg);
661       goto bail_out;
662     }
663     /*
664      * We read the Options or Signature into fname
665      *  to prevent overrun, now copy it to proper location.
666      */
667     PmStrcpy(Opts_Digest, fname);
668     p = fd->msg;
669     SkipNonspaces(&p); /* skip FileIndex */
670     SkipSpaces(&p);
671     SkipNonspaces(&p); /* skip Stream */
672     SkipSpaces(&p);
673     SkipNonspaces(&p); /* skip Opts_Digest */
674     p++;               /* skip space */
675     fn = fname;
676     while (*p != 0) { *fn++ = *p++; /* copy filename */ }
677     *fn = *p++; /* term filename and point to attribs */
678     attr = p;
679 
680     /*
681      * Got attributes stream, decode it
682      */
683     switch (stream) {
684       case STREAM_UNIX_ATTRIBUTES:
685       case STREAM_UNIX_ATTRIBUTES_EX:
686         int32_t LinkFIf, LinkFIc;
687         Dmsg2(400, "file_index=%d attr=%s\n", file_index, attr);
688         jcr->JobFiles++;
689         jcr->impl->FileIndex = file_index; /* remember attribute file_index */
690         jcr->impl->previous_jr.FileIndex = file_index;
691         DecodeStat(attr, &statf, sizeof(statf),
692                    &LinkFIf); /* decode file stat packet */
693         do_Digest = CRYPTO_DIGEST_NONE;
694         jcr->impl->fn_printed = false;
695         PmStrcpy(jcr->impl->fname,
696                  fname); /* move filename into JobControlRecord */
697 
698         Dmsg2(040, "dird<filed: stream=%d %s\n", stream, jcr->impl->fname);
699         Dmsg1(020, "dird<filed: attr=%s\n", attr);
700 
701         /*
702          * Find equivalent record in the database
703          */
704         fdbr.FileId = 0;
705         if (!jcr->db->GetFileAttributesRecord(jcr, jcr->impl->fname,
706                                               &jcr->impl->previous_jr, &fdbr)) {
707           Jmsg(jcr, M_INFO, 0, _("New file: %s\n"), jcr->impl->fname);
708           Dmsg1(020, _("File not in catalog: %s\n"), jcr->impl->fname);
709           jcr->setJobStatus(JS_Differences);
710           continue;
711         } else {
712           /*
713            * mark file record as visited by stuffing the
714            * current JobId, which is unique, into the MarkId field.
715            */
716           jcr->db->MarkFileRecord(jcr, fdbr.FileId, jcr->JobId);
717         }
718 
719         Dmsg3(400, "Found %s in catalog. inx=%d Opts=%s\n", jcr->impl->fname,
720               file_index, Opts_Digest.c_str());
721         DecodeStat(fdbr.LStat, &statc, sizeof(statc),
722                    &LinkFIc); /* decode catalog stat */
723         /*
724          * Loop over options supplied by user and verify the
725          * fields he requests.
726          */
727         for (p = Opts_Digest.c_str(); *p; p++) {
728           char ed1[30], ed2[30];
729           switch (*p) {
730             case 'i': /* compare INODEs */
731               if (statc.st_ino != statf.st_ino) {
732                 PrtFname(jcr);
733                 Jmsg(jcr, M_INFO, 0,
734                      _("      st_ino   differ. Cat: %s File: %s\n"),
735                      edit_uint64((uint64_t)statc.st_ino, ed1),
736                      edit_uint64((uint64_t)statf.st_ino, ed2));
737                 jcr->setJobStatus(JS_Differences);
738               }
739               break;
740             case 'p': /* permissions bits */
741               if (statc.st_mode != statf.st_mode) {
742                 PrtFname(jcr);
743                 Jmsg(jcr, M_INFO, 0,
744                      _("      st_mode  differ. Cat: %x File: %x\n"),
745                      (uint32_t)statc.st_mode, (uint32_t)statf.st_mode);
746                 jcr->setJobStatus(JS_Differences);
747               }
748               break;
749             case 'n': /* number of links */
750               if (statc.st_nlink != statf.st_nlink) {
751                 PrtFname(jcr);
752                 Jmsg(jcr, M_INFO, 0,
753                      _("      st_nlink differ. Cat: %d File: %d\n"),
754                      (uint32_t)statc.st_nlink, (uint32_t)statf.st_nlink);
755                 jcr->setJobStatus(JS_Differences);
756               }
757               break;
758             case 'u': /* user id */
759               if (statc.st_uid != statf.st_uid) {
760                 PrtFname(jcr);
761                 Jmsg(jcr, M_INFO, 0,
762                      _("      st_uid   differ. Cat: %u File: %u\n"),
763                      (uint32_t)statc.st_uid, (uint32_t)statf.st_uid);
764                 jcr->setJobStatus(JS_Differences);
765               }
766               break;
767             case 'g': /* group id */
768               if (statc.st_gid != statf.st_gid) {
769                 PrtFname(jcr);
770                 Jmsg(jcr, M_INFO, 0,
771                      _("      st_gid   differ. Cat: %u File: %u\n"),
772                      (uint32_t)statc.st_gid, (uint32_t)statf.st_gid);
773                 jcr->setJobStatus(JS_Differences);
774               }
775               break;
776             case 's': /* size */
777               if (statc.st_size != statf.st_size) {
778                 PrtFname(jcr);
779                 Jmsg(jcr, M_INFO, 0,
780                      _("      st_size  differ. Cat: %s File: %s\n"),
781                      edit_uint64((uint64_t)statc.st_size, ed1),
782                      edit_uint64((uint64_t)statf.st_size, ed2));
783                 jcr->setJobStatus(JS_Differences);
784               }
785               break;
786             case 'a': /* access time */
787               if (statc.st_atime != statf.st_atime) {
788                 PrtFname(jcr);
789                 Jmsg(jcr, M_INFO, 0, _("      st_atime differs\n"));
790                 jcr->setJobStatus(JS_Differences);
791               }
792               break;
793             case 'm':
794               if (statc.st_mtime != statf.st_mtime) {
795                 PrtFname(jcr);
796                 Jmsg(jcr, M_INFO, 0, _("      st_mtime differs\n"));
797                 jcr->setJobStatus(JS_Differences);
798               }
799               break;
800             case 'c': /* ctime */
801               if (statc.st_ctime != statf.st_ctime) {
802                 PrtFname(jcr);
803                 Jmsg(jcr, M_INFO, 0, _("      st_ctime differs\n"));
804                 jcr->setJobStatus(JS_Differences);
805               }
806               break;
807             case 'd': /* file size decrease */
808               if (statc.st_size > statf.st_size) {
809                 PrtFname(jcr);
810                 Jmsg(jcr, M_INFO, 0,
811                      _("      st_size  decrease. Cat: %s File: %s\n"),
812                      edit_uint64((uint64_t)statc.st_size, ed1),
813                      edit_uint64((uint64_t)statf.st_size, ed2));
814                 jcr->setJobStatus(JS_Differences);
815               }
816               break;
817             case '5': /* compare MD5 */
818               Dmsg1(500, "set Do_MD5 for %s\n", jcr->impl->fname);
819               do_Digest = CRYPTO_DIGEST_MD5;
820               break;
821             case '1': /* compare SHA1 */
822               do_Digest = CRYPTO_DIGEST_SHA1;
823               break;
824             case ':':
825             case 'V':
826             default:
827               break;
828           }
829         }
830         break;
831 
832       case STREAM_RESTORE_OBJECT:
833         Dmsg1(400, "RESTORE_OBJECT %s\n", jcr->impl->fname);
834         break;
835 
836       default:
837         /*
838          * Got Digest Signature from Storage daemon
839          *  It came across in the Opts_Digest field.
840          */
841         if (CryptoDigestStreamType(stream) != CRYPTO_DIGEST_NONE) {
842           Dmsg2(400, "stream=Digest inx=%d Digest=%s\n", file_index,
843                 Opts_Digest.c_str());
844           /*
845            * When ever we get a digest it MUST have been
846            * preceded by an attributes record, which sets attr_file_index
847            */
848           if (jcr->impl->FileIndex != (uint32_t)file_index) {
849             Jmsg2(jcr, M_FATAL, 0,
850                   _("MD5/SHA1 index %d not same as attributes %d\n"),
851                   file_index, jcr->impl->FileIndex);
852             goto bail_out;
853           }
854           if (do_Digest != CRYPTO_DIGEST_NONE) {
855             jcr->db->EscapeString(jcr, buf.c_str(), Opts_Digest.c_str(),
856                                   strlen(Opts_Digest.c_str()));
857             if (!bstrcmp(buf.c_str(), fdbr.Digest)) {
858               PrtFname(jcr);
859               Jmsg(jcr, M_INFO, 0, _("      %s differs. File=%s Cat=%s\n"),
860                    stream_to_ascii(stream), buf.c_str(), fdbr.Digest);
861               jcr->setJobStatus(JS_Differences);
862             }
863             do_Digest = CRYPTO_DIGEST_NONE;
864           }
865         }
866         break;
867     }
868     jcr->JobFiles = file_index;
869   }
870 
871   if (IsBnetError(fd)) {
872     BErrNo be;
873     Jmsg2(jcr, M_FATAL, 0,
874           _("dir<filed: bad attributes from filed n=%d : %s\n"), n,
875           be.bstrerror());
876     goto bail_out;
877   }
878 
879   /* Now find all the files that are missing -- i.e. all files in
880    *  the database where the MarkId != current JobId
881    */
882   jcr->impl->fn_printed = false;
883   Mmsg(buf,
884        "SELECT Path.Path,File.Name FROM File,Path "
885        "WHERE File.JobId=%d AND File.FileIndex > 0 "
886        "AND File.MarkId!=%d AND File.PathId=Path.PathId ",
887        JobId, jcr->JobId);
888   /* MissingHandler is called for each file found */
889   jcr->db->SqlQuery(buf.c_str(), MissingHandler, (void*)jcr);
890   if (jcr->impl->fn_printed) { jcr->setJobStatus(JS_Differences); }
891 
892 bail_out:
893   FreePoolMemory(fname);
894 }
895 
896 /**
897  * We are called here for each record that matches the above
898  *  SQL query -- that is for each file contained in the Catalog
899  *  that was not marked earlier. This means that the file in
900  *  question is a missing file (in the Catalog but not on Disk).
901  */
MissingHandler(void * ctx,int num_fields,char ** row)902 static int MissingHandler(void* ctx, int num_fields, char** row)
903 {
904   JobControlRecord* jcr = (JobControlRecord*)ctx;
905 
906   if (JobCanceled(jcr)) { return 1; }
907   if (!jcr->impl->fn_printed) {
908     Qmsg(jcr, M_WARNING, 0,
909          _("The following files are in the Catalog but not on %s:\n"),
910          jcr->getJobLevel() == L_VERIFY_VOLUME_TO_CATALOG ? "the Volume(s)"
911                                                           : "disk");
912     jcr->impl->fn_printed = true;
913   }
914   Qmsg(jcr, M_INFO, 0, "      %s%s\n", row[0] ? row[0] : "",
915        row[1] ? row[1] : "");
916   return 0;
917 }
918 
919 /**
920  * Print filename for verify
921  */
PrtFname(JobControlRecord * jcr)922 static void PrtFname(JobControlRecord* jcr)
923 {
924   if (!jcr->impl->fn_printed) {
925     Jmsg(jcr, M_INFO, 0, _("File: %s\n"), jcr->impl->fname);
926     jcr->impl->fn_printed = true;
927   }
928 }
929 } /* namespace directordaemon */
930