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 <stdlib.h>
20#include <umem.h>
21#include <stddef.h>
22#include <string.h>
23#include <sys/types.h>
24#include <sys/errno.h>
25#include <sys/list.h>
26
27extern int errno;
28
29typedef enum {
30	SEG_HOLE,
31	SEG_DATA,
32	SEG_TYPES
33} seg_type_t;
34
35typedef struct segment {
36	list_node_t	seg_node;
37	seg_type_t	seg_type;
38	off_t		seg_offset;
39	off_t		seg_len;
40} seg_t;
41
42static int
43no_memory(void) {
44	(void) fprintf(stderr, "malloc failed\n");
45	return (UMEM_CALLBACK_EXIT(255));
46}
47
48static void
49usage(char *msg, int exit_value)
50{
51	(void) fprintf(stderr, "mkholes [-d|h offset:length] ... filename\n");
52	(void) fprintf(stderr, "%s\n", msg);
53	exit(exit_value);
54}
55
56static char *
57get_random_buffer(size_t len)
58{
59	int	rand_fd;
60	char	*buf;
61
62	buf = umem_alloc(len, UMEM_NOFAIL);
63
64	/*
65	 * Fill the buffer from /dev/urandom to counteract the
66	 * effects of compression.
67	 */
68	if ((rand_fd = open("/dev/urandom", O_RDONLY)) < 0) {
69		perror("open /dev/urandom failed");
70		exit(1);
71	}
72
73	if (read(rand_fd, buf, len) < 0) {
74		perror("read /dev/urandom failed");
75		exit(1);
76	}
77
78	(void) close(rand_fd);
79
80	return (buf);
81}
82
83static void
84push_segment(list_t *seg_list, seg_type_t seg_type, char *optarg)
85{
86	char		*off_str, *len_str;
87	static off_t	file_size = 0;
88	off_t		off, len;
89	seg_t		*seg;
90
91	off_str = strtok(optarg, ":");
92	len_str = strtok(NULL, ":");
93
94	if (off_str == NULL || len_str == NULL)
95		usage("Bad offset or length", 1);
96
97	off = strtoull(off_str, NULL, 0);
98	len = strtoull(len_str, NULL, 0);
99
100	if (file_size >= off + len)
101		usage("Ranges must ascend and may not overlap.", 1);
102	file_size = off + len;
103
104	seg = umem_alloc(sizeof (seg_t), UMEM_NOFAIL);
105	seg->seg_type = seg_type;
106	seg->seg_offset = off;
107	seg->seg_len = len;
108
109	list_insert_tail(seg_list, seg);
110}
111
112int
113main(int argc, char *argv[])
114{
115	int	c, fd;
116	char	*fname;
117	list_t	seg_list;
118	seg_t	*seg;
119
120	umem_nofail_callback(no_memory);
121	list_create(&seg_list, sizeof (seg_t), offsetof(seg_t, seg_node));
122
123	while ((c = getopt(argc, argv, "d:h:")) != -1) {
124		switch (c) {
125		case 'd':
126			push_segment(&seg_list, SEG_DATA, optarg);
127			break;
128		case 'h':
129			push_segment(&seg_list, SEG_HOLE, optarg);
130			break;
131		}
132	}
133	argc -= optind;
134	argv += optind;
135
136	if ((fname = argv[0]) == NULL)
137		usage("No filename specified", 1);
138	fname = argv[0];
139
140	if ((fd = open(fname, O_LARGEFILE | O_RDWR | O_CREAT | O_SYNC,
141	    00666)) < 0) {
142		perror("open failed");
143		exit(1);
144	}
145
146	while ((seg = list_remove_head(&seg_list)) != NULL) {
147		char	*buf, *vbuf;
148		off_t	off = seg->seg_offset;
149		off_t	len = seg->seg_len;
150
151		if (seg->seg_type == SEG_HOLE) {
152			struct flock	fl;
153			off_t bytes_read = 0;
154			ssize_t readlen = 1024 * 1024 * 16;
155
156			fl.l_whence = SEEK_SET;
157			fl.l_start = off;
158			fl.l_len = len;
159			if (fcntl(fd, F_FREESP, &fl) != 0) {
160				perror("freesp failed");
161				exit(1);
162			}
163
164			buf = (char *)umem_alloc(readlen, UMEM_NOFAIL);
165			vbuf = (char *)umem_zalloc(readlen, UMEM_NOFAIL);
166			while (bytes_read < len) {
167				ssize_t bytes = pread(fd, buf, readlen, off);
168				if (bytes < 0) {
169					perror("pread hole failed");
170					exit(1);
171				}
172
173				if (memcmp(buf, vbuf, bytes) != 0) {
174					(void) fprintf(stderr, "Read back hole "
175					    "didn't match.\n");
176					exit(1);
177				}
178				bytes_read += bytes;
179				off += bytes;
180			}
181
182			umem_free(buf, readlen);
183			umem_free(vbuf, readlen);
184			umem_free(seg, sizeof (seg_t));
185		} else if (seg->seg_type == SEG_DATA) {
186			buf = get_random_buffer(len);
187			vbuf = (char *)umem_alloc(len, UMEM_NOFAIL);
188			if ((pwrite(fd, buf, len, off)) < 0) {
189				perror("pwrite failed");
190				exit(1);
191			}
192
193			if ((pread(fd, vbuf, len, off)) != len) {
194				perror("pread failed");
195				exit(1);
196			}
197
198			if (memcmp(buf, vbuf, len) != 0) {
199				(void) fprintf(stderr, "Read back buf didn't "
200				    "match.\n");
201				exit(1);
202			}
203
204			umem_free(buf, len);
205			umem_free(vbuf, len);
206			umem_free(seg, sizeof (seg_t));
207		}
208	}
209
210	(void) close(fd);
211	return (0);
212}
213