123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- /*
- * Copyright 2010 by Garmin Ltd. or its subsidiaries
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Performs a simple write/readback test to verify correct functionality
- * of direct i/o on a block device node.
- */
- /* For large-file support */
- #define _FILE_OFFSET_BITS 64
- #define _LARGEFILE_SOURCE
- #define _LARGEFILE64_SOURCE
- /* For O_DIRECT */
- #define _GNU_SOURCE
- #include <ctype.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <inttypes.h>
- #include <limits.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/ioctl.h>
- #include <sys/mman.h>
- #include <sys/stat.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <linux/fs.h>
- #define NUM_TEST_BLKS 128
- /*
- * Allocate page-aligned memory. Could use posix_memalign(3), but some
- * systems don't support it. Also pre-faults memory since we'll be using
- * it all right away anyway.
- */
- static void *pagealign_alloc(size_t size)
- {
- void *ret = mmap(NULL, size, PROT_READ | PROT_WRITE,
- MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE | MAP_LOCKED,
- -1, 0);
- if (ret == MAP_FAILED) {
- perror("mmap");
- ret = NULL;
- }
- return ret;
- }
- static void pagealign_free(void *addr, size_t size)
- {
- int ret = munmap(addr, size);
- if (ret == -1)
- perror("munmap");
- }
- static ssize_t do_read(int fd, void *buf, off64_t start, size_t count)
- {
- ssize_t ret;
- size_t bytes_read = 0;
- lseek64(fd, start, SEEK_SET);
- do {
- ret = read(fd, (char *)buf + bytes_read, count - bytes_read);
- if (ret == -1) {
- perror("read");
- return -1;
- } else if (ret == 0) {
- fprintf(stderr, "Unexpected end-of-file\n");
- return -1;
- }
- bytes_read += ret;
- } while (bytes_read < count);
- return bytes_read;
- }
- static ssize_t do_write(int fd, const void *buf, off64_t start, size_t count)
- {
- ssize_t ret;
- size_t bytes_out = 0;
- lseek64(fd, start, SEEK_SET);
- do {
- ret = write(fd, (char *)buf + bytes_out, count - bytes_out);
- if (ret == -1) {
- perror("write");
- return -1;
- } else if (ret == 0) {
- fprintf(stderr, "write returned 0\n");
- return -1;
- }
- bytes_out += ret;
- } while (bytes_out < count);
- return bytes_out;
- }
- /*
- * Initializes test buffer with locally-unique test pattern. High 16-bits of
- * each 32-bit word contain first disk block number of the test area, low
- * 16-bits contain word offset into test area. The goal is that a given test
- * area should never contain the same data as a nearby test area, and that the
- * data for a given test area be easily reproducable given the start block and
- * test area size.
- */
- static void init_test_buf(void *buf, uint64_t start_blk, size_t len)
- {
- uint32_t *data = buf;
- size_t i;
- len /= sizeof(uint32_t);
- for (i = 0; i < len; i++)
- data[i] = (start_blk & 0xFFFF) << 16 | (i & 0xFFFF);
- }
- static void dump_hex(const void *buf, int len)
- {
- const uint8_t *data = buf;
- int i;
- char ascii_buf[17];
- ascii_buf[16] = '\0';
- for (i = 0; i < len; i++) {
- int val = data[i];
- int off = i % 16;
- if (off == 0)
- printf("%08x ", i);
- printf("%02x ", val);
- ascii_buf[off] = isprint(val) ? val : '.';
- if (off == 15)
- printf(" %-16s\n", ascii_buf);
- }
- i %= 16;
- if (i) {
- ascii_buf[i] = '\0';
- while (i++ < 16)
- printf(" ");
- printf(" %-16s\n", ascii_buf);
- }
- }
- static void update_progress(int current, int total)
- {
- double pct_done = (double)current * 100 / total;
- printf("Testing area %d/%d (%6.2f%% complete)\r", current, total,
- pct_done);
- fflush(stdout);
- }
- int main(int argc, const char *argv[])
- {
- int ret = 1;
- const char *path;
- int fd;
- struct stat stat;
- void *read_buf = NULL, *write_buf = NULL;
- int blk_size;
- uint64_t num_blks;
- size_t test_size;
- int test_areas, i;
- if (argc != 2) {
- printf("Usage: directiotest blkdev_path\n");
- exit(1);
- }
- path = argv[1];
- fd = open(path, O_RDWR | O_DIRECT | O_LARGEFILE);
- if (fd == -1) {
- perror("open");
- exit(1);
- }
- if (fstat(fd, &stat) == -1) {
- perror("stat");
- goto cleanup;
- } else if (!S_ISBLK(stat.st_mode)) {
- fprintf(stderr, "%s is not a block device\n", path);
- goto cleanup;
- }
- if (ioctl(fd, BLKSSZGET, &blk_size) == -1) {
- perror("ioctl");
- goto cleanup;
- }
- if (ioctl(fd, BLKGETSIZE64, &num_blks) == -1) {
- perror("ioctl");
- goto cleanup;
- }
- num_blks /= blk_size;
- test_size = (size_t)blk_size * NUM_TEST_BLKS;
- read_buf = pagealign_alloc(test_size);
- write_buf = pagealign_alloc(test_size);
- if (!read_buf || !write_buf) {
- fprintf(stderr, "Error allocating test buffers\n");
- goto cleanup;
- }
- /*
- * Start the actual test. Go through the entire device, writing
- * locally-unique patern to each test block and then reading it
- * back.
- */
- if (num_blks / NUM_TEST_BLKS > INT_MAX) {
- printf("Warning: Device too large for test variables\n");
- printf("Entire device will not be tested\n");
- test_areas = INT_MAX;
- } else {
- test_areas = num_blks / NUM_TEST_BLKS;
- }
- printf("Starting test\n");
- for (i = 0; i < test_areas; i++) {
- uint64_t cur_blk = (uint64_t)i * NUM_TEST_BLKS;
- update_progress(i + 1, test_areas);
- init_test_buf(write_buf, cur_blk, test_size);
- if (do_write(fd, write_buf, cur_blk * blk_size, test_size) !=
- (ssize_t)test_size) {
- fprintf(stderr, "write failed, aborting test\n");
- goto cleanup;
- }
- if (do_read(fd, read_buf, cur_blk * blk_size, test_size) !=
- (ssize_t)test_size) {
- fprintf(stderr, "read failed, aborting test\n");
- goto cleanup;
- }
- if (memcmp(write_buf, read_buf, test_size)) {
- printf("Readback verification failed at block %" PRIu64 "\n\n",
- cur_blk);
- printf("Written data:\n");
- dump_hex(write_buf, test_size);
- printf("\nRead data:\n");
- dump_hex(read_buf, test_size);
- goto cleanup;
- }
- }
- printf("\nTest complete\n");
- ret = 0;
- cleanup:
- if (read_buf)
- pagealign_free(read_buf, test_size);
- if (write_buf)
- pagealign_free(write_buf, test_size);
- close(fd);
- return ret;
- }
|