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