text: // Copyright 2017 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <errno.h>
  #include <fcntl.h>
  #include <fuchsia/hardware/block/c/fidl.h>
  #include <lib/fdio/cpp/caller.h>
  #include <lib/zx/channel.h>
  #include <lib/zx/fifo.h>
  #include <lib/zx/vmo.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  #include <zircon/device/block.h>
  #include <zircon/syscalls.h>
  #include <zircon/time.h>
  #include <zircon/types.h>
  
  #include <block-client/client.h>
  #include <ramdevice-client/ramdisk.h>
  
  static uint64_t number(const char* str) {
    char* end;
    uint64_t n = strtoull(str, &end, 10);
  
    uint64_t m = 1;
    switch (*end) {
      case 'G':
      case 'g':
        m = 1024 * 1024 * 1024;
        break;
      case 'M':
      case 'm':
        m = 1024 * 1024;
        break;
      case 'K':
      case 'k':
        m = 1024;
        break;
    }
    return m * n;
  }
  
  static void bytes_per_second(uint64_t bytes, uint64_t nanos) {
    double s = ((double)nanos) / ((double)1000000000);
    double rate = ((double)bytes) / s;
  
    const char* unit = "B";
    if (rate > 1024 * 1024) {
      unit = "MB";
      rate /= 1024 * 1024;
    } else if (rate > 1024) {
      unit = "KB";
      rate /= 1024;
    }
    fprintf(stderr, "%g %s/s\n", rate, unit);
  }
  
  static zx_duration_t iotime_posix(int is_read, int fd, size_t total, size_t bufsz) {
    void* buffer = malloc(bufsz);
    if (buffer == NULL) {
      fprintf(stderr, "error: out of memory\n");
      return ZX_TIME_INFINITE;
    }
  
    zx_time_t t0 = zx_clock_get_monotonic();
    size_t n = total;
    const char* fn_name = is_read ? "read" : "write";
    while (n > 0) {
      size_t xfer = (n > bufsz) ? bufsz : n;
      ssize_t r = is_read ? read(fd, buffer, xfer) : write(fd, buffer, xfer);
      if (r < 0) {
        fprintf(stderr, "error: %s() error %d\n", fn_name, errno);
        return ZX_TIME_INFINITE;
      }
      if ((size_t)r != xfer) {
        fprintf(stderr, "error: %s() %zu of %zu bytes processed\n", fn_name, r, xfer);
        return ZX_TIME_INFINITE;
      }
      n -= xfer;
    }
    zx_time_t t1 = zx_clock_get_monotonic();
  
    return zx_time_sub_time(t1, t0);
  }
  
  static zx_duration_t iotime_block(int is_read, int fd, size_t total, size_t bufsz) {
    if ((total % 4096) || (bufsz % 4096)) {
      fprintf(stderr, "error: total and buffer size must be multiples of 4K\n");
      return ZX_TIME_INFINITE;
    }
  
    return iotime_posix(is_read, fd, total, bufsz);
  }
  
  static zx_duration_t iotime_fifo(char* dev, int is_read, int fd, size_t total, size_t bufsz) {
    zx_status_t status;
    zx::vmo vmo;
    if ((status = zx::vmo::create(bufsz, 0, &vmo)) != ZX_OK) {
      fprintf(stderr, "error: out of memory %d\n", status);
      return ZX_TIME_INFINITE;
    }
  
    fdio_cpp::UnownedFdioCaller disk_connection(fd);
    zx::unowned_channel channel(disk_connection.borrow_channel());
    fuchsia_hardware_block_BlockInfo info;
  
    zx_status_t io_status = fuchsia_hardware_block_BlockGetInfo(channel->get(), &status, &info);
    if (io_status != ZX_OK || status != ZX_OK) {
      fprintf(stderr, "error: cannot get info for '%s'\n", dev);
      return ZX_TIME_INFINITE;
    }
  
    zx::fifo fifo;
    io_status =
        fuchsia_hardware_block_BlockGetFifo(channel->get(), &status, fifo.reset_and_get_address());
    if (io_status != ZX_OK || status != ZX_OK) {
      fprintf(stderr, "error: cannot get fifo for '%s'\n", dev);
      return ZX_TIME_INFINITE;
    }
  
    groupid_t group = 0;
  
    zx::vmo dup;
    if ((status = vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup)) != ZX_OK) {
      fprintf(stderr, "error: cannot duplicate handle %d\n", status);
      return ZX_TIME_INFINITE;
    }
  
    fuchsia_hardware_block_VmoId vmoid;
    io_status = fuchsia_hardware_block_BlockAttachVmo(channel->get(), dup.release(), &status, &vmoid);
    if (io_status != ZX_OK || status != ZX_OK) {
      fprintf(stderr, "error: cannot attach vmo for '%s'\n", dev);
      return ZX_TIME_INFINITE;
    }
  
    fifo_client_t* client;
    if ((status = block_fifo_create_client(fifo.release(), &client)) != ZX_OK) {
      fprintf(stderr, "error: cannot create block client for '%s' %d\n", dev, status);
      return ZX_TIME_INFINITE;
    }
  
    zx_time_t t0 = zx_clock_get_monotonic();
    size_t n = total;
    while (n > 0) {
      size_t xfer = (n > bufsz) ? bufsz : n;
      block_fifo_request_t request = {
          .opcode = static_cast<uint32_t>(is_read ? BLOCKIO_READ : BLOCKIO_WRITE),
          .reqid = 0,
          .group = group,
          .vmoid = vmoid.id,
          .length = static_cast<uint32_t>(xfer / info.block_size),
          .vmo_offset = 0,
          .dev_offset = (total - n) / info.block_size,
      };
      if ((status = block_fifo_txn(client, &request, 1)) != ZX_OK) {
        fprintf(stderr, "error: block_fifo_txn error %d\n", status);
        return ZX_TIME_INFINITE;
      }
      n -= xfer;
    }
    zx_time_t t1 = zx_clock_get_monotonic();
    return zx_time_sub_time(t1, t0);
  }
  
  static int usage(void) {
    fprintf(stderr,
            "usage: iotime <read|write> <posix|block|fifo> <device|--ramdisk> <bytes> <bufsize>\n\n"
            "        <bytes> and <bufsize> must be a multiple of 4k for block mode\n"
            "        --ramdisk only supported for block mode\n");
    return -1;
  }
  
  int main(int argc, char** argv) {
    if (argc != 6) {
      return usage();
    }
  
    int is_read = !strcmp(argv[1], "read");
    size_t total = number(argv[4]);
    size_t bufsz = number(argv[5]);
  
    int r = -1;
    ramdisk_client_t* ramdisk = NULL;
    int fd;
    if (!strcmp(argv[3], "--ramdisk")) {
      if (strcmp(argv[2], "block")) {
        fprintf(stderr, "ramdisk only supported for block\n");
        goto done;
      }
      zx_status_t status = ramdisk_create(512, total / 512, &ramdisk);
      if (status != ZX_OK) {
        fprintf(stderr, "error: cannot create %zu-byte ramdisk\n", total);
        goto done;
      }
      fd = ramdisk_get_block_fd(ramdisk);
    } else {
      if ((fd = open(argv[3], is_read ? O_RDONLY : O_WRONLY)) < 0) {
        fprintf(stderr, "error: cannot open '%s'\n", argv[3]);
        goto done;
      }
    }
  
    zx_duration_t res;
    if (!strcmp(argv[2], "posix")) {
      res = iotime_posix(is_read, fd, total, bufsz);
    } else if (!strcmp(argv[2], "block")) {
      res = iotime_block(is_read, fd, total, bufsz);
    } else if (!strcmp(argv[2], "fifo")) {
      res = iotime_fifo(argv[3], is_read, fd, total, bufsz);
    } else {
      fprintf(stderr, "error: unknown mode '%s'\n", argv[2]);
      goto done;
    }
  
    if (res != ZX_TIME_INFINITE) {
      fprintf(stderr, "%s %zu bytes in %zu ns: ", is_read ? "read" : "write", total, res);
      bytes_per_second(total, res);
      r = 0;
      goto done;
    } else {
      goto done;
    }
  
  done:
    if (ramdisk != NULL) {
      ramdisk_destroy(ramdisk);
    }
    return r;
  }
  