/****************************************************************************** * disk-filltest.c - Program to fill a hard disk with random data * * Usage: ./disk-filltest * * The program will fill the current directory with files called random-#####. * Each file is up to 1 GiB in size and contains randomly generated integers. * When the disk is full, writing is finished and all files are read from disk. * During reading the file contents is checked against the pseudo-random * sequence to detect changed data blocks. Any unexpected value will output * an error. Reading and writing speed are shown during operation. * ****************************************************************************** * Copyright (C) 2012-2020 Timo Bingmann * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . *****************************************************************************/ #define VERSION "0.8.2" #include #include #include #include #include #include #include #include #include #include #include #if defined(_MSC_VER) || defined(__MINGW32__) /* no */ #else #include #define HAVE_STATVFS 1 #endif /* random seed used */ unsigned int g_seed; /* only perform read operation */ int gopt_readonly = 0; /* immediately unlink files after open */ int gopt_unlink_immediate = 0; /* unlink files after complete run */ int gopt_unlink_after = 0; /* skip file verification (e.g. for wiping a disk) */ int gopt_skip_verify = 0; /* individual file size in MiB */ unsigned int gopt_file_size = 0; /* file number limit */ unsigned int gopt_file_limit = UINT_MAX; /* number of repetitions */ int gopt_repeat = 1; /* size of last file written */ unsigned int g_last_filesize = UINT_MAX; /* return the current timestamp */ double timestamp(void) { struct timeval tv; gettimeofday(&tv, 0); return ((double)(tv.tv_sec) + (double)(tv.tv_usec/1e6)); } /* simple linear congruential random generator, faster than rand() and totally * sufficient for this cause. */ uint64_t lcg_random(uint64_t *xn) { *xn = 0x27BB2EE687B0B0FDLLU * *xn + 0xB504F32DLU; return *xn; } /* item type used in blocks written to disk */ typedef uint64_t item_type; /* a list of open file handles */ int* g_filehandle = NULL; unsigned int g_filehandle_size = 0; unsigned int g_filehandle_limit = 0; /* append to the list of open file handles */ void filehandle_append(int fd) { if (g_filehandle_size >= g_filehandle_limit) { int* new_filehandle; g_filehandle_limit *= 2; if (g_filehandle_limit < 128) g_filehandle_limit = 128; new_filehandle = realloc( g_filehandle, sizeof(int) * g_filehandle_limit); if (!new_filehandle) { fprintf(stderr, "Out of memory when allocating new file handle buffer.\n"); exit(EXIT_FAILURE); } g_filehandle = new_filehandle; } g_filehandle[ g_filehandle_size++ ] = fd; } /* produce nicely formatted time in seconds */ void format_time(unsigned int sec, char output[64]) { /* maximum digits of 32-bit unsigned int are 9. */ if (sec >= 24 * 3600) { unsigned int days = sec / (24 * 3600); sec -= days * (24 * 3600); unsigned int hours = sec / 3600; sec -= hours * 3600; unsigned int minutes = sec / 60; sec -= minutes * 60; sprintf(output, "%ud%uh%um%us", days, hours, minutes, sec); } else if (sec >= 3600) { unsigned int hours = sec / 3600; sec -= hours * 3600; unsigned int minutes = sec / 60; sec -= minutes * 60; sprintf(output, "%uh%um%us", hours, minutes, sec); } else if (sec >= 60) { unsigned int minutes = sec / 60; sec -= minutes * 60; sprintf(output, "%um%us", minutes, sec); } else { sprintf(output, "%us", sec); } } /* for compatibility with windows, use O_BINARY if available */ #ifndef O_BINARY #define O_BINARY 0 #endif /* print command line usage */ void print_usage(char* argv[]) { fprintf(stderr, "Usage: %s [-s seed] [-f files] [-S size] [-r] [-u] [-U] [-C dir]\n" "\n" "disk-filltest " VERSION " is a simple program which fills a path with random\n" "data and then rereads the files to check that the random sequence was\n" "correctly stored.\n" "\n" "Options: \n" " -C Change into given directory before starting work.\n" " -f Only write this number of 1 GiB sized files.\n" " -N Skip verification, e.g. for just wiping a disk.\n" " -r Only verify existing data files with given random seed.\n" " -R Repeat fill/test/wipe steps given number of times.\n" " -s Use random seed to write or verify data files.\n" " -S Size of each random file in MiB (default: 1024).\n" " -u Remove files after successful test.\n" " -U Immediately remove files, write and verify via file handles.\n" " -V Print version and exit.\n" "\n", argv[0]); exit(EXIT_FAILURE); } /* parse command line parameters */ void parse_commandline(int argc, char* argv[]) { int opt; while ((opt = getopt(argc, argv, "hs:S:f:ruUC:NR:V")) != -1) { switch (opt) { case 's': g_seed = atoi(optarg); break; case 'S': gopt_file_size = atoi(optarg); break; case 'f': gopt_file_limit = atoi(optarg); break; case 'r': gopt_readonly = 1; break; case 'u': gopt_unlink_after = 1; break; case 'U': gopt_unlink_immediate = 1; break; case 'C': if (chdir(optarg) != 0) { printf("Error chdir to %s: %s\n", optarg, strerror(errno)); exit(EXIT_FAILURE); } break; case 'N': gopt_skip_verify = 1; break; case 'R': gopt_repeat = atoi(optarg); break; case 'V': printf("disk-filltest " VERSION "\n"); exit(EXIT_SUCCESS); case 'h': default: print_usage(argv); } } if (optind < argc) print_usage(argv); if (gopt_file_size == 0) gopt_file_size = 1024; } /* unlink old random files */ void unlink_randfiles(void) { unsigned int filenum = 0; while (filenum < UINT_MAX) { char filename[32]; sprintf(filename, "random-%08u", filenum); if (unlink(filename) != 0) break; if (filenum == 0) printf("Removing old files ."); else printf("."); fflush(stdout); ++filenum; } if (filenum > 0) printf(" total: %u.\n", filenum); } /* fill disk */ void write_randfiles(void) { unsigned int filenum = 0; int done = 0; unsigned int expected_file_limit = UINT_MAX; if (gopt_file_limit == UINT_MAX) { #if HAVE_STATVFS struct statvfs buf; if (statvfs(".", &buf) == 0) { uint64_t free_size = (uint64_t)(buf.f_blocks) * (uint64_t)(buf.f_bsize); expected_file_limit = (free_size + gopt_file_size - 1) / (1024 * 1024) / gopt_file_size; } #endif /* HAVE_STATVFS */ } else { expected_file_limit = gopt_file_limit; } printf("Writing files random-######## with seed %u\n", g_seed); while (!done && filenum < gopt_file_limit) { char filename[32], eta[64]; int fd; ssize_t wb; unsigned int i, blocknum, wp; uint64_t wtotal; double ts1, ts2, speed; uint64_t rnd; item_type block[(1024 * 1024) / sizeof(item_type)]; sprintf(filename, "random-%08u", filenum); fd = open(filename, O_RDWR | O_CREAT | O_TRUNC | O_BINARY, 0600); if (fd < 0) { printf("Error opening next file %s: %s\n", filename, strerror(errno)); break; } if (gopt_unlink_immediate) { if (unlink(filename) != 0) { printf("Error unlinking opened file %s: %s\n", filename, strerror(errno)); } } /* reset random generator for each 1 GiB file */ rnd = g_seed + (++filenum); wtotal = 0; ts1 = timestamp(); for (blocknum = 0; blocknum < gopt_file_size; ++blocknum) { for (i = 0; i < sizeof(block) / sizeof(item_type); ++i) block[i] = lcg_random(&rnd); wp = 0; while ( wp != sizeof(block) && !done ) { wb = write(fd, (char*)block + wp, sizeof(block) - wp); if (wb <= 0) { printf("Error writing next file %s: %s\n", filename, strerror(errno)); done = 1; break; } else { wp += wb; } } wtotal += wp; } if (gopt_unlink_immediate) { /* do not close file handle! */ filehandle_append(fd); } else { close(fd); } ts2 = timestamp(); speed = wtotal / 1024.0 / 1024.0 / (ts2 - ts1); g_last_filesize = wtotal; if (expected_file_limit != UINT_MAX && filenum <= expected_file_limit) { format_time( (expected_file_limit - filenum) * gopt_file_size / speed, eta); printf("Wrote %.0f MiB random data to %s with %f MiB/s, eta %s.\n", (wtotal / 1024.0 / 1024.0), filename, speed, eta); } else { printf("Wrote %.0f MiB random data to %s with %f MiB/s.\n", (wtotal / 1024.0 / 1024.0), filename, speed); } fflush(stdout); } errno = 0; } /* read files and check random sequence*/ void read_randfiles(void) { unsigned int filenum = 0; int done = 0; unsigned int expected_file_limit = UINT_MAX; if (gopt_unlink_immediate) { expected_file_limit = g_filehandle_size; } else { char filename[32]; int fd; for (expected_file_limit = 0; ; ++expected_file_limit) { /* attempt to open file */ sprintf(filename, "random-%08u", expected_file_limit); fd = open(filename, O_RDONLY | O_BINARY); if (fd < 0) break; close(fd); } } printf("Verifying %u files random-######## with seed %u\n", expected_file_limit, g_seed); while (!done) { char filename[32], eta[64]; int fd; ssize_t rb; unsigned int i, blocknum; uint64_t rtotal; double ts1, ts2, speed; uint64_t rnd; item_type block[(1024 * 1024) / sizeof(item_type)]; sprintf(filename, "random-%08u", filenum); if (gopt_unlink_immediate) { if (filenum >= g_filehandle_size) { printf("Finished all opened file handles.\n"); break; } fd = g_filehandle[filenum]; if (lseek(fd, 0, SEEK_SET) != 0) { printf("Error seeking in next file %s: %s\n", filename, strerror(errno)); exit(EXIT_FAILURE); } } else { fd = open(filename, O_RDONLY | O_BINARY); if (fd < 0) { printf("Error opening next file %s: %s\n", filename, strerror(errno)); break; } } /* reset random generator for each 1 GiB file */ rnd = g_seed + (++filenum); rtotal = 0; ts1 = timestamp(); for (blocknum = 0; blocknum < gopt_file_size; ++blocknum) { unsigned int read_size = sizeof(block); if (filenum == expected_file_limit && g_last_filesize != UINT_MAX && blocknum * sizeof(block) > g_last_filesize) { read_size = g_last_filesize - (blocknum - 1) * sizeof(block); } rb = read(fd, block, read_size); if (rb == 0) { /* got EOF on file */ if (filenum != expected_file_limit || (g_last_filesize != UINT_MAX && rtotal != g_last_filesize)) { printf("Unexpectedly short file %s: " "read %u of expected %"PRIu64" bytes\n", filename, g_last_filesize, rtotal); done = 1; exit(EXIT_FAILURE); } done = 1; break; } else if (rb < 0) { printf("Error reading file %s: %s\n", filename, strerror(errno)); done = 1; exit(EXIT_FAILURE); } for (i = 0; i < rb / sizeof(item_type); ++i) { if (block[i] != lcg_random(&rnd)) { printf("Mismatch to random sequence " "in file %s block %d at offset %lu\n", filename, blocknum, (long unsigned)(i * sizeof(int))); gopt_unlink_after = 0; exit(EXIT_FAILURE); } } rtotal += rb; } close(fd); ts2 = timestamp(); speed = rtotal / 1024.0 / 1024.0 / (ts2 - ts1); format_time( (expected_file_limit - filenum) * gopt_file_size / speed, eta); printf("Read %.0f MiB random data from %s with %f MiB/s, eta %s.\n", (rtotal / 1024.0 / 1024.0), filename, speed, eta); fflush(stdout); } printf("Successfully verified %u files random-######## with seed %u\n", expected_file_limit, g_seed); } int main(int argc, char* argv[]) { int r; g_seed = time(NULL); parse_commandline(argc, argv); for (r = 0; r < gopt_repeat; ++r) { if (gopt_readonly) { read_randfiles(); if (gopt_unlink_after) unlink_randfiles(); } else { unlink_randfiles(); write_randfiles(); if (!gopt_skip_verify) read_randfiles(); if (gopt_unlink_after) unlink_randfiles(); } } return 0; } /******************************************************************************/