Пример #1
0
/* Read data. */
static int
split_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
{
  struct handle *h = handle;

  while (count > 0) {
    struct file *file = get_file (h, offset);
    uint64_t foffs = offset - file->offset;
    uint64_t max;
    ssize_t r;

    max = file->size - foffs;
    if (max > count)
      max = count;

    r = pread (file->fd, buf, max, foffs);
    if (r == -1) {
      nbdkit_error ("pread: %m");
      return -1;
    }
    if (r == 0) {
      nbdkit_error ("pread: unexpected end of file");
      return -1;
    }
    buf += r;
    count -= r;
    offset += r;
  }

  return 0;
}
Пример #2
0
/* Create the per-connection handle. */
static void *
file_open (int readonly)
{
  struct handle *h;
  int flags;

  h = malloc (sizeof *h);
  if (h == NULL) {
    nbdkit_error ("malloc: %m");
    return NULL;
  }

  flags = O_CLOEXEC|O_NOCTTY;
  if (readonly)
    flags |= O_RDONLY;
  else
    flags |= O_RDWR;

  h->fd = open (filename, flags);
  if (h->fd == -1) {
    nbdkit_error ("open: %s: %m", filename);
    free (h);
    return NULL;
  }

  return h;
}
Пример #3
0
static int
send_simple_reply (struct connection *conn,
                   uint64_t handle, uint16_t cmd,
                   const char *buf, uint32_t count,
                   uint32_t error)
{
  ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&conn->write_lock);
  struct simple_reply reply;
  int r;

  reply.magic = htobe32 (NBD_SIMPLE_REPLY_MAGIC);
  reply.handle = handle;
  reply.error = htobe32 (nbd_errno (error, false));

  r = conn->send (conn, &reply, sizeof reply);
  if (r == -1) {
    nbdkit_error ("write reply: %s: %m", name_of_nbd_cmd (cmd));
    return connection_set_status (conn, -1);
  }

  /* Send the read data buffer. */
  if (cmd == NBD_CMD_READ && !error) {
    r = conn->send (conn, buf, count);
    if (r == -1) {
      nbdkit_error ("write data: %s: %m", name_of_nbd_cmd (cmd));
      return connection_set_status (conn, -1);
    }
  }

  return 1;                     /* command processed ok */
}
Пример #4
0
static int
skip_over_write_buffer (int sock, size_t count)
{
  char buf[BUFSIZ];
  ssize_t r;

  if (count > MAX_REQUEST_SIZE * 2) {
    nbdkit_error ("write request too large to skip");
    return -1;
  }

  while (count > 0) {
    r = read (sock, buf, count > BUFSIZ ? BUFSIZ : count);
    if (r == -1) {
      nbdkit_error ("skipping write buffer: %m");
      return -1;
    }
    if (r == 0)  {
      nbdkit_error ("unexpected early EOF");
      errno = EBADMSG;
      return -1;
    }
    count -= r;
  }
  return 0;
}
Пример #5
0
static int
send_structured_reply_error (struct connection *conn,
                             uint64_t handle, uint16_t cmd, uint16_t flags,
                             uint32_t error)
{
  ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&conn->write_lock);
  struct structured_reply reply;
  struct structured_reply_error error_data;
  int r;

  reply.magic = htobe32 (NBD_STRUCTURED_REPLY_MAGIC);
  reply.handle = handle;
  reply.flags = htobe16 (NBD_REPLY_FLAG_DONE);
  reply.type = htobe16 (NBD_REPLY_TYPE_ERROR);
  reply.length = htobe32 (0 /* no human readable error */ + sizeof error_data);

  r = conn->send (conn, &reply, sizeof reply);
  if (r == -1) {
    nbdkit_error ("write error reply: %m");
    return connection_set_status (conn, -1);
  }

  /* Send the error. */
  error_data.error = htobe32 (nbd_errno (error, flags & NBD_CMD_FLAG_DF));
  error_data.len = htobe16 (0);
  r = conn->send (conn, &error_data, sizeof error_data);
  if (r == -1) {
    nbdkit_error ("write data: %s: %m", name_of_nbd_cmd (cmd));
    return connection_set_status (conn, -1);
  }
  /* No human readable error message at the moment. */

  return 1;                     /* command processed ok */
}
int read_metadata(char *dev_dir_path, metadata_t *metadata)
{
    int err;

    size_t buff_size = strlen(dev_dir_path) + strlen("/metadata") + 1;
    char metadata_path[buff_size];
    snprintf(metadata_path, buff_size, "%s/metadata", dev_dir_path);

    int fd = open(metadata_path, O_RDONLY);
    if (fd == -1)
    {
        nbdkit_error("Unable to open '%s': %m", metadata_path);
        return -1;
    }

    int ret = _read_metadata_from_open_file(fd, metadata_path, metadata);

    if (close(fd) == -1)
    {
        nbdkit_error("Unable to close '%s': %m", metadata_path);
        return -1;
    }

    return ret;
}
Пример #7
0
static int
send_structured_reply_block_status (struct connection *conn,
                                    uint64_t handle,
                                    uint16_t cmd, uint16_t flags,
                                    uint32_t count, uint64_t offset,
                                    struct nbdkit_extents *extents)
{
  ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&conn->write_lock);
  struct structured_reply reply;
  CLEANUP_FREE struct block_descriptor *blocks = NULL;
  size_t nr_blocks;
  uint32_t context_id;
  size_t i;
  int r;

  assert (conn->meta_context_base_allocation);
  assert (cmd == NBD_CMD_BLOCK_STATUS);

  blocks = extents_to_block_descriptors (extents, flags, count, offset,
                                         &nr_blocks);
  if (blocks == NULL)
    return connection_set_status (conn, -1);

  reply.magic = htobe32 (NBD_STRUCTURED_REPLY_MAGIC);
  reply.handle = handle;
  reply.flags = htobe16 (NBD_REPLY_FLAG_DONE);
  reply.type = htobe16 (NBD_REPLY_TYPE_BLOCK_STATUS);
  reply.length = htobe32 (sizeof context_id +
                          nr_blocks * sizeof (struct block_descriptor));

  r = conn->send (conn, &reply, sizeof reply);
  if (r == -1) {
    nbdkit_error ("write reply: %s: %m", name_of_nbd_cmd (cmd));
    return connection_set_status (conn, -1);
  }

  /* Send the base:allocation context ID. */
  context_id = htobe32 (base_allocation_id);
  r = conn->send (conn, &context_id, sizeof context_id);
  if (r == -1) {
    nbdkit_error ("write reply: %s: %m", name_of_nbd_cmd (cmd));
    return connection_set_status (conn, -1);
  }

  /* Send each block descriptor. */
  for (i = 0; i < nr_blocks; ++i) {
    r = conn->send (conn, &blocks[i], sizeof blocks[i]);
    if (r == -1) {
      nbdkit_error ("write reply: %s: %m", name_of_nbd_cmd (cmd));
      return connection_set_status (conn, -1);
    }
  }

  return 1;                     /* command processed ok */
}
Пример #8
0
static int fakestick_config_complete(void) {
  assert(fd == -1);
  if((fd = open(path, O_RDWR|O_CREAT, 0666)) < 0) {
    nbdkit_error("opening backing store: %s", strerror(errno));
    return -1;
  }
  if(ftruncate(fd, actual_size) < 0) {
    nbdkit_error("resizing backing store: %s", strerror(errno));
    return -1;
  }
  return 0;
}
Пример #9
0
/* Called for each key=value passed on the command line. */
static int
curl_config (const char *key, const char *value)
{
  if (strcmp (key, "url") == 0)
    url = value;

  else if (strcmp (key, "user") == 0)
    user = value;

  else if (strcmp (key, "password") == 0) {
    free (password);
    password = NULL;

    if (strcmp (value, "-") == 0) {
      ssize_t r;

      printf ("password: "******"could not read password from stdin: %m");
        return -1;
      }
      if (password && r > 0 && password[r-1] == '\n')
        password[r-1] = '\0';
    }
    else
      password = strdup (value);
  }

  else if (strcmp (key, "sslverify") == 0) {
    if (sscanf (value, "%d", &sslverify) != 1) {
      nbdkit_error ("'sslverify' must be 0 or 1");
      return -1;
    }
  }

  else if (strcmp (key, "timeout") == 0) {
    if (sscanf (value, "%d", &timeout) != 1 || timeout < 0) {
      nbdkit_error ("'timeout' must be 0 or a positive timeout in seconds");
      return -1;
    }
  }

  else {
    nbdkit_error ("unknown parameter '%s'", key);
    return -1;
  }

  return 0;
}
Пример #10
0
/* Open the logfile. */
static int
log_config_complete (nbdkit_next_config_complete *next, void *nxdata)
{
  if (!logfilename) {
    nbdkit_error ("missing logfile= parameter for the log filter");
    return -1;
  }
  logfile = fopen (logfilename, append ? "a" : "w");
  if (!logfile) {
    nbdkit_error ("fopen: %m");
    return -1;
  }

  return next (nxdata);
}
Пример #11
0
/* Ideally the read plugin would be optional. */
static int
zero_pread (void *handle, void *buf, uint32_t count, uint64_t offset,
            uint32_t flags)
{
  nbdkit_error ("unexpected call to pread");
  return -1;
}
Пример #12
0
/* Write data to the file. */
static int
split_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset)
{
  struct handle *h = handle;

  while (count > 0) {
    struct file *file = get_file (h, offset);
    uint64_t foffs = offset - file->offset;
    uint64_t max;
    ssize_t r;

    max = file->size - foffs;
    if (max > count)
      max = count;

    r = pwrite (file->fd, buf, max, offset);
    if (r == -1) {
      nbdkit_error ("pwrite: %m");
      return -1;
    }
    buf += r;
    count -= r;
    offset += r;
  }

  return 0;
}
Пример #13
0
/* Caching. */
static int
split_cache (void *handle, uint32_t count, uint64_t offset, uint32_t flags)
{
  struct handle *h = handle;

  /* Cache is advisory, we don't care if this fails */
  while (count > 0) {
    struct file *file = get_file (h, offset);
    uint64_t foffs = offset - file->offset;
    uint64_t max;
    int r;

    max = file->size - foffs;
    if (max > count)
      max = count;

    r = posix_fadvise (file->fd, offset, max, POSIX_FADV_WILLNEED);
    if (r) {
      errno = r;
      nbdkit_error ("posix_fadvise: %m");
      return -1;
    }
    count -= r;
    offset += r;
  }

  return 0;
}
Пример #14
0
static int
send_structured_reply_read (struct connection *conn,
                            uint64_t handle, uint16_t cmd,
                            const char *buf, uint32_t count, uint64_t offset)
{
  /* Once we are really using structured replies and sending data back
   * in chunks, we'll be able to grab the write lock for each chunk,
   * allowing other threads to interleave replies.  As we're not doing
   * that yet we acquire the lock for the whole function.
   */
  ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&conn->write_lock);
  struct structured_reply reply;
  struct structured_reply_offset_data offset_data;
  int r;

  assert (cmd == NBD_CMD_READ);

  reply.magic = htobe32 (NBD_STRUCTURED_REPLY_MAGIC);
  reply.handle = handle;
  reply.flags = htobe16 (NBD_REPLY_FLAG_DONE);
  reply.type = htobe16 (NBD_REPLY_TYPE_OFFSET_DATA);
  reply.length = htobe32 (count + sizeof offset_data);

  r = conn->send (conn, &reply, sizeof reply);
  if (r == -1) {
    nbdkit_error ("write reply: %s: %m", name_of_nbd_cmd (cmd));
    return connection_set_status (conn, -1);
  }

  /* Send the offset + read data buffer. */
  offset_data.offset = htobe64 (offset);
  r = conn->send (conn, &offset_data, sizeof offset_data);
  if (r == -1) {
    nbdkit_error ("write data: %s: %m", name_of_nbd_cmd (cmd));
    return connection_set_status (conn, -1);
  }

  r = conn->send (conn, buf, count);
  if (r == -1) {
    nbdkit_error ("write data: %s: %m", name_of_nbd_cmd (cmd));
    return connection_set_status (conn, -1);
  }

  return 1;                     /* command processed ok */
}
Пример #15
0
/* Check the user did pass a file=<FILENAME> parameter. */
static int
file_config_complete (void)
{
  if (filename == NULL) {
    nbdkit_error ("you must supply the file=<FILENAME> parameter after the plugin name on the command line");
    return -1;
  }

  return 0;
}
Пример #16
0
/* Check the user did pass a url parameter. */
static int
curl_config_complete (void)
{
  if (url == NULL) {
    nbdkit_error ("you must supply the url=<URL> parameter after the plugin name on the command line");
    return -1;
  }

  return 0;
}
Пример #17
0
/* Flush the file to disk. */
static int
file_flush (void *handle)
{
  struct handle *h = handle;

  if (fdatasync (h->fd) == -1) {
    nbdkit_error ("fdatasync: %m");
    return -1;
  }

  return 0;
}
Пример #18
0
/* Read data from the file. */
static int
file_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
{
  struct handle *h = handle;

  while (count > 0) {
    ssize_t r = pread (h->fd, buf, count, offset);
    if (r == -1) {
      nbdkit_error ("pread: %m");
      return -1;
    }
    if (r == 0) {
      nbdkit_error ("pread: unexpected end of file");
      return -1;
    }
    buf += r;
    count -= r;
    offset += r;
  }

  return 0;
}
Пример #19
0
static int fakestick_config(const char *key, const char *value) {
  if(!strcmp(key, "actual_size"))
    actual_size = nbdkit_parse_size(value);
  else if(!strcmp(key, "advertized_size"))
    advertized_size = nbdkit_parse_size(value);
  else if(!strcmp(key, "path"))
    path = strdup(value);
  else {
    nbdkit_error("unrecognized fakestick configuration key '%s'", key);
    return -1;
  }
  return 0;
}
Пример #20
0
/* Get the file size. */
static int64_t
file_get_size (void *handle)
{
  struct handle *h = handle;
  struct stat statbuf;

  if (fstat (h->fd, &statbuf) == -1) {
    nbdkit_error ("stat: %m");
    return -1;
  }

  return statbuf.st_size;
}
Пример #21
0
static int
random_config (const char *key, const char *value)
{
  int64_t r;

  if (strcmp (key, "seed") == 0) {
    if (sscanf (value, "%" SCNu32, &seed) != 1) {
      nbdkit_error ("could not parse seed parameter");
      return -1;
    }
  }
  else if (strcmp (key, "size") == 0) {
    r = nbdkit_parse_size (value);
    if (r == -1)
      return -1;
    size = r;
  }
  else {
    nbdkit_error ("unknown parameter '%s'", key);
    return -1;
  }

  return 0;
}
Пример #22
0
static int
split_config (const char *key, const char *value)
{
  char **new_filenames;

  if (strcmp (key, "file") == 0) {
    new_filenames = realloc (filenames, (nr_files+1) * sizeof (char *));
    if (new_filenames == NULL) {
      nbdkit_error ("malloc: %m");
      return -1;
    }
    filenames = new_filenames;
    filenames[nr_files] = nbdkit_realpath (value);
    if (filenames[nr_files] == NULL)
      return -1;
    nr_files++;
  }
  else {
    nbdkit_error ("unknown parameter '%s'", key);
    return -1;
  }

  return 0;
}
Пример #23
0
/* Called for each key=value passed on the command line.  This plugin
 * only accepts file=<filename>, which is required.
 */
static int
file_config (const char *key, const char *value)
{
  if (strcmp (key, "file") == 0) {
    /* See FILENAMES AND PATHS in nbdkit-plugin(3). */
    filename = nbdkit_absolute_path (value);
    if (!filename)
      return -1;
  }
  else {
    nbdkit_error ("unknown parameter '%s'", key);
    return -1;
  }

  return 0;
}
Пример #24
0
/* Write data to the file. */
static int
file_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset)
{
  struct handle *h = handle;

  while (count > 0) {
    ssize_t r = pwrite (h->fd, buf, count, offset);
    if (r == -1) {
      nbdkit_error ("pwrite: %m");
      return -1;
    }
    buf += r;
    count -= r;
    offset += r;
  }

  return 0;
}
Пример #25
0
/* Open a connection. */
static void *
log_open (nbdkit_next_open *next, void *nxdata, int readonly)
{
  struct handle *h;

  if (next (nxdata, readonly) == -1)
    return NULL;

  h = malloc (sizeof *h);
  if (h == NULL) {
    nbdkit_error ("malloc: %m");
    return NULL;
  }

  ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
  h->connection = ++connections;
  h->id = 0;
  return h;
}
Пример #26
0
/* Called for each key=value passed on the command line. */
static int
log_config (nbdkit_next_config *next, void *nxdata,
            const char *key, const char *value)
{
  if (strcmp (key, "logfile") == 0) {
    free (logfilename);
    logfilename = strdup (value);
    if (!logfilename) {
      nbdkit_error ("strdup: %m");
      return -1;
    }
    return 0;
  }
  if (strcmp (key, "logappend") == 0) {
    append = nbdkit_parse_bool (value);
    if (append < 0)
      return -1;
    return 0;
  }
  return next (nxdata, key, value);
}
Пример #27
0
append_one_region (struct regions *regions, struct region region)
{
  struct region *p;

  /* The assertions in this function are meant to maintain the
   * invariant about the array as described at the top of this file.
   */
  assert (region.start == virtual_size (regions));
  assert (region.len > 0);
  assert (region.end >= region.start);
  assert (region.len == region.end - region.start + 1);

  p = realloc (regions->regions,
               (regions->nr_regions+1) * sizeof (struct region));
  if (p == NULL) {
    nbdkit_error ("realloc: %m");
    return -1;
  }
  regions->regions = p;
  regions->regions[regions->nr_regions] = region;
  regions->nr_regions++;

  return 0;
}
Пример #28
0
static int
zero_config (const char *key, const char *value)
{
  nbdkit_error ("unknown parameter '%s'", key);
  return -1;
}
Пример #29
0
int
protocol_recv_request_send_reply (struct connection *conn)
{
  int r;
  struct request request;
  uint16_t cmd, flags;
  uint32_t magic, count, error = 0;
  uint64_t offset;
  char *buf = NULL;
  CLEANUP_EXTENTS_FREE struct nbdkit_extents *extents = NULL;

  /* Read the request packet. */
  {
    ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&conn->read_lock);
    r = connection_get_status (conn);
    if (r <= 0)
      return r;
    r = conn->recv (conn, &request, sizeof request);
    if (r == -1) {
      nbdkit_error ("read request: %m");
      return connection_set_status (conn, -1);
    }
    if (r == 0) {
      debug ("client closed input socket, closing connection");
      return connection_set_status (conn, 0); /* disconnect */
    }

    magic = be32toh (request.magic);
    if (magic != NBD_REQUEST_MAGIC) {
      nbdkit_error ("invalid request: 'magic' field is incorrect (0x%x)",
                    magic);
      return connection_set_status (conn, -1);
    }

    flags = be16toh (request.flags);
    cmd = be16toh (request.type);

    offset = be64toh (request.offset);
    count = be32toh (request.count);

    if (cmd == NBD_CMD_DISC) {
      debug ("client sent %s, closing connection", name_of_nbd_cmd (cmd));
      return connection_set_status (conn, 0); /* disconnect */
    }

    /* Validate the request. */
    if (!validate_request (conn, cmd, flags, offset, count, &error)) {
      if (cmd == NBD_CMD_WRITE &&
          skip_over_write_buffer (conn->sockin, count) < 0)
        return connection_set_status (conn, -1);
      goto send_reply;
    }

    /* Get the data buffer used for either read or write requests.
     * This is a common per-thread data buffer, it must not be freed.
     */
    if (cmd == NBD_CMD_READ || cmd == NBD_CMD_WRITE) {
      buf = threadlocal_buffer ((size_t) count);
      if (buf == NULL) {
        error = ENOMEM;
        if (cmd == NBD_CMD_WRITE &&
            skip_over_write_buffer (conn->sockin, count) < 0)
          return connection_set_status (conn, -1);
        goto send_reply;
      }
    }

    /* Allocate the extents list for block status only. */
    if (cmd == NBD_CMD_BLOCK_STATUS) {
      extents = nbdkit_extents_new (offset, conn->exportsize);
      if (extents == NULL) {
        error = ENOMEM;
        goto send_reply;
      }
    }

    /* Receive the write data buffer. */
    if (cmd == NBD_CMD_WRITE) {
      r = conn->recv (conn, buf, count);
      if (r == 0) {
        errno = EBADMSG;
        r = -1;
      }
      if (r == -1) {
        nbdkit_error ("read data: %s: %m", name_of_nbd_cmd (cmd));
        return connection_set_status (conn, -1);
      }
    }
  }

  /* Perform the request.  Only this part happens inside the request lock. */
  if (quit || !connection_get_status (conn)) {
    error = ESHUTDOWN;
  }
  else {
    lock_request (conn);
    error = handle_request (conn, cmd, flags, offset, count, buf, extents);
    assert ((int) error >= 0);
    unlock_request (conn);
  }

  /* Send the reply packet. */
 send_reply:
  if (connection_get_status (conn) < 0)
    return -1;

  if (error != 0) {
    /* Since we're about to send only the limited NBD_E* errno to the
     * client, don't lose the information about what really happened
     * on the server side.  Make sure there is a way for the operator
     * to retrieve the real error.
     */
    debug ("sending error reply: %s", strerror (error));
  }

  /* Currently we prefer to send simple replies for everything except
   * where we have to (ie. NBD_CMD_READ and NBD_CMD_BLOCK_STATUS when
   * structured_replies have been negotiated).  However this prevents
   * us from sending human-readable error messages to the client, so
   * we should reconsider this in future.
   */
  if (conn->structured_replies &&
      (cmd == NBD_CMD_READ || cmd == NBD_CMD_BLOCK_STATUS)) {
    if (!error) {
      if (cmd == NBD_CMD_READ)
        return send_structured_reply_read (conn, request.handle, cmd,
                                           buf, count, offset);
      else /* NBD_CMD_BLOCK_STATUS */
        return send_structured_reply_block_status (conn, request.handle,
                                                   cmd, flags,
                                                   count, offset,
                                                   extents);
    }
    else
      return send_structured_reply_error (conn, request.handle, cmd, flags,
                                          error);
  }
  else
    return send_simple_reply (conn, request.handle, cmd, buf, count, error);
}
Пример #30
0
static bool
validate_request (struct connection *conn,
                  uint16_t cmd, uint16_t flags, uint64_t offset, uint32_t count,
                  uint32_t *error)
{
  /* Readonly connection? */
  if (conn->readonly &&
      (cmd == NBD_CMD_WRITE || cmd == NBD_CMD_TRIM ||
       cmd == NBD_CMD_WRITE_ZEROES)) {
    nbdkit_error ("invalid request: %s: write request on readonly connection",
                  name_of_nbd_cmd (cmd));
    *error = EROFS;
    return false;
  }

  /* Validate cmd, offset, count. */
  switch (cmd) {
  case NBD_CMD_READ:
  case NBD_CMD_CACHE:
  case NBD_CMD_WRITE:
  case NBD_CMD_TRIM:
  case NBD_CMD_WRITE_ZEROES:
  case NBD_CMD_BLOCK_STATUS:
    if (!valid_range (conn, offset, count)) {
      /* XXX Allow writes to extend the disk? */
      nbdkit_error ("invalid request: %s: offset and count are out of range: "
                    "offset=%" PRIu64 " count=%" PRIu32,
                    name_of_nbd_cmd (cmd), offset, count);
      *error = (cmd == NBD_CMD_WRITE ||
                cmd == NBD_CMD_WRITE_ZEROES) ? ENOSPC : EINVAL;
      return false;
    }
    break;

  case NBD_CMD_FLUSH:
    if (offset != 0 || count != 0) {
      nbdkit_error ("invalid request: %s: expecting offset and count = 0",
                    name_of_nbd_cmd (cmd));
      *error = EINVAL;
      return false;
    }
    break;

  default:
    nbdkit_error ("invalid request: unknown command (%" PRIu32 ") ignored",
                  cmd);
    *error = EINVAL;
    return false;
  }

  /* Validate flags */
  if (flags & ~(NBD_CMD_FLAG_FUA | NBD_CMD_FLAG_NO_HOLE |
                NBD_CMD_FLAG_DF | NBD_CMD_FLAG_REQ_ONE)) {
    nbdkit_error ("invalid request: unknown flag (0x%x)", flags);
    *error = EINVAL;
    return false;
  }
  if ((flags & NBD_CMD_FLAG_NO_HOLE) &&
      cmd != NBD_CMD_WRITE_ZEROES) {
    nbdkit_error ("invalid request: NO_HOLE flag needs WRITE_ZEROES request");
    *error = EINVAL;
    return false;
  }
  if (flags & NBD_CMD_FLAG_DF) {
    if (cmd != NBD_CMD_READ) {
      nbdkit_error ("invalid request: DF flag needs READ request");
      *error = EINVAL;
      return false;
    }
    if (!conn->structured_replies) {
      nbdkit_error ("invalid request: "
                    "%s: structured replies was not negotiated",
                    name_of_nbd_cmd (cmd));
      *error = EINVAL;
      return false;
    }
  }
  if ((flags & NBD_CMD_FLAG_REQ_ONE) &&
      cmd != NBD_CMD_BLOCK_STATUS) {
    nbdkit_error ("invalid request: REQ_ONE flag needs BLOCK_STATUS request");
    *error = EINVAL;
    return false;
  }
  if (!conn->can_fua && (flags & NBD_CMD_FLAG_FUA)) {
    nbdkit_error ("invalid request: FUA flag not supported");
    *error = EINVAL;
    return false;
  }

  /* Refuse over-large read and write requests. */
  if ((cmd == NBD_CMD_WRITE || cmd == NBD_CMD_READ) &&
      count > MAX_REQUEST_SIZE) {
    nbdkit_error ("invalid request: %s: data request is too large (%" PRIu32
                  " > %d)",
                  name_of_nbd_cmd (cmd), count, MAX_REQUEST_SIZE);
    *error = ENOMEM;
    return false;
  }

  /* Flush allowed? */
  if (!conn->can_flush && cmd == NBD_CMD_FLUSH) {
    nbdkit_error ("invalid request: %s: flush operation not supported",
                  name_of_nbd_cmd (cmd));
    *error = EINVAL;
    return false;
  }

  /* Trim allowed? */
  if (!conn->can_trim && cmd == NBD_CMD_TRIM) {
    nbdkit_error ("invalid request: %s: trim operation not supported",
                  name_of_nbd_cmd (cmd));
    *error = EINVAL;
    return false;
  }

  /* Zero allowed? */
  if (!conn->can_zero && cmd == NBD_CMD_WRITE_ZEROES) {
    nbdkit_error ("invalid request: %s: write zeroes operation not supported",
                  name_of_nbd_cmd (cmd));
    *error = EINVAL;
    return false;
  }

  /* Cache allowed? */
  if (!conn->can_cache && cmd == NBD_CMD_CACHE) {
    nbdkit_error ("invalid request: %s: cache operation not supported",
                  name_of_nbd_cmd (cmd));
    *error = EINVAL;
    return false;
  }

  /* Block status allowed? */
  if (cmd == NBD_CMD_BLOCK_STATUS) {
    if (!conn->structured_replies) {
      nbdkit_error ("invalid request: "
                    "%s: structured replies was not negotiated",
                    name_of_nbd_cmd (cmd));
      *error = EINVAL;
      return false;
    }
    if (!conn->meta_context_base_allocation) {
      nbdkit_error ("invalid request: "
                    "%s: base:allocation was not negotiated",
                    name_of_nbd_cmd (cmd));
      *error = EINVAL;
      return false;
    }
  }

  return true;                     /* Command validates. */
}