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