/* * Copyright 2009, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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=\t\tThe number of actions to perform.\n" "\t\t\t\tDefaults to %lu.\n" " -s, --seed=\t\tThe base seed to use for the random numbers.\n" " -f, --file-count=\tThe maximal number of files to create.\n" "\t\t\t\tDefaults to %lu.\n" " -d, --dir-count=\tThe maximal number of directories to create.\n" "\t\t\t\tDefaults to %lu.\n" " -m, --max-file-size=\tThe maximal file size of the files.\n" "\t\t\t\tDefaults to %lld.\n" " -b, --base-dir=\t\tThe base directory for the actions. " "Defaults\n" "\t\t\t\tto %s.\n" " -c, --check-interval=\tCheck after every 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=\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) { // in the append case, we need to check the file size 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)); } // first check if size matches 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; } // check contents off_t size = file.size; off_t offset = 0; sReadTotal += size; bigtime_t start = system_time(); while (size > 0) { // read block 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)); } // compare with generated block 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)); } // #pragma mark - Actions 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) { // TODO: in rare cases, we could remove all files 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); } // #pragma mark - int main(int argc, char** argv) { // parse arguments 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 seed = strtoul(optarg, NULL, 0); break; case 'f': // file count maxFileCount = strtoul(optarg, NULL, 0); if (maxFileCount < 5) maxFileCount = 5; else if (maxFileCount > kMaxFileCount) maxFileCount = kMaxFileCount; break; case 'd': // directory count maxDirCount = strtoul(optarg, NULL, 0); if (maxDirCount < 1) maxDirCount = 1; else if (maxDirCount > kMaxDirCount) maxDirCount = kMaxDirCount; break; case 'c': // check interval checkInterval = strtoul(optarg, NULL, 0); if (checkInterval < 0) checkInterval = 0; break; case 'm': // max file size 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) { // create a single file if (files.size() < maxFileCount) create_file(dirs, files); } else { // create some more files to fill up the list (ie. 10%) 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) { // create a single directory if (dirs.size() < maxDirCount) create_dir(dirs); } else { // create some more directories to fill up the list (ie. 10%) 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) { // Always remount image before checking its contents 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; }