directiotest.c revision c610219fe9441f673e3aee12cf9479ae57feae25
1/*
2 * Copyright 2010 by Garmin Ltd. or its subsidiaries
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 * Performs a simple write/readback test to verify correct functionality
17 * of direct i/o on a block device node.
18 */
19
20/* For large-file support */
21#define _FILE_OFFSET_BITS 64
22#define _LARGEFILE_SOURCE
23#define _LARGEFILE64_SOURCE
24
25/* For O_DIRECT */
26#define _GNU_SOURCE
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <stdint.h>
31#include <string.h>
32#include <ctype.h>
33#include <errno.h>
34#include <limits.h>
35#include <fcntl.h>
36#include <unistd.h>
37#include <sys/stat.h>
38#include <sys/types.h>
39#include <sys/ioctl.h>
40#include <sys/mman.h>
41
42#include <linux/fs.h>
43
44#define NUM_TEST_BLKS 128
45
46/*
47 * Allocate page-aligned memory.  Could use posix_memalign(3), but some
48 * systems don't support it.  Also pre-faults memory since we'll be using
49 * it all right away anyway.
50 */
51static void *pagealign_alloc(size_t size)
52{
53	void *ret = mmap(NULL, size, PROT_READ | PROT_WRITE,
54			MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE | MAP_LOCKED,
55			-1, 0);
56	if (ret == MAP_FAILED) {
57		perror("mmap");
58		ret = NULL;
59	}
60	return ret;
61}
62
63static void pagealign_free(void *addr, size_t size)
64{
65	int ret = munmap(addr, size);
66	if (ret == -1)
67		perror("munmap");
68}
69
70static ssize_t do_read(int fd, void *buf, off64_t start, size_t count)
71{
72	ssize_t ret;
73	size_t bytes_read = 0;
74
75	lseek64(fd, start, SEEK_SET);
76
77	do {
78		ret = read(fd, (char *)buf + bytes_read, count - bytes_read);
79		if (ret == -1) {
80			perror("read");
81			return -1;
82		} else if (ret == 0) {
83			fprintf(stderr, "Unexpected end-of-file\n");
84			return -1;
85		}
86		bytes_read += ret;
87	} while (bytes_read < count);
88
89	return bytes_read;
90}
91
92static ssize_t do_write(int fd, const void *buf, off64_t start, size_t count)
93{
94	ssize_t ret;
95	size_t bytes_out = 0;
96
97	lseek64(fd, start, SEEK_SET);
98
99	do {
100		ret = write(fd, (char *)buf + bytes_out, count - bytes_out);
101		if (ret == -1) {
102			perror("write");
103			return -1;
104		} else if (ret == 0) {
105			fprintf(stderr, "write returned 0\n");
106			return -1;
107		}
108		bytes_out += ret;
109	} while (bytes_out < count);
110
111	return bytes_out;
112}
113
114/*
115 * Initializes test buffer with locally-unique test pattern.  High 16-bits of
116 * each 32-bit word contain first disk block number of the test area, low
117 * 16-bits contain word offset into test area.  The goal is that a given test
118 * area should never contain the same data as a nearby test area, and that the
119 * data for a given test area be easily reproducable given the start block and
120 * test area size.
121 */
122static void init_test_buf(void *buf, uint64_t start_blk, size_t len)
123{
124	uint32_t *data = buf;
125	size_t i;
126
127	len /= sizeof(uint32_t);
128	for (i = 0; i < len; i++)
129		data[i] = (start_blk & 0xFFFF) << 16 | (i & 0xFFFF);
130}
131
132static void dump_hex(const void *buf, int len)
133{
134	const uint8_t *data = buf;
135	int i;
136	char ascii_buf[17];
137
138	ascii_buf[16] = '\0';
139
140	for (i = 0; i < len; i++) {
141		int val = data[i];
142		int off = i % 16;
143
144		if (off == 0)
145			printf("%08x  ", i);
146		printf("%02x ", val);
147		ascii_buf[off] = isprint(val) ? val : '.';
148		if (off == 15)
149			printf(" %-16s\n", ascii_buf);
150	}
151
152	i %= 16;
153	if (i) {
154		ascii_buf[i] = '\0';
155		while (i++ < 16)
156			printf("   ");
157		printf(" %-16s\n", ascii_buf);
158	}
159}
160
161static void update_progress(int current, int total)
162{
163	double pct_done = (double)current * 100 / total;
164	printf("Testing area %d/%d (%6.2f%% complete)\r", current, total,
165			pct_done);
166	fflush(stdout);
167}
168
169int main(int argc, const char *argv[])
170{
171	int ret = 1;
172	const char *path;
173	int fd;
174	struct stat stat;
175	void *read_buf = NULL, *write_buf = NULL;
176	int blk_size;
177	uint64_t num_blks;
178	size_t test_size;
179	int test_areas, i;
180
181	if (argc != 2) {
182		printf("Usage: directiotest blkdev_path\n");
183		exit(1);
184	}
185
186	path = argv[1];
187	fd = open(path, O_RDWR | O_DIRECT | O_LARGEFILE);
188	if (fd == -1) {
189		perror("open");
190		exit(1);
191	}
192	if (fstat(fd, &stat) == -1) {
193		perror("stat");
194		goto cleanup;
195	} else if (!S_ISBLK(stat.st_mode)) {
196		fprintf(stderr, "%s is not a block device\n", path);
197		goto cleanup;
198	}
199
200	if (ioctl(fd, BLKSSZGET, &blk_size) == -1) {
201		perror("ioctl");
202		goto cleanup;
203	}
204	if (ioctl(fd, BLKGETSIZE64, &num_blks) == -1) {
205		perror("ioctl");
206		goto cleanup;
207	}
208	num_blks /= blk_size;
209
210	test_size = (size_t)blk_size * NUM_TEST_BLKS;
211	read_buf = pagealign_alloc(test_size);
212	write_buf = pagealign_alloc(test_size);
213	if (!read_buf || !write_buf) {
214		fprintf(stderr, "Error allocating test buffers\n");
215		goto cleanup;
216	}
217
218	/*
219	 * Start the actual test.  Go through the entire device, writing
220	 * locally-unique patern to each test block and then reading it
221	 * back.
222	 */
223	if (num_blks / NUM_TEST_BLKS > INT_MAX) {
224		printf("Warning: Device too large for test variables\n");
225		printf("Entire device will not be tested\n");
226		test_areas = INT_MAX;
227	} else {
228		test_areas = num_blks / NUM_TEST_BLKS;
229	}
230
231	printf("Starting test\n");
232
233	for (i = 0; i < test_areas; i++) {
234		uint64_t cur_blk = (uint64_t)i * NUM_TEST_BLKS;
235
236		update_progress(i + 1, test_areas);
237
238		init_test_buf(write_buf, cur_blk, test_size);
239
240		if (do_write(fd, write_buf, cur_blk * blk_size, test_size) !=
241				(ssize_t)test_size) {
242			fprintf(stderr, "write failed, aborting test\n");
243			goto cleanup;
244		}
245		if (do_read(fd, read_buf, cur_blk * blk_size, test_size) !=
246				(ssize_t)test_size) {
247			fprintf(stderr, "read failed, aborting test\n");
248			goto cleanup;
249		}
250
251		if (memcmp(write_buf, read_buf, test_size)) {
252			printf("Readback verification failed at block %llu\n\n",
253					cur_blk);
254			printf("Written data:\n");
255			dump_hex(write_buf, test_size);
256			printf("\nRead data:\n");
257			dump_hex(read_buf, test_size);
258			goto cleanup;
259		}
260	}
261
262	printf("\nTest complete\n");
263	ret = 0;
264
265cleanup:
266	if (read_buf)
267		pagealign_free(read_buf, test_size);
268	if (write_buf)
269		pagealign_free(write_buf, test_size);
270	close(fd);
271	return ret;
272}
273