xref: /haiku/src/tests/add-ons/kernel/file_systems/shared/random_file_actions.cpp (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
1 /*
2  * Copyright 2009, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <new>
8 #include <string>
9 #include <vector>
10 
11 #include <dirent.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <getopt.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20 
21 #include <fs_attr.h>
22 #include <fs_volume.h>
23 #include <OS.h>
24 #include <TypeConstants.h>
25 
26 
27 enum file_action {
28 	kCreateFile,
29 	kCreateDir,
30 	kRenameFile,
31 	kRemoveFile,
32 	kRemoveDir,
33 	kAppendFile,
34 	kReplaceFile,
35 	kTruncateFile,
36 
37 	kNumActions
38 };
39 
40 struct block_identifier {
41 	off_t		offset;
42 	uint32		identifier;
43 	uint16		data[0];
44 };
45 
46 struct entry {
47 	std::string	name;
48 	uint32		identifier;
49 	off_t		size;
50 };
51 
52 typedef std::vector<entry> EntryVector;
53 
54 
55 const char* kDefaultBaseDir = "./random_file_temp";
56 const char* kIdentifierAttribute = "rfa:identifier";
57 
58 const uint32 kDefaultDirCount = 1;
59 const uint32 kDefaultFileCount = 10;
60 const uint32 kDefaultRunCount = 100;
61 const off_t kDefaultMaxFileSize = 32768;
62 
63 const uint32 kMaxFileCount = 1000000;
64 const uint32 kMaxDirCount = 100000;
65 
66 const uint32 kBlockSize = 256;
67 
68 
69 extern const char *__progname;
70 static const char *kProgramName = __progname;
71 
72 static bool sDisableFileCache = false;
73 static bool sVerbose = false;
74 static bool sCheckBeforeRemove = false;
75 static off_t sMaxFileSize = kDefaultMaxFileSize;
76 static uint32 sCount = 0;
77 
78 static off_t sWriteTotal = 0;
79 static off_t sReadTotal = 0;
80 static bigtime_t sWriteTime = 0;
81 static bigtime_t sReadTime = 0;
82 static uint32 sRun = 0;
83 
84 
85 static off_t
86 string_to_size(const char* valueWithUnit)
87 {
88 	char* unit;
89 	off_t size = strtoull(valueWithUnit, &unit, 10);
90 
91 	if (strchr(unit, 'G') || strchr(unit, 'g'))
92 		size *= 1024 * 1024 * 1024;
93 	else if (strchr(unit, 'M') || strchr(unit, 'm'))
94 		size *= 1024 * 1024;
95 	else if (strchr(unit, 'K') || strchr(unit, 'k'))
96 		size *= 1024;
97 
98 	return size;
99 }
100 
101 
102 static std::string
103 size_to_string(off_t size)
104 {
105 	char buffer[256];
106 
107 	if (size > 10LL * 1024 * 1024 * 1024) {
108 		snprintf(buffer, sizeof(buffer), "%g GB",
109 			size / (1024.0 * 1024 * 1024));
110 	} else if (size > 10 * 1024 * 1024)
111 		snprintf(buffer, sizeof(buffer), "%g MB", size / (1024.0 * 1024));
112 	else if (size > 10 * 1024)
113 		snprintf(buffer, sizeof(buffer), "%g KB", size / (1024.0));
114 	else
115 		snprintf(buffer, sizeof(buffer), "%lld B", size);
116 
117 	return buffer;
118 }
119 
120 
121 static std::string
122 time_to_string(bigtime_t usecs)
123 {
124 	static const bigtime_t kSecond = 1000000ULL;
125 	static const bigtime_t kHour = 3600 * kSecond;
126 	static const bigtime_t kMinute = 60 * kSecond;
127 
128 	uint32 hours = usecs / kHour;
129 	uint32 minutes = usecs / kMinute;
130 	uint32 seconds = usecs / kSecond;
131 
132 	char buffer[256];
133 	if (usecs >= kHour) {
134 		minutes %= 60;
135 		seconds %= 60;
136 		snprintf(buffer, sizeof(buffer), "%luh %02lum %02lus", hours, minutes,
137 			seconds);
138 	} else if (usecs > 100 * kSecond) {
139 		seconds %= 60;
140 		snprintf(buffer, sizeof(buffer), "%lum %02lus", minutes, seconds);
141 	} else
142 		snprintf(buffer, sizeof(buffer), "%gs", 1.0 * usecs / kSecond);
143 
144 	return buffer;
145 }
146 
147 
148 static void
149 usage(int status)
150 {
151 	fprintf(stderr,
152 		"Usage: %s [options]\n"
153 		"Performs some random file actions for file system testing.\n"
154 		"\n"
155 		"  -r, --runs=<count>\t\tThe number of actions to perform.\n"
156 		"\t\t\t\tDefaults to %lu.\n"
157 		"  -s, --seed=<seed>\t\tThe base seed to use for the random numbers.\n"
158 		"  -f, --file-count=<count>\tThe maximal number of files to create.\n"
159 		"\t\t\t\tDefaults to %lu.\n"
160 		"  -d, --dir-count=<count>\tThe maximal number of directories to create.\n"
161 		"\t\t\t\tDefaults to %lu.\n"
162 		"  -m, --max-file-size=<size>\tThe maximal file size of the files.\n"
163 		"\t\t\t\tDefaults to %lld.\n"
164 		"  -b, --base-dir=<path>\t\tThe base directory for the actions. "
165 			"Defaults\n"
166 		"\t\t\t\tto %s.\n"
167 		"  -c, --check-interval=<count>\tCheck after every <count> runs. "
168 			"Defaults to 0,\n"
169 		"\t\t\t\tmeaning only check once at the end.\n"
170 		"  -n, --no-cache\t\tDisables the file cache when doing I/O on\n"
171 		"\t\t\t\ta file.\n"
172 		"  -a, --always-check\t\tAlways check contents before removing data.\n"
173 		"  -k, --keep-dirty\t\tDo not remove the working files on quit.\n"
174 		"  -i, --mount-image=<image>\tMounts an image for the actions, and "
175 			"remounts\n"
176 		"\t\t\t\tit before checking (each time).\n"
177 		"  -v, --verbose\t\t\tShow the actions as being performed\n",
178 		kProgramName, kDefaultRunCount, kDefaultFileCount, kDefaultDirCount,
179 		kDefaultMaxFileSize, kDefaultBaseDir);
180 
181 	exit(status);
182 }
183 
184 
185 static void
186 error(const char* format, ...)
187 {
188 	va_list args;
189 	va_start(args, format);
190 
191 	fprintf(stderr, "%s: ", kProgramName);
192 	vfprintf(stderr, format, args);
193 	fputc('\n', stderr);
194 
195 	va_end(args);
196 	fflush(stderr);
197 
198 	exit(1);
199 }
200 
201 
202 static void
203 warning(const char* format, ...)
204 {
205 	va_list args;
206 	va_start(args, format);
207 
208 	fprintf(stderr, "%s: ", kProgramName);
209 	vfprintf(stderr, format, args);
210 	fputc('\n', stderr);
211 
212 	va_end(args);
213 	fflush(stderr);
214 }
215 
216 
217 static void
218 verbose(const char* format, ...)
219 {
220 	if (!sVerbose)
221 		return;
222 
223 	va_list args;
224 	va_start(args, format);
225 
226 	vprintf(format, args);
227 	putchar('\n');
228 
229 	va_end(args);
230 	fflush(stdout);
231 }
232 
233 
234 static void
235 action(const char* format, ...)
236 {
237 	if (!sVerbose)
238 		return;
239 
240 	va_list args;
241 	va_start(args, format);
242 
243 	printf("%7lu  ", sRun + 1);
244 	vprintf(format, args);
245 	putchar('\n');
246 
247 	va_end(args);
248 	fflush(stdout);
249 }
250 
251 
252 static file_action
253 choose_action()
254 {
255 	return (file_action)(rand() % kNumActions);
256 }
257 
258 
259 static inline int
260 choose_index(const EntryVector& entries)
261 {
262 	return rand() % entries.size();
263 }
264 
265 
266 static inline const std::string&
267 choose_parent(const EntryVector& entries)
268 {
269 	return entries[choose_index(entries)].name;
270 }
271 
272 
273 static std::string
274 create_name(const std::string& parent, const char* prefix)
275 {
276 	char buffer[1024];
277 	snprintf(buffer, sizeof(buffer), "%s/%s-%lu", parent.c_str(), prefix,
278 		sCount++);
279 
280 	std::string name = buffer;
281 	return name;
282 }
283 
284 
285 static int
286 open_file(const std::string& name, int mode)
287 {
288 	return open(name.c_str(), mode | (sDisableFileCache ? O_DIRECT : 0), 0666);
289 }
290 
291 
292 static void
293 generate_block(char* buffer, const struct entry& entry, off_t offset)
294 {
295 	block_identifier* block = (block_identifier*)buffer;
296 	block->offset = offset;
297 	block->identifier = entry.identifier;
298 
299 	uint32 count = (kBlockSize - offsetof(block_identifier, data))  / 2;
300 	offset += offsetof(block_identifier, data);
301 
302 	for (uint32 i = 0; i < count; i++) {
303 		block->data[i] = offset + i * 2;
304 	}
305 }
306 
307 
308 static void
309 write_blocks(int fd, struct entry& entry, bool append = false)
310 {
311 	off_t size = min_c(rand() % sMaxFileSize, sMaxFileSize);
312 	off_t offset = 0;
313 
314 	if (append) {
315 		// in the append case, we need to check the file size
316 		struct stat stat;
317 		if (fstat(fd, &stat) != 0)
318 			error("stat file failed: %s\n", strerror(errno));
319 
320 		if (size + stat.st_size > sMaxFileSize)
321 			size = sMaxFileSize - stat.st_size;
322 
323 		offset = stat.st_size;
324 	}
325 
326 	verbose("\t\twrite %lu bytes", size);
327 
328 	entry.size += size;
329 	uint32 blockOffset = offset % kBlockSize;
330 	sWriteTotal += size;
331 
332 	bigtime_t start = system_time();
333 
334 	while (size > 0) {
335 		char block[kBlockSize];
336 		generate_block(block, entry, offset - blockOffset);
337 
338 		ssize_t toWrite = min_c(size, kBlockSize - blockOffset);
339 		ssize_t bytesWritten = write(fd, block + blockOffset, toWrite);
340 		if (bytesWritten != toWrite)
341 			error("writing failed: %s", strerror(errno));
342 
343 		offset += toWrite;
344 		size -= toWrite;
345 		blockOffset = 0;
346 	}
347 
348 	sWriteTime += system_time() - start;
349 }
350 
351 
352 static void
353 dump_block(const char* buffer, int size, const char* prefix)
354 {
355 	const int DUMPED_BLOCK_SIZE = 16;
356 	int i;
357 
358 	for (i = 0; i < size;) {
359 		int start = i;
360 
361 		printf("%s%04x ", prefix, i);
362 		for (; i < start + DUMPED_BLOCK_SIZE; i++) {
363 			if (!(i % 4))
364 				printf(" ");
365 
366 			if (i >= size)
367 				printf("  ");
368 			else
369 				printf("%02x", *(unsigned char*)(buffer + i));
370 		}
371 		printf("  ");
372 
373 		for (i = start; i < start + DUMPED_BLOCK_SIZE; i++) {
374 			if (i < size) {
375 				char c = buffer[i];
376 
377 				if (c < 30)
378 					printf(".");
379 				else
380 					printf("%c", c);
381 			} else
382 				break;
383 		}
384 		printf("\n");
385 	}
386 }
387 
388 
389 static void
390 check_file(const struct entry& file)
391 {
392 	int fd = open_file(file.name, O_RDONLY);
393 	if (fd < 0) {
394 		error("opening file \"%s\" failed: %s", file.name.c_str(),
395 			strerror(errno));
396 	}
397 
398 	// first check if size matches
399 
400 	struct stat stat;
401 	if (fstat(fd, &stat) != 0)
402 		error("stat file \"%s\" failed: %s", file.name.c_str(), strerror(errno));
403 
404 	if (file.size != stat.st_size) {
405 		warning("size does not match for \"%s\"! Expected %lld reported %lld",
406 			file.name.c_str(), file.size, stat.st_size);
407 		close(fd);
408 		return;
409 	}
410 
411 	// check contents
412 
413 	off_t size = file.size;
414 	off_t offset = 0;
415 	sReadTotal += size;
416 
417 	bigtime_t start = system_time();
418 
419 	while (size > 0) {
420 		// read block
421 		char block[kBlockSize];
422 		ssize_t toRead = min_c(size, kBlockSize);
423 		ssize_t bytesRead = read(fd, block, toRead);
424 		if (bytesRead != toRead) {
425 			error("reading \"%s\" failed: %s", file.name.c_str(),
426 				strerror(errno));
427 		}
428 
429 		// compare with generated block
430 		char generatedBlock[kBlockSize];
431 		generate_block(generatedBlock, file, offset);
432 
433 		if (memcmp(generatedBlock, block, bytesRead) != 0) {
434 			dump_block(generatedBlock, bytesRead, "generated: ");
435 			dump_block(block, bytesRead, "read:      ");
436 			error("block at %lld differ in \"%s\"!", offset, file.name.c_str());
437 		}
438 
439 		offset += toRead;
440 		size -= toRead;
441 	}
442 
443 	sReadTime += system_time() - start;
444 
445 	close(fd);
446 }
447 
448 
449 static void
450 check_files(EntryVector& files)
451 {
452 	verbose("check all files...");
453 
454 	for (EntryVector::iterator i = files.begin(); i != files.end(); i++) {
455 		const struct entry& file = *i;
456 
457 		check_file(file);
458 	}
459 }
460 
461 
462 static void
463 remove_dirs(const std::string& path)
464 {
465 	DIR* dir = opendir(path.c_str());
466 	if (dir == NULL) {
467 		warning("Could not open directory \"%s\": %s", path.c_str(),
468 			strerror(errno));
469 		return;
470 	}
471 
472 	while (struct dirent* entry = readdir(dir)) {
473 		if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
474 			continue;
475 
476 		std::string subPath = path + "/" + entry->d_name;
477 		remove_dirs(subPath);
478 	}
479 
480 	closedir(dir);
481 	rmdir(path.c_str());
482 }
483 
484 
485 static void
486 mount_image(const char* image, const char* mountPoint)
487 {
488 	dev_t volume = fs_mount_volume(mountPoint, image, NULL, 0, NULL);
489 	if (volume < 0)
490 		error("mounting failed: %s", strerror(volume));
491 }
492 
493 
494 static void
495 unmount_image(const char* mountPoint)
496 {
497 	status_t status = fs_unmount_volume(mountPoint, 0);
498 	if (status != B_OK)
499 		error("unmounting failed: %s", strerror(status));
500 }
501 
502 
503 //	#pragma mark - Actions
504 
505 
506 static void
507 create_dir(EntryVector& dirs)
508 {
509 	std::string parent = choose_parent(dirs);
510 	std::string name = create_name(parent, "dir");
511 
512 	action("create dir %s (identifier %lu)", name.c_str(), sCount);
513 
514 	if (mkdir(name.c_str(), 0777) != 0)
515 		error("creating dir \"%s\" failed: %s", name.c_str(), strerror(errno));
516 
517 	struct entry dir;
518 	dir.name = name;
519 	dir.identifier = sCount;
520 	dir.size = 0;
521 
522 	dirs.push_back(dir);
523 }
524 
525 
526 static void
527 remove_dir(EntryVector& dirs)
528 {
529 	if (dirs.empty())
530 		return;
531 
532 	int index = choose_index(dirs);
533 	if (index == 0)
534 		return;
535 
536 	const std::string& name = dirs[index].name;
537 
538 	if (rmdir(name.c_str()) != 0) {
539 		if (errno == ENOTEMPTY || errno == EEXIST) {
540 			// TODO: in rare cases, we could remove all files
541 			return;
542 		}
543 
544 		error("removing dir \"%s\" failed: %s", name.c_str(), strerror(errno));
545 	}
546 
547 	action("removed dir %s", name.c_str());
548 
549 	EntryVector::iterator iterator = dirs.begin();
550 	dirs.erase(iterator + index);
551 }
552 
553 
554 static void
555 create_file(const EntryVector& dirs, EntryVector& files)
556 {
557 	std::string parent = choose_parent(dirs);
558 	std::string name = create_name(parent, "file");
559 
560 	action("create file %s (identifier %lu)", name.c_str(), sCount);
561 
562 	int fd = open_file(name, O_RDWR | O_CREAT | O_TRUNC);
563 	if (fd < 0)
564 		error("creating file \"%s\" failed: %s", name.c_str(), strerror(errno));
565 
566 	struct entry file;
567 	file.name = name;
568 	file.identifier = sCount;
569 	file.size = 0;
570 	write_blocks(fd, file);
571 
572 	files.push_back(file);
573 
574 	fs_write_attr(fd, kIdentifierAttribute, B_UINT32_TYPE, 0, &file.identifier,
575 		sizeof(uint32));
576 
577 	close(fd);
578 }
579 
580 
581 static void
582 remove_file(EntryVector& files)
583 {
584 	if (files.empty())
585 		return;
586 
587 	int index = choose_index(files);
588 	const std::string& name = files[index].name;
589 
590 	if (sCheckBeforeRemove)
591 		check_file(files[index]);
592 
593 	if (remove(name.c_str()) != 0)
594 		error("removing file \"%s\" failed: %s", name.c_str(), strerror(errno));
595 
596 	action("removed file %s", name.c_str());
597 
598 	EntryVector::iterator iterator = files.begin();
599 	files.erase(iterator + index);
600 }
601 
602 
603 static void
604 rename_file(const EntryVector& dirs, EntryVector& files)
605 {
606 	if (files.empty())
607 		return;
608 
609 	std::string parent = choose_parent(dirs);
610 	std::string newName = create_name(parent, "renamed-file");
611 
612 	int index = choose_index(files);
613 	const std::string& oldName = files[index].name;
614 
615 	action("rename file \"%s\" to \"%s\"", oldName.c_str(), newName.c_str());
616 
617 	if (rename(oldName.c_str(), newName.c_str()) != 0) {
618 		error("renaming file \"%s\" to \"%s\" failed: %s", oldName.c_str(),
619 			newName.c_str(), strerror(errno));
620 	}
621 
622 	files[index].name = newName;
623 }
624 
625 
626 static void
627 append_file(EntryVector& files)
628 {
629 	if (files.empty())
630 		return;
631 
632 	struct entry& file = files[choose_index(files)];
633 
634 	action("append to \"%s\"", file.name.c_str());
635 
636 	int fd = open_file(file.name, O_WRONLY | O_APPEND);
637 	if (fd < 0) {
638 		error("appending to file \"%s\" failed: %s", file.name.c_str(),
639 			strerror(errno));
640 	}
641 
642 	write_blocks(fd, file, true);
643 	close(fd);
644 }
645 
646 
647 static void
648 replace_file(EntryVector& files)
649 {
650 	if (files.empty())
651 		return;
652 
653 	struct entry& file = files[choose_index(files)];
654 
655 	action("replace \"%s\" contents", file.name.c_str());
656 
657 	if (sCheckBeforeRemove)
658 		check_file(file);
659 
660 	int fd = open_file(file.name, O_CREAT | O_WRONLY | O_TRUNC);
661 	if (fd < 0) {
662 		error("replacing file \"%s\" failed: %s", file.name.c_str(),
663 			strerror(errno));
664 	}
665 
666 	file.size = 0;
667 	write_blocks(fd, file);
668 
669 	close(fd);
670 }
671 
672 
673 static void
674 truncate_file(EntryVector& files)
675 {
676 	if (files.empty())
677 		return;
678 
679 	struct entry& file = files[choose_index(files)];
680 
681 	action("truncate \"%s\"", file.name.c_str());
682 
683 	if (sCheckBeforeRemove)
684 		check_file(file);
685 
686 	int fd = open_file(file.name, O_WRONLY | O_TRUNC);
687 	if (fd < 0) {
688 		error("truncating file \"%s\" failed: %s", file.name.c_str(),
689 			strerror(errno));
690 	}
691 
692 	file.size = 0;
693 
694 	close(fd);
695 }
696 
697 
698 //	#pragma mark -
699 
700 
701 int
702 main(int argc, char** argv)
703 {
704 	// parse arguments
705 
706 	const static struct option kOptions[] = {
707 		{"runs", required_argument, 0, 'r'},
708 		{"seed", required_argument, 0, 's'},
709 		{"file-count", required_argument, 0, 'f'},
710 		{"dir-count", required_argument, 0, 'd'},
711 		{"check-interval", required_argument, 0, 'c'},
712 		{"max-file-size", required_argument, 0, 'm'},
713 		{"base-dir", required_argument, 0, 'b'},
714 		{"no-cache", no_argument, 0, 'n'},
715 		{"always-check", no_argument, 0, 'a'},
716 		{"keep-dirty", no_argument, 0, 'k'},
717 		{"mount-image", required_argument, 0, 'i'},
718 		{"verbose", no_argument, 0, 'v'},
719 		{"help", no_argument, 0, 'h'},
720 		{NULL}
721 	};
722 
723 	uint32 maxFileCount = kDefaultFileCount;
724 	uint32 maxDirCount = kDefaultDirCount;
725 	uint32 runs = kDefaultRunCount;
726 	uint32 checkInterval = 0;
727 	uint32 seed = 0;
728 	bool keepDirty = false;
729 	const char* mountImage = NULL;
730 
731 	struct entry base;
732 	base.name = kDefaultBaseDir;
733 	base.identifier = 0;
734 	base.size = 0;
735 
736 	int c;
737 	while ((c = getopt_long(argc, argv, "r:s:f:d:c:m:b:naki:vh", kOptions,
738 			NULL)) != -1) {
739 		switch (c) {
740 			case 0:
741 				break;
742 			case 'r':
743 				runs = strtoul(optarg, NULL, 0);
744 				if (runs < 1)
745 					runs = 1;
746 				break;
747 			case 's':
748 				// seed
749 				seed = strtoul(optarg, NULL, 0);
750 				break;
751 			case 'f':
752 				// file count
753 				maxFileCount = strtoul(optarg, NULL, 0);
754 				if (maxFileCount < 5)
755 					maxFileCount = 5;
756 				else if (maxFileCount > kMaxFileCount)
757 					maxFileCount = kMaxFileCount;
758 				break;
759 			case 'd':
760 				// directory count
761 				maxDirCount = strtoul(optarg, NULL, 0);
762 				if (maxDirCount < 1)
763 					maxDirCount = 1;
764 				else if (maxDirCount > kMaxDirCount)
765 					maxDirCount = kMaxDirCount;
766 				break;
767 			case 'c':
768 				// check interval
769 				checkInterval = strtoul(optarg, NULL, 0);
770 				if (checkInterval < 0)
771 					checkInterval = 0;
772 				break;
773 			case 'm':
774 				// max file size
775 				sMaxFileSize = string_to_size(optarg);
776 				break;
777 			case 'b':
778 				base.name = optarg;
779 				break;
780 			case 'n':
781 				sDisableFileCache = true;
782 				break;
783 			case 'a':
784 				sCheckBeforeRemove = true;
785 				break;
786 			case 'k':
787 				keepDirty = true;
788 				break;
789 			case 'i':
790 				mountImage = optarg;
791 				break;
792 			case 'v':
793 				sVerbose = true;
794 				break;
795 			case 'h':
796 				usage(0);
797 				break;
798 			default:
799 				usage(1);
800 				break;
801 		}
802 	}
803 
804 	if (mkdir(base.name.c_str(), 0777) != 0 && errno != EEXIST) {
805 		fprintf(stderr, "%s: cannot create base directory: %s\n",
806 			kProgramName, strerror(errno));
807 		return 1;
808 	}
809 	if (mountImage != NULL)
810 		mount_image(mountImage, base.name.c_str());
811 
812 	EntryVector dirs;
813 	EntryVector files;
814 
815 	dirs.push_back(base);
816 
817 	srand(seed);
818 
819 	verbose("%lu runs, %lu files (up to %s in size), %lu dirs, seed %lu\n", runs,
820 		maxFileCount, size_to_string(sMaxFileSize).c_str(), maxDirCount, seed);
821 
822 	for (sRun = 0; sRun < runs; sRun++) {
823 		file_action action = choose_action();
824 
825 		switch (action) {
826 			case kCreateFile:
827 				if (files.size() > maxFileCount / 2) {
828 					// create a single file
829 					if (files.size() < maxFileCount)
830 						create_file(dirs, files);
831 				} else {
832 					// create some more files to fill up the list (ie. 10%)
833 					uint32 count
834 						= min_c(maxFileCount, files.size() + maxFileCount / 10);
835 					for (uint32 i = files.size(); i < count; i++) {
836 						create_file(dirs, files);
837 					}
838 				}
839 				break;
840 			case kCreateDir:
841 				if (dirs.size() > maxDirCount / 2) {
842 					// create a single directory
843 					if (dirs.size() < maxDirCount)
844 						create_dir(dirs);
845 				} else {
846 					// create some more directories to fill up the list (ie. 10%)
847 					uint32 count
848 						= min_c(maxDirCount, dirs.size() + maxDirCount / 10);
849 					for (uint32 i = dirs.size(); i < count; i++) {
850 						create_dir(dirs);
851 					}
852 				}
853 				break;
854 			case kRenameFile:
855 				rename_file(dirs, files);
856 				break;
857 			case kRemoveFile:
858 				remove_file(files);
859 				break;
860 			case kRemoveDir:
861 				remove_dir(dirs);
862 				break;
863 			case kAppendFile:
864 				append_file(files);
865 				break;
866 			case kReplaceFile:
867 				replace_file(files);
868 				break;
869 			case kTruncateFile:
870 				truncate_file(files);
871 				break;
872 
873 			default:
874 				break;
875 		}
876 
877 		if (checkInterval != 0 && sRun > 0 && (sRun % checkInterval) == 0
878 			&& sRun + 1 < runs) {
879 			if (mountImage != NULL) {
880 				// Always remount image before checking its contents
881 				unmount_image(base.name.c_str());
882 				mount_image(mountImage, base.name.c_str());
883 			}
884 			check_files(files);
885 		}
886 	}
887 
888 	if (mountImage != NULL) {
889 		unmount_image(base.name.c_str());
890 		mount_image(mountImage, base.name.c_str());
891 	}
892 
893 	check_files(files);
894 
895 	if (!keepDirty) {
896 		for (int i = files.size(); i-- > 0;) {
897 			remove_file(files);
898 		}
899 		remove_dirs(base.name);
900 	}
901 
902 	if (mountImage != NULL) {
903 		unmount_image(base.name.c_str());
904 		if (!keepDirty)
905 			remove_dirs(base.name);
906 	}
907 
908 	printf("%s written in %s, %s/s\n", size_to_string(sWriteTotal).c_str(),
909 		time_to_string(sWriteTime).c_str(),
910 		size_to_string(int64(0.5 + sWriteTotal
911 			/ (sWriteTime / 1000000.0))).c_str());
912 	printf("%s read in %s, %s/s\n", size_to_string(sReadTotal).c_str(),
913 		time_to_string(sReadTime).c_str(),
914 		size_to_string(int64(0.5 + sReadTotal
915 			/ (sReadTime / 1000000.0))).c_str());
916 
917 	return 0;
918 }
919