1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright (c) 2014 by Delphix. All rights reserved. 14 */ 15 16 #include <stdio.h> 17 #include <fcntl.h> 18 #include <unistd.h> 19 #include <libzfs.h> 20 #include <umem.h> 21 #include <stdlib.h> 22 #include <stddef.h> 23 #include <sys/types.h> 24 #include <sys/list.h> 25 #include <sys/stat.h> 26 #include <sys/errno.h> 27 28 #define PRINT_HOLE 0x1 29 #define PRINT_DATA 0x2 30 #define PRINT_VERBOSE 0x4 31 32 extern int errno; 33 34 static void 35 usage(char *msg, int exit_value) 36 { 37 (void) fprintf(stderr, "getholes [-dhv] filename\n"); 38 (void) fprintf(stderr, "%s\n", msg); 39 exit(exit_value); 40 } 41 42 typedef struct segment { 43 list_node_t seg_node; 44 int seg_type; 45 off_t seg_offset; 46 off_t seg_len; 47 } seg_t; 48 49 /* 50 * Return an appropriate whence value, depending on whether the file begins 51 * with a holes or data. 52 */ 53 static int 54 starts_with_hole(int fd) 55 { 56 off_t off; 57 58 if ((off = lseek(fd, 0, SEEK_HOLE)) == -1) { 59 /* ENXIO means no holes were found */ 60 if (errno == ENXIO) 61 return (SEEK_DATA); 62 perror("lseek failed"); 63 exit(1); 64 } 65 66 return (off == 0 ? SEEK_HOLE : SEEK_DATA); 67 } 68 69 static void 70 print_list(list_t *seg_list, char *fname, int options) 71 { 72 uint64_t lz_holes, bs = 0; 73 uint64_t hole_blks_seen = 0, data_blks_seen = 0; 74 seg_t *seg; 75 76 if (0 == bs) 77 if (zfs_get_hole_count(fname, &lz_holes, &bs) != 0) { 78 perror("zfs_get_hole_count"); 79 exit(1); 80 } 81 82 while ((seg = list_remove_head(seg_list)) != NULL) { 83 if (options & PRINT_VERBOSE) 84 (void) fprintf(stdout, "%c %llu:%llu\n", 85 seg->seg_type == SEEK_HOLE ? 'h' : 'd', 86 seg->seg_offset, seg->seg_len); 87 88 if (seg->seg_type == SEEK_HOLE) { 89 hole_blks_seen += seg->seg_len / bs; 90 } else { 91 data_blks_seen += seg->seg_len / bs; 92 } 93 umem_free(seg, sizeof (seg_t)); 94 } 95 96 /* Verify libzfs sees the same number of hole blocks found manually. */ 97 if (lz_holes != hole_blks_seen) { 98 (void) fprintf(stderr, "Counted %llu holes, but libzfs found " 99 "%llu\n", hole_blks_seen, lz_holes); 100 exit(1); 101 } 102 103 if (options & PRINT_HOLE && options & PRINT_DATA) { 104 (void) fprintf(stdout, "datablks: %llu\n", data_blks_seen); 105 (void) fprintf(stdout, "holeblks: %llu\n", hole_blks_seen); 106 return; 107 } 108 109 if (options & PRINT_DATA) 110 (void) fprintf(stdout, "%llu\n", data_blks_seen); 111 if (options & PRINT_HOLE) 112 (void) fprintf(stdout, "%llu\n", hole_blks_seen); 113 } 114 115 int 116 main(int argc, char *argv[]) 117 { 118 off_t len, off = 0; 119 int c, fd, options = 0, whence = SEEK_DATA; 120 struct stat statbuf; 121 char *fname; 122 list_t seg_list; 123 seg_t *seg = NULL; 124 125 list_create(&seg_list, sizeof (seg_t), offsetof(seg_t, seg_node)); 126 127 while ((c = getopt(argc, argv, "dhv")) != -1) { 128 switch (c) { 129 case 'd': 130 options |= PRINT_DATA; 131 break; 132 case 'h': 133 options |= PRINT_HOLE; 134 break; 135 case 'v': 136 options |= PRINT_VERBOSE; 137 break; 138 } 139 } 140 argc -= optind; 141 argv += optind; 142 143 if (argc != 1) 144 usage("Incorrect number of arguments.", 1); 145 146 if ((fname = argv[0]) == NULL) 147 usage("No filename provided.", 1); 148 149 if ((fd = open(fname, O_LARGEFILE | O_RDONLY)) < 0) { 150 perror("open failed"); 151 exit(1); 152 } 153 154 if (fstat(fd, &statbuf) != 0) { 155 perror("fstat failed"); 156 exit(1); 157 } 158 len = statbuf.st_size; 159 160 whence = starts_with_hole(fd); 161 while ((off = lseek(fd, off, whence)) != -1) { 162 seg_t *s; 163 164 seg = umem_alloc(sizeof (seg_t), UMEM_DEFAULT); 165 seg->seg_type = whence; 166 seg->seg_offset = off; 167 168 list_insert_tail(&seg_list, seg); 169 if ((s = list_prev(&seg_list, seg)) != NULL) 170 s->seg_len = seg->seg_offset - s->seg_offset; 171 172 whence = whence == SEEK_HOLE ? SEEK_DATA : SEEK_HOLE; 173 } 174 if (errno != ENXIO) { 175 perror("lseek failed"); 176 exit(1); 177 } 178 (void) close(fd); 179 180 /* 181 * If this file ends with a hole block, then populate the length of 182 * the last segment, otherwise this is the end of the file, so 183 * discard the remaining zero length segment. 184 */ 185 if (seg && seg->seg_offset != len) { 186 seg->seg_len = len - seg->seg_offset; 187 } else { 188 (void) list_remove_tail(&seg_list); 189 } 190 191 print_list(&seg_list, fname, options); 192 list_destroy(&seg_list); 193 return (0); 194 } 195