dedup

deduplicating backup program
git clone git://git.2f30.org/dedup.git
Log | Files | Refs | README | LICENSE

snap.c (10921B)


      1 /* Snapshot archive implementation */
      2 #include <sys/types.h>
      3 #include <sys/stat.h>
      4 
      5 #include <assert.h>
      6 #include <errno.h>
      7 #include <fcntl.h>
      8 #include <limits.h>
      9 #include <stdint.h>
     10 #include <stdio.h>
     11 #include <stdlib.h>
     12 #include <string.h>
     13 #include <strings.h>
     14 #include <unistd.h>
     15 
     16 #include <sodium.h>
     17 
     18 #include "config.h"
     19 #include "misc.h"
     20 #include "queue.h"
     21 #include "snap.h"
     22 #include "state.h"
     23 
     24 /* snapshot encryption algorithms */
     25 #define SNONETYPE	0x400
     26 #define SCHACHATYPE	0x401
     27 
     28 /* snapshot header constants */
     29 #define SHDRMAGIC	"SNAPSNAPPYSNOOP"
     30 #define NSHDRMAGIC	16
     31 
     32 #define VMIN		0
     33 #define VMAJ		1
     34 #define VMINMASK	0xff
     35 #define VMAJSHIFT	8
     36 #define VMAJMASK	0xff
     37 
     38 #define CRYPTOHDRSIZE	crypto_secretstream_xchacha20poly1305_HEADERBYTES
     39 #define SHDRSIZE	(NSHDRMAGIC + CRYPTOHDRSIZE + 8 + 8)
     40 
     41 extern struct param param;
     42 
     43 /* misc helpers */
     44 extern int pack(unsigned char *, char *, ...);
     45 extern int unpack(unsigned char *, char *, ...);
     46 
     47 /* Snapshot header structure */
     48 struct shdr {
     49 	char magic[NSHDRMAGIC];			/* magic number for file(1) */
     50 	unsigned char header[CRYPTOHDRSIZE];	/* xchacha20-poly1305 crypto header */
     51 	uint64_t flags;				/* version number */
     52 	uint64_t nbd;				/* number of block hashes */
     53 };
     54 
     55 struct mdnode {
     56 	unsigned char md[MDSIZE];		/* hash of block */
     57 	TAILQ_ENTRY(mdnode) e;			/* mdhead link node */
     58 };
     59 
     60 struct sctx {
     61 	TAILQ_HEAD(mdhead, mdnode) mdhead;	/* list of hashes contained in snapshot */
     62 	struct mdnode *mdnext;			/* next hash to be returned via sget() */
     63 	int type;				/* encryption algorithm */
     64 	int fd;					/* underlying snapshot file descriptor */
     65 	int rdonly;				/* when set, ssync() is a no-op */
     66 	struct shdr shdr;			/* snapshot header */
     67 };
     68 
     69 /* Unpack snapshot header */
     70 static int
     71 unpackshdr(unsigned char *buf, struct shdr *shdr)
     72 {
     73 	char fmt[BUFSIZ];
     74 	int n;
     75 
     76 	snprintf(fmt, sizeof(fmt), "'%d'%dqq", NSHDRMAGIC, CRYPTOHDRSIZE);
     77 	n = unpack(buf, fmt,
     78 	           shdr->magic,
     79 	           shdr->header,
     80 	           &shdr->flags,
     81 	           &shdr->nbd);
     82 
     83 	assert(n == SHDRSIZE);
     84 	return n;
     85 }
     86 
     87 /* Pack snapshot header */
     88 static int
     89 packshdr(unsigned char *buf, struct shdr *shdr)
     90 {
     91 	char fmt[BUFSIZ];
     92 	int n;
     93 
     94 	snprintf(fmt, sizeof(fmt), "'%d'%dqq", NSHDRMAGIC, CRYPTOHDRSIZE);
     95 	n = pack(buf, fmt,
     96 	         shdr->magic,
     97 	         shdr->header,
     98 	         shdr->flags,
     99 	         shdr->nbd);
    100 
    101 	assert(n == SHDRSIZE);
    102 	return n;
    103 }
    104 
    105 static int
    106 loadmdnone(struct sctx *sctx, int first)
    107 {
    108 	struct mdnode *mdnode;
    109 
    110 	mdnode = calloc(1, sizeof(*mdnode));
    111 	if (mdnode == NULL) {
    112 		seterr("calloc: out of memory");
    113 		return -1;
    114 	}
    115 
    116 	if (xread(sctx->fd, mdnode->md, MDSIZE) != MDSIZE) {
    117 		seterr("failed to read block hash: %s", strerror(errno));
    118 		return -1;
    119 	}
    120 
    121 	TAILQ_INSERT_TAIL(&sctx->mdhead, mdnode, e);
    122 	return 0;
    123 }
    124 
    125 static int
    126 loadmdchacha(struct sctx *sctx, int first)
    127 {
    128 	unsigned char buf[MDSIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
    129 	unsigned char hdr[SHDRSIZE];
    130 	static crypto_secretstream_xchacha20poly1305_state state;
    131 	struct mdnode *mdnode;
    132 	struct shdr *shdr;
    133 
    134 	shdr = &sctx->shdr;
    135 	packshdr(hdr, shdr);
    136 	if (first && crypto_secretstream_xchacha20poly1305_init_pull(&state,
    137 	                                                             shdr->header,
    138 	                                                             param.key) < 0) {
    139 		seterr("invalid crypto header");
    140 		return -1;
    141 	}
    142 
    143 	if (xread(sctx->fd, buf, sizeof(buf)) != sizeof(buf)) {
    144 		seterr("failed to read block hash: %s", strerror(errno));
    145 		return -1;
    146 	}
    147 
    148 	mdnode = calloc(1, sizeof(*mdnode));
    149 	if (mdnode == NULL) {
    150 		seterr("calloc: out of memory");
    151 		return -1;
    152 	}
    153 
    154 	if (crypto_secretstream_xchacha20poly1305_pull(&state, mdnode->md, NULL,
    155 	                                               NULL, buf, sizeof(buf),
    156 	                                               hdr, sizeof(hdr)) < 0) {
    157 		free(mdnode);
    158 		seterr("authentication failed");
    159 		return -1;
    160 	}
    161 
    162 	TAILQ_INSERT_TAIL(&sctx->mdhead, mdnode, e);
    163 	return 0;
    164 }
    165 
    166 static int
    167 initmdhead(struct sctx *sctx)
    168 {
    169 	struct shdr *shdr;
    170 	int (*loadmd)(struct sctx *, int);
    171 	uint64_t i;
    172 
    173 	if (sctx->type == SNONETYPE)
    174 		loadmd = loadmdnone;
    175 	else
    176 		loadmd = loadmdchacha;
    177 
    178 	shdr = &sctx->shdr;
    179 	for (i = 0; i < shdr->nbd; i++) {
    180 		if ((*loadmd)(sctx, i == 0) == 0)
    181 			continue;
    182 
    183 		while (!TAILQ_EMPTY(&sctx->mdhead)) {
    184 			struct mdnode *mdnode;
    185 
    186 			mdnode = TAILQ_FIRST(&sctx->mdhead);
    187 			TAILQ_REMOVE(&sctx->mdhead, mdnode, e);
    188 			free(mdnode);
    189 		}
    190 		return -1;
    191 	}
    192 	return 0;
    193 }
    194 
    195 int
    196 screat(char *path, int mode, struct sctx **sctx)
    197 {
    198 	unsigned char buf[SHDRSIZE];
    199 	struct shdr *shdr;
    200 	int type;
    201 	int fd;
    202 
    203 	if (path == NULL || sctx == NULL) {
    204 		seterr("invalid params");
    205 		return -1;
    206 	}
    207 
    208 	/* Determine algorithm type */
    209 	if (strcasecmp(param.ealgo, "none") == 0) {
    210 		type = SNONETYPE;
    211 	} else if (strcasecmp(param.ealgo, "XChaCha20-Poly1305") == 0) {
    212 		type = SCHACHATYPE;
    213 	} else {
    214 		seterr("invalid encryption type: %s", param.ealgo);
    215 		return -1;
    216 	}
    217 
    218 	/* Ensure a key has been provided if caller requested encryption */
    219 	if (type != SNONETYPE && !param.keyloaded) {
    220 		seterr("expected encryption key");
    221 		return -1;
    222 	}
    223 
    224 	if (sodium_init() < 0) {
    225 		seterr("sodium_init: failed");
    226 		return -1;
    227 	}
    228 
    229 	fd = open(path, O_RDWR | O_CREAT | O_EXCL, mode);
    230 	if (fd < 0) {
    231 		seterr("open: %s", strerror(errno));
    232 		return -1;
    233 	}
    234 
    235 	*sctx = calloc(1, sizeof(**sctx));
    236 	if (*sctx == NULL) {
    237 		close(fd);
    238 		seterr("calloc: out of memory");
    239 		return -1;
    240 	}
    241 
    242 	TAILQ_INIT(&(*sctx)->mdhead);
    243 	(*sctx)->mdnext = NULL;
    244 	(*sctx)->type = type;
    245 	(*sctx)->fd = fd;
    246 
    247 	shdr = &(*sctx)->shdr;
    248 	memcpy(shdr->magic, SHDRMAGIC, NSHDRMAGIC);
    249 	shdr->flags = (VMAJ << VMAJSHIFT) | VMIN;
    250 	shdr->nbd = 0;
    251 
    252 	packshdr(buf, shdr);
    253 	if (xwrite(fd, buf, SHDRSIZE) != SHDRSIZE) {
    254 		free(*sctx);
    255 		close(fd);
    256 		seterr("failed to write snapshot header: %s", strerror(errno));
    257 		return -1;
    258 	}
    259 	return 0;
    260 }
    261 
    262 int
    263 sopen(char *path, int flags, int mode, struct sctx **sctx)
    264 {
    265 	unsigned char buf[SHDRSIZE];
    266 	struct shdr *shdr;
    267 	int type;
    268 	int fd;
    269 
    270 	if (path == NULL || sctx == NULL) {
    271 		seterr("invalid params");
    272 		return -1;
    273 	}
    274 
    275 	/* Existing snapshots are immutable */
    276 	if (flags != S_READ) {
    277 		seterr("invalid params");
    278 		return -1;
    279 	}
    280 
    281 	/* Determine algorithm type */
    282 	if (strcasecmp(param.ealgo, "none") == 0) {
    283 		type = SNONETYPE;
    284 	} else if (strcasecmp(param.ealgo, "XChaCha20-Poly1305") == 0) {
    285 		type = SCHACHATYPE;
    286 	} else {
    287 		seterr("invalid encryption type: %s", param.ealgo);
    288 		return -1;
    289 	}
    290 
    291 	/* Ensure a key has been provided if caller requested encryption */
    292 	if (type != SNONETYPE && !param.keyloaded) {
    293 		seterr("expected encryption key");
    294 		return -1;
    295 	}
    296 
    297 	if (sodium_init() < 0) {
    298 		seterr("sodium_init: failed");
    299 		return -1;
    300 	}
    301 
    302 	fd = open(path, O_RDONLY, mode);
    303 	if (fd < 0) {
    304 		seterr("open: %s", strerror(errno));
    305 		return -1;
    306 	}
    307 
    308 	*sctx = calloc(1, sizeof(**sctx));
    309 	if (*sctx == NULL) {
    310 		close(fd);
    311 		seterr("calloc: out of memory");
    312 		return -1;
    313 	}
    314 
    315 	TAILQ_INIT(&(*sctx)->mdhead);
    316 	(*sctx)->mdnext = NULL;
    317 	(*sctx)->type = type;
    318 	(*sctx)->fd = fd;
    319 	(*sctx)->rdonly = 1;
    320 
    321 	shdr = &(*sctx)->shdr;
    322 
    323 	if (xread(fd, buf, SHDRSIZE) != SHDRSIZE) {
    324 		free(*sctx);
    325 		close(fd);
    326 		seterr("failed to read snapshot header: %s", strerror(errno));
    327 		return -1;
    328 	}
    329 	unpackshdr(buf, shdr);
    330 
    331 	if (memcmp(shdr->magic, SHDRMAGIC, NSHDRMAGIC) != 0) {
    332 		free(*sctx);
    333 		close(fd);
    334 		seterr("unknown snapshot header magic");
    335 		return -1;
    336 	}
    337 
    338 	/* If the major version is different, the format is incompatible */
    339 	if (((shdr->flags >> VMAJSHIFT) & VMAJMASK) != VMAJ) {
    340 		free(*sctx);
    341 		close(fd);
    342 		seterr("snapshot header version mismatch");
    343 		return -1;
    344 	}
    345 
    346 	if (initmdhead(*sctx) < 0) {
    347 		free(*sctx);
    348 		close(fd);
    349 		return -1;
    350 	}
    351 	return 0;
    352 }
    353 
    354 int
    355 sput(struct sctx *sctx, unsigned char *md)
    356 {
    357 	struct shdr *shdr;
    358 	struct mdnode *mdnode;
    359 
    360 	if (sctx == NULL || md == NULL) {
    361 		seterr("invalid params");
    362 		return -1;
    363 	}
    364 
    365 	mdnode = calloc(1, sizeof(*mdnode));
    366 	if (mdnode == NULL) {
    367 		seterr("calloc: out of memory");
    368 		return -1;
    369 	}
    370 	shdr = &sctx->shdr;
    371 	shdr->nbd++;
    372 	memcpy(mdnode->md, md, MDSIZE);
    373 	TAILQ_INSERT_TAIL(&sctx->mdhead, mdnode, e);
    374 	return 0;
    375 }
    376 
    377 int
    378 sget(struct sctx *sctx, unsigned char *md)
    379 {
    380 	struct mdnode *mdnode;
    381 
    382 	if (sctx == NULL || md == NULL) {
    383 		seterr("invalid params");
    384 		return -1;
    385 	}
    386 
    387 	mdnode = sctx->mdnext;
    388 	if (mdnode == NULL)
    389 		mdnode = TAILQ_FIRST(&sctx->mdhead);
    390 	else
    391 		mdnode = TAILQ_NEXT(mdnode, e);
    392 	sctx->mdnext = mdnode;
    393 	if (mdnode != NULL) {
    394 		memcpy(md, mdnode->md, MDSIZE);
    395 		return MDSIZE;
    396 	}
    397 	return 0;
    398 }
    399 
    400 static int
    401 syncnone(struct sctx *sctx)
    402 {
    403 	unsigned char hdr[SHDRSIZE];
    404 	struct mdnode *mdnode;
    405 	struct shdr *shdr;
    406 
    407 	shdr = &sctx->shdr;
    408 	packshdr(hdr, shdr);
    409 	if (xwrite(sctx->fd, hdr, SHDRSIZE) != SHDRSIZE) {
    410 		seterr("failed to write snapshot header: %s", strerror(errno));
    411 		return -1;
    412 	}
    413 
    414 	TAILQ_FOREACH(mdnode, &sctx->mdhead, e) {
    415 		if (xwrite(sctx->fd, mdnode->md, MDSIZE) != MDSIZE) {
    416 			seterr("failed to write block hash: %s",
    417 			        strerror(errno));
    418 			return -1;
    419 		}
    420 	}
    421 	return 0;
    422 }
    423 
    424 static int
    425 syncchacha(struct sctx *sctx)
    426 {
    427 	unsigned char hdr[SHDRSIZE];
    428 	crypto_secretstream_xchacha20poly1305_state state;
    429 	struct mdnode *mdnode;
    430 	struct shdr *shdr;
    431 
    432 	shdr = &sctx->shdr;
    433 	crypto_secretstream_xchacha20poly1305_init_push(&state,
    434 	                                                shdr->header,
    435 	                                                param.key);
    436 
    437 	packshdr(hdr, shdr);
    438 	if (xwrite(sctx->fd, hdr, SHDRSIZE) != SHDRSIZE) {
    439 		seterr("failed to write snapshot header: %s", strerror(errno));
    440 		return -1;
    441 	}
    442 
    443 	TAILQ_FOREACH(mdnode, &sctx->mdhead, e) {
    444 		unsigned char buf[MDSIZE + crypto_secretstream_xchacha20poly1305_ABYTES];
    445 		unsigned char tag;
    446 
    447 		if (TAILQ_LAST(&sctx->mdhead, mdhead) == mdnode)
    448 			tag = crypto_secretstream_xchacha20poly1305_TAG_FINAL;
    449 		else
    450 			tag = 0;
    451 
    452 		crypto_secretstream_xchacha20poly1305_push(&state,
    453 		                                           buf, NULL,
    454 		                                           mdnode->md, MDSIZE,
    455 		                                           hdr, SHDRSIZE, tag);
    456 		if (xwrite(sctx->fd, buf, sizeof(buf)) != sizeof(buf)) {
    457 			seterr("failed to write block hash: %s",
    458 			        strerror(errno));
    459 			return -1;
    460 		}
    461 	}
    462 	return 0;
    463 }
    464 
    465 int
    466 ssync(struct sctx *sctx)
    467 {
    468 	if (sctx == NULL) {
    469 		seterr("invalid params");
    470 		return -1;
    471 	}
    472 
    473 	if (sctx->rdonly)
    474 		return 0;
    475 
    476 	if (lseek(sctx->fd, 0, SEEK_SET) < 0) {
    477 		seterr("lseek: %s", strerror(errno));
    478 		return -1;
    479 	}
    480 
    481 	if (sctx->type == SNONETYPE)
    482 		syncnone(sctx);
    483 	else
    484 		syncchacha(sctx);
    485 
    486 	fsync(sctx->fd);
    487 	return 0;
    488 }
    489 
    490 int
    491 sclose(struct sctx *sctx)
    492 {
    493 	int r;
    494 
    495 	if (sctx == NULL)
    496 		return -1;
    497 
    498 	if (ssync(sctx) < 0)
    499 		return -1;
    500 
    501 	/* Free block hash list */
    502 	while (!TAILQ_EMPTY(&sctx->mdhead)) {
    503 		struct mdnode *mdnode;
    504 
    505 		mdnode = TAILQ_FIRST(&sctx->mdhead);
    506 		TAILQ_REMOVE(&sctx->mdhead, mdnode, e);
    507 		free(mdnode);
    508 	}
    509 
    510 	r = close(sctx->fd);
    511 	free(sctx);
    512 	if (r < 0)
    513 		seterr("close: %s", strerror(errno));
    514 	return r;
    515 }