* Copyright 2009, Axel Dörfler, axeld@pinc-software.de.
* Distributed under the terms of the MIT License.
*/
#include <new>
#include <string>
#include <vector>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fs_attr.h>
#include <fs_volume.h>
#include <OS.h>
#include <TypeConstants.h>
enum file_action {
kCreateFile,
kCreateDir,
kRenameFile,
kRemoveFile,
kRemoveDir,
kAppendFile,
kReplaceFile,
kTruncateFile,
kNumActions
};
struct block_identifier {
off_t offset;
uint32 identifier;
uint16 data[0];
};
struct entry {
std::string name;
uint32 identifier;
off_t size;
};
typedef std::vector<entry> EntryVector;
const char* kDefaultBaseDir = "./random_file_temp";
const char* kIdentifierAttribute = "rfa:identifier";
const uint32 kDefaultDirCount = 1;
const uint32 kDefaultFileCount = 10;
const uint32 kDefaultRunCount = 100;
const off_t kDefaultMaxFileSize = 32768;
const uint32 kMaxFileCount = 1000000;
const uint32 kMaxDirCount = 100000;
const uint32 kBlockSize = 256;
extern const char *__progname;
static const char *kProgramName = __progname;
static bool sDisableFileCache = false;
static bool sVerbose = false;
static bool sCheckBeforeRemove = false;
static off_t sMaxFileSize = kDefaultMaxFileSize;
static uint32 sCount = 0;
static off_t sWriteTotal = 0;
static off_t sReadTotal = 0;
static bigtime_t sWriteTime = 0;
static bigtime_t sReadTime = 0;
static uint32 sRun = 0;
static off_t
string_to_size(const char* valueWithUnit)
{
char* unit;
off_t size = strtoull(valueWithUnit, &unit, 10);
if (strchr(unit, 'G') || strchr(unit, 'g'))
size *= 1024 * 1024 * 1024;
else if (strchr(unit, 'M') || strchr(unit, 'm'))
size *= 1024 * 1024;
else if (strchr(unit, 'K') || strchr(unit, 'k'))
size *= 1024;
return size;
}
static std::string
size_to_string(off_t size)
{
char buffer[256];
if (size > 10LL * 1024 * 1024 * 1024) {
snprintf(buffer, sizeof(buffer), "%g GB",
size / (1024.0 * 1024 * 1024));
} else if (size > 10 * 1024 * 1024)
snprintf(buffer, sizeof(buffer), "%g MB", size / (1024.0 * 1024));
else if (size > 10 * 1024)
snprintf(buffer, sizeof(buffer), "%g KB", size / (1024.0));
else
snprintf(buffer, sizeof(buffer), "%lld B", size);
return buffer;
}
static std::string
time_to_string(bigtime_t usecs)
{
static const bigtime_t kSecond = 1000000ULL;
static const bigtime_t kHour = 3600 * kSecond;
static const bigtime_t kMinute = 60 * kSecond;
uint32 hours = usecs / kHour;
uint32 minutes = usecs / kMinute;
uint32 seconds = usecs / kSecond;
char buffer[256];
if (usecs >= kHour) {
minutes %= 60;
seconds %= 60;
snprintf(buffer, sizeof(buffer), "%luh %02lum %02lus", hours, minutes,
seconds);
} else if (usecs > 100 * kSecond) {
seconds %= 60;
snprintf(buffer, sizeof(buffer), "%lum %02lus", minutes, seconds);
} else
snprintf(buffer, sizeof(buffer), "%gs", 1.0 * usecs / kSecond);
return buffer;
}
static void
usage(int status)
{
fprintf(stderr,
"Usage: %s [options]\n"
"Performs some random file actions for file system testing.\n"
"\n"
" -r, --runs=<count>\t\tThe number of actions to perform.\n"
"\t\t\t\tDefaults to %lu.\n"
" -s, --seed=<seed>\t\tThe base seed to use for the random numbers.\n"
" -f, --file-count=<count>\tThe maximal number of files to create.\n"
"\t\t\t\tDefaults to %lu.\n"
" -d, --dir-count=<count>\tThe maximal number of directories to create.\n"
"\t\t\t\tDefaults to %lu.\n"
" -m, --max-file-size=<size>\tThe maximal file size of the files.\n"
"\t\t\t\tDefaults to %lld.\n"
" -b, --base-dir=<path>\t\tThe base directory for the actions. "
"Defaults\n"
"\t\t\t\tto %s.\n"
" -c, --check-interval=<count>\tCheck after every <count> runs. "
"Defaults to 0,\n"
"\t\t\t\tmeaning only check once at the end.\n"
" -n, --no-cache\t\tDisables the file cache when doing I/O on\n"
"\t\t\t\ta file.\n"
" -a, --always-check\t\tAlways check contents before removing data.\n"
" -k, --keep-dirty\t\tDo not remove the working files on quit.\n"
" -i, --mount-image=<image>\tMounts an image for the actions, and "
"remounts\n"
"\t\t\t\tit before checking (each time).\n"
" -v, --verbose\t\t\tShow the actions as being performed\n",
kProgramName, kDefaultRunCount, kDefaultFileCount, kDefaultDirCount,
kDefaultMaxFileSize, kDefaultBaseDir);
exit(status);
}
static void
error(const char* format, ...)
{
va_list args;
va_start(args, format);
fprintf(stderr, "%s: ", kProgramName);
vfprintf(stderr, format, args);
fputc('\n', stderr);
va_end(args);
fflush(stderr);
exit(1);
}
static void
warning(const char* format, ...)
{
va_list args;
va_start(args, format);
fprintf(stderr, "%s: ", kProgramName);
vfprintf(stderr, format, args);
fputc('\n', stderr);
va_end(args);
fflush(stderr);
}
static void
verbose(const char* format, ...)
{
if (!sVerbose)
return;
va_list args;
va_start(args, format);
vprintf(format, args);
putchar('\n');
va_end(args);
fflush(stdout);
}
static void
action(const char* format, ...)
{
if (!sVerbose)
return;
va_list args;
va_start(args, format);
printf("%7lu ", sRun + 1);
vprintf(format, args);
putchar('\n');
va_end(args);
fflush(stdout);
}
static file_action
choose_action()
{
return (file_action)(rand() % kNumActions);
}
static inline int
choose_index(const EntryVector& entries)
{
return rand() % entries.size();
}
static inline const std::string&
choose_parent(const EntryVector& entries)
{
return entries[choose_index(entries)].name;
}
static std::string
create_name(const std::string& parent, const char* prefix)
{
char buffer[1024];
snprintf(buffer, sizeof(buffer), "%s/%s-%lu", parent.c_str(), prefix,
sCount++);
std::string name = buffer;
return name;
}
static int
open_file(const std::string& name, int mode)
{
return open(name.c_str(), mode | (sDisableFileCache ? O_DIRECT : 0), 0666);
}
static void
generate_block(char* buffer, const struct entry& entry, off_t offset)
{
block_identifier* block = (block_identifier*)buffer;
block->offset = offset;
block->identifier = entry.identifier;
uint32 count = (kBlockSize - offsetof(block_identifier, data)) / 2;
offset += offsetof(block_identifier, data);
for (uint32 i = 0; i < count; i++) {
block->data[i] = offset + i * 2;
}
}
static void
write_blocks(int fd, struct entry& entry, bool append = false)
{
off_t size = min_c(rand() % sMaxFileSize, sMaxFileSize);
off_t offset = 0;
if (append) {
struct stat stat;
if (fstat(fd, &stat) != 0)
error("stat file failed: %s\n", strerror(errno));
if (size + stat.st_size > sMaxFileSize)
size = sMaxFileSize - stat.st_size;
offset = stat.st_size;
}
verbose("\t\twrite %lu bytes", size);
entry.size += size;
uint32 blockOffset = offset % kBlockSize;
sWriteTotal += size;
bigtime_t start = system_time();
while (size > 0) {
char block[kBlockSize];
generate_block(block, entry, offset - blockOffset);
ssize_t toWrite = min_c(size, kBlockSize - blockOffset);
ssize_t bytesWritten = write(fd, block + blockOffset, toWrite);
if (bytesWritten != toWrite)
error("writing failed: %s", strerror(errno));
offset += toWrite;
size -= toWrite;
blockOffset = 0;
}
sWriteTime += system_time() - start;
}
static void
dump_block(const char* buffer, int size, const char* prefix)
{
const int DUMPED_BLOCK_SIZE = 16;
int i;
for (i = 0; i < size;) {
int start = i;
printf("%s%04x ", prefix, i);
for (; i < start + DUMPED_BLOCK_SIZE; i++) {
if (!(i % 4))
printf(" ");
if (i >= size)
printf(" ");
else
printf("%02x", *(unsigned char*)(buffer + i));
}
printf(" ");
for (i = start; i < start + DUMPED_BLOCK_SIZE; i++) {
if (i < size) {
char c = buffer[i];
if (c < 30)
printf(".");
else
printf("%c", c);
} else
break;
}
printf("\n");
}
}
static void
check_file(const struct entry& file)
{
int fd = open_file(file.name, O_RDONLY);
if (fd < 0) {
error("opening file \"%s\" failed: %s", file.name.c_str(),
strerror(errno));
}
struct stat stat;
if (fstat(fd, &stat) != 0)
error("stat file \"%s\" failed: %s", file.name.c_str(), strerror(errno));
if (file.size != stat.st_size) {
warning("size does not match for \"%s\"! Expected %lld reported %lld",
file.name.c_str(), file.size, stat.st_size);
close(fd);
return;
}
off_t size = file.size;
off_t offset = 0;
sReadTotal += size;
bigtime_t start = system_time();
while (size > 0) {
char block[kBlockSize];
ssize_t toRead = min_c(size, kBlockSize);
ssize_t bytesRead = read(fd, block, toRead);
if (bytesRead != toRead) {
error("reading \"%s\" failed: %s", file.name.c_str(),
strerror(errno));
}
char generatedBlock[kBlockSize];
generate_block(generatedBlock, file, offset);
if (memcmp(generatedBlock, block, bytesRead) != 0) {
dump_block(generatedBlock, bytesRead, "generated: ");
dump_block(block, bytesRead, "read: ");
error("block at %lld differ in \"%s\"!", offset, file.name.c_str());
}
offset += toRead;
size -= toRead;
}
sReadTime += system_time() - start;
close(fd);
}
static void
check_files(EntryVector& files)
{
verbose("check all files...");
for (EntryVector::iterator i = files.begin(); i != files.end(); i++) {
const struct entry& file = *i;
check_file(file);
}
}
static void
remove_dirs(const std::string& path)
{
DIR* dir = opendir(path.c_str());
if (dir == NULL) {
warning("Could not open directory \"%s\": %s", path.c_str(),
strerror(errno));
return;
}
while (struct dirent* entry = readdir(dir)) {
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
continue;
std::string subPath = path + "/" + entry->d_name;
remove_dirs(subPath);
}
closedir(dir);
rmdir(path.c_str());
}
static void
mount_image(const char* image, const char* mountPoint)
{
dev_t volume = fs_mount_volume(mountPoint, image, NULL, 0, NULL);
if (volume < 0)
error("mounting failed: %s", strerror(volume));
}
static void
unmount_image(const char* mountPoint)
{
status_t status = fs_unmount_volume(mountPoint, 0);
if (status != B_OK)
error("unmounting failed: %s", strerror(status));
}
static void
create_dir(EntryVector& dirs)
{
std::string parent = choose_parent(dirs);
std::string name = create_name(parent, "dir");
action("create dir %s (identifier %lu)", name.c_str(), sCount);
if (mkdir(name.c_str(), 0777) != 0)
error("creating dir \"%s\" failed: %s", name.c_str(), strerror(errno));
struct entry dir;
dir.name = name;
dir.identifier = sCount;
dir.size = 0;
dirs.push_back(dir);
}
static void
remove_dir(EntryVector& dirs)
{
if (dirs.empty())
return;
int index = choose_index(dirs);
if (index == 0)
return;
const std::string& name = dirs[index].name;
if (rmdir(name.c_str()) != 0) {
if (errno == ENOTEMPTY || errno == EEXIST) {
return;
}
error("removing dir \"%s\" failed: %s", name.c_str(), strerror(errno));
}
action("removed dir %s", name.c_str());
EntryVector::iterator iterator = dirs.begin();
dirs.erase(iterator + index);
}
static void
create_file(const EntryVector& dirs, EntryVector& files)
{
std::string parent = choose_parent(dirs);
std::string name = create_name(parent, "file");
action("create file %s (identifier %lu)", name.c_str(), sCount);
int fd = open_file(name, O_RDWR | O_CREAT | O_TRUNC);
if (fd < 0)
error("creating file \"%s\" failed: %s", name.c_str(), strerror(errno));
struct entry file;
file.name = name;
file.identifier = sCount;
file.size = 0;
write_blocks(fd, file);
files.push_back(file);
fs_write_attr(fd, kIdentifierAttribute, B_UINT32_TYPE, 0, &file.identifier,
sizeof(uint32));
close(fd);
}
static void
remove_file(EntryVector& files)
{
if (files.empty())
return;
int index = choose_index(files);
const std::string& name = files[index].name;
if (sCheckBeforeRemove)
check_file(files[index]);
if (remove(name.c_str()) != 0)
error("removing file \"%s\" failed: %s", name.c_str(), strerror(errno));
action("removed file %s", name.c_str());
EntryVector::iterator iterator = files.begin();
files.erase(iterator + index);
}
static void
rename_file(const EntryVector& dirs, EntryVector& files)
{
if (files.empty())
return;
std::string parent = choose_parent(dirs);
std::string newName = create_name(parent, "renamed-file");
int index = choose_index(files);
const std::string& oldName = files[index].name;
action("rename file \"%s\" to \"%s\"", oldName.c_str(), newName.c_str());
if (rename(oldName.c_str(), newName.c_str()) != 0) {
error("renaming file \"%s\" to \"%s\" failed: %s", oldName.c_str(),
newName.c_str(), strerror(errno));
}
files[index].name = newName;
}
static void
append_file(EntryVector& files)
{
if (files.empty())
return;
struct entry& file = files[choose_index(files)];
action("append to \"%s\"", file.name.c_str());
int fd = open_file(file.name, O_WRONLY | O_APPEND);
if (fd < 0) {
error("appending to file \"%s\" failed: %s", file.name.c_str(),
strerror(errno));
}
write_blocks(fd, file, true);
close(fd);
}
static void
replace_file(EntryVector& files)
{
if (files.empty())
return;
struct entry& file = files[choose_index(files)];
action("replace \"%s\" contents", file.name.c_str());
if (sCheckBeforeRemove)
check_file(file);
int fd = open_file(file.name, O_CREAT | O_WRONLY | O_TRUNC);
if (fd < 0) {
error("replacing file \"%s\" failed: %s", file.name.c_str(),
strerror(errno));
}
file.size = 0;
write_blocks(fd, file);
close(fd);
}
static void
truncate_file(EntryVector& files)
{
if (files.empty())
return;
struct entry& file = files[choose_index(files)];
action("truncate \"%s\"", file.name.c_str());
if (sCheckBeforeRemove)
check_file(file);
int fd = open_file(file.name, O_WRONLY | O_TRUNC);
if (fd < 0) {
error("truncating file \"%s\" failed: %s", file.name.c_str(),
strerror(errno));
}
file.size = 0;
close(fd);
}
int
main(int argc, char** argv)
{
const static struct option kOptions[] = {
{"runs", required_argument, 0, 'r'},
{"seed", required_argument, 0, 's'},
{"file-count", required_argument, 0, 'f'},
{"dir-count", required_argument, 0, 'd'},
{"check-interval", required_argument, 0, 'c'},
{"max-file-size", required_argument, 0, 'm'},
{"base-dir", required_argument, 0, 'b'},
{"no-cache", no_argument, 0, 'n'},
{"always-check", no_argument, 0, 'a'},
{"keep-dirty", no_argument, 0, 'k'},
{"mount-image", required_argument, 0, 'i'},
{"verbose", no_argument, 0, 'v'},
{"help", no_argument, 0, 'h'},
{NULL}
};
uint32 maxFileCount = kDefaultFileCount;
uint32 maxDirCount = kDefaultDirCount;
uint32 runs = kDefaultRunCount;
uint32 checkInterval = 0;
uint32 seed = 0;
bool keepDirty = false;
const char* mountImage = NULL;
struct entry base;
base.name = kDefaultBaseDir;
base.identifier = 0;
base.size = 0;
int c;
while ((c = getopt_long(argc, argv, "r:s:f:d:c:m:b:naki:vh", kOptions,
NULL)) != -1) {
switch (c) {
case 0:
break;
case 'r':
runs = strtoul(optarg, NULL, 0);
if (runs < 1)
runs = 1;
break;
case 's':
seed = strtoul(optarg, NULL, 0);
break;
case 'f':
maxFileCount = strtoul(optarg, NULL, 0);
if (maxFileCount < 5)
maxFileCount = 5;
else if (maxFileCount > kMaxFileCount)
maxFileCount = kMaxFileCount;
break;
case 'd':
maxDirCount = strtoul(optarg, NULL, 0);
if (maxDirCount < 1)
maxDirCount = 1;
else if (maxDirCount > kMaxDirCount)
maxDirCount = kMaxDirCount;
break;
case 'c':
checkInterval = strtoul(optarg, NULL, 0);
if (checkInterval < 0)
checkInterval = 0;
break;
case 'm':
sMaxFileSize = string_to_size(optarg);
break;
case 'b':
base.name = optarg;
break;
case 'n':
sDisableFileCache = true;
break;
case 'a':
sCheckBeforeRemove = true;
break;
case 'k':
keepDirty = true;
break;
case 'i':
mountImage = optarg;
break;
case 'v':
sVerbose = true;
break;
case 'h':
usage(0);
break;
default:
usage(1);
break;
}
}
if (mkdir(base.name.c_str(), 0777) != 0 && errno != EEXIST) {
fprintf(stderr, "%s: cannot create base directory: %s\n",
kProgramName, strerror(errno));
return 1;
}
if (mountImage != NULL)
mount_image(mountImage, base.name.c_str());
EntryVector dirs;
EntryVector files;
dirs.push_back(base);
srand(seed);
verbose("%lu runs, %lu files (up to %s in size), %lu dirs, seed %lu\n", runs,
maxFileCount, size_to_string(sMaxFileSize).c_str(), maxDirCount, seed);
for (sRun = 0; sRun < runs; sRun++) {
file_action action = choose_action();
switch (action) {
case kCreateFile:
if (files.size() > maxFileCount / 2) {
if (files.size() < maxFileCount)
create_file(dirs, files);
} else {
uint32 count
= min_c(maxFileCount, files.size() + maxFileCount / 10);
for (uint32 i = files.size(); i < count; i++) {
create_file(dirs, files);
}
}
break;
case kCreateDir:
if (dirs.size() > maxDirCount / 2) {
if (dirs.size() < maxDirCount)
create_dir(dirs);
} else {
uint32 count
= min_c(maxDirCount, dirs.size() + maxDirCount / 10);
for (uint32 i = dirs.size(); i < count; i++) {
create_dir(dirs);
}
}
break;
case kRenameFile:
rename_file(dirs, files);
break;
case kRemoveFile:
remove_file(files);
break;
case kRemoveDir:
remove_dir(dirs);
break;
case kAppendFile:
append_file(files);
break;
case kReplaceFile:
replace_file(files);
break;
case kTruncateFile:
truncate_file(files);
break;
default:
break;
}
if (checkInterval != 0 && sRun > 0 && (sRun % checkInterval) == 0
&& sRun + 1 < runs) {
if (mountImage != NULL) {
unmount_image(base.name.c_str());
mount_image(mountImage, base.name.c_str());
}
check_files(files);
}
}
if (mountImage != NULL) {
unmount_image(base.name.c_str());
mount_image(mountImage, base.name.c_str());
}
check_files(files);
if (!keepDirty) {
for (int i = files.size(); i-- > 0;) {
remove_file(files);
}
remove_dirs(base.name);
}
if (mountImage != NULL) {
unmount_image(base.name.c_str());
if (!keepDirty)
remove_dirs(base.name);
}
printf("%s written in %s, %s/s\n", size_to_string(sWriteTotal).c_str(),
time_to_string(sWriteTime).c_str(),
size_to_string(int64(0.5 + sWriteTotal
/ (sWriteTime / 1000000.0))).c_str());
printf("%s read in %s, %s/s\n", size_to_string(sReadTotal).c_str(),
time_to_string(sReadTime).c_str(),
size_to_string(int64(0.5 + sReadTotal
/ (sReadTime / 1000000.0))).c_str());
return 0;
}