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