#include <pthread.h>
#include "qapi/error.h"
-#include "qemu-common.h"
#include "qemu/cutils.h"
#include "sysemu/block-backend.h"
#include "block/block_int.h"
#include "block/nbd.h"
#include "qemu/main-loop.h"
+#include "qemu/option.h"
#include "qemu/error-report.h"
#include "qemu/config-file.h"
#include "qemu/bswap.h"
#include "qemu/log.h"
#include "qemu/systemd.h"
#include "block/snapshot.h"
+#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qstring.h"
#include "qom/object_interfaces.h"
#include "io/channel-socket.h"
+#include "io/net-listener.h"
#include "crypto/init.h"
#include "trace/control.h"
#include "qemu-version.h"
+#ifdef __linux__
+#define HAVE_NBD_DEVICE 1
+#else
+#define HAVE_NBD_DEVICE 0
+#endif
+
#define SOCKET_PATH "/var/lock/qemu-nbd-%s"
#define QEMU_NBD_OPT_CACHE 256
#define QEMU_NBD_OPT_AIO 257
#define QEMU_NBD_OPT_TLSCREDS 261
#define QEMU_NBD_OPT_IMAGE_OPTS 262
#define QEMU_NBD_OPT_FORK 263
+#define QEMU_NBD_OPT_TLSAUTHZ 264
#define MBR_SIZE 512
-static NBDExport *exp;
-static bool newproto;
+static NBDExport *export;
static int verbose;
static char *srcpath;
static SocketAddress *saddr;
static enum { RUNNING, TERMINATE, TERMINATING, TERMINATED } state;
static int shared = 1;
static int nb_fds;
-static QIOChannelSocket *server_ioc;
-static int server_watch = -1;
+static QIONetListener *server;
static QCryptoTLSCreds *tlscreds;
+static const char *tlsauthz;
static void usage(const char *name)
{
(printf) (
"Usage: %s [OPTIONS] FILE\n"
-"QEMU Disk Network Block Device Server\n"
+" or: %s -L [OPTIONS]\n"
+"QEMU Disk Network Block Device Utility\n"
"\n"
" -h, --help display this help and exit\n"
" -V, --version output version information and exit\n"
" -e, --shared=NUM device can be shared by NUM clients (default '1')\n"
" -t, --persistent don't exit on the last connection\n"
" -v, --verbose display extra debugging information\n"
-" -x, --export-name=NAME expose export by name\n"
-" -D, --description=TEXT with -x, also export a human-readable description\n"
+" -x, --export-name=NAME expose export by name (default is empty string)\n"
+" -D, --description=TEXT export a human-readable description\n"
"\n"
"Exposing part of the image:\n"
" -o, --offset=OFFSET offset into the image\n"
" -P, --partition=NUM only expose partition NUM\n"
+" -B, --bitmap=NAME expose a persistent dirty bitmap\n"
"\n"
"General purpose options:\n"
+" -L, --list list exports available from another NBD server\n"
" --object type,id=ID,... define an object such as 'secret' for providing\n"
" passwords and/or encryption keys\n"
+" --tls-creds=ID use id of an earlier --object to provide TLS\n"
+" --tls-authz=ID use id of an earlier --object to provide\n"
+" authorization\n"
" -T, --trace [[enable=]<pattern>][,events=<file>][,file=<file>]\n"
" specify tracing options\n"
" --fork fork off the server process and exit the parent\n"
" once the server is running\n"
-#ifdef __linux__
+#if HAVE_NBD_DEVICE
+"\n"
"Kernel NBD client support:\n"
" -c, --connect=DEV connect FILE to the local NBD device DEV\n"
" -d, --disconnect disconnect the specified device\n"
-"\n"
#endif
"\n"
"Block device options:\n"
" --image-opts treat FILE as a full set of image options\n"
"\n"
QEMU_HELP_BOTTOM "\n"
- , name, NBD_DEFAULT_PORT, "DEVICE");
+ , name, name, NBD_DEFAULT_PORT, "DEVICE");
}
static void version(const char *name)
{
printf(
-"%s " QEMU_VERSION QEMU_PKGVERSION "\n"
+"%s " QEMU_FULL_VERSION "\n"
"Written by Anthony Liguori.\n"
"\n"
QEMU_COPYRIGHT "\n"
}
static int find_partition(BlockBackend *blk, int partition,
- off_t *offset, off_t *size)
+ uint64_t *offset, uint64_t *size)
{
struct partition_record mbr[4];
uint8_t data[MBR_SIZE];
}
+static int qemu_nbd_client_list(SocketAddress *saddr, QCryptoTLSCreds *tls,
+ const char *hostname)
+{
+ int ret = EXIT_FAILURE;
+ int rc;
+ Error *err = NULL;
+ QIOChannelSocket *sioc;
+ NBDExportInfo *list;
+ int i, j;
+
+ sioc = qio_channel_socket_new();
+ if (qio_channel_socket_connect_sync(sioc, saddr, &err) < 0) {
+ error_report_err(err);
+ return EXIT_FAILURE;
+ }
+ rc = nbd_receive_export_list(QIO_CHANNEL(sioc), tls, hostname, &list,
+ &err);
+ if (rc < 0) {
+ if (err) {
+ error_report_err(err);
+ }
+ goto out;
+ }
+ printf("exports available: %d\n", rc);
+ for (i = 0; i < rc; i++) {
+ printf(" export: '%s'\n", list[i].name);
+ if (list[i].description && *list[i].description) {
+ printf(" description: %s\n", list[i].description);
+ }
+ if (list[i].flags & NBD_FLAG_HAS_FLAGS) {
+ printf(" size: %" PRIu64 "\n", list[i].size);
+ printf(" flags: 0x%x (", list[i].flags);
+ if (list[i].flags & NBD_FLAG_READ_ONLY) {
+ printf(" readonly");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_FLUSH) {
+ printf(" flush");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_FUA) {
+ printf(" fua");
+ }
+ if (list[i].flags & NBD_FLAG_ROTATIONAL) {
+ printf(" rotational");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_TRIM) {
+ printf(" trim");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_WRITE_ZEROES) {
+ printf(" zeroes");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_DF) {
+ printf(" df");
+ }
+ if (list[i].flags & NBD_FLAG_CAN_MULTI_CONN) {
+ printf(" multi");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_RESIZE) {
+ printf(" resize");
+ }
+ if (list[i].flags & NBD_FLAG_SEND_CACHE) {
+ printf(" cache");
+ }
+ printf(" )\n");
+ }
+ if (list[i].min_block) {
+ printf(" min block: %u\n", list[i].min_block);
+ printf(" opt block: %u\n", list[i].opt_block);
+ printf(" max block: %u\n", list[i].max_block);
+ }
+ if (list[i].n_contexts) {
+ printf(" available meta contexts: %d\n", list[i].n_contexts);
+ for (j = 0; j < list[i].n_contexts; j++) {
+ printf(" %s\n", list[i].contexts[j]);
+ }
+ }
+ }
+ nbd_free_export_list(list, rc);
+
+ ret = EXIT_SUCCESS;
+ out:
+ object_unref(OBJECT(sioc));
+ return ret;
+}
+
+
+#if HAVE_NBD_DEVICE
static void *show_parts(void *arg)
{
char *device = arg;
static void *nbd_client_thread(void *arg)
{
char *device = arg;
- NBDExportInfo info = { .request_sizes = false, };
+ NBDExportInfo info = { .request_sizes = false, .name = g_strdup("") };
QIOChannelSocket *sioc;
int fd;
int ret;
goto out;
}
- ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), NULL,
+ ret = nbd_receive_negotiate(QIO_CHANNEL(sioc),
NULL, NULL, NULL, &info, &local_error);
if (ret < 0) {
if (local_error) {
}
close(fd);
object_unref(OBJECT(sioc));
+ g_free(info.name);
kill(getpid(), SIGTERM);
return (void *) EXIT_SUCCESS;
out_socket:
object_unref(OBJECT(sioc));
out:
+ g_free(info.name);
kill(getpid(), SIGTERM);
return (void *) EXIT_FAILURE;
}
+#endif /* HAVE_NBD_DEVICE */
static int nbd_can_accept(void)
{
return state == RUNNING && nb_fds < shared;
}
-static void nbd_export_closed(NBDExport *exp)
+static void nbd_export_closed(NBDExport *export)
{
assert(state == TERMINATING);
state = TERMINATED;
nbd_client_put(client);
}
-static gboolean nbd_accept(QIOChannel *ioc, GIOCondition cond, gpointer opaque)
+static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
+ gpointer opaque)
{
- QIOChannelSocket *cioc;
-
- cioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc),
- NULL);
- if (!cioc) {
- return TRUE;
- }
-
if (state >= TERMINATE) {
- object_unref(OBJECT(cioc));
- return TRUE;
+ return;
}
nb_fds++;
nbd_update_server_watch();
- nbd_client_new(newproto ? NULL : exp, cioc,
- tlscreds, NULL, nbd_client_closed);
- object_unref(OBJECT(cioc));
-
- return TRUE;
+ nbd_client_new(cioc, tlscreds, tlsauthz, nbd_client_closed);
}
static void nbd_update_server_watch(void)
{
if (nbd_can_accept()) {
- if (server_watch == -1) {
- server_watch = qio_channel_add_watch(QIO_CHANNEL(server_ioc),
- G_IO_IN,
- nbd_accept,
- NULL, NULL);
- }
+ qio_net_listener_set_client_func(server, nbd_accept, NULL, NULL);
} else {
- if (server_watch != -1) {
- g_source_remove(server_watch);
- server_watch = -1;
- }
+ qio_net_listener_set_client_func(server, NULL, NULL, NULL);
}
}
-static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, Error **errp)
+static QCryptoTLSCreds *nbd_get_tls_creds(const char *id, bool list,
+ Error **errp)
{
Object *obj;
QCryptoTLSCreds *creds;
return NULL;
}
- if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
- error_setg(errp,
- "Expecting TLS credentials with a server endpoint");
- return NULL;
+ if (list) {
+ if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT) {
+ error_setg(errp,
+ "Expecting TLS credentials with a client endpoint");
+ return NULL;
+ }
+ } else {
+ if (creds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+ error_setg(errp,
+ "Expecting TLS credentials with a server endpoint");
+ return NULL;
+ }
}
object_ref(obj);
return creds;
static const char *socket_activation_validate_opts(const char *device,
const char *sockpath,
const char *address,
- const char *port)
+ const char *port,
+ bool list)
{
if (device != NULL) {
return "NBD device can't be set when using socket activation";
return "TCP port number can't be set when using socket activation";
}
+ if (list) {
+ return "List mode is incompatible with socket activation";
+ }
+
return NULL;
}
+static void qemu_nbd_shutdown(void)
+{
+ job_cancel_sync_all();
+ bdrv_close_all();
+}
+
int main(int argc, char **argv)
{
BlockBackend *blk;
BlockDriverState *bs;
- off_t dev_offset = 0;
+ uint64_t dev_offset = 0;
uint16_t nbdflags = 0;
bool disconnect = false;
const char *bindto = NULL;
const char *port = NULL;
char *sockpath = NULL;
char *device = NULL;
- off_t fd_size;
+ int64_t fd_size;
QemuOpts *sn_opts = NULL;
const char *sn_id_or_name = NULL;
- const char *sopt = "hVb:o:p:rsnP:c:dvk:e:f:tl:x:T:D:";
+ const char *sopt = "hVb:o:p:rsnP:c:dvk:e:f:tl:x:T:D:B:L";
struct option lopt[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "offset", required_argument, NULL, 'o' },
{ "read-only", no_argument, NULL, 'r' },
{ "partition", required_argument, NULL, 'P' },
+ { "bitmap", required_argument, NULL, 'B' },
{ "connect", required_argument, NULL, 'c' },
{ "disconnect", no_argument, NULL, 'd' },
+ { "list", no_argument, NULL, 'L' },
{ "snapshot", no_argument, NULL, 's' },
{ "load-snapshot", required_argument, NULL, 'l' },
{ "nocache", no_argument, NULL, 'n' },
{ "export-name", required_argument, NULL, 'x' },
{ "description", required_argument, NULL, 'D' },
{ "tls-creds", required_argument, NULL, QEMU_NBD_OPT_TLSCREDS },
+ { "tls-authz", required_argument, NULL, QEMU_NBD_OPT_TLSAUTHZ },
{ "image-opts", no_argument, NULL, QEMU_NBD_OPT_IMAGE_OPTS },
{ "trace", required_argument, NULL, 'T' },
{ "fork", no_argument, NULL, QEMU_NBD_OPT_FORK },
};
int ch;
int opt_ind = 0;
- char *end;
int flags = BDRV_O_RDWR;
- int partition = -1;
+ int partition = 0;
int ret = 0;
bool seen_cache = false;
bool seen_discard = false;
Error *local_err = NULL;
BlockdevDetectZeroesOptions detect_zeroes = BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF;
QDict *options = NULL;
- const char *export_name = NULL;
+ const char *export_name = NULL; /* defaults to "" later for server mode */
const char *export_description = NULL;
+ const char *bitmap = NULL;
const char *tlscredsid = NULL;
bool imageOpts = false;
bool writethrough = true;
char *trace_file = NULL;
bool fork_process = false;
+ bool list = false;
int old_stderr = -1;
unsigned socket_activation;
signal(SIGPIPE, SIG_IGN);
#endif
+ error_init(argv[0]);
module_call_init(MODULE_INIT_TRACE);
qcrypto_init(&error_fatal);
port = optarg;
break;
case 'o':
- dev_offset = strtoll (optarg, &end, 0);
- if (*end) {
- error_report("Invalid offset `%s'", optarg);
- exit(EXIT_FAILURE);
- }
- if (dev_offset < 0) {
- error_report("Offset must be positive `%s'", optarg);
+ if (qemu_strtou64(optarg, NULL, 0, &dev_offset) < 0) {
+ error_report("Invalid offset '%s'", optarg);
exit(EXIT_FAILURE);
}
break;
flags &= ~BDRV_O_RDWR;
break;
case 'P':
- partition = strtol(optarg, &end, 0);
- if (*end) {
- error_report("Invalid partition `%s'", optarg);
- exit(EXIT_FAILURE);
- }
- if (partition < 1 || partition > 8) {
- error_report("Invalid partition %d", partition);
+ warn_report("The '-P' option is deprecated; use --image-opts with "
+ "a raw device wrapper for subset exports instead");
+ if (qemu_strtoi(optarg, NULL, 0, &partition) < 0 ||
+ partition < 1 || partition > 8) {
+ error_report("Invalid partition '%s'", optarg);
exit(EXIT_FAILURE);
}
break;
+ case 'B':
+ bitmap = optarg;
+ break;
case 'k':
sockpath = optarg;
if (sockpath[0] != '/') {
device = optarg;
break;
case 'e':
- shared = strtol(optarg, &end, 0);
- if (*end) {
+ if (qemu_strtoi(optarg, NULL, 0, &shared) < 0 ||
+ shared < 1) {
error_report("Invalid shared device number '%s'", optarg);
exit(EXIT_FAILURE);
}
- if (shared < 1) {
- error_report("Shared device number must be greater than 0");
- exit(EXIT_FAILURE);
- }
break;
case 'f':
fmt = optarg;
g_free(trace_file);
trace_file = trace_opt_parse(optarg);
break;
+ case QEMU_NBD_OPT_TLSAUTHZ:
+ tlsauthz = optarg;
+ break;
case QEMU_NBD_OPT_FORK:
fork_process = true;
break;
+ case 'L':
+ list = true;
+ break;
}
}
- if ((argc - optind) != 1) {
+ if (list) {
+ if (argc != optind) {
+ error_report("List mode is incompatible with a file name");
+ exit(EXIT_FAILURE);
+ }
+ if (export_name || export_description || dev_offset || partition ||
+ device || disconnect || fmt || sn_id_or_name || bitmap ||
+ seen_aio || seen_discard || seen_cache) {
+ error_report("List mode is incompatible with per-device settings");
+ exit(EXIT_FAILURE);
+ }
+ if (fork_process) {
+ error_report("List mode is incompatible with forking");
+ exit(EXIT_FAILURE);
+ }
+ } else if ((argc - optind) != 1) {
error_report("Invalid number of arguments");
error_printf("Try `%s --help' for more information.\n", argv[0]);
exit(EXIT_FAILURE);
+ } else if (!export_name) {
+ export_name = "";
}
- if (qemu_opts_foreach(&qemu_object_opts,
- user_creatable_add_opts_foreach,
- NULL, NULL)) {
- exit(EXIT_FAILURE);
- }
+ qemu_opts_foreach(&qemu_object_opts,
+ user_creatable_add_opts_foreach,
+ NULL, &error_fatal);
if (!trace_init_backends()) {
exit(1);
} else {
/* Using socket activation - check user didn't use -p etc. */
const char *err_msg = socket_activation_validate_opts(device, sockpath,
- bindto, port);
+ bindto, port,
+ list);
if (err_msg != NULL) {
error_report("%s", err_msg);
exit(EXIT_FAILURE);
error_report("TLS is not supported with a host device");
exit(EXIT_FAILURE);
}
- if (!export_name) {
- /* Set the default NBD protocol export name, since
- * we *must* use new style protocol for TLS */
- export_name = "";
+ if (tlsauthz && list) {
+ error_report("TLS authorization is incompatible with export list");
+ exit(EXIT_FAILURE);
}
- tlscreds = nbd_get_tls_creds(tlscredsid, &local_err);
+ tlscreds = nbd_get_tls_creds(tlscredsid, list, &local_err);
if (local_err) {
error_report("Failed to get TLS creds %s",
error_get_pretty(local_err));
exit(EXIT_FAILURE);
}
+ } else {
+ if (tlsauthz) {
+ error_report("--tls-authz is not permitted without --tls-creds");
+ exit(EXIT_FAILURE);
+ }
}
+ if (list) {
+ saddr = nbd_build_socket_address(sockpath, bindto, port);
+ return qemu_nbd_client_list(saddr, tlscreds, bindto);
+ }
+
+#if !HAVE_NBD_DEVICE
+ if (disconnect || device) {
+ error_report("Kernel /dev/nbdN support not available");
+ exit(EXIT_FAILURE);
+ }
+#else /* HAVE_NBD_DEVICE */
if (disconnect) {
int nbdfd = open(argv[optind], O_RDWR);
if (nbdfd < 0) {
return 0;
}
+#endif
if ((device && !verbose) || fork_process) {
int stderr_fd[2];
snprintf(sockpath, 128, SOCKET_PATH, basename(device));
}
+ server = qio_net_listener_new();
if (socket_activation == 0) {
- server_ioc = qio_channel_socket_new();
saddr = nbd_build_socket_address(sockpath, bindto, port);
- if (qio_channel_socket_listen_sync(server_ioc, saddr, &local_err) < 0) {
- object_unref(OBJECT(server_ioc));
+ if (qio_net_listener_open_sync(server, saddr, &local_err) < 0) {
+ object_unref(OBJECT(server));
error_report_err(local_err);
- return 1;
+ exit(EXIT_FAILURE);
}
} else {
+ size_t i;
/* See comment in check_socket_activation above. */
- assert(socket_activation == 1);
- server_ioc = qio_channel_socket_new_fd(FIRST_SOCKET_ACTIVATION_FD,
- &local_err);
- if (server_ioc == NULL) {
- error_report("Failed to use socket activation: %s",
- error_get_pretty(local_err));
- exit(EXIT_FAILURE);
+ for (i = 0; i < socket_activation; i++) {
+ QIOChannelSocket *sioc;
+ sioc = qio_channel_socket_new_fd(FIRST_SOCKET_ACTIVATION_FD + i,
+ &local_err);
+ if (sioc == NULL) {
+ object_unref(OBJECT(server));
+ error_report("Failed to use socket activation: %s",
+ error_get_pretty(local_err));
+ exit(EXIT_FAILURE);
+ }
+ qio_net_listener_add(server, sioc);
+ object_unref(OBJECT(sioc));
}
}
exit(EXIT_FAILURE);
}
bdrv_init();
- atexit(bdrv_close_all);
+ atexit(qemu_nbd_shutdown);
srcpath = argv[optind];
if (imageOpts) {
}
if (dev_offset >= fd_size) {
- error_report("Offset (%lld) has to be smaller than the image size "
- "(%lld)",
- (long long int)dev_offset, (long long int)fd_size);
+ error_report("Offset (%" PRIu64 ") has to be smaller than the image "
+ "size (%" PRId64 ")", dev_offset, fd_size);
exit(EXIT_FAILURE);
}
fd_size -= dev_offset;
- if (partition != -1) {
- ret = find_partition(blk, partition, &dev_offset, &fd_size);
+ if (partition) {
+ uint64_t limit;
+
+ if (dev_offset) {
+ error_report("Cannot request partition and offset together");
+ exit(EXIT_FAILURE);
+ }
+ ret = find_partition(blk, partition, &dev_offset, &limit);
if (ret < 0) {
error_report("Could not find partition %d: %s", partition,
strerror(-ret));
exit(EXIT_FAILURE);
}
+ /*
+ * MBR partition limits are (32-bit << 9); this assert lets
+ * the compiler know that we can't overflow 64 bits.
+ */
+ assert(dev_offset + limit >= dev_offset);
+ if (dev_offset + limit > fd_size) {
+ error_report("Discovered partition %d at offset %" PRIu64
+ " size %" PRIu64 ", but size exceeds file length %"
+ PRId64, partition, dev_offset, limit, fd_size);
+ exit(EXIT_FAILURE);
+ }
+ fd_size = limit;
}
- exp = nbd_export_new(bs, dev_offset, fd_size, nbdflags, nbd_export_closed,
- writethrough, NULL, &local_err);
- if (!exp) {
- error_report_err(local_err);
- exit(EXIT_FAILURE);
- }
- if (export_name) {
- nbd_export_set_name(exp, export_name);
- nbd_export_set_description(exp, export_description);
- newproto = true;
- } else if (export_description) {
- error_report("Export description requires an export name");
- exit(EXIT_FAILURE);
- }
+ export = nbd_export_new(bs, dev_offset, fd_size, export_name,
+ export_description, bitmap, nbdflags,
+ nbd_export_closed, writethrough, NULL,
+ &error_fatal);
if (device) {
+#if HAVE_NBD_DEVICE
int ret;
ret = pthread_create(&client_thread, NULL, nbd_client_thread, device);
error_report("Failed to create client thread: %s", strerror(ret));
exit(EXIT_FAILURE);
}
+#endif
} else {
/* Shut up GCC warnings. */
memset(&client_thread, 0, sizeof(client_thread));
main_loop_wait(false);
if (state == TERMINATE) {
state = TERMINATING;
- nbd_export_close(exp);
- nbd_export_put(exp);
- exp = NULL;
+ nbd_export_close(export);
+ nbd_export_put(export);
+ export = NULL;
}
} while (state != TERMINATED);