| /* ============================================================ |
| * Copyright (c) 2014 Actifio Inc. All Rights Reserved |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include "bitmap.h" |
| |
| #ifndef ZFS_MAXNAMELEN |
| #define ZFS_MAXNAMELEN (MAXNAMELEN - 1) |
| #endif |
| |
| #define GRAINSIZE ((unsigned)(64*1024)) |
| |
| int verbosity = 0; |
| int debug = 0; |
| unsigned int grainsize = GRAINSIZE; |
| |
| int usage(void) |
| { |
| printf("Bitmap generation usage:\n"); |
| printf(" zfstool [options] [bitmap|zst-bitmap] clone1-name clone2-name /path/to/bitmap/file\n"); |
| printf("\toptions include:\n" |
| "\t -v - verbosity (repeated one or more times, e.g. -v -v -v)\n" |
| "\t -g size - grain size in bytes, default %d\n" |
| "\t -z - generate a separate bitmap of writes, stored in /path/to/bitmap/file,\n" |
| "\t\t and a bitmap of holes, stored in /path/to/bitmap/file.holes,\n" |
| "\t -c /path/to/clone1/holes - option is valid only if -z is provided,\n" |
| "\t\t provides path to the hole bitmap file for clone1\n", |
| grainsize); |
| printf("\tcommands include:\n" |
| "\t bitmap - use ZFS send stream to produce bitmap\n" |
| "\t zst-bitmap - same as bitmap, included for backward compatibility\n"); |
| printf("Bitmap verification test usage:\n"); |
| printf(" zfstool [options] bitmap-verify clone-name clone-name {byte_offset}\n"); |
| printf("\toptions include:\n" |
| "\t -v - verbosity (repeated one or more times, e.g. -v -v -v)\n" |
| "\t -g size - grain size in bytes, default %d\n" |
| "\t -z - generate separate bitmaps for writes and frees\n" |
| "\t -c /path/to/clone1/holes - option is valid only if -z is provided,\n" |
| "\t\t provides path to the hole bitmap file for clone1\n" |
| "\t if the byte_offset is specified, check the corresponding grain,\n" |
| "\t otherwise, the whole bitmap is verified\n", |
| grainsize); |
| printf("Bitmap display usage:\n"); |
| printf(" zfstool bitmap-show bitmap-file {byte_offset}\n" |
| "\t if the byte_offset is specified, show the corresponding grain,\n" |
| "\t otherwise, the whole bitmap is displayed\n"); |
| return (-1); |
| } |
| |
| /* ZFS send style bitmap calculation */ |
| #include <sys/fs/zfs.h> |
| #include <libzfs.h> |
| #include <sys/zfs_ioctl.h> |
| |
| #include "zst.h" |
| |
| #define ZVOL_OBJECT (1) |
| #define ZVOL_OBJLEN_MARKER (~0ULL) |
| |
| typedef struct { |
| ram_bitmap_pair_t *bitmap_pair; |
| uint64_t parent_volsize; |
| uint64_t child_volsize; |
| } zst_callback_arg_t; |
| |
| /* |
| * Callbacks to be registered with ZST for getting the block level volume diffs |
| */ |
| static int |
| write_callback(int type, void *arg1, void *arg2) |
| { |
| struct drr_write *drrw = (struct drr_write *)arg1; |
| zst_callback_arg_t *arg = (zst_callback_arg_t *)arg2; |
| |
| assert(type == DRR_WRITE); |
| |
| /* skip non-zvol object */ |
| if (drrw->drr_object != ZVOL_OBJECT) |
| return (0); |
| |
| if (drrw->drr_offset + drrw->drr_logical_size > arg->child_volsize) { |
| fprintf(stderr, "Write [0x%" PRIx64 ":0x%" PRIx64 ") outside zvol " |
| "byte range [0x0:0x%" PRIx64 ")\n", |
| drrw->drr_offset, drrw->drr_logical_size, arg->child_volsize); |
| return (EINVAL); |
| } |
| |
| if (verbosity > 1) |
| fprintf(stderr, "Write record found [0x%" PRIx64 ":0x%" PRIx64 ")\n", |
| drrw->drr_offset, drrw->drr_offset + drrw->drr_logical_size); |
| |
| /* make a call to change the bitmap */ |
| bitmap_addbit(arg->bitmap_pair->diff_bitmap, drrw->drr_offset, drrw->drr_logical_size); |
| |
| return (0); |
| } |
| |
| static int |
| write_embedded_callback(int type, void *arg1, void *arg2) |
| { |
| struct drr_write_embedded *drrwe = (struct drr_write_embedded *)arg1; |
| zst_callback_arg_t *arg = (zst_callback_arg_t *)arg2; |
| |
| assert(type == DRR_WRITE_EMBEDDED); |
| |
| if (drrwe->drr_offset + drrwe->drr_length > arg->child_volsize) { |
| fprintf(stderr, "Write [%" PRIx64 ":%" PRIx64 ") outside zvol " |
| "byte range [0:%" PRIx64 ")\n", |
| drrwe->drr_offset, drrwe->drr_length, arg->child_volsize); |
| return (EINVAL); |
| } |
| |
| /* skip non-zvol object */ |
| if (drrwe->drr_object != ZVOL_OBJECT) |
| return (0); |
| |
| if (verbosity) |
| fprintf(stderr, "Write record found [%" PRIx64 ":%" PRIx64 ")\n", |
| drrwe->drr_offset, drrwe->drr_offset + drrwe->drr_length); |
| |
| /* make a call to change the bitmap */ |
| bitmap_addbit(arg->bitmap_pair->diff_bitmap, drrwe->drr_offset, drrwe->drr_length); |
| |
| return (0); |
| } |
| |
| static int |
| free_callback(int type, void *arg1, void *arg2) |
| { |
| struct drr_free *drrf = (struct drr_free *)arg1; |
| zst_callback_arg_t *arg = (zst_callback_arg_t *)arg2; |
| |
| assert(type == DRR_FREE); |
| |
| /* skip non-zvol object */ |
| if (drrf->drr_object != ZVOL_OBJECT) |
| return (0); |
| |
| if (drrf->drr_length == ZVOL_OBJLEN_MARKER) { |
| if (verbosity > 1) |
| fprintf(stderr, "Truncate record found; offset 0x%" PRIx64 "\n", |
| drrf->drr_offset); |
| if (drrf->drr_offset > arg->child_volsize) { |
| fprintf(stderr, "Offset 0x%" PRIx64 " in trancate record is " |
| "greater than child_volsize 0x%" PRIx64 "\n", |
| drrf->drr_offset, arg->child_volsize); |
| return (EINVAL); |
| } |
| /* |
| * avoid writing zeros in place of a whole at the end of the child |
| * volume; update the size to the last offset used |
| */ |
| arg->child_volsize = drrf->drr_offset; |
| return (0); |
| } |
| |
| if (verbosity > 1) |
| fprintf(stderr, "Free record found [0x%" PRIx64 ":0x%" PRIx64 ")\n", |
| drrf->drr_offset, drrf->drr_offset + drrf->drr_length); |
| |
| /* |
| * it is necessary to keep track of new holes that can be ligitimately |
| * created with overwrites with zeros and block discards |
| */ |
| |
| if (drrf->drr_offset + drrf->drr_length > arg->child_volsize) { |
| if (verbosity > 1) |
| fprintf(stderr, "\tThis free record goes outside the larger zvol " |
| "byte range [0x0:0x%"PRIx64"), perhaps due to previously " |
| "encountered truncate\n", arg->child_volsize); |
| if (drrf->drr_offset >= arg->child_volsize) { |
| if (verbosity > 1) |
| fprintf(stderr, "\tThis free record if entirely outside " |
| "zvol byte range [0x0:0x%"PRIx64"), perhaps due to " |
| "previously encountered truncate\n", |
| arg->child_volsize); |
| return (0); |
| } |
| /* |
| * adjust the lenght such that the change is within the zvol byte range |
| */ |
| drrf->drr_length = arg->child_volsize - drrf->drr_offset; |
| if (verbosity > 1) |
| fprintf(stderr, "Adjusted record to [0x%" PRIx64 ":0x%" PRIx64 ")\n", |
| drrf->drr_offset, drrf->drr_offset + drrf->drr_length); |
| } |
| |
| /* |
| * Make a call to change bitmaps. |
| * If free_bitmap is present, generate separate bitmaps for writes and frees |
| * and therefore, update the free_bitmap; otherwise, generate a consolidated |
| * bitmap by writing free bits into the diff_bitmap. |
| */ |
| if (arg->bitmap_pair->free_bitmap) { |
| if (verbosity > 1) |
| fprintf(stderr, "Generating separate bitmap diffs for free region\n"); |
| bitmap_addbit(arg->bitmap_pair->free_bitmap, drrf->drr_offset, drrf->drr_length); |
| } else { |
| if (verbosity > 1) |
| fprintf(stderr, "Generating consolidated bitmap diffs for free region\n"); |
| bitmap_addbit(arg->bitmap_pair->diff_bitmap, drrf->drr_offset, drrf->drr_length); |
| } |
| |
| return (0); |
| } |
| |
| |
| /* |
| * Given the separate bitmaps for writes and frees, post-process as follows: |
| * |
| * - if the clone1_holes_file is available, filter the old holes (clone1_holes) |
| * out from the new holes bitmap (free_bitmap) |
| * - consolidate the writes and holes bitmaps into the diff_bitmap |
| */ |
| int |
| post_process_bitmap_pair_zst(ram_bitmap_pair_t *bitmap_pair, |
| const char* clone1_holes_file) |
| { |
| struct stat cbstat; |
| int rc = 0; |
| ram_bitmap_t *clone1_holes = NULL, *consolidated_holes = NULL; |
| |
| if ((consolidated_holes = bitmap_dup(bitmap_pair->free_bitmap)) == NULL) |
| return (-1); |
| |
| if (debug) { |
| fprintf(stdout, "Writes bitmap as calculated:\n"); |
| bitmap_show(bitmap_pair->diff_bitmap, ~0ULL); |
| fprintf(stdout, "Holes bitmap as calculated:\n"); |
| bitmap_show(bitmap_pair->free_bitmap, ~0ULL); |
| } |
| |
| do { |
| if (stat(clone1_holes_file, &cbstat) == 0) { |
| if ((clone1_holes = bitmap_read(clone1_holes_file)) == NULL) |
| break; |
| if (debug) { |
| fprintf(stdout, "Clone1 holes bitmap as specified:\n"); |
| bitmap_show(clone1_holes, ~0ULL); |
| } |
| /* |
| * consolidate the holes by adding old consolidated holes and |
| * new holes, then subtracting the writes (in the common code |
| * path below) |
| */ |
| if ((rc = bitmap_add(consolidated_holes, clone1_holes, |
| bitmap_pair->free_bitmap))) |
| break; |
| if (debug) { |
| fprintf(stdout, "Consolidated holes before subtracting writes:\n"); |
| bitmap_show(consolidated_holes, ~0ULL); |
| } |
| /* |
| * calculate new holes by subtracting old consolidated holes |
| * from the calculated new holes |
| */ |
| if ((rc = bitmap_subtract(bitmap_pair->free_bitmap, |
| bitmap_pair->free_bitmap, clone1_holes))) |
| break; |
| if (debug) { |
| fprintf(stdout, "New holes (calculated - old holes):\n"); |
| bitmap_show(bitmap_pair->free_bitmap, ~0ULL); |
| } |
| |
| bitmap_destroy(clone1_holes); |
| clone1_holes = NULL; |
| } |
| |
| /* |
| * subtract the new writes from the consolidated holes |
| */ |
| if ((rc = bitmap_subtract(consolidated_holes, consolidated_holes, |
| bitmap_pair->diff_bitmap))) |
| break; |
| /* |
| * at this point, the consolidated_holes are new consolidated holes, and |
| * the bitmap_pair->free_bitmap is new holes with old ones filtered out |
| * |
| * now calculate the diff bitmap by OR-ing bitmap_pair->diff_bitmap and |
| * the filtered bitmap_pair->free_bitmap |
| */ |
| if ((rc = bitmap_add(bitmap_pair->diff_bitmap, bitmap_pair->diff_bitmap, |
| bitmap_pair->free_bitmap))) |
| break; |
| /* |
| * now replace free_bitmap in the pair with the new consolidated bitmap |
| */ |
| bitmap_destroy(bitmap_pair->free_bitmap); |
| bitmap_pair->free_bitmap = consolidated_holes; |
| |
| if (debug) { |
| fprintf(stdout, "Diff bitmap post-processed:\n"); |
| bitmap_show(bitmap_pair->diff_bitmap, ~0ULL); |
| fprintf(stdout, "Holes bitmap post-processed:\n"); |
| bitmap_show(bitmap_pair->free_bitmap, ~0ULL); |
| } |
| } while (0); |
| |
| /* cleanup */ |
| if (rc) { |
| if (clone1_holes) |
| bitmap_destroy(clone1_holes); |
| if (consolidated_holes) |
| bitmap_destroy(consolidated_holes); |
| } |
| |
| return (rc); |
| } |
| |
| /* |
| * The function calculates "GRAIN"-wise differential bitmap, and optionally, |
| * generates bitmap of holes reported in the zfs send stream, between two zfs |
| * snapshots. The parent and child names below are device names of the clones |
| * in the format 'poolname/volname'; the clones' 'origin' |
| * property contains the snapshot names to be diffed. |
| * Therefore, proceed as follows: |
| * obtain the snapshot names (snap1 - parent, snap2 - child) |
| * call zfs send -Bi snap1 snap2 | diff_fd |
| * read stream records form diff_fd and fill out the (initially empty) bitmap(s) |
| */ |
| ram_bitmap_pair_t * |
| generate_bitmap_zst(const char *parent, const char *child, int separate_bitmaps, |
| const char* clone1_holes_file) |
| { |
| libzfs_handle_t *g_zfs = NULL; |
| zfs_handle_t *parent_zhp = NULL, *child_zhp = NULL; |
| zst_handle_t *hdl = NULL; |
| zprop_source_t src; |
| char parent_origin[ZFS_MAXNAMELEN]; |
| char child_origin[ZFS_MAXNAMELEN]; |
| char buffer[64 + 2 * ZFS_MAXNAMELEN]; |
| int diff_fd = -1, i; |
| FILE *diff_fp = NULL; |
| uint64_t parent_volsize = ~0ULL, child_volsize = ~0ULL; |
| uint64_t blocksize = ~0ULL; |
| zst_callback_descr_t zc[3]; |
| ram_bitmap_pair_t *bitmap_pair = NULL; |
| zst_callback_arg_t arg; |
| int rc = 0; |
| |
| /* init libzfs handle */ |
| if ((g_zfs = libzfs_init()) == NULL) { |
| fprintf(stderr, "Failed to initialize libzfs handle\n"); |
| exit(-1); |
| } |
| |
| /* must be zvols */ |
| if ((parent_zhp = zfs_open(g_zfs, parent, ZFS_TYPE_VOLUME)) == NULL || |
| (child_zhp = zfs_open(g_zfs, child, ZFS_TYPE_VOLUME)) == NULL) { |
| fprintf(stderr, "Parent %s or child %s is not a volume\n", |
| parent, child); |
| exit(-1); |
| } |
| |
| /* get origin and size properties */ |
| (void) zfs_prop_get(parent_zhp, ZFS_PROP_ORIGIN, parent_origin, |
| ZFS_MAXNAMELEN, &src, NULL, 0, B_FALSE); |
| (void) zfs_prop_get(child_zhp, ZFS_PROP_ORIGIN, child_origin, |
| ZFS_MAXNAMELEN, &src, NULL, 0, B_FALSE); |
| (void) zfs_prop_get(parent_zhp, ZFS_PROP_VOLSIZE, buffer, |
| ZFS_MAXNAMELEN, &src, NULL, 0, B_TRUE); |
| sscanf(buffer, "%" PRIu64 "", &parent_volsize); |
| (void) zfs_prop_get(child_zhp, ZFS_PROP_VOLSIZE, buffer, |
| ZFS_MAXNAMELEN, &src, NULL, 0, B_TRUE); |
| sscanf(buffer, "%" PRIu64 "", &child_volsize); |
| (void) zfs_prop_get(child_zhp, ZFS_PROP_VOLBLOCKSIZE, buffer, |
| ZFS_MAXNAMELEN, &src, NULL, 0, B_TRUE); |
| sscanf(buffer, "%" PRIu64 "", &blocksize); |
| |
| /* done with the datasets */ |
| zfs_close(parent_zhp); |
| zfs_close(child_zhp); |
| libzfs_fini(g_zfs); |
| |
| /* must be a snapshot name */ |
| if (NULL == strchr(parent_origin, '@') || |
| NULL == strchr(child_origin, '@')) { |
| fprintf(stderr, "Origin of parent %s or child %s (%s/%s) is not a " |
| "snapshot\n", parent, child, parent_origin, child_origin); |
| } |
| |
| if (parent_volsize > child_volsize) { |
| fprintf(stderr, "Parent volume size 0x%" PRIx64 " cannot be larger " |
| "than child volume size 0x%" PRIx64 "\n", |
| parent_volsize, child_volsize); |
| exit(-1); |
| } |
| |
| /* create the bitmap */ |
| bitmap_pair = bitmap_pair_create(child_volsize, grainsize); |
| if (bitmap_pair == NULL) { |
| fprintf(stderr, "Failed to create bitmaps, not enough memory\n"); |
| exit(-1); |
| } |
| |
| if (separate_bitmaps == 0) { |
| /* free the bitmap to indicate consolidated bitmap processing */ |
| bitmap_destroy(bitmap_pair->free_bitmap); |
| bitmap_pair->free_bitmap = NULL; |
| } |
| |
| /* build callback context argument */ |
| arg.bitmap_pair = bitmap_pair; |
| arg.parent_volsize = parent_volsize; |
| arg.child_volsize = child_volsize; |
| |
| /* run zfs send and get the output in a pipe */ |
| (void) snprintf(buffer, sizeof(buffer), "zfs send -B -i %s %s", |
| parent_origin, child_origin); |
| diff_fp = popen(buffer, "r"); |
| if (NULL == diff_fp) { |
| fprintf(stderr, "failed to run %s\n", buffer); |
| exit(-1); |
| } |
| diff_fd = fileno(diff_fp); |
| if (diff_fd == -1) { |
| fprintf(stderr, "invaid file descriptor from popen(%s)\n", buffer); |
| exit(-1); |
| } |
| |
| /* init send stream traversal handle */ |
| if ((hdl = zst_init(diff_fd)) == NULL) { |
| fprintf(stderr, "zst_init() failed, not enough memory\n"); |
| exit(-1); |
| } |
| |
| /* register traversal callbacks */ |
| zc[0].rtype = DRR_WRITE; |
| zc[0].cb = write_callback; |
| zc[0].arg = &arg; |
| zc[1].rtype = DRR_WRITE_EMBEDDED; |
| zc[1].cb = write_embedded_callback; |
| zc[1].arg = &arg; |
| zc[2].rtype = DRR_FREE; |
| zc[2].cb = free_callback; |
| zc[2].arg = &arg; |
| |
| for (i = 0; i < 3; i++) { |
| if (zst_register_callback(hdl, &zc[i]) < 0) { |
| fprintf(stderr, "zst_register_callback() failed\n"); |
| exit(-1); |
| } |
| } |
| |
| /* invoke traversal */ |
| if ((rc = zst_traverse(hdl))) { |
| fprintf(stderr, "zst_traverse() error: %d\n", rc); |
| } |
| |
| /* fini handle */ |
| if (zst_fini(hdl) < 0) { |
| fprintf(stderr, "zsf_fini() error\n"); |
| exit(-1); |
| } |
| |
| /* done with the pipe */ |
| (void) pclose(diff_fp); |
| |
| if (rc) { |
| bitmap_pair_destroy(bitmap_pair); |
| return (NULL); |
| } |
| |
| if (separate_bitmaps) { |
| /* |
| * make sure that the grain is less or equal to the block size |
| * if using the separate bitmap optimization |
| */ |
| if (blocksize % grainsize) { |
| fprintf(stderr, |
| "cannot use separate bitmaps when blocksize %" PRIu64 |
| " is not a multiple of grainsize %d\n", |
| blocksize, grainsize); |
| exit(-1); |
| } |
| |
| rc = post_process_bitmap_pair_zst(bitmap_pair, clone1_holes_file); |
| if (rc) { |
| fprintf(stderr, "bitmap post-process error\n"); |
| exit(-1); |
| } |
| } |
| |
| return (bitmap_pair); |
| } |
| |
| int |
| create_bitmap_zst(const char *parent, const char *child, |
| const char *bitmap, int separate_bitmaps, |
| const char *clone1_holes_file) |
| { |
| ram_bitmap_pair_t *bitmap_pair = NULL; |
| |
| if ((bitmap_pair = generate_bitmap_zst(parent, child, |
| separate_bitmaps, |
| clone1_holes_file)) == NULL) |
| return (-1); |
| |
| if (separate_bitmaps) { |
| /* |
| * get path for the new holes file - 'bitmap-path'.holes\0 |
| * write out the new holes |
| */ |
| char *new_path = malloc(strlen(bitmap)+strlen(".holes")+1); |
| strcpy(new_path, bitmap); |
| strcat(new_path, ".holes"); |
| holemap_write(bitmap_pair->free_bitmap, new_path); |
| free(new_path); |
| } |
| |
| bitmap_write(bitmap_pair->diff_bitmap, bitmap); |
| bitmap_pair_destroy(bitmap_pair); |
| |
| return (0); |
| } |
| |
| /* Bitmap verification modes */ |
| |
| ram_bitmap_t * |
| generate_bitmap_raw(const char *parent, const char *child, uint64_t byte_offset, uint64_t byte_length) |
| { |
| enum { PARENT, CHILD, DONE}; |
| int fd[DONE]; |
| ram_bitmap_t *diff_bitmap = NULL; |
| libzfs_handle_t *g_zfs = NULL; |
| zfs_handle_t *parent_zhp = NULL, *child_zhp = NULL; |
| zprop_source_t src; |
| char buffer[64 + ZFS_MAXNAMELEN]; |
| uint64_t parent_volsize = ~0ULL, child_volsize = ~0ULL; |
| uint64_t i = 0; |
| /* round down start offset */ |
| uint64_t grain_offset_start = byte_offset / grainsize; |
| /* round up end offset */ |
| uint64_t grain_offset_end = (byte_offset + byte_length + grainsize - 1) / grainsize; |
| |
| /* init libzfs handle */ |
| if ((g_zfs = libzfs_init()) == NULL) { |
| fprintf(stderr, "Failed to initialize libzfs handle\n"); |
| exit(-1); |
| } |
| |
| /* must be zvols */ |
| if ((parent_zhp = zfs_open(g_zfs, parent, ZFS_TYPE_VOLUME)) == NULL || |
| (child_zhp = zfs_open(g_zfs, child, ZFS_TYPE_VOLUME)) == NULL) { |
| fprintf(stderr, "Parent %s or child %s is not a volume\n", |
| parent, child); |
| exit(-1); |
| } |
| |
| /* get size properties */ |
| (void) zfs_prop_get(parent_zhp, ZFS_PROP_VOLSIZE, buffer, |
| ZFS_MAXNAMELEN, &src, NULL, 0, B_TRUE); |
| sscanf(buffer, "%" PRIu64 "", &parent_volsize); |
| (void) zfs_prop_get(child_zhp, ZFS_PROP_VOLSIZE, buffer, |
| ZFS_MAXNAMELEN, &src, NULL, 0, B_TRUE); |
| sscanf(buffer, "%" PRIu64 "", &child_volsize); |
| |
| /* done with the datasets */ |
| zfs_close(parent_zhp); |
| zfs_close(child_zhp); |
| libzfs_fini(g_zfs); |
| |
| if (parent_volsize > child_volsize) { |
| fprintf(stderr, "Parent volume size 0x%" PRIx64 " cannot be larger " |
| "than child volume size 0x%" PRIx64 "\n", |
| parent_volsize, child_volsize); |
| exit(-1); |
| } |
| |
| /* volume sizes should be integer number of grains */ |
| if (parent_volsize % grainsize || child_volsize % grainsize) { |
| fprintf(stderr, "Parent volume size 0x%" PRIx64 |
| "or child volume size 0x%" PRIx64 |
| "is not a multiple of grain size 0x%x\n", |
| parent_volsize, child_volsize, grainsize); |
| exit(-1); |
| } |
| |
| /* create the bitmap */ |
| diff_bitmap = bitmap_create(child_volsize, grainsize); |
| if (diff_bitmap == NULL) { |
| fprintf(stderr, "Failed to create bitmap, not enough memory\n"); |
| return (NULL); |
| } |
| /* open parent and child */ |
| sprintf(buffer, "/dev/zvol/%s", parent); |
| if ((fd[PARENT] = open(buffer, O_RDONLY)) < 0) { |
| sprintf(buffer, "parent %s open() failed", parent); |
| perror(buffer); |
| exit(-1); |
| } |
| sprintf(buffer, "/dev/zvol/%s", child); |
| if ((fd[CHILD] = open(buffer, O_RDONLY)) < 0) { |
| sprintf(buffer, "child %s open() failed", child); |
| perror(buffer); |
| exit(-1); |
| } |
| |
| /* read grains from parent and child and memcmp() those */ |
| for (i = 0; i < parent_volsize/grainsize; i++) { |
| char dbuffer[DONE][grainsize]; |
| ssize_t rd, rc; |
| int a; |
| |
| /* only interested in one specific byte range */ |
| if ((byte_offset != ~0ULL) && |
| (i < grain_offset_start || i >= grain_offset_end)) |
| continue; |
| |
| for (a = PARENT; a < DONE; a++) { |
| for (rd = 0; rd < grainsize; rd += rc) { |
| rc = pread(fd[a], &(dbuffer[a][rd]), grainsize, i * grainsize); |
| if (rc <= 0) |
| break; |
| } |
| if (rd < grainsize) { |
| fprintf(stderr, "read() of %s failed", |
| (a == PARENT) ? "parent" : "child"); |
| exit(-1); |
| } |
| } |
| if (memcmp(&(dbuffer[PARENT][0]), &(dbuffer[CHILD][0]), grainsize)) { |
| /* grain is different, add bit to the bitmap */ |
| bitmap_addbit(diff_bitmap, i*grainsize, grainsize); |
| } |
| } |
| |
| /* |
| * if child is larger than the parent, all but zero-filled grains beyond the |
| * parent size are different; the latter is due to the sparse nature of zvols |
| * that are used |
| */ |
| if (i < child_volsize/grainsize) { |
| char zeros[grainsize]; |
| |
| memset(zeros, 0, grainsize); |
| |
| for (; i < child_volsize/grainsize; i++) { |
| char dbuffer[grainsize]; |
| ssize_t rd, rc; |
| |
| for (rd = 0; rd < grainsize; rd += rc) { |
| rc = read(fd[CHILD], &(dbuffer[rd]), grainsize); |
| if (rc <= 0) |
| break; |
| } |
| if (rd < grainsize) { |
| fprintf(stderr, "read() of child failed"); |
| exit(-1); |
| } |
| |
| if (memcmp(&(dbuffer[0]), &(zeros[0]), grainsize)) { |
| /* grain is different, add bit to the bitmap */ |
| bitmap_addbit(diff_bitmap, i*grainsize, grainsize); |
| } |
| } |
| } |
| |
| close(fd[PARENT]); |
| close(fd[CHILD]); |
| |
| return (diff_bitmap); |
| } |
| |
| /* |
| * Verify the bitmap generation code by comparing a bitmap generated with |
| * ZST based approaches with a raw bitmap generated by bitwise comparison |
| * of grain size blocks of the parent and the child |
| */ |
| int verify_bitmaps(const char *parent, const char *child, uint64_t byte_offset, |
| uint64_t byte_length, int separate_bitmaps, |
| const char* clone1_holes) |
| { |
| ram_bitmap_pair_t *bmap_pair_zst = NULL; |
| ram_bitmap_t *bmap_raw = NULL; |
| int rc = 0; |
| |
| printf("Verifying ZST based bitmap by bitwise comparison of the volumes\n"); |
| |
| bmap_pair_zst = generate_bitmap_zst(parent, child, separate_bitmaps, |
| clone1_holes); |
| bmap_raw = generate_bitmap_raw(parent, child, byte_offset, byte_length); |
| |
| /* Make sure the grainsize argument is correct */ |
| if (grainsize != bmap_pair_zst->diff_bitmap->mqhdr.grain_size_lbas*LBAS_SIZE) { |
| printf("Invalid grain size %u specified - the bitmap uses %u\n", grainsize, |
| bmap_pair_zst->diff_bitmap->mqhdr.grain_size_lbas*LBAS_SIZE); |
| return (EINVAL); |
| } |
| |
| if (byte_offset == ~0ULL) { |
| /* generate/verify whole bitmaps */ |
| uint64_t |
| raw_bits = bitmap_get_bitcount(bmap_raw), |
| write_bits = bitmap_get_bitcount(bmap_pair_zst->diff_bitmap), |
| free_bits = (bmap_pair_zst->free_bitmap) ? |
| bitmap_get_bitcount(bmap_pair_zst->free_bitmap) : 0; |
| printf("Summary: %s has %"PRIi64" bits set, " |
| "%s has %"PRIi64" bits set, " |
| "%s has %"PRIi64" bits set\n", |
| "raw bitmap", raw_bits, |
| "write bitmap", write_bits, |
| "free bitmap", free_bits); |
| /* |
| * compare - in this comparison, we are looking to make sure that the |
| * the second bitmap is not missing any bits set in the first bitmap |
| */ |
| printf("Verifying ZST based bitmap against raw bitmap\n"); |
| rc = bitmap_compare(bmap_raw, bmap_pair_zst->diff_bitmap); |
| if (rc) |
| printf("Error - missing bits in ZST bitmap\n"); |
| else |
| printf("ZST bitmap has at least as many bits as " |
| "raw bitmap\n"); |
| } else { |
| /* grain range check */ |
| uint64_t i; |
| |
| printf("%24s %5s %5s %5s\n", "Byte offset", "Diff", "Raw", "Hole"); |
| for (i = byte_offset; i < (byte_offset + byte_length); i+=grainsize) { |
| int zst_bit = bitmap_get_bit(bmap_pair_zst->diff_bitmap, i); |
| int raw_bit = bitmap_get_bit(bmap_raw, i); |
| int hole_bit = bitmap_get_bit(bmap_pair_zst->diff_bitmap, i); |
| |
| printf("0x%-21"PRIx64" %5d %5d %5d\n", i, zst_bit, raw_bit, hole_bit); |
| } |
| } |
| |
| if (rc) { |
| /* write out the bitmaps for further analysis */ |
| printf("Bitmaps are saved for further analysis in /tmp/bitmap.*\n"); |
| bitmap_write(bmap_pair_zst->diff_bitmap, "/tmp/bitmap.diff"); |
| if (bmap_pair_zst->free_bitmap) |
| holemap_write(bmap_pair_zst->free_bitmap, "/tmp/bitmap.hole"); |
| bitmap_write(bmap_raw, "/tmp/bitmap.raw"); |
| } |
| |
| /* cleanup */ |
| bitmap_pair_destroy(bmap_pair_zst); |
| bitmap_destroy(bmap_raw); |
| |
| return (rc); |
| } |
| |
| void show_bitmap(char *bitmap_file, uint64_t offset) |
| { |
| ram_bitmap_t *bitmap = bitmap_read(bitmap_file); |
| |
| if (bitmap == NULL) |
| exit(-1); |
| |
| bitmap_show(bitmap, offset); |
| bitmap_destroy(bitmap); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| int rc = 0, opt, index = 0, separate_bitmaps = 0; |
| char *clone1_holes = NULL; |
| |
| /* process getopt-style arguments */ |
| while ((opt = getopt(argc, argv, "hvzdg:c:")) != -1) { |
| switch (opt) { |
| case 'h': |
| usage(); |
| return (0); |
| break; |
| case 'v': |
| verbosity++; |
| index++; |
| break; |
| case 'd': |
| debug++; |
| index++; |
| break; |
| case 'g': |
| index++; |
| grainsize = (unsigned)atoi(optarg); |
| index++; |
| break; |
| case 'z': |
| separate_bitmaps = 1; |
| index++; |
| break; |
| case 'c': |
| index++; |
| clone1_holes = optarg; |
| index++; |
| break; |
| case '?': |
| usage(); |
| fprintf(stderr, "invalid argument %c\n", optopt); |
| return (-1); |
| } |
| } |
| |
| /* fast-forward the arguments */ |
| argc -= index; |
| argv += index; |
| |
| if (argc < 3) |
| return (usage()); |
| if (separate_bitmaps == 0 && clone1_holes) |
| return (usage()); |
| do { |
| int mask; |
| for (mask = grainsize - 1; (mask && (mask & 1)); mask >>= 1) |
| ; |
| if (mask) { |
| fprintf(stderr, "grainsize %u is not a power of two\n", grainsize); |
| } |
| } while (0); |
| |
| if ((strcmp (argv[1], "bitmap") == 0) || |
| (strcmp (argv[1], "zst-bitmap") == 0)) { |
| /* |
| * invoke ZFS send-based bitmap generation code |
| */ |
| if (argc != 5) |
| return (usage()); |
| rc = create_bitmap_zst(argv[2], argv[3], argv[4], |
| separate_bitmaps, clone1_holes); |
| if (rc) { |
| fprintf(stdout, "failed to generate bitmap, error %d\n", rc); |
| } |
| } |
| else if (strcmp (argv[1], "bitmap-verify") == 0) { |
| uint64_t offset = ~0ULL, length = 0ULL; |
| if (argc < 4) |
| return (usage()); |
| if (argc > 4) |
| sscanf(argv[4], "%"PRIi64"", &offset); |
| if (argc > 5) |
| sscanf(argv[5], "%"PRIi64"", &length); |
| rc = verify_bitmaps(argv[2], argv[3], offset, length, |
| separate_bitmaps, clone1_holes); |
| if (rc) { |
| fprintf(stdout, "failed to verify bitmap, error %d\n", rc); |
| } |
| } |
| else if (strcmp (argv[1], "bitmap-show") == 0) { |
| uint64_t offset = ~0ULL; |
| if (argc < 3) |
| return (usage()); |
| if (argc > 3) |
| sscanf(argv[3], "%"PRIi64"", &offset); |
| show_bitmap(argv[2], offset); |
| } |
| else return usage(); |
| |
| return (rc); |
| } |