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