| // Copyright Martin J. Bligh & Google. <mbligh@google.com>. |
| // New Year's Eve, 2006 |
| // Released under the GPL v2. |
| // |
| // Compile with -D_FILE_OFFSET_BITS=64 -D _GNU_SOURCE |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <time.h> |
| #include <getopt.h> |
| #include <errno.h> |
| #include <malloc.h> |
| #include <string.h> |
| |
| struct pattern { |
| unsigned int sector; |
| unsigned int signature; |
| }; |
| |
| #define SECTOR_SIZE 512 |
| #define PATTERN_PER_SECTOR (SECTOR_SIZE / sizeof(struct pattern)) |
| |
| char *filename = "testfile"; |
| volatile int stop = 0; |
| int init_only = 0; |
| int verify_only = 0; |
| unsigned int megabytes = 1; |
| unsigned int skip_mb = 0; |
| unsigned int start_block = 0; |
| unsigned int blocksize = 4096; |
| unsigned int seconds = 15; |
| unsigned int linear_tasks = 1; |
| unsigned int random_tasks = 4; |
| unsigned int blocks; |
| unsigned int sectors_per_block; |
| unsigned int signature = 0; |
| unsigned int stop_on_error = 0; |
| |
| void die(char *error) |
| { |
| fprintf(stderr, "%s\n", error); |
| exit(1); |
| } |
| |
| /* |
| * Fill a block with it's own sector number |
| * buf must be at least blocksize |
| */ |
| void write_block(int fd, unsigned int block, struct pattern *buffer) |
| { |
| unsigned int i, sec_offset, sector; |
| off_t offset; |
| struct pattern *sector_buffer; |
| |
| for (sec_offset = 0; sec_offset < sectors_per_block; sec_offset++) { |
| sector = (block * sectors_per_block) + sec_offset; |
| sector_buffer = &buffer[sec_offset * PATTERN_PER_SECTOR]; |
| |
| for (i = 0; i < PATTERN_PER_SECTOR; i++) { |
| sector_buffer[i].sector = sector; |
| sector_buffer[i].signature = signature; |
| } |
| } |
| |
| offset = block; offset *= blocksize; // careful of overflow |
| lseek(fd, offset, SEEK_SET); |
| if (write(fd, buffer, blocksize) != blocksize) { |
| fprintf(stderr, "Write failed : file %s : block %d\n", filename, block); |
| exit(1); |
| } |
| } |
| |
| /* |
| * Verify a block contains the correct signature and sector numbers for |
| * each sector within that block. We check every copy within the sector |
| * and count how many were wrong. |
| * |
| * buf must be at least blocksize |
| */ |
| int verify_block(int fd, unsigned int block, struct pattern *buffer, char *err) |
| { |
| unsigned int sec_offset, sector; |
| off_t offset; |
| int i, errors = 0; |
| struct pattern *sector_buffer; |
| |
| offset = block; offset *= blocksize; // careful of overflow |
| lseek(fd, offset, SEEK_SET); |
| if (read(fd, buffer, blocksize) != blocksize) { |
| fprintf(stderr, "read failed: block %d (errno: %d) filename %s %s\n", block, errno, filename, err); |
| exit(1); |
| } |
| |
| for (sec_offset = 0; sec_offset < sectors_per_block; sec_offset++) { |
| unsigned int read_sector = 0, read_signature = 0; |
| unsigned int sector_errors = 0, signature_errors = 0; |
| |
| sector = (block * sectors_per_block) + sec_offset; |
| sector_buffer = &buffer[sec_offset * PATTERN_PER_SECTOR]; |
| |
| for (i = 0; i < PATTERN_PER_SECTOR; i++) { |
| if (sector_buffer[i].sector != sector) { |
| read_sector = sector_buffer[i].sector; |
| sector_errors++; |
| errors++; |
| } |
| if (sector_buffer[i].signature != signature) { |
| read_signature = sector_buffer[i].signature; |
| signature_errors++; |
| errors++; |
| } |
| } |
| if (sector_errors) |
| printf("Block %d (from %d to %d) sector %08x has wrong sector number %08x (%d/%lu) filename %s %s\n", |
| block, start_block, start_block+blocks, |
| sector, read_sector, |
| sector_errors, PATTERN_PER_SECTOR, |
| filename, err); |
| if (signature_errors) |
| printf("Block %d (from %d to %d) sector %08x signature is %08x should be %08x (%d/%lu) filename %s %s\n", |
| block, start_block, start_block+blocks, |
| sector, read_signature, signature, |
| signature_errors, PATTERN_PER_SECTOR, |
| filename, err); |
| |
| } |
| return errors; |
| } |
| |
| void write_file(unsigned int end_time, int random_access) |
| { |
| int fd, pid; |
| unsigned int block; |
| void *buffer; |
| |
| fflush(stdout); fflush(stderr); |
| pid = fork(); |
| |
| if (pid < 0) |
| die ("error forking child"); |
| if (pid != 0) // parent |
| return; |
| |
| fd = open(filename, O_RDWR, 0666); |
| buffer = malloc(blocksize); |
| |
| if (random_access) { |
| srandom(time(NULL) - getpid()); |
| while(time(NULL) < end_time) { |
| block = start_block + (unsigned int)(random() % blocks); |
| write_block(fd, block, buffer); |
| } |
| } else { |
| while(time(NULL) < end_time) |
| for (block = start_block; block < start_block + blocks; block++) |
| write_block(fd, block, buffer); |
| } |
| free(buffer); |
| exit(0); |
| } |
| |
| void verify_file(unsigned int end_time, int random_access, int direct) |
| { |
| int pid, error = 0; |
| char err_msg[40]; |
| char *err = err_msg; |
| fflush(stdout); fflush(stderr); |
| pid = fork(); |
| |
| if (pid < 0) |
| die ("error forking child"); |
| if (pid != 0) // parent |
| return; |
| |
| int fd; |
| unsigned int block; |
| unsigned int align = (blocksize > 4096) ? blocksize : 4096; |
| void *buffer = memalign(align, blocksize); |
| |
| if (direct) { |
| fd = open(filename, O_RDONLY | O_DIRECT); |
| strcpy(err, "direct"); |
| err += 6; |
| } else { |
| fd = open(filename, O_RDONLY); |
| strcpy(err, "cached"); |
| err += 6; |
| } |
| |
| if (random_access) { |
| strcpy(err, ",random"); |
| srandom(time(NULL) - getpid()); |
| while(time(NULL) < end_time) { |
| block = start_block + (unsigned int)(random() % blocks); |
| if (verify_block(fd, block, buffer, err_msg)) |
| error = 1; |
| } |
| } else { |
| strcpy(err, ",linear"); |
| while(time(NULL) < end_time) |
| for (block = start_block; block < start_block + blocks; block++) |
| if (verify_block(fd, block, buffer, err_msg)) |
| error = 1; |
| } |
| free(buffer); |
| exit(error); |
| } |
| |
| void usage(void) |
| { |
| printf("Usage: disktest\n"); |
| printf(" [-f filename] filename to use (testfile)\n"); |
| printf(" [-s seconds] seconds to run for (15)\n"); |
| printf(" [-m megabytes] megabytes to use (1)\n"); |
| printf(" [-M megabytes] megabytes to skip (0)\n"); |
| printf(" [-b blocksize] blocksize (4096)\n"); |
| printf(" [-l linear tasks] linear access tasks (4)\n"); |
| printf(" [-r random tasks] random access tasks (4)\n"); |
| printf(" [-v] verify pre-existing file\n"); |
| printf(" [-i] only do init phase\n"); |
| printf(" [-S] stop immediately on error\n"); |
| printf("\n"); |
| } |
| |
| unsigned int double_verify(int fd, void *buffer, char *err) |
| { |
| unsigned int block, errors = 0; |
| |
| for (block = start_block; block < start_block + blocks; block++) { |
| if (verify_block(fd, block, buffer, err)) { |
| if (stop_on_error) |
| return 1; |
| errors++; |
| } |
| } |
| return errors; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| unsigned int block; |
| time_t start_time, end_time; |
| int tasks, opt, retcode, pid; |
| void *init_buffer; |
| |
| /* Parse all input options */ |
| while ((opt = getopt(argc, argv, "vf:s:m:M:b:l:r:iS")) != -1) { |
| switch (opt) { |
| case 'v': |
| verify_only = 1; |
| break; |
| case 'f': |
| filename = optarg; |
| break; |
| case 's': |
| seconds = atoi(optarg); |
| break; |
| case 'm': |
| megabytes = atoi(optarg); |
| break; |
| case 'M': |
| skip_mb = atoi(optarg); |
| break; |
| case 'b': |
| blocksize = atoi(optarg); |
| break; |
| case 'l': |
| linear_tasks = atoi(optarg); |
| break; |
| case 'r': |
| random_tasks = atoi(optarg); |
| break; |
| case 'i': |
| init_only = 1; |
| break; |
| case 'S': |
| stop_on_error = 1; |
| break; |
| default: |
| usage(); |
| exit(1); |
| } |
| } |
| argc -= optind; |
| argv += optind; |
| |
| /* blocksize must be < 1MB, and a divisor. Tough */ |
| blocks = megabytes * (1024 * 1024 / blocksize); |
| start_block = skip_mb * (1024 * 1024 / blocksize); |
| sectors_per_block = blocksize / SECTOR_SIZE; |
| init_buffer = malloc(blocksize); |
| |
| if (verify_only) { |
| struct stat stat_buf; |
| |
| printf("Verifying %s\n", filename); |
| int fd = open(filename, O_RDONLY); |
| if (fd < 0) |
| die("open failed"); |
| |
| if (fstat(fd, &stat_buf) != 0) |
| die("fstat failed"); |
| megabytes = stat_buf.st_size / (1024 * 1024); |
| blocks = megabytes * (1024 * 1024 / blocksize); |
| if (read(fd, init_buffer, SECTOR_SIZE) != SECTOR_SIZE) { |
| fprintf(stderr, "read failed of initial sector (errno: %d) filename %s\n", errno, filename); |
| exit(1); |
| } |
| lseek(fd, 0, SEEK_SET); |
| signature = ((struct pattern *)init_buffer)->signature; |
| |
| printf("Checking %d megabytes using signature %08x\n", |
| megabytes, signature); |
| if (double_verify(fd, init_buffer, "init1")) |
| exit(1); |
| else |
| exit(0); |
| } |
| |
| signature = (getpid() << 16) + ((unsigned int) time(NULL) & 0xffff); |
| |
| /* Initialise file */ |
| int fd = open(filename, O_RDWR | O_TRUNC | O_CREAT, 0666); |
| if (fd < 0) |
| die("open failed"); |
| |
| start_time = time(NULL); |
| |
| printf("Ininitializing block %d to %d in file %s (signature %08x)\n", start_block, start_block+blocks, filename, signature); |
| /* Initialise all file data to correct blocks */ |
| for (block = start_block; block < start_block+blocks; block++) |
| write_block(fd, block, init_buffer); |
| if(fsync(fd) != 0) |
| die("fsync failed"); |
| if (double_verify(fd, init_buffer, "init1")) { |
| if (!stop_on_error) { |
| printf("First verify failed. Repeating for posterity\n"); |
| double_verify(fd, init_buffer, "init2"); |
| } |
| exit(1); |
| } |
| |
| printf("Wrote %d MB to %s (%d seconds)\n", megabytes, filename, (int) (time(NULL) - start_time)); |
| |
| free(init_buffer); |
| if (init_only) |
| exit(0); |
| |
| end_time = time(NULL) + seconds; |
| |
| /* Fork off all linear access pattern tasks */ |
| for (tasks = 0; tasks < linear_tasks; tasks++) |
| write_file(end_time, 0); |
| |
| /* Fork off all random access pattern tasks */ |
| for (tasks = 0; tasks < random_tasks; tasks++) |
| write_file(end_time, 1); |
| |
| /* Verify in all four possible ways */ |
| verify_file(end_time, 0, 0); |
| verify_file(end_time, 0, 1); |
| verify_file(end_time, 1, 0); |
| verify_file(end_time, 1, 1); |
| |
| for (tasks = 0; tasks < linear_tasks + random_tasks + 4; tasks++) { |
| pid = wait(&retcode); |
| if (retcode != 0) { |
| printf("pid %d exited with status %d\n", pid, retcode); |
| exit(1); |
| } |
| } |
| return 0; |
| } |