1// Copyright 2017 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <assert.h> 6#include <fcntl.h> 7#include <math.h> 8#include <stdio.h> 9#include <stdlib.h> 10#include <string.h> 11#include <unistd.h> 12 13#include <block-client/cpp/client.h> 14#include <fbl/atomic.h> 15#include <fbl/auto_lock.h> 16#include <fbl/macros.h> 17#include <fbl/mutex.h> 18#include <fbl/unique_fd.h> 19#include <fbl/unique_ptr.h> 20#include <lib/fzl/mapped-vmo.h> 21#include <lib/zx/fifo.h> 22#include <lib/zx/thread.h> 23 24#include <zircon/assert.h> 25#include <zircon/device/block.h> 26#include <zircon/device/skip-block.h> 27#include <lib/zircon-internal/xorshiftrand.h> 28#include <zircon/process.h> 29#include <zircon/syscalls.h> 30#include <zircon/threads.h> 31 32namespace { 33 34constexpr char kUsageMessage[] = R"""( 35usage: iochk [OPTIONS] <device> 36 37 -bs block_size - number of bytes to treat as a unit (default=device block size) 38 -t thread# - the number of threads to run (default=1) 39 -c block_count - number of blocks to read (default=the whole device) 40 -o offset - block-size offset to start reading from (default=0) 41 -s seed - the seed to use for pseudorandom testing 42 --live-dangerously - skip confirmation prompt 43 --skip - verify skip-block interface instead of block interface 44)"""; 45 46constexpr uint64_t kBlockHeader = 0xdeadbeef; 47 48// Flags. 49bool skip = false; 50uint32_t start_block = 0; 51size_t block_size = 0; 52uint32_t block_count = 0; 53 54// Constant after init. 55uint64_t base_seed; 56 57// Not thread safe. 58class ProgressBar { 59public: 60 ProgressBar() 61 : total_work_(0) {} 62 ProgressBar(uint32_t block_count, size_t num_threads) 63 : total_work_(static_cast<uint32_t>(static_cast<int>(block_count * log(block_count)) * 64 num_threads)) {} 65 66 ProgressBar(const ProgressBar& other) = default; 67 ProgressBar& operator=(const ProgressBar& other) = default; 68 69 void Update(uint32_t was_read) { 70 int old_progress = static_cast<int>(100 * blocks_read_ / total_work_); 71 blocks_read_ += was_read; 72 int progress = static_cast<int>(100 * blocks_read_ / total_work_); 73 74 if (old_progress != progress) { 75 int ticks = 40; 76 char str[ticks + 1]; 77 memset(str, ' ', ticks); 78 memset(str, '=', ticks * progress / 100); 79 str[ticks] = '\0'; 80 printf("\r[%s] %02d%%", str, progress); 81 fflush(stdout); 82 } 83 if (progress == 100) { 84 printf("\n"); 85 } 86 } 87 88private: 89 uint32_t total_work_; 90 uint32_t blocks_read_ = 0; 91}; 92 93// Context for thread workers. 94class WorkContext { 95public: 96 WorkContext(fbl::unique_fd fd, ProgressBar progress) 97 : fd(fbl::move(fd)), progress(progress) {} 98 ~WorkContext() {} 99 100 DISALLOW_COPY_ASSIGN_AND_MOVE(WorkContext); 101 102 // File descriptor to device being tested. 103 fbl::unique_fd fd; 104 // Implementation specific information. 105 struct { 106 block_client::Client client; 107 block_info_t info = {}; 108 } block; 109 struct { 110 skip_block_partition_info_t info = {}; 111 } skip; 112 // Protects |iochk_failure| and |progress| 113 fbl::Mutex lock; 114 bool iochk_failure = false; 115 ProgressBar progress; 116}; 117 118// Interface to abstract over block/skip-block device interface differences. 119class Checker { 120public: 121 // Fills the device with data based on location in the block. 122 virtual zx_status_t Fill(uint32_t start, uint32_t count) { return ZX_ERR_NOT_SUPPORTED; } 123 124 // Validates that data in specified was region on device is what was written 125 // by Fill. 126 virtual zx_status_t Check(uint32_t start, uint32_t count) { return ZX_ERR_NOT_SUPPORTED; } 127 128 virtual ~Checker() = default; 129 DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(Checker); 130 131protected: 132 Checker(void* buffer) 133 : buffer_(buffer) {} 134 135 void GenerateBlockData(int block_idx, size_t length) const { 136 // Block size should be a multiple of sizeof(uint64_t), but assert just to be safe 137 ZX_ASSERT(length % sizeof(uint64_t) == 0); 138 139 rand64_t seed_gen = RAND63SEED(base_seed + block_idx); 140 for (int i = 0; i < 10; i++) { 141 rand64(&seed_gen); 142 } 143 rand64_t data_gen = RAND63SEED(rand64(&seed_gen)); 144 145 auto* buf = static_cast<uint64_t*>(buffer_); 146 size_t idx = 0; 147 uint64_t data = kBlockHeader | (static_cast<uint64_t>(block_idx) << 32); 148 149 while (idx < length / sizeof(uint64_t)) { 150 buf[idx] = data; 151 data = rand64(&data_gen); 152 idx++; 153 } 154 } 155 156 int CheckBlockData(int block_idx, size_t length) const { 157 rand64_t seed_gen = RAND63SEED(base_seed + block_idx); 158 for (int i = 0; i < 10; i++) { 159 rand64(&seed_gen); 160 } 161 rand64_t data_gen = RAND63SEED(rand64(&seed_gen)); 162 163 auto* buf = static_cast<uint64_t*>(buffer_); 164 uint64_t expected = kBlockHeader | (static_cast<uint64_t>(block_idx) << 32); 165 size_t idx = 0; 166 167 while (idx < length / sizeof(uint64_t)) { 168 if (buf[idx] != expected) { 169 printf("inital read verification failed: " 170 "block_idx=%d offset=%zu expected=0x%016lx val=0x%016lx\n", 171 block_idx, idx, expected, buf[idx]); 172 return ZX_ERR_INTERNAL; 173 } 174 idx++; 175 expected = rand64(&data_gen); 176 } 177 return 0; 178 } 179 180 void* buffer_; 181}; 182 183class BlockChecker : public Checker { 184public: 185 static zx_status_t Initialize(const fbl::unique_fd& fd, block_info_t info, 186 block_client::Client& client, 187 fbl::unique_ptr<Checker>* checker) { 188 fbl::unique_ptr<fzl::MappedVmo> mapped_vmo; 189 zx_status_t status = fzl::MappedVmo::Create(block_size, "", &mapped_vmo); 190 if (status != ZX_OK) { 191 printf("Failled to create MappedVmo\n"); 192 return status; 193 } 194 195 zx_handle_t dup; 196 status = zx_handle_duplicate(mapped_vmo->GetVmo(), ZX_RIGHT_SAME_RIGHTS, &dup); 197 if (status != ZX_OK) { 198 printf("cannot duplicate handle\n"); 199 return status; 200 } 201 202 size_t s; 203 vmoid_t vmoid; 204 if ((s = ioctl_block_attach_vmo(fd.get(), &dup, &vmoid) != sizeof(vmoid_t))) { 205 printf("cannot attach vmo for init %lu\n", s); 206 return ZX_ERR_IO; 207 } 208 209 groupid_t group = next_txid_.fetch_add(1); 210 ZX_ASSERT(group < MAX_TXN_GROUP_COUNT); 211 212 checker->reset(new BlockChecker(fbl::move(mapped_vmo), info, client, vmoid, group)); 213 return ZX_OK; 214 } 215 216 static void ResetAtomic() { 217 next_txid_.store(0); 218 } 219 220 virtual zx_status_t Fill(uint32_t start, uint32_t count) override { 221 for (uint32_t block_idx = start; block_idx < count; block_idx++) { 222 uint64_t length = (info_.block_size * info_.block_count) - (block_idx * block_size); 223 if (length > block_size) { 224 length = block_size; 225 } 226 227 GenerateBlockData(block_idx, block_size); 228 block_fifo_request_t request = { 229 .opcode = BLOCKIO_WRITE, 230 .reqid = 0, 231 .group = group_, 232 .vmoid = vmoid_, 233 .length = static_cast<uint32_t>(length / info_.block_size), 234 .vmo_offset = 0, 235 .dev_offset = (block_idx * block_size) / info_.block_size, 236 }; 237 zx_status_t st; 238 if ((st = client_.Transaction(&request, 1)) != ZX_OK) { 239 printf("write block_fifo_txn error %d\n", st); 240 return st; 241 } 242 } 243 return ZX_OK; 244 } 245 246 virtual zx_status_t Check(uint32_t start, uint32_t count) override { 247 for (uint32_t block_idx = start; block_idx < count; block_idx++) { 248 uint64_t length = (info_.block_size * info_.block_count) - (block_idx * block_size); 249 if (length > block_size) { 250 length = block_size; 251 } 252 253 block_fifo_request_t request = { 254 .opcode = BLOCKIO_READ, 255 .reqid = 0, 256 .group = group_, 257 .vmoid = vmoid_, 258 .length = static_cast<uint32_t>(length / info_.block_size), 259 .vmo_offset = 0, 260 .dev_offset = (block_idx * block_size) / info_.block_size, 261 }; 262 zx_status_t st; 263 if ((st = client_.Transaction(&request, 1)) != ZX_OK) { 264 printf("read block_fifo_txn error %d\n", st); 265 return st; 266 } 267 if ((st = CheckBlockData(block_idx, length)) != ZX_OK) { 268 return st; 269 } 270 } 271 return ZX_OK; 272 } 273 274 DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(BlockChecker); 275 276private: 277 BlockChecker(fbl::unique_ptr<fzl::MappedVmo> mapped_vmo, block_info_t info, 278 block_client::Client& client, vmoid_t vmoid, groupid_t group) 279 : Checker(mapped_vmo->GetData()), mapped_vmo_(fbl::move(mapped_vmo)), info_(info), 280 client_(client), vmoid_(vmoid), group_(group) {} 281 ~BlockChecker() = default; 282 283 static fbl::atomic<uint16_t> next_txid_; 284 285 fbl::unique_ptr<fzl::MappedVmo> mapped_vmo_; 286 block_info_t info_; 287 block_client::Client& client_; 288 vmoid_t vmoid_; 289 groupid_t group_; 290}; 291 292fbl::atomic<uint16_t> BlockChecker::next_txid_; 293 294class SkipBlockChecker : public Checker { 295public: 296 static zx_status_t Initialize(fbl::unique_fd& fd, skip_block_partition_info_t info, 297 fbl::unique_ptr<Checker>* checker) { 298 fbl::unique_ptr<fzl::MappedVmo> mapped_vmo; 299 zx_status_t status = fzl::MappedVmo::Create(block_size, "", &mapped_vmo); 300 if (status != ZX_OK) { 301 printf("Failled to create MappedVmo\n"); 302 return status; 303 } 304 305 checker->reset(new SkipBlockChecker(fbl::move(mapped_vmo), fd, info)); 306 return ZX_OK; 307 } 308 309 virtual zx_status_t Fill(uint32_t start, uint32_t count) override { 310 for (uint32_t block_idx = start; block_idx < count; block_idx++) { 311 uint64_t length = (info_.block_size_bytes * info_.partition_block_count) - 312 (block_idx * block_size); 313 if (length > block_size) { 314 length = block_size; 315 } 316 317 zx_handle_t dup; 318 zx_status_t st = zx_handle_duplicate(mapped_vmo_->GetVmo(), ZX_RIGHT_SAME_RIGHTS, &dup); 319 if (st != ZX_OK) { 320 printf("cannot duplicate handle\n"); 321 return st; 322 } 323 324 GenerateBlockData(block_idx, block_size); 325 skip_block_rw_operation_t request = { 326 .vmo = dup, 327 .vmo_offset = 0, 328 .block = static_cast<uint32_t>((block_idx * block_size) / info_.block_size_bytes), 329 .block_count = static_cast<uint32_t>(length / info_.block_size_bytes), 330 }; 331 bool bad_block_grown; 332 ssize_t s = ioctl_skip_block_write(fd_.get(), &request, &bad_block_grown); 333 if (s < static_cast<ssize_t>(sizeof(bad_block_grown))) { 334 printf("ioctl_skip_block_write error %zd\n", s); 335 return s < 0 ? static_cast<zx_status_t>(s) : ZX_ERR_IO; 336 } 337 } 338 return ZX_OK; 339 } 340 341 virtual zx_status_t Check(uint32_t start, uint32_t count) override { 342 for (uint32_t block_idx = start; block_idx < count; block_idx++) { 343 uint64_t length = (info_.block_size_bytes * info_.partition_block_count) - 344 (block_idx * block_size); 345 if (length > block_size) { 346 length = block_size; 347 } 348 349 zx_handle_t dup; 350 zx_status_t st = zx_handle_duplicate(mapped_vmo_->GetVmo(), ZX_RIGHT_SAME_RIGHTS, &dup); 351 if (st != ZX_OK) { 352 printf("cannot duplicate handle\n"); 353 return st; 354 } 355 356 skip_block_rw_operation_t request = { 357 .vmo = dup, 358 .vmo_offset = 0, 359 .block = static_cast<uint32_t>((block_idx * block_size) / info_.block_size_bytes), 360 .block_count = static_cast<uint32_t>(length / info_.block_size_bytes), 361 }; 362 st = static_cast<zx_status_t>(ioctl_skip_block_read(fd_.get(), &request)); 363 if (st != ZX_OK) { 364 printf("read block_fifo_txn error %d\n", st); 365 return st; 366 } 367 if ((st = CheckBlockData(block_idx, length)) != ZX_OK) { 368 return st; 369 } 370 } 371 return ZX_OK; 372 } 373 374 DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(SkipBlockChecker); 375 376private: 377 SkipBlockChecker(fbl::unique_ptr<fzl::MappedVmo> mapped_vmo, fbl::unique_fd& fd, 378 skip_block_partition_info_t info) 379 : Checker(mapped_vmo->GetData()), mapped_vmo_(fbl::move(mapped_vmo)), fd_(fd), info_(info) {} 380 ~SkipBlockChecker() = default; 381 382 fbl::unique_ptr<fzl::MappedVmo> mapped_vmo_; 383 fbl::unique_fd& fd_; 384 skip_block_partition_info_t info_; 385}; 386 387zx_status_t InitializeChecker(WorkContext& ctx, fbl::unique_ptr<Checker>* checker) { 388 return skip ? SkipBlockChecker::Initialize(ctx.fd, ctx.skip.info, checker) 389 : BlockChecker::Initialize(ctx.fd, ctx.block.info, ctx.block.client, checker); 390} 391 392zx_status_t InitializeDevice(WorkContext& ctx) { 393 fbl::unique_ptr<Checker> checker; 394 zx_status_t status; 395 if ((status = InitializeChecker(ctx, &checker)) != ZX_OK) { 396 printf("Failed to alloc resources to init device\n"); 397 return status; 398 } 399 400 printf("writing test data to device...\n"); 401 fflush(stdout); 402 if ((status = checker->Fill(start_block, block_count)) != ZX_OK) { 403 printf("failed to write test data\n"); 404 return status; 405 } 406 printf("done\n"); 407 408 printf("verifying test data...\n"); 409 fflush(stdout); 410 if ((status = checker->Check(start_block, block_count)) != ZX_OK) { 411 printf("failed to verify test data\n"); 412 return status; 413 } 414 printf("done\n"); 415 416 return 0; 417} 418 419int DoWork(void* arg) { 420 auto* ctx = static_cast<WorkContext*>(arg); 421 422 fbl::unique_ptr<Checker> checker; 423 zx_status_t status; 424 if ((status = InitializeChecker(*ctx, &checker)) != ZX_OK) { 425 printf("Failed to alloc resources to init device\n"); 426 return status; 427 } 428 429 auto tid = static_cast<uintptr_t>(zx::thread::self()->get()); 430 rand32_t seed_gen = RAND32SEED(static_cast<uint32_t>(base_seed + tid)); 431 for (int i = 0; i < 20; i++) { 432 } 433 rand32_t work_gen = RAND32SEED(rand32(&seed_gen)); 434 // The expected number of random pages we need to hit all of them is 435 // approx n*log(n) (the coupon collector problem) 436 uint32_t blocks_left = static_cast<uint32_t>(block_count * log(block_count)); 437 438 while (blocks_left > 0 && !ctx->iochk_failure) { 439 uint32_t to_read = (rand32(&work_gen) % blocks_left) + 1; 440 uint32_t work_offset = rand32(&work_gen) % block_count; 441 if (work_offset + to_read > block_count) { 442 to_read = block_count - work_offset; 443 } 444 445 zx_status_t status; 446 if (rand32(&work_gen) % 2) { 447 status = checker->Check(start_block + work_offset, to_read); 448 } else { 449 status = checker->Fill(start_block + work_offset, to_read); 450 } 451 452 fbl::AutoLock al(&ctx->lock); 453 if (status != ZX_OK) { 454 ctx->iochk_failure = true; 455 } else if (!ctx->iochk_failure) { 456 ctx->progress.Update(to_read); 457 blocks_left -= to_read; 458 } 459 } 460 461 return 0; 462} 463 464uint64_t Number(const char* str) { 465 char* end; 466 uint64_t n = strtoull(str, &end, 10); 467 468 uint64_t m = 1; 469 switch (*end) { 470 case 'G': 471 case 'g': 472 m = 1024 * 1024 * 1024; 473 break; 474 case 'M': 475 case 'm': 476 m = 1024 * 1024; 477 break; 478 case 'K': 479 case 'k': 480 m = 1024; 481 break; 482 } 483 return m * n; 484} 485 486int Usage(void) { 487 printf("%s\n", kUsageMessage); 488 return -1; 489} 490 491} // namespace 492 493int iochk(int argc, char** argv) { 494 const char* device = argv[argc - 1]; 495 fbl::unique_fd fd(open(device, O_RDONLY)); 496 if (fd.get() < 0) { 497 printf("cannot open '%s'\n", device); 498 return Usage(); 499 } 500 501 bool seed_set = false; 502 size_t num_threads = 1; 503 bool confirmed = false; 504 char** end = argv + argc - 1; 505 argv++; 506 while (argv < end) { 507 if (strcmp(*argv, "-t") == 0) { 508 num_threads = atoi(argv[1]); 509 argv += 2; 510 } else if (strcmp(*argv, "-c") == 0) { 511 block_count = atoi(argv[1]); 512 argv += 2; 513 } else if (strcmp(*argv, "-o") == 0) { 514 start_block = atoi(argv[1]); 515 argv += 2; 516 } else if (strcmp(*argv, "-bs") == 0) { 517 block_size = Number(argv[1]); 518 argv += 2; 519 } else if (strcmp(*argv, "-s") == 0) { 520 base_seed = atoll(argv[1]); 521 seed_set = true; 522 argv += 2; 523 } else if (strcmp(*argv, "--live-dangerously") == 0) { 524 confirmed = true; 525 argv++; 526 } else if (strcmp(*argv, "--skip") == 0) { 527 skip = true; 528 argv++; 529 } else if (strcmp(*argv, "-h") == 0 || 530 strcmp(*argv, "--help") == 0) { 531 return Usage(); 532 } else { 533 printf("Invalid arg %s\n", *argv); 534 return Usage(); 535 } 536 } 537 538 if (!confirmed) { 539 constexpr char kWarning[] = "\033[0;31mWARNING\033[0m"; 540 printf("%s: iochk is a destructive operation.\n", kWarning); 541 printf("%s: All data on %s in the given range will be overwritten.\n", 542 kWarning, device); 543 printf("%s: Type 'y' to continue, 'n' or ESC to cancel:\n", kWarning); 544 for (;;) { 545 char c; 546 ssize_t r = read(STDIN_FILENO, &c, 1); 547 if (r < 0) { 548 printf("Error reading from stdin\n"); 549 return -1; 550 } 551 if (c == 'y' || c == 'Y') { 552 break; 553 } else if (c == 'n' || c == 'N' || c == 27) { 554 return 0; 555 } 556 } 557 } 558 559 if (!seed_set) { 560 base_seed = zx_clock_get_monotonic(); 561 } 562 printf("seed is %ld\n", base_seed); 563 564 WorkContext ctx(fbl::move(fd), ProgressBar()); 565 566 if (skip) { 567 // Skip Block Device Setup. 568 skip_block_partition_info_t info; 569 ssize_t s = ioctl_skip_block_get_partition_info(ctx.fd.get(), &info); 570 if (s != sizeof(info)) { 571 printf("unable to get skip-block partition info: %zd\n", s); 572 printf("fd: %d\n", ctx.fd.get()); 573 return -1; 574 } 575 printf("opened %s - block_size_bytes=%zu, partition_block_count=%lu\n", device, 576 info.block_size_bytes, info.partition_block_count); 577 578 ctx.skip.info = info; 579 580 if (block_size == 0) { 581 block_size = info.block_size_bytes; 582 } else if (block_size % info.block_size_bytes != 0) { 583 printf("block-size is not a multiple of device block size\n"); 584 return -1; 585 } 586 uint32_t dev_blocks_per_block = static_cast<uint32_t>(block_size / info.block_size_bytes); 587 588 if (dev_blocks_per_block * start_block >= info.partition_block_count) { 589 printf("offset past end of device\n"); 590 return -1; 591 } 592 593 if (block_count == 0) { 594 block_count = static_cast<uint32_t>((info.partition_block_count + 595 dev_blocks_per_block - 1) / 596 dev_blocks_per_block); 597 } else if (dev_blocks_per_block * (block_count + start_block) >= 598 dev_blocks_per_block + info.partition_block_count) { 599 // Don't allow blocks to start past the end of the device 600 printf("block_count+offset too large\n"); 601 return -1; 602 } 603 } else { 604 // Block Device Setup. 605 block_info_t info; 606 if (ioctl_block_get_info(ctx.fd.get(), &info) != sizeof(info)) { 607 printf("unable to get block info\n"); 608 return -1; 609 } 610 printf("opened %s - block_size=%u, block_count=%lu\n", 611 device, info.block_size, info.block_count); 612 613 ctx.block.info = info; 614 615 if (block_size == 0) { 616 block_size = static_cast<uint32_t>(info.block_size); 617 } else if (block_size % info.block_size != 0) { 618 printf("block-size is not a multiple of device block size\n"); 619 return -1; 620 } 621 uint32_t dev_blocks_per_block = static_cast<uint32_t>(block_size / info.block_size); 622 623 if (dev_blocks_per_block * start_block >= info.block_count) { 624 printf("offset past end of device\n"); 625 return -1; 626 } 627 628 if (block_count == 0) { 629 block_count = static_cast<uint32_t>((info.block_count + dev_blocks_per_block - 1) / 630 dev_blocks_per_block); 631 } else if (dev_blocks_per_block * (block_count + start_block) >= 632 dev_blocks_per_block + info.block_count) { 633 // Don't allow blocks to start past the end of the device 634 printf("block_count+offset too large\n"); 635 return -1; 636 } 637 638 if (info.max_transfer_size < block_size) { 639 printf("block-size is larger than max transfer size (%d)\n", info.max_transfer_size); 640 return -1; 641 } 642 643 zx::fifo fifo; 644 if (ioctl_block_get_fifos(ctx.fd.get(), fifo.reset_and_get_address()) != sizeof(fifo)) { 645 printf("cannot get fifo for device\n"); 646 return -1; 647 } 648 649 if (block_client::Client::Create(fbl::move(fifo), &ctx.block.client) != ZX_OK) { 650 printf("cannot create block client for device\n"); 651 return -1; 652 } 653 654 BlockChecker::ResetAtomic(); 655 } 656 657 ctx.progress = ProgressBar(block_count, num_threads); 658 659 if (InitializeDevice(ctx)) { 660 printf("device initialization failed\n"); 661 return -1; 662 } 663 664 // Reset before launching any worker threads. 665 if (!skip) { 666 BlockChecker::ResetAtomic(); 667 } 668 669 printf("starting worker threads...\n"); 670 thrd_t threads[num_threads]; 671 672 if (num_threads > MAX_TXN_GROUP_COUNT) { 673 printf("number of threads capped at %u\n", MAX_TXN_GROUP_COUNT); 674 num_threads = MAX_TXN_GROUP_COUNT; 675 } 676 677 for (auto& thread : threads) { 678 if (thrd_create(&thread, DoWork, &ctx) != thrd_success) { 679 printf("thread creation failed\n"); 680 return -1; 681 } 682 } 683 684 for (auto& thread : threads) { 685 thrd_join(thread, nullptr); 686 } 687 688 // Reset after launching worker threads to avoid hitting the capacity. 689 if (!skip) { 690 BlockChecker::ResetAtomic(); 691 } 692 693 if (!ctx.iochk_failure) { 694 printf("re-verifying device...\n"); 695 fflush(stdout); 696 fbl::unique_ptr<Checker> checker; 697 zx_status_t status; 698 if ((status = InitializeChecker(ctx, &checker)) != ZX_OK) { 699 printf("failed to initialize verification thread\n"); 700 return status; 701 } 702 if (checker->Check(start_block, block_count) != ZX_OK) { 703 printf("failed to re-verify test data\n"); 704 ctx.iochk_failure = true; 705 } else { 706 printf("done\n"); 707 } 708 } 709 710 if (!ctx.iochk_failure) { 711 printf("iochk completed successfully\n"); 712 return 0; 713 } else { 714 printf("iochk failed (seed was %ld)\n", base_seed); 715 return -1; 716 } 717} 718 719int main(int argc, char** argv) { 720 if (argc < 2) { 721 return Usage(); 722 } 723 724 int res = iochk(argc, argv); 725 return res; 726} 727