// 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;
}