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
usage(char * msg,int exit_value)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
starts_with_hole(int fd)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
print_list(list_t * seg_list,char * fname,int options)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
main(int argc,char * argv[])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