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
27enum file_action {
28	kCreateFile,
29	kCreateDir,
30	kRenameFile,
31	kRemoveFile,
32	kRemoveDir,
33	kAppendFile,
34	kReplaceFile,
35	kTruncateFile,
36
37	kNumActions
38};
39
40struct block_identifier {
41	off_t		offset;
42	uint32		identifier;
43	uint16		data[0];
44};
45
46struct entry {
47	std::string	name;
48	uint32		identifier;
49	off_t		size;
50};
51
52typedef std::vector<entry> EntryVector;
53
54
55const char* kDefaultBaseDir = "./random_file_temp";
56const char* kIdentifierAttribute = "rfa:identifier";
57
58const uint32 kDefaultDirCount = 1;
59const uint32 kDefaultFileCount = 10;
60const uint32 kDefaultRunCount = 100;
61const off_t kDefaultMaxFileSize = 32768;
62
63const uint32 kMaxFileCount = 1000000;
64const uint32 kMaxDirCount = 100000;
65
66const uint32 kBlockSize = 256;
67
68
69extern const char *__progname;
70static const char *kProgramName = __progname;
71
72static bool sDisableFileCache = false;
73static bool sVerbose = false;
74static bool sCheckBeforeRemove = false;
75static off_t sMaxFileSize = kDefaultMaxFileSize;
76static uint32 sCount = 0;
77
78static off_t sWriteTotal = 0;
79static off_t sReadTotal = 0;
80static bigtime_t sWriteTime = 0;
81static bigtime_t sReadTime = 0;
82static uint32 sRun = 0;
83
84
85static off_t
86string_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
102static std::string
103size_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
121static std::string
122time_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
148static void
149usage(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
185static void
186error(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
202static void
203warning(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
217static void
218verbose(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
234static void
235action(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
252static file_action
253choose_action()
254{
255	return (file_action)(rand() % kNumActions);
256}
257
258
259static inline int
260choose_index(const EntryVector& entries)
261{
262	return rand() % entries.size();
263}
264
265
266static inline const std::string&
267choose_parent(const EntryVector& entries)
268{
269	return entries[choose_index(entries)].name;
270}
271
272
273static std::string
274create_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
285static int
286open_file(const std::string& name, int mode)
287{
288	return open(name.c_str(), mode | (sDisableFileCache ? O_DIRECT : 0), 0666);
289}
290
291
292static void
293generate_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
308static void
309write_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
352static void
353dump_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
389static void
390check_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
449static void
450check_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
462static void
463remove_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
485static void
486mount_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
494static void
495unmount_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
506static void
507create_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
526static void
527remove_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
554static void
555create_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
581static void
582remove_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
603static void
604rename_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
626static void
627append_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
647static void
648replace_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
673static void
674truncate_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
701int
702main(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