1 /* lcb_verify.c -- replication-based backup api - verify functions
2 *
3 * Copyright (c) 1994-2015 Carnegie Mellon University. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided with the
15 * distribution.
16 *
17 * 3. The name "Carnegie Mellon University" must not be used to
18 * endorse or promote products derived from this software without
19 * prior written permission. For permission or any legal
20 * details, please contact
21 * Carnegie Mellon University
22 * Center for Technology Transfer and Enterprise Creation
23 * 4615 Forbes Avenue
24 * Suite 302
25 * Pittsburgh, PA 15213
26 * (412) 268-7393, fax: (412) 268-7395
27 * innovation@andrew.cmu.edu
28 *
29 * 4. Redistributions of any form whatsoever must retain the following
30 * acknowledgment:
31 * "This product includes software developed by Computing Services
32 * at Carnegie Mellon University (http://www.cmu.edu/computing/)."
33 *
34 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
35 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
36 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
37 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
38 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
39 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
40 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
41 *
42 */
43 #include <assert.h>
44 #include <syslog.h>
45
46 #include "lib/gzuncat.h"
47 #include "lib/hash.h"
48 #include "lib/map.h"
49 #include "lib/xmalloc.h"
50 #include "lib/xsha1.h"
51
52 #include "backup/backup.h"
53
54 #define LIBCYRUS_BACKUP_SOURCE /* this file is part of libcyrus_backup */
55 #include "backup/lcb_internal.h"
56 #include "backup/lcb_sqlconsts.h"
57
58 static int verify_chunk_checksums(struct backup *backup, struct backup_chunk *chunk,
59 struct gzuncat *gzuc, int verbose,
60 FILE *out);
61 static int verify_chunk_messages(struct backup *backup, struct backup_chunk *chunk,
62 struct gzuncat *gzuc, unsigned level,
63 int verbose, FILE *out);
64 static int verify_chunk_mailbox_links(struct backup *backup, struct backup_chunk *chunk,
65 struct gzuncat *gzuc, int verbose,
66 FILE *out);
67
backup_verify(struct backup * backup,unsigned level,int verbose,FILE * out)68 EXPORTED int backup_verify(struct backup *backup, unsigned level, int verbose, FILE *out)
69 {
70 struct backup_chunk_list *chunk_list = NULL;
71 struct gzuncat *gzuc = NULL;
72 int r = 0;
73
74 /* don't double-verify last checksum when verifying all */
75 if ((level & BACKUP_VERIFY_ALL_CHECKSUMS))
76 level &= ~BACKUP_VERIFY_LAST_CHECKSUM;
77
78 /* don't double-verify message links when verifying message guids */
79 if ((level & BACKUP_VERIFY_MESSAGE_GUIDS))
80 level &= ~BACKUP_VERIFY_MESSAGE_LINKS;
81
82 chunk_list = backup_get_chunks(backup);
83 if (!chunk_list || !chunk_list->count) goto done;
84
85 gzuc = gzuc_new(backup->fd);
86 if (!gzuc) {
87 r = -1;
88 goto done;
89 }
90
91 if (!r && (level & BACKUP_VERIFY_LAST_CHECKSUM))
92 r = verify_chunk_checksums(backup, chunk_list->tail, gzuc, verbose, out);
93
94 if (!r && level > BACKUP_VERIFY_LAST_CHECKSUM) {
95 struct backup_chunk *chunk = chunk_list->head;
96 while (!r && chunk) {
97 if (!r && (level & BACKUP_VERIFY_ALL_CHECKSUMS))
98 r = verify_chunk_checksums(backup, chunk, gzuc, verbose, out);
99
100 if (!r && (level & BACKUP_VERIFY_MESSAGES))
101 r = verify_chunk_messages(backup, chunk, gzuc, level, verbose, out);
102
103 if (!r && (level & BACKUP_VERIFY_MAILBOX_LINKS))
104 r = verify_chunk_mailbox_links(backup, chunk, gzuc, verbose, out);
105
106 chunk = chunk->next;
107 }
108 }
109
110 done:
111 if (gzuc) gzuc_free(&gzuc);
112 if (chunk_list) backup_chunk_list_free(&chunk_list);
113 return r;
114 }
115
verify_chunk_checksums(struct backup * backup,struct backup_chunk * chunk,struct gzuncat * gzuc,int verbose,FILE * out)116 static int verify_chunk_checksums(struct backup *backup, struct backup_chunk *chunk,
117 struct gzuncat *gzuc, int verbose, FILE *out)
118 {
119 int r;
120
121 if (out && verbose)
122 fprintf(out, "checking chunk %d checksums...\n", chunk->id);
123
124 /* validate file-prior-to-this-chunk checksum */
125 if (out && verbose > 1)
126 fprintf(out, " checking file checksum...\n");
127 char file_sha1[2 * SHA1_DIGEST_LENGTH + 1];
128 sha1_file(backup->fd, backup->data_fname, chunk->offset, file_sha1);
129 r = strncmp(chunk->file_sha1, file_sha1, sizeof(file_sha1));
130 if (r) {
131 syslog(LOG_DEBUG, "%s: %s (chunk %d) file checksum mismatch: %s on disk, %s in index\n",
132 __func__, backup->data_fname, chunk->id, file_sha1, chunk->file_sha1);
133 if (out)
134 fprintf(out, "file checksum mismatch for chunk %d: %s on disk, %s in index\n",
135 chunk->id, file_sha1, chunk->file_sha1);
136 goto done;
137 }
138
139 /* validate data-within-this-chunk checksum */
140 // FIXME length and data_sha1 are set at backup_append_end.
141 // detect and correctly report case where this hasn't occurred.
142 if (out && verbose > 1)
143 fprintf(out, " checking data length\n");
144 char buf[8192]; /* FIXME whatever */
145 size_t len = 0;
146 SHA_CTX sha_ctx;
147 SHA1_Init(&sha_ctx);
148 gzuc_member_start_from(gzuc, chunk->offset);
149 while (!gzuc_member_eof(gzuc)) {
150 ssize_t n = gzuc_read(gzuc, buf, sizeof(buf));
151 if (n >= 0) {
152 SHA1_Update(&sha_ctx, buf, n);
153 len += n;
154 }
155 }
156 gzuc_member_end(gzuc, NULL);
157 if (len != chunk->length) {
158 syslog(LOG_DEBUG, "%s: %s (chunk %d) data length mismatch: "
159 SIZE_T_FMT " on disk,"
160 SIZE_T_FMT " in index\n",
161 __func__, backup->data_fname, chunk->id, len, chunk->length);
162 if (out)
163 fprintf(out, "data length mismatch for chunk %d: "
164 SIZE_T_FMT " on disk,"
165 SIZE_T_FMT " in index\n",
166 chunk->id, len, chunk->length);
167 r = -1;
168 goto done;
169 }
170
171 if (out && verbose > 1)
172 fprintf(out, " checking data checksum...\n");
173 unsigned char sha1_raw[SHA1_DIGEST_LENGTH];
174 char data_sha1[2 * SHA1_DIGEST_LENGTH + 1];
175 SHA1_Final(sha1_raw, &sha_ctx);
176 r = bin_to_hex(sha1_raw, SHA1_DIGEST_LENGTH, data_sha1, BH_LOWER);
177 assert(r == 2 * SHA1_DIGEST_LENGTH);
178 r = strncmp(chunk->data_sha1, data_sha1, sizeof(data_sha1));
179 if (r) {
180 syslog(LOG_DEBUG, "%s: %s (chunk %d) data checksum mismatch: %s on disk, %s in index\n",
181 __func__, backup->data_fname, chunk->id, data_sha1, chunk->data_sha1);
182 if (out)
183 fprintf(out, "data checksum mismatch for chunk %d: %s on disk, %s in index\n",
184 chunk->id, data_sha1, chunk->data_sha1);
185 goto done;
186 }
187
188 done:
189 syslog(LOG_DEBUG, "%s: checksum %s!\n", __func__, r ? "failed" : "passed");
190 if (out && verbose)
191 fprintf(out, "%s\n", r ? "error" : "ok");
192 return r;
193 }
194
_prot_fill_cb(unsigned char * buf,size_t len,void * rock)195 static ssize_t _prot_fill_cb(unsigned char *buf, size_t len, void *rock)
196 {
197 struct gzuncat *gzuc = (struct gzuncat *) rock;
198 return gzuc_read(gzuc, buf, len);
199 }
200
201 struct verify_message_rock {
202 struct gzuncat *gzuc;
203 int verify_guid;
204 struct dlist *cached_dlist;
205 off_t cached_offset;
206 int verbose;
207 FILE *out;
208 };
209
_verify_message_cb(const struct backup_message * message,void * rock)210 static int _verify_message_cb(const struct backup_message *message, void *rock)
211 {
212 struct verify_message_rock *vmrock = (struct verify_message_rock *) rock;
213 struct dlist *dl = NULL;
214 struct dlist *di = NULL;
215 FILE *out = vmrock->out;
216 int r;
217
218 /* cache the dlist so that multiple reads from the same offset don't
219 * cause expensive reverse seeks in decompression stream
220 */
221 if (!vmrock->cached_dlist || vmrock->cached_offset != message->offset) {
222 if (vmrock->cached_dlist) {
223 dlist_unlink_files(vmrock->cached_dlist);
224 dlist_free(&vmrock->cached_dlist);
225 }
226
227 r = gzuc_seekto(vmrock->gzuc, message->offset);
228 if (r) return r;
229
230 struct protstream *ps = prot_readcb(_prot_fill_cb, vmrock->gzuc);
231 prot_setisclient(ps, 1); /* don't sync literals */
232 r = parse_backup_line(ps, NULL, NULL, &dl);
233
234 if (r == EOF) {
235 const char *error = prot_error(ps);
236 if (error && 0 != strcmp(error, PROT_EOF_STRING)) {
237 syslog(LOG_ERR,
238 "%s: error reading message %i at offset " OFF_T_FMT ", byte %i: %s",
239 __func__, message->id, message->offset, prot_bytes_in(ps), error);
240 if (out)
241 fprintf(out, "error reading message %i at offset " OFF_T_FMT ", byte %i: %s",
242 message->id, message->offset, prot_bytes_in(ps), error);
243 }
244 prot_free(ps);
245 return r;
246 }
247
248 prot_free(ps);
249
250 vmrock->cached_dlist = dl;
251 vmrock->cached_offset = message->offset;
252 }
253 else {
254 dl = vmrock->cached_dlist;
255 }
256
257 r = strcmp(dl->name, "MESSAGE");
258 if (r) return r;
259
260 r = -1;
261 for (di = dl->head; di; di = di->next) {
262 struct message_guid *guid = NULL;
263 const char *fname = NULL;
264
265 if (!dlist_tofile(di, NULL, &guid, NULL, &fname))
266 continue;
267
268 r = message_guid_cmp(guid, message->guid);
269 if (!r) {
270 if (vmrock->verify_guid) {
271 const char *msg_base = NULL;
272 size_t msg_len = 0;
273 struct message_guid computed_guid;
274 int fd;
275
276 fd = open(fname, O_RDWR);
277 if (fd != -1) {
278 map_refresh(fd, 1, &msg_base, &msg_len, MAP_UNKNOWN_LEN, fname, NULL);
279
280 message_guid_generate(&computed_guid, msg_base, msg_len);
281 r = message_guid_cmp(&computed_guid, message->guid);
282 if (r && out)
283 fprintf(out, "guid mismatch for message %i\n", message->id);
284
285 map_free(&msg_base, &msg_len);
286 close(fd);
287 }
288 else {
289 syslog(LOG_ERR, "IOERROR: %s open %s: %m", __func__, fname);
290 if (out)
291 fprintf(out, "error reading staging file for message %i\n", message->id);
292 r = -1;
293 }
294 }
295 break;
296 }
297 }
298
299 return r;
300 }
301
302 /* verify that each message exists within the chunk the index claims */
verify_chunk_messages(struct backup * backup,struct backup_chunk * chunk,struct gzuncat * gzuc,unsigned level,int verbose,FILE * out)303 static int verify_chunk_messages(struct backup *backup, struct backup_chunk *chunk,
304 struct gzuncat *gzuc, unsigned level,
305 int verbose, FILE *out)
306 {
307 int r;
308
309 struct verify_message_rock vmrock = {
310 gzuc,
311 (level & BACKUP_VERIFY_MESSAGE_GUIDS),
312 NULL,
313 0,
314 verbose,
315 out,
316 };
317
318 if (out && verbose)
319 fprintf(out, "checking chunk %d messages...\n", chunk->id);
320
321 r = gzuc_member_start_from(gzuc, chunk->offset);
322 if (!r) {
323 r = backup_message_foreach(backup, chunk->id, NULL,
324 _verify_message_cb, &vmrock);
325 gzuc_member_end(gzuc, NULL);
326 }
327
328 if (vmrock.cached_dlist) {
329 dlist_unlink_files(vmrock.cached_dlist);
330 dlist_free(&vmrock.cached_dlist);
331 }
332
333 syslog(LOG_DEBUG, "%s: chunk %d %s!\n", __func__, chunk->id,
334 r ? "failed" : "passed");
335 if (out && verbose)
336 fprintf(out, "%s\n", r ? "error" : "ok");
337
338 return r;
339 }
340
mailbox_matches(const struct backup_mailbox * mailbox,struct dlist * dlist)341 static int mailbox_matches(const struct backup_mailbox *mailbox,
342 struct dlist *dlist)
343 {
344 const char *mboxname = NULL;
345 uint32_t last_uid = 0;
346 modseq_t highestmodseq = 0;
347 uint32_t recentuid = 0;
348 time_t recenttime = 0;
349 time_t last_appenddate = 0;
350 uint32_t uidvalidity = 0;
351 const char *partition = NULL;
352 const char *acl = NULL;
353 const char *options = NULL;
354 modseq_t xconvmodseq = 0;
355 struct synccrcs synccrcs = { 0, 0 };
356
357 if (!dlist_getatom(dlist, "MBOXNAME", &mboxname)
358 || strcmp(mboxname, mailbox->mboxname) != 0)
359 return 0;
360
361 if (!dlist_getnum32(dlist, "LAST_UID", &last_uid)
362 || last_uid != mailbox->last_uid)
363 return 0;
364
365 if (!dlist_getnum64(dlist, "HIGHESTMODSEQ", &highestmodseq)
366 || highestmodseq != mailbox->highestmodseq)
367 return 0;
368
369 if (!dlist_getnum32(dlist, "RECENTUID", &recentuid)
370 || recentuid != mailbox->recentuid)
371 return 0;
372
373 if (!dlist_getdate(dlist, "RECENTTIME", &recenttime)
374 || recenttime != mailbox->recenttime)
375 return 0;
376
377 if (!dlist_getdate(dlist, "LAST_APPENDDATE", &last_appenddate)
378 || last_appenddate != mailbox->last_appenddate)
379 return 0;
380
381 if (!dlist_getnum32(dlist, "UIDVALIDITY", &uidvalidity)
382 || uidvalidity != mailbox->uidvalidity)
383 return 0;
384
385 if (!dlist_getatom(dlist, "PARTITION", &partition)
386 || strcmp(partition, mailbox->partition) != 0)
387 return 0;
388
389 if (!dlist_getatom(dlist, "ACL", &acl)
390 || strcmp(acl, mailbox->acl) != 0)
391 return 0;
392
393 if (!dlist_getatom(dlist, "OPTIONS", &options)
394 || strcmp(options, mailbox->options) != 0)
395 return 0;
396
397 /* optional */
398 dlist_getnum64(dlist, "XCONVMODSEQ", &xconvmodseq);
399 if (xconvmodseq != mailbox->xconvmodseq)
400 return 0;
401
402 /* CRCs */
403 dlist_getnum32(dlist, "SYNC_CRC", &synccrcs.basic);
404 dlist_getnum32(dlist, "SYNC_CRC_ANNOT", &synccrcs.annot);
405 if (synccrcs.basic != mailbox->sync_crc)
406 return 0;
407 if (synccrcs.annot != mailbox->sync_crc_annot)
408 return 0;
409
410 syslog(LOG_DEBUG, "%s: %s matches!\n", __func__, mailbox->uniqueid);
411 return 1;
412 }
413
mailbox_message_matches(const struct backup_mailbox_message * mailbox_message,struct dlist * dlist)414 static int mailbox_message_matches(const struct backup_mailbox_message *mailbox_message,
415 struct dlist *dlist)
416 {
417 modseq_t modseq;
418 uint32_t last_updated;
419 uint32_t internaldate;
420 uint32_t size;
421 struct message_guid *guid;
422
423 if (!dlist_getnum64(dlist, "MODSEQ", &modseq)
424 || modseq != mailbox_message->modseq)
425 return 0;
426
427 if (!dlist_getnum32(dlist, "LAST_UPDATED", &last_updated)
428 || (time_t) last_updated != mailbox_message->last_updated)
429 return 0;
430
431 if (!dlist_getnum32(dlist, "INTERNALDATE", &internaldate)
432 || (time_t) internaldate != mailbox_message->internaldate)
433 return 0;
434
435 if (!dlist_getnum32(dlist, "SIZE", &size)
436 || size != mailbox_message->size)
437 return 0;
438
439 if (!dlist_getguid(dlist, "GUID", &guid)
440 || !message_guid_equal(guid, &mailbox_message->guid))
441 return 0;
442
443 syslog(LOG_DEBUG, "%s: %s:%u matches!\n", __func__,
444 mailbox_message->mailbox_uniqueid, mailbox_message->uid);
445 return 1;
446 }
447
448 /* verify that the matching MAILBOX exists within the claimed chunk
449 * for each mailbox or mailbox_message in the index
450 */
verify_chunk_mailbox_links(struct backup * backup,struct backup_chunk * chunk,struct gzuncat * gzuc,int verbose,FILE * out)451 static int verify_chunk_mailbox_links(struct backup *backup, struct backup_chunk *chunk,
452 struct gzuncat *gzuc, int verbose, FILE *out)
453 {
454 /*
455 * get list of mailboxes in chunk
456 * get list of mailbox_messages in chunk
457 * index mailboxes list by uniqueid
458 * index mailbox_messages list by uniqueid:uid
459 * open chunk
460 * foreach line in chunk
461 * read dlist
462 * skip if it's not a mailbox
463 * if details in dlist match details in mailbox
464 * remove from mailbox list/index
465 * foreach record in dlist
466 * if details in dlist match details in mailbox_message
467 * remove from mailbox_message list/index
468 * failed if either list of mailboxes or list of mailbox_messages is not empty
469 */
470
471 struct backup_mailbox_list *mailbox_list = NULL;
472 struct backup_mailbox_message_list *mailbox_message_list = NULL;
473 hash_table mailbox_list_index = HASH_TABLE_INITIALIZER;
474 hash_table mailbox_message_list_index = HASH_TABLE_INITIALIZER;
475 struct backup_mailbox *mailbox = NULL;
476 struct backup_mailbox_message *mailbox_message = NULL;
477 int r;
478
479 if (out && verbose)
480 fprintf(out, "checking chunk %d mailbox links...\n", chunk->id);
481
482 mailbox_list = backup_get_mailboxes(backup, chunk->id, BACKUP_MAILBOX_NO_RECORDS);
483 mailbox_message_list = backup_get_mailbox_messages(backup, chunk->id);
484
485 if (mailbox_list->count == 0 && mailbox_message_list->count == 0) {
486 /* nothing we care about in this chunk */
487 free(mailbox_list);
488 free(mailbox_message_list);
489 if (out && verbose)
490 fprintf(out, "ok\n");
491 return 0;
492 }
493
494 /* XXX consider whether the two hashes should use pools */
495
496 if (mailbox_list->count) {
497 /* build an index of the mailbox list */
498 construct_hash_table(&mailbox_list_index, mailbox_list->count, 0);
499 mailbox = mailbox_list->head;
500 while (mailbox) {
501 hash_insert(mailbox->uniqueid, mailbox, &mailbox_list_index);
502 mailbox = mailbox->next;
503 }
504 }
505
506 if (mailbox_message_list->count) {
507 /* build an index of the mailbox message list */
508 construct_hash_table(&mailbox_message_list_index,
509 mailbox_message_list->count, 0);
510 mailbox_message = mailbox_message_list->head;
511 while (mailbox_message) {
512 char keybuf[1024]; // FIXME whatever
513 snprintf(keybuf, sizeof(keybuf), "%s:%d",
514 mailbox_message->mailbox_uniqueid, mailbox_message->uid);
515 hash_insert(keybuf, mailbox_message, &mailbox_message_list_index);
516 mailbox_message = mailbox_message->next;
517 }
518 }
519
520 r = gzuc_member_start_from(gzuc, chunk->offset);
521 if (r) {
522 syslog(LOG_ERR, "%s: error reading chunk %i at offset " OFF_T_FMT ": %s",
523 __func__, chunk->id, chunk->offset, zError(r));
524 if (out)
525 fprintf(out, "error reading chunk %i at offset " OFF_T_FMT ": %s",
526 chunk->id, chunk->offset, zError(r));
527 goto done;
528 }
529 struct protstream *ps = prot_readcb(_prot_fill_cb, gzuc);
530 prot_setisclient(ps, 1); /* don't sync literals */
531
532 struct buf cmd = BUF_INITIALIZER;
533 while (1) {
534 struct dlist *dl = NULL;
535 struct dlist *record = NULL;
536 struct dlist *di = NULL;
537 const char *uniqueid = NULL;
538
539 int c = parse_backup_line(ps, NULL, &cmd, &dl);
540 if (c == EOF) {
541 const char *error = prot_error(ps);
542 if (error && 0 != strcmp(error, PROT_EOF_STRING)) {
543 syslog(LOG_ERR,
544 "%s: error reading chunk %i data at offset " OFF_T_FMT ", byte %i: %s",
545 __func__, chunk->id, chunk->offset, prot_bytes_in(ps), error);
546 if (out)
547 fprintf(out, "error reading chunk %i data at offset " OFF_T_FMT ", byte %i: %s",
548 chunk->id, chunk->offset, prot_bytes_in(ps), error);
549 r = EOF;
550 }
551 break;
552 }
553
554 if (strcmp(buf_cstring(&cmd), "APPLY") != 0)
555 goto next_line;
556
557 if (strcmp(dl->name, "MAILBOX") != 0)
558 goto next_line;
559
560 if (!dlist_getatom(dl, "UNIQUEID", &uniqueid))
561 goto next_line;
562
563 if (mailbox_list->count) {
564 mailbox = (struct backup_mailbox *) hash_lookup(uniqueid, &mailbox_list_index);
565
566 if (mailbox && mailbox_matches(mailbox, dl)) {
567 backup_mailbox_list_remove(mailbox_list, mailbox);
568 hash_del(uniqueid, &mailbox_list_index);
569 backup_mailbox_free(&mailbox);
570 }
571 }
572
573 if (mailbox_message_list->count) {
574 if (!dlist_getlist(dl, "RECORD", &record))
575 goto next_line;
576
577 for (di = record->head; di; di = di->next) {
578 char keybuf[1024]; // FIXME whatever
579 uint32_t uid;
580
581 if (!dlist_getnum32(di, "UID", &uid))
582 continue;
583
584 snprintf(keybuf, sizeof(keybuf), "%s:%d", uniqueid, uid);
585 mailbox_message = (struct backup_mailbox_message *) hash_lookup(
586 keybuf, &mailbox_message_list_index);
587
588 if (!mailbox_message)
589 continue;
590
591 if (!mailbox_message_matches(mailbox_message, di))
592 continue;
593
594 backup_mailbox_message_list_remove(mailbox_message_list, mailbox_message);
595 hash_del(keybuf, &mailbox_message_list_index);
596 backup_mailbox_message_free(&mailbox_message);
597 }
598 }
599
600 next_line:
601 if (dl) {
602 dlist_unlink_files(dl);
603 dlist_free(&dl);
604 }
605 }
606 buf_free(&cmd);
607
608 prot_free(ps);
609 gzuc_member_end(gzuc, NULL);
610
611 /* anything left in either of the lists is missing from the chunk data. bad! */
612 mailbox = mailbox_list->head;
613 while (mailbox) {
614 syslog(LOG_DEBUG, "%s: chunk %d missing mailbox data for %s (%s)\n",
615 __func__, chunk->id, mailbox->uniqueid, mailbox->mboxname);
616 if (out)
617 fprintf(out, "chunk %d missing mailbox data for %s (%s)\n",
618 chunk->id, mailbox->uniqueid, mailbox->mboxname);
619 mailbox = mailbox->next;
620 }
621
622 mailbox_message = mailbox_message_list->head;
623 while (mailbox_message) {
624 syslog(LOG_DEBUG, "%s: chunk %d missing mailbox_message data for %s uid %u\n",
625 __func__, chunk->id, mailbox_message->mailbox_uniqueid,
626 mailbox_message->uid);
627 if (out)
628 fprintf(out, "chunk %d missing mailbox_message data for %s uid %u\n",
629 chunk->id, mailbox_message->mailbox_uniqueid,
630 mailbox_message->uid);
631 mailbox_message = mailbox_message->next;
632 }
633
634 if (!r) r = mailbox_list->count || mailbox_message_list->count ? -1 : 0;
635
636 done:
637 free_hash_table(&mailbox_list_index, NULL);
638 free_hash_table(&mailbox_message_list_index, NULL);
639
640 backup_mailbox_list_empty(mailbox_list);
641 free(mailbox_list);
642
643 backup_mailbox_message_list_empty(mailbox_message_list);
644 free(mailbox_message_list);
645
646 syslog(LOG_DEBUG, "%s: chunk %d %s!\n", __func__, chunk->id,
647 r ? "failed" : "passed");
648 if (out && verbose)
649 fprintf(out, "%s\n", r ? "error" : "ok");
650 return r;
651 }
652