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