Initial commit
authorChristophe Varoqui <christophe.varoqui@opensvc.com>
Thu, 13 May 2010 13:46:33 +0000 (15:46 +0200)
committeropensvc <opensvc@opensvc.com>
Thu, 13 May 2010 13:46:33 +0000 (15:46 +0200)
dds.c [new file with mode: 0644]

diff --git a/dds.c b/dds.c
new file mode 100644 (file)
index 0000000..eda6fda
--- /dev/null
+++ b/dds.c
@@ -0,0 +1,623 @@
+/*
+ * Author:      Christophe Varoqui
+ *
+ *              This program is distributed in the hope that it will be useful,
+ *              but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *              See the GNU General Public License for more details.
+ *
+ *              This program is free software; you can redistribute it and/or
+ *              modify it under the terms of the GNU General Public License
+ *              as published by the Free Software Foundation; either version
+ *              2 of the License, or (at your option) any later version.
+ *
+ * Copyright (c) 2009 Christophe Varoqui
+ */
+#define _GNU_SOURCE
+#define _XOPEN_SOURCE 600
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <sys/uio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>    /* sysconf */
+#include <string.h>    /* memcpy */
+#include <stdint.h>    /* u_int32, u_int64 */
+
+/*
+ * chunk layout on cow device
+ *
+ * ----------------------------------------
+ * | chunk number | chunk type            |
+ * ----------------------------------------
+ * | 0            | header                |
+ * | 1            | 1st exception chunk   |
+ * | 2...n-1      | exception data chunks |
+ * | n...n+1      | 2nd exception chunk   |
+ * | ...          | ...                   |
+ * ----------------------------------------
+ *
+ * where n = exception chunk number * (chunk size / 16 + 1) + 1
+ *                                                   ^        ^
+ *                                                   |        `- header chunk offset
+ *                                                   |
+ *                                                    `- size of exception struct
+ */
+
+/* 
+ * Magic for persistent snapshots: "SnAp" - Feeble isn't it. 
+ */ 
+#define SNAP_MAGIC 0x70416e53 
+
+/* 
+ * The on-disk version of the metadata. 
+ */ 
+#define SNAPSHOT_DISK_VERSION 1 
+
+struct disk_header { 
+       uint32_t magic;
+
+       /* 
+        * Is this snapshot valid. There is no way of recovering 
+        * an invalid snapshot. 
+        */ 
+       uint32_t valid;
+
+       /* 
+        * Simple, incrementing version. no backward 
+        * compatibility. 
+        */ 
+       uint32_t version;
+
+       /* In sectors */ 
+       uint32_t chunk_size;
+};
+
+struct disk_exception { 
+       uint64_t old_chunk;
+       uint64_t new_chunk;
+};
+
+#define SECTOR_SIZE 512
+#define MAX_SNAP_CHUNK_SIZE 524288
+
+/*
+ * globals
+ */
+size_t chunk_size_bytes;
+int fdin, fdout, fdcow;
+char * buf;
+int showchunks, verbose, extract, merge;
+
+static int init_buffer () {
+       buf = malloc(MAX_SNAP_CHUNK_SIZE);
+       if (!buf) {
+               fprintf(stderr, "malloc generic buffer\n");
+               return 1;
+       }
+       return 0;
+}
+
+static uint64_t checksum(const unsigned char *data, uint32_t data_length) {
+       uint64_t result = 0;
+       uint32_t i;
+
+       for (i = 0; i < data_length; i++)
+               result = result + data[i];
+
+       return result;
+}
+
+static void print_disk_header (struct disk_header * h) {
+       printf("Snapshot header:\n");
+       printf("  magic             0x%x\n", h->magic);
+       printf("  version           %i\n", h->version);
+       printf("  valid             %s\n", h->valid ? "yes" : "no");
+       printf("  chunk_size        %i KB\n", chunk_size_bytes / 1024);
+       return;
+}
+
+static int read_disk_header() {
+       int len;
+       struct disk_header * h;
+
+       /* minimum aligned read */
+       len = read(fdcow, buf, SECTOR_SIZE);
+       h = (struct disk_header *)buf;
+       if (!len && !errno && fdcow == STDIN_FILENO) {
+               fprintf(stderr, "stdin is empty\n");
+               return 1;
+       }
+       if (len != SECTOR_SIZE) {
+               perror("read cow header");
+               return 1;
+       }
+       if (h->magic != SNAP_MAGIC) {
+               fprintf(stderr, "cow device error (magic mismatch)\n");
+               return 1;
+       }
+       if (h->version != SNAPSHOT_DISK_VERSION) {
+               fprintf(stderr, "wrong version snapshot\n");
+               return 1;
+       }
+       if (!h->valid) {
+               fprintf(stderr, "snapshot is invalid\n");
+               return 1;
+       }
+       if (h->chunk_size & (h->chunk_size - 1) != 0 ) {
+               fprintf(stderr, "snapshot chunk size is not a power of 2\n");
+               return 1;
+       }
+       chunk_size_bytes = h->chunk_size * SECTOR_SIZE;
+
+       if (verbose)
+               print_disk_header(h);
+
+       /* consume the rest of the chunk for non-seekable fdcow */
+       len = read(fdcow, buf, chunk_size_bytes - SECTOR_SIZE);
+       if (!len && !errno && fdcow == STDIN_FILENO) {
+               fprintf(stderr, "stdin is empty\n");
+               return 1;
+       }
+       if (len != chunk_size_bytes - SECTOR_SIZE) {
+               perror("read cow header padding");
+               return 1;
+       }
+
+       return 0;
+}
+
+/*
+ * copy the whole 1st chunk
+ */
+int write_disk_header () {
+       size_t len;
+
+       if (lseek64(fdcow, 0, SEEK_SET) == -1) {
+               perror("seek cow to header chunk");
+               return 1;
+       }
+        len = 0;
+        while (len >= 0 && len != chunk_size_bytes) {
+               len += read(fdcow, buf, chunk_size_bytes - len);
+       }
+       if (len != chunk_size_bytes) {
+               perror("read cow header");
+                printf("len = %i\n", len);
+               return 1;
+       }
+       len = write(fdout, buf, chunk_size_bytes);
+       if (len != chunk_size_bytes) {
+               perror("write cow header");
+               return 1;
+       }
+       return 0;
+}
+
+static int write_exception (struct disk_exception * e) {
+       size_t len;
+
+       if (lseek64(fdin, e->old_chunk * chunk_size_bytes, SEEK_SET) == -1) {
+               perror("seek source to data chunk");
+               return 1;
+       }
+        len = 0;
+        while (len >= 0 && len != chunk_size_bytes) {
+               len += read(fdin, buf, chunk_size_bytes - len);
+       }
+       if (len != chunk_size_bytes) {
+               perror("read cow header");
+                printf("len = %i\n", len);
+               return 1;
+       }
+       len = write(fdout, buf, chunk_size_bytes);
+       if (len != chunk_size_bytes) {
+               perror("write cow header");
+               return 1;
+       }
+       if (showchunks)
+               printf("  %-16llu  %-16llu\n", e->old_chunk, e->new_chunk);
+       return 0;
+}
+
+/*
+ * exception chunks are thus located
+ */
+static int seek_to_exception_table (const int n) {
+       uint64_t epc = chunk_size_bytes / sizeof(struct disk_exception);
+       uint64_t pos = (n * (epc + 1) + 1) * chunk_size_bytes;
+       if (lseek64(fdcow, pos, SEEK_SET) == -1) {
+               perror("seek cow to expection chunk");
+               return 1;
+       }
+       return 0;
+}
+
+static int load_exception (struct disk_exception * e) {
+       size_t len;
+
+       len = 0;
+        while (len >=0 && len != chunk_size_bytes) {
+               len += read(fdcow, buf, chunk_size_bytes - len);
+       }
+       if (len != chunk_size_bytes) {
+               perror("read cow data chunk to load");
+               return 1;
+       }
+       if (lseek64(fdout, e->old_chunk * chunk_size_bytes, SEEK_SET) == -1) {
+               perror("seek dest to load data chunk");
+               return 1;
+       }
+       len = write(fdout, buf, chunk_size_bytes);
+       if (len != chunk_size_bytes) {
+               perror("write cow exceptions");
+               return 1;
+       }
+       if (showchunks)
+               printf("  %-16llu  %-16llu\n", e->old_chunk, e->new_chunk);
+       return 0;
+}
+
+static void report (uint64_t exception_chunk_count, uint64_t exception_count) {
+       printf("Report:\n");
+       printf("  Exception chunks  %llu\n", exception_chunk_count);
+       printf("  Exceptions        %llu\n", exception_count);
+       printf("  Output size meta  %llu KB\n",
+               (exception_chunk_count + 1) * chunk_size_bytes / 1024);
+       printf("  Output size data  %llu KB\n",
+               exception_count * chunk_size_bytes / 1024);
+       printf("  Output size total %llu KB\n",
+               (exception_chunk_count + 1 + exception_count) *
+               chunk_size_bytes / 1024);
+       return;
+}
+
+int load_exceptions () {
+       struct disk_exception * e, * pe;
+       int i, len;
+       int err = 0;
+       uint64_t exception_chunk_count = 0;
+       uint64_t n, exception_count = 0;
+       char * exception_chunk_buf;
+
+       if (showchunks) {
+               printf("Exception table:\n");
+               printf("  chunk number      chunk number\n");
+               printf("  on source         on dest\n");
+       }
+
+       exception_chunk_buf = malloc(chunk_size_bytes);
+       if (!exception_chunk_buf) {
+               fprintf(stderr, "alloc exception chunk cache\n");
+               return 1;
+       }
+
+       do {
+               exception_chunk_count++;
+
+               /* read it all */
+               len = read(fdcow, buf, chunk_size_bytes);
+               if (len != chunk_size_bytes) {
+                       perror("read cow exceptions");
+                       err = 1;
+                       goto out;
+               }
+
+               /* buf will be used for other reads, use a copy */
+               memcpy(exception_chunk_buf, buf, chunk_size_bytes);
+
+               for (i=0; i<len ; i+=sizeof(struct disk_exception)) {
+                       e = (struct disk_exception *)&exception_chunk_buf[i];
+
+                       /* detect end-of-exceptions */
+                       if (e->new_chunk == 0)
+                               goto out;
+                       load_exception(e);
+                       exception_count++;
+               }
+       } while (1);
+out:
+       if (verbose)
+               report(exception_chunk_count, exception_count);
+       return err;
+}
+
+int write_exceptions () {
+       struct disk_exception * e, * pe;
+       int i, len;
+       int err = 0;
+       uint64_t exception_chunk_count = 0;
+       uint64_t n, exception_count = 0;
+       char * exception_chunk_buf;
+
+       if (showchunks) {
+               printf("Exception table:\n");
+               printf("  chunk number      chunk number\n");
+               printf("  on source         on dest\n");
+       }
+
+       exception_chunk_buf = malloc(chunk_size_bytes);
+       if (!exception_chunk_buf) {
+               fprintf(stderr, "alloc exception chunk cache\n");
+               return 1;
+       }
+
+       do {
+               /* seek next exception chunk */
+               if (seek_to_exception_table(exception_chunk_count++))
+                       return 1;
+
+               /* read it all */
+               len = read(fdcow, buf, chunk_size_bytes);
+               if (len != chunk_size_bytes) {
+                       perror("read cow exceptions");
+                       err = 1;
+                       goto out;
+               }
+
+               /* serialize the exception new chunk number. start at 2 */
+               n = exception_count;
+               for (i=0; i<len ; i+=sizeof(struct disk_exception)) {
+                       e = (struct disk_exception *)&buf[i];
+
+                       /* detect end-of-exceptions */
+                       if (e->new_chunk == 0)
+                               break;
+
+                       e->new_chunk = n + exception_chunk_count + 1;
+                       n++;
+               }
+
+               /* write it to dest */
+               len = write(fdout, buf, chunk_size_bytes);
+               if (len != chunk_size_bytes) {
+                       perror("write cow exceptions");
+                       return 1;
+               }
+
+               /* buf will be used for other reads, use a copy */
+               memcpy(exception_chunk_buf, buf, chunk_size_bytes);
+
+               for (i=0; i<len ; i+=sizeof(struct disk_exception)) {
+                       e = (struct disk_exception *)&exception_chunk_buf[i];
+
+                       /* detect end-of-exceptions */
+                       if (e->new_chunk == 0)
+                               goto out;
+                       write_exception(e);
+                       exception_count++;
+               }
+       } while (1);
+out:
+       if (verbose)
+               report(exception_chunk_count, exception_count);
+       return err;
+}
+
+static int do_extract () {
+       if (write_disk_header() != 0)
+               return 1;
+
+       if (write_exceptions() != 0)
+               return 1;
+
+       return 0;
+}
+
+static int do_merge () {
+       /*
+        * no seek on fdcow in this code path, as cow might be fed through stdin
+        */
+       if (load_exceptions() != 0)
+               return 1;
+
+       return 0;
+}
+
+void usage (char * prog) {
+       printf("usage:\n");
+       printf(" %s --extract|--merge --cow cowdev --source srcdev "
+               "[--dest dstdev] [--file replay_binfile]\n", prog);
+       printf("\n");
+       printf("extract as a stream or file the binary delta between two\n");
+       printf("device-mapper snapshots (v1 format)\n");
+       printf("\n");
+       printf("options:\n");
+       printf(" -c, --cow        copy on write device\n");
+       printf(" -s, --source     device hosting the data to copy from\n");
+       printf(" -d, --dest       file to copy the data to (default stdout)\n");
+       printf(" -h, --help       this message\n");
+       printf(" -v, --verbose    display more information\n");
+       printf(" -C, --showchunks display chunk remappings\n");
+       return;
+}
+
+static int open_and_stat (const char * file, const int flags, const int advice) {
+       int fd;
+       struct stat sb;
+
+       fd = open(file, flags);
+       if (fd < 0) {
+               fprintf(stderr, "open error on %s (%s)\n",
+                       file, strerror(errno));
+               return -1;
+       }
+
+       if (stat(file, &sb) == -1) {
+               fprintf(stderr, "stat error on %s (%s)\n",
+                       file, strerror(errno));
+               return -1;
+       }
+
+       if (posix_fadvise(fd, 0, sb.st_size, advice)) {
+               fprintf(stderr, "fadvise error on %s\n", file);
+               return -1;
+       }
+       return fd;
+}
+
+static int stat_and_open (const char * file, const int flags, const int advice) {
+       int fd;
+       struct stat sb;
+
+       if (stat(file, &sb) == -1) {
+               fprintf(stderr, "stat error on %s (%s)\n",
+                       file, strerror(errno));
+               return -1;
+       }
+
+       fd = open(file, flags);
+       if (fd < 0) {
+               fprintf(stderr, "open error on %s (%s)\n",
+                       file, strerror(errno));
+               return -1;
+       }
+
+       if (posix_fadvise(fd, 0, sb.st_size, advice)) {
+               fprintf(stderr, "fadvise error on %s\n", file);
+               return -1;
+       }
+       return fd;
+}
+
+int main (int argc, char * const * argv) {
+       char c;
+       char * source = NULL;
+       char * dest = NULL;
+       char * cow = NULL;
+
+       verbose = 0;
+       showchunks = 0;
+       extract = 0;
+       merge = 0;
+
+       while (1) {
+               int option_index = 0;
+               static struct option long_options[] = {
+                       {"cow", 1, 0, 'c'},
+                       {"dest", 1, 0, 'd'},
+                       {"extract", 0, 0, 'x'},
+                       {"merge", 0, 0, 'm'},
+                       {"help", 0, 0, 'h'},
+                       {"source", 1, 0, 's'},
+                       {"verbose", 0, 0, 'v'},
+                       {"showchunks", 0, 0, 'C'},
+                       {0, 0, 0, 0},
+               };
+               c = getopt_long(argc, argv, "c:d:hms:vx",
+                               long_options, &option_index);
+               if (c == -1)
+                       break;
+
+               switch (c) {
+               case 'c':
+                       cow = strdup(optarg);
+                       break;
+               case 'C':
+                       showchunks = 1;
+                       break;
+               case 'd':
+                       dest = strdup(optarg);
+                       break;
+               case 'h':
+                       usage(argv[0]);
+                       break;
+               case 'm':
+                       merge = 1;
+                       break;
+               case 's':
+                       source = strdup(optarg);
+                       break;
+               case 'v':
+                       verbose = 1;
+                       break;
+               case 'x':
+                       extract = 1;
+                       break;
+               case '?':
+                       break;
+               }
+       }
+       if (optind < argc)
+               printf("syntax error");
+
+       /*
+        * sanity checks
+        */
+       if (extract && (verbose || showchunks) && !dest) {
+               fprintf(stderr, "extract verbose and showchunks modes needs --dest "
+                               "(stdout usage conflict)\n");
+               return 1;
+       }
+       if (extract && merge) {
+               fprintf(stderr, "extract conflicts with merge\n");
+               return 1;
+       }
+       if (!extract && !merge) {
+               fprintf(stderr, "need either --extract or --merge\n");
+               return 1;
+       }
+       if (extract && !source) {
+               fprintf(stderr, "extract mode need --source\n");
+               return 1;
+       }
+       if (extract && !cow) {
+               fprintf(stderr, "extract mode need --cow\n");
+               return 1;
+       }
+       if (merge && source) {
+               fprintf(stderr, "extract mode conflicts with --source\n");
+               return 1;
+       }
+       if (merge && !dest) {
+               fprintf(stderr, "extract mode need --dest\n");
+               return 1;
+       }
+
+       /*
+        * open files
+        */
+       if (cow) {
+               fdcow = stat_and_open(cow, O_RDONLY|O_LARGEFILE, POSIX_FADV_DONTNEED);
+               if (fdcow < 0)
+                       return 1;
+       } else if (merge)
+               fdcow = STDIN_FILENO;
+
+       if (source) {
+               fdin = stat_and_open(source, O_RDONLY|O_LARGEFILE, POSIX_FADV_DONTNEED);
+               if (fdin < 0)
+                       return 1;
+       }
+
+       if (dest) {
+               fdout = open_and_stat(dest, O_WRONLY|O_CREAT|O_LARGEFILE, POSIX_FADV_DONTNEED);
+               if (fdout < 0)
+                       return 1;
+       } else if (extract)
+               fdout = STDOUT_FILENO;
+
+       if (init_buffer() != 0)
+               return 1;
+
+       /*
+        * read and validate snapshot metadata (mostly chunk size)
+        */
+       if (read_disk_header() != 0)
+               return 1;
+
+       if (extract && do_extract())
+               return 1;
+
+       if (merge && do_merge())
+               return 1;
+               
+       return 0;
+}
+