/* 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; }
/* 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; }
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 */ }
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; }
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; }
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 */ }
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; }
/* 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; }
/* 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); }
/* 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; }
/* 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; }
/* 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; }
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 */ }
/* 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; }
/* 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; }
/* 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; }
/* 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; }
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; }
/* 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; }
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; }
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; }
/* 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; }
/* 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; }
/* 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; }
/* 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); }
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; }
static int zero_config (const char *key, const char *value) { nbdkit_error ("unknown parameter '%s'", key); return -1; }
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); }
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. */ }