bstorage.c - dedup - deduplicating backup program
HTML git clone git://bitreich.org/dedup/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/dedup/
DIR Log
DIR Files
DIR Refs
DIR Tags
DIR README
DIR LICENSE
---
bstorage.c (13644B)
---
1 /*
2 * Storage layer implementation using a single backing file.
3 * The file format is as follows:
4 *
5 * [block header]
6 * [block descriptor 0]
7 * [data]
8 * [block descriptor 1]
9 * [data]
10 * ...
11 */
12 #include <sys/types.h>
13 #include <sys/stat.h>
14
15 #include <assert.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <stdint.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <strings.h>
23 #include <unistd.h>
24
25 #include "block.h"
26 #include "compat.h"
27 #include "config.h"
28 #include "misc.h"
29 #include "queue.h"
30 #include "tree.h"
31
32 /* block header constants */
33 #define BHDRMAGIC "DEDUPDIDUPDIDUP"
34 #define NBHDRMAGIC 16
35
36 #define VMIN 0
37 #define VMAJ 1
38 #define VMINMASK 0xff
39 #define VMAJSHIFT 8
40 #define VMAJMASK 0xff
41
42 #define BHDRSIZE (NBHDRMAGIC + 8 + 8)
43
44 /* block descriptor constants */
45 #define BDTYPE 0x100
46 #define BDSIZE (8 + 8 + 8 + 8 + (MDSIZE))
47
48 /* misc helpers */
49 extern int pack(unsigned char *, char *, ...);
50 extern int unpack(unsigned char *, char *, ...);
51
52 static int bscreat(struct bctx *, char *, int);
53 static int bsopen(struct bctx *, char *, int, int);
54 static int bsput(struct bctx *, void *, size_t, unsigned char *);
55 static int bsget(struct bctx *, unsigned char *, void *, size_t *);
56 static int bsrm(struct bctx *, unsigned char *);
57 static int bsgc(struct bctx *);
58 static int bssync(struct bctx *);
59 static int bsclose(struct bctx *);
60
61 static struct bops bops = {
62 .creat = bscreat,
63 .open = bsopen,
64 .put = bsput,
65 .get = bsget,
66 .rm = bsrm,
67 .gc = bsgc,
68 .sync = bssync,
69 .close = bsclose,
70 };
71
72 /* Block header structure */
73 struct bhdr {
74 char magic[NBHDRMAGIC]; /* magic number for file(1) */
75 uint64_t flags; /* version number */
76 uint64_t nbd; /* number of block descriptors */
77 };
78
79 /* Block descriptor */
80 struct bd {
81 uint16_t type; /* BDTYPE */
82 unsigned char reserved[6]; /* should be set to 0 when writing */
83 uint64_t offset; /* block offset */
84 uint64_t size; /* block size */
85 uint64_t refcnt; /* reference count of block, 0 if block is removed */
86 unsigned char md[MDSIZE]; /* hash of block */
87 RB_ENTRY(bd) rbe; /* bdcache link node */
88 SLIST_ENTRY(bd) sle; /* gchead link node */
89 };
90 RB_HEAD(bdcache, bd);
91
92 /* Storage layer context */
93 struct sctx {
94 struct bdcache bdcache; /* cache of block descriptors */
95 SLIST_HEAD(gchead, bd) gchead; /* list of all blocks with a zero refcount */
96 int fd; /* underlying storage file descriptor */
97 int rdonly; /* when set to 1, the bssync() operation is a no-op */
98 struct bhdr bhdr; /* block header */
99 };
100
101 static int
102 bd_cmp(struct bd *b1, struct bd *b2)
103 {
104 int r;
105
106 r = memcmp(b1->md, b2->md, MDSIZE);
107 if (r > 0)
108 return 1;
109 else if (r < 0)
110 return -1;
111 return 0;
112 }
113 static RB_PROTOTYPE(bdcache, bd, rbe, bd_cmp)
114 static RB_GENERATE(bdcache, bd, rbe, bd_cmp)
115
116 /* Unpack block header */
117 static int
118 unpackbhdr(unsigned char *buf, struct bhdr *bhdr)
119 {
120 char fmt[BUFSIZ];
121 int n;
122
123 snprintf(fmt, sizeof(fmt), "'%dqq", NBHDRMAGIC);
124 n = unpack(buf, fmt,
125 bhdr->magic,
126 &bhdr->flags,
127 &bhdr->nbd);
128
129 assert(n == BHDRSIZE);
130 return n;
131 }
132
133 /* Pack block header */
134 static int
135 packbhdr(unsigned char *buf, struct bhdr *bhdr)
136 {
137 char fmt[BUFSIZ];
138 int n;
139
140 snprintf(fmt, sizeof(fmt), "'%dqq", NBHDRMAGIC);
141 n = pack(buf, fmt,
142 bhdr->magic,
143 bhdr->flags,
144 bhdr->nbd);
145
146 assert(n == BHDRSIZE);
147 return n;
148 }
149
150 /* Unpack block descriptor */
151 static int
152 unpackbd(unsigned char *buf, struct bd *bd)
153 {
154 char fmt[BUFSIZ];
155 int n;
156
157 snprintf(fmt, sizeof(fmt), "s'6qqq'%d", MDSIZE);
158 n = unpack(buf, fmt,
159 &bd->type,
160 bd->reserved,
161 &bd->offset,
162 &bd->size,
163 &bd->refcnt,
164 bd->md);
165
166 assert(n == BDSIZE);
167 return n;
168 }
169
170 /* Write block descriptor */
171 static int
172 packbd(unsigned char *buf, struct bd *bd)
173 {
174 char fmt[BUFSIZ];
175 int n;
176
177 snprintf(fmt, sizeof(fmt), "s'6qqq'%d", MDSIZE);
178 n = pack(buf, fmt,
179 bd->type,
180 bd->reserved,
181 bd->offset,
182 bd->size,
183 bd->refcnt,
184 bd->md);
185
186 assert(n == BDSIZE);
187 return n;
188 }
189
190 /* Load block descriptor from file */
191 static int
192 loadbd(struct sctx *sctx)
193 {
194 unsigned char bdbuf[BDSIZE];
195 struct bd *bd;
196
197 bd = calloc(1, sizeof(*bd));
198 if (bd == NULL) {
199 seterr("calloc: out of memory");
200 return -1;
201 }
202
203 if (xread(sctx->fd, bdbuf, BDSIZE) != BDSIZE) {
204 free(bd);
205 seterr("failed to read block descriptor: %s",
206 strerror(errno));
207 return -1;
208 }
209 unpackbd(bdbuf, bd);
210
211 if (bd->type != BDTYPE) {
212 free(bd);
213 seterr("invalid block descriptor type: %d", bd->type);
214 return -1;
215 }
216
217 /* Move to the next block descriptor */
218 if (lseek(sctx->fd, bd->size, SEEK_CUR) < 0) {
219 free(bd);
220 seterr("lseek: %s", strerror(errno));
221 return -1;
222 }
223
224 /*
225 * When refcount is 0 the block has been removed.
226 * In that case, the block descriptor is still present
227 * in the file as it is used to locate the next block
228 * descriptor which could be live.
229 *
230 * The garbage collection list links together all block
231 * descriptors that have a reference count of 0.
232 * This is needed to implement the gc operation.
233 */
234 if (bd->refcnt > 0)
235 RB_INSERT(bdcache, &sctx->bdcache, bd);
236 else
237 SLIST_INSERT_HEAD(&sctx->gchead, bd, sle);
238 return 0;
239 }
240
241 /* Initialize block descriptor cache */
242 static int
243 initbdcache(struct sctx *sctx)
244 {
245 struct bhdr *bhdr;
246 uint64_t i;
247
248 bhdr = &sctx->bhdr;
249 for (i = 0; i < bhdr->nbd; i++) {
250 struct bd *bd, *tmp;
251
252 if (loadbd(sctx) == 0)
253 continue;
254
255 /* Free block descriptor cache */
256 RB_FOREACH_SAFE(bd, bdcache, &sctx->bdcache, tmp) {
257 RB_REMOVE(bdcache, &sctx->bdcache, bd);
258 free(bd);
259 }
260
261 /* Free garbage collector list */
262 while (!SLIST_EMPTY(&sctx->gchead)) {
263 bd = SLIST_FIRST(&sctx->gchead);
264 SLIST_REMOVE(&sctx->gchead, bd, bd, sle);
265 free(bd);
266 }
267 return -1;
268 }
269 return 0;
270 }
271
272 /* Create storage file */
273 static int
274 bscreat(struct bctx *bctx, char *path, int mode)
275 {
276 unsigned char bhdrbuf[BHDRSIZE];
277 struct sctx *sctx;
278 struct bhdr *bhdr;
279 int fd;
280
281 fd = open(path, O_RDWR | O_CREAT | O_EXCL, mode);
282 if (fd < 0) {
283 seterr("open: %s", strerror(errno));
284 return -1;
285 }
286
287 bctx->sctx = calloc(1, sizeof(struct sctx));
288 if (bctx->sctx == NULL) {
289 close(fd);
290 seterr("calloc: out of memory");
291 return -1;
292 }
293
294 sctx = bctx->sctx;
295 RB_INIT(&sctx->bdcache);
296 SLIST_INIT(&sctx->gchead);
297 sctx->fd = fd;
298
299 bhdr = &sctx->bhdr;
300 memcpy(bhdr->magic, BHDRMAGIC, NBHDRMAGIC);
301 bhdr->flags = (VMAJ << VMAJSHIFT) | VMIN;
302 bhdr->nbd = 0;
303
304 packbhdr(bhdrbuf, bhdr);
305 if (xwrite(fd, bhdrbuf, BHDRSIZE) != BHDRSIZE) {
306 free(sctx);
307 close(fd);
308 seterr("failed to write block header: %s", strerror(errno));
309 return -1;
310 }
311 return 0;
312 }
313
314 /* Open storage file */
315 static int
316 bsopen(struct bctx *bctx, char *path, int flags, int mode)
317 {
318 unsigned char bhdrbuf[BHDRSIZE];
319 struct sctx *sctx;
320 struct bhdr *bhdr;
321 int fd;
322
323 switch (flags) {
324 case B_READ:
325 flags = O_RDONLY;
326 break;
327 case B_RDWR:
328 flags = O_RDWR;
329 break;
330 default:
331 seterr("invalid params");
332 return -1;
333 }
334
335 fd = open(path, flags, mode);
336 if (fd < 0) {
337 seterr("open: %s", strerror(errno));
338 return -1;
339 }
340
341 bctx->sctx = calloc(1, sizeof(struct sctx));
342 if (bctx->sctx == NULL) {
343 close(fd);
344 seterr("calloc: out of memory");
345 return -1;
346 }
347
348 sctx = bctx->sctx;
349 RB_INIT(&sctx->bdcache);
350 SLIST_INIT(&sctx->gchead);
351 bhdr = &sctx->bhdr;
352
353 if (xread(fd, bhdrbuf, BHDRSIZE) != BHDRSIZE) {
354 free(sctx);
355 close(fd);
356 seterr("failed to read block header: %s", strerror(errno));
357 return -1;
358 }
359 unpackbhdr(bhdrbuf, bhdr);
360
361 if (memcmp(bhdr->magic, BHDRMAGIC, NBHDRMAGIC) != 0) {
362 free(sctx);
363 close(fd);
364 seterr("unknown block header magic");
365 return -1;
366 }
367
368 /* If the major version is different, the format is incompatible */
369 if (((bhdr->flags >> VMAJSHIFT) & VMAJMASK) != VMAJ) {
370 free(sctx);
371 close(fd);
372 seterr("block header version mismatch");
373 return -1;
374 }
375
376 sctx->fd = fd;
377 sctx->rdonly = flags == O_RDONLY;
378
379 if (initbdcache(sctx) < 0) {
380 free(sctx);
381 close(fd);
382 return -1;
383 }
384 return 0;
385 }
386
387 /* Write a block to the storage file */
388 static int
389 bsput(struct bctx *bctx, void *buf, size_t n, unsigned char *md)
390 {
391 unsigned char bdbuf[BDSIZE];
392 struct sctx *sctx;
393 struct bhdr *bhdr;
394 struct bd key, *bd;
395 off_t offs;
396
397 /*
398 * If the block is already present in the cache
399 * just increment the reference count and write back
400 * the block descriptor associated with that block.
401 */
402 sctx = bctx->sctx;
403 memcpy(key.md, md, MDSIZE);
404 bd = RB_FIND(bdcache, &sctx->bdcache, &key);
405 if (bd != NULL) {
406 off_t bdoffs;
407
408 bdoffs = bd->offset - BDSIZE;
409 if (lseek(sctx->fd, bdoffs, SEEK_SET) < 0) {
410 seterr("lseek: %s", strerror(errno));
411 return -1;
412 }
413
414 bd->refcnt++;
415 packbd(bdbuf, bd);
416 if (xwrite(sctx->fd, bdbuf, BDSIZE) != BDSIZE) {
417 bd->refcnt--;
418 seterr("failed to write block descriptor: %s",
419 strerror(errno));
420 return -1;
421 }
422
423 memcpy(md, bd->md, MDSIZE);
424 return 0;
425 }
426
427 /* New blocks are appended at the end of storage file */
428 offs = lseek(sctx->fd, 0, SEEK_END);
429 if (offs < 0) {
430 seterr("lseek: %s", strerror(errno));
431 return -1;
432 }
433
434 bd = calloc(1, sizeof(*bd));
435 if (bd == NULL) {
436 seterr("calloc: out of memory");
437 return -1;
438 }
439 bd->type = BDTYPE;
440 bd->offset = offs + BDSIZE;
441 bd->size = n;
442 bd->refcnt = 1;
443 memcpy(bd->md, key.md, MDSIZE);
444
445 packbd(bdbuf, bd);
446 if (xwrite(sctx->fd, bdbuf, BDSIZE) != BDSIZE) {
447 /* Shouldn't fail but if it does rewind storage file state */
448 ftruncate(sctx->fd, offs);
449 free(bd);
450 seterr("failed to write block descriptor: %s",
451 strerror(errno));
452 return -1;
453 }
454
455 if (xwrite(sctx->fd, buf, n) != n) {
456 /* Shouldn't fail but if it does rewind storage file state */
457 ftruncate(sctx->fd, offs);
458 free(bd);
459 seterr("failed to write block: %s", strerror(errno));
460 return -1;
461 }
462
463 /*
464 * Update block entry header.
465 * The header will be written to the storage file
466 * when bsclose() or bssync() is called.
467 */
468 bhdr = &sctx->bhdr;
469 bhdr->nbd++;
470
471 RB_INSERT(bdcache, &sctx->bdcache, bd);
472 memcpy(md, bd->md, MDSIZE);
473 return bd->size;
474 }
475
476 /* Read a block from the storage file */
477 static int
478 bsget(struct bctx *bctx, unsigned char *md, void *buf, size_t *n)
479 {
480 struct sctx *sctx;
481 struct bd key, *bd;
482
483 sctx = bctx->sctx;
484 memcpy(key.md, md, MDSIZE);
485 bd = RB_FIND(bdcache, &sctx->bdcache, &key);
486 if (bd == NULL) {
487 seterr("block not found");
488 return -1;
489 }
490
491 if (*n < bd->size) {
492 seterr("buffer too small");
493 return -1;
494 }
495
496 if (lseek(sctx->fd, bd->offset, SEEK_SET) < 0) {
497 seterr("lseek: %s", strerror(errno));
498 return -1;
499 }
500 if (xread(sctx->fd, buf, bd->size) != bd->size) {
501 seterr("failed to read block: %s", strerror(errno));
502 return -1;
503 }
504 *n = bd->size;
505 return 0;
506 }
507
508 /* Remove a block with the given hash */
509 static int
510 bsrm(struct bctx *bctx, unsigned char *md)
511 {
512 unsigned char bdbuf[BDSIZE];
513 struct sctx *sctx;
514 struct bd key, *bd;
515 off_t bdoffs;
516
517 sctx = bctx->sctx;
518 memcpy(key.md, md, MDSIZE);
519 bd = RB_FIND(bdcache, &sctx->bdcache, &key);
520 if (bd == NULL) {
521 seterr("block not found");
522 return -1;
523 }
524
525 bdoffs = bd->offset - BDSIZE;
526 if (lseek(sctx->fd, bdoffs, SEEK_SET) < 0) {
527 seterr("lseek: %s", strerror(errno));
528 return -1;
529 }
530
531 bd->refcnt--;
532 packbd(bdbuf, bd);
533 if (xwrite(sctx->fd, bdbuf, BDSIZE) != BDSIZE) {
534 bd->refcnt++;
535 seterr("failed to write block descriptor: %s",
536 strerror(errno));
537 return -1;
538 }
539
540 /* This block is still referenced so just return */
541 if (bd->refcnt > 0)
542 return 0;
543
544 if (punchhole(sctx->fd, bd->offset, bd->size) < 0) {
545 /*
546 * Filesystem does not support hole punching.
547 * Restore reference count.
548 */
549 lseek(sctx->fd, bdoffs, SEEK_SET);
550 bd->refcnt++;
551 packbd(bdbuf, bd);
552 xwrite(sctx->fd, bdbuf, BDSIZE);
553 seterr("operation not supported");
554 return -1;
555 }
556
557 /*
558 * Remove block from block descriptor cache as this is no
559 * longer a valid block. Insert it into the garbage collector
560 * list instead.
561 */
562 RB_REMOVE(bdcache, &sctx->bdcache, bd);
563 SLIST_INSERT_HEAD(&sctx->gchead, bd, sle);
564 return 0;
565 }
566
567 /*
568 * Re-punch all holes in the storage file.
569 * This is needed when the storage file is copied from
570 * one system to another and back. The target system
571 * may not support hole punching so the holes will be
572 * filled with literal zeroes, negating the space saving
573 * effects.
574 */
575 static int
576 bsgc(struct bctx *bctx)
577 {
578 struct sctx *sctx;
579 struct bd *bd;
580
581 sctx = bctx->sctx;
582 SLIST_FOREACH(bd, &sctx->gchead, sle) {
583 assert(bd->refcnt == 0);
584 punchhole(sctx->fd, bd->offset, bd->size);
585 }
586 return 0;
587 }
588
589 /* Sync block header to storage file */
590 static int
591 bssync(struct bctx *bctx)
592 {
593 unsigned char bhdrbuf[BHDRSIZE];
594 struct sctx *sctx;
595 struct bhdr *bhdr;
596
597 sctx = bctx->sctx;
598 if (sctx->rdonly)
599 return 0;
600
601 if (lseek(sctx->fd, 0, SEEK_SET) < 0) {
602 seterr("lseek: %s", strerror(errno));
603 return -1;
604 }
605
606 bhdr = &sctx->bhdr;
607 packbhdr(bhdrbuf, bhdr);
608 if (xwrite(sctx->fd, bhdrbuf, BHDRSIZE) != BHDRSIZE) {
609 seterr("failed to write block header: %s", strerror(errno));
610 return -1;
611 }
612 fsync(sctx->fd);
613 return 0;
614 }
615
616 /* Close storage handle */
617 static int
618 bsclose(struct bctx *bctx)
619 {
620 struct sctx *sctx;
621 struct bd *bd, *tmp;
622 int r;
623
624 /* Free block descriptor cache */
625 sctx = bctx->sctx;
626 RB_FOREACH_SAFE(bd, bdcache, &sctx->bdcache, tmp) {
627 RB_REMOVE(bdcache, &sctx->bdcache, bd);
628 free(bd);
629 }
630
631 /* Free garbage collector list */
632 while (!SLIST_EMPTY(&sctx->gchead)) {
633 bd = SLIST_FIRST(&sctx->gchead);
634 SLIST_REMOVE(&sctx->gchead, bd, bd, sle);
635 free(bd);
636 }
637
638 r = close(sctx->fd);
639 free(sctx);
640 if (r < 0)
641 seterr("close: %s", strerror(errno));
642 return r;
643 }
644
645 struct bops *
646 bstorageops(void)
647 {
648 return &bops;
649 }