#include <libgen.h>
#include <pthread.h>
-#include "qemu-common.h"
+#include "qemu/help-texts.h"
#include "qapi/error.h"
#include "qemu/cutils.h"
#include "sysemu/block-backend.h"
#include "trace/control.h"
#include "qemu-version.h"
+#ifdef CONFIG_SELINUX
+#include <selinux/selinux.h>
+#endif
+
#ifdef __linux__
#define HAVE_NBD_DEVICE 1
#else
#define QEMU_NBD_OPT_FORK 263
#define QEMU_NBD_OPT_TLSAUTHZ 264
#define QEMU_NBD_OPT_PID_FILE 265
+#define QEMU_NBD_OPT_SELINUX_LABEL 266
+#define QEMU_NBD_OPT_TLSHOSTNAME 267
#define MBR_SIZE 512
-static int verbose;
-static char *srcpath;
-static SocketAddress *saddr;
static int persistent = 0;
static enum { RUNNING, TERMINATE, TERMINATED } state;
static int shared = 1;
" --fork fork off the server process and exit the parent\n"
" once the server is running\n"
" --pid-file=PATH store the server's process ID in the given file\n"
+#ifdef CONFIG_SELINUX
+" --selinux-label=LABEL set SELinux process label on listening socket\n"
+#endif
#if HAVE_NBD_DEVICE
"\n"
"Kernel NBD client support:\n"
[NBD_FLAG_SEND_RESIZE_BIT] = "resize",
[NBD_FLAG_SEND_CACHE_BIT] = "cache",
[NBD_FLAG_SEND_FAST_ZERO_BIT] = "fast-zero",
+ [NBD_FLAG_BLOCK_STAT_PAYLOAD_BIT] = "block-status-payload",
};
printf(" size: %" PRIu64 "\n", list[i].size);
printf(" opt block: %u\n", list[i].opt_block);
printf(" max block: %u\n", list[i].max_block);
}
+ printf(" transaction size: %s\n",
+ list[i].mode >= NBD_MODE_EXTENDED ?
+ "64-bit" : "32-bit");
if (list[i].n_contexts) {
printf(" available meta contexts: %d\n", list[i].n_contexts);
for (j = 0; j < list[i].n_contexts; j++) {
}
+struct NbdClientOpts {
+ char *device;
+ char *srcpath;
+ SocketAddress *saddr;
+ int old_stderr;
+ bool fork_process;
+ bool verbose;
+};
+
+static void nbd_client_release_pipe(int old_stderr)
+{
+ /* Close stderr so that the qemu-nbd process exits. */
+ if (dup2(old_stderr, STDERR_FILENO) < 0) {
+ error_report("Could not release pipe to parent: %s",
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ if (old_stderr != STDOUT_FILENO && close(old_stderr) < 0) {
+ error_report("Could not release qemu-nbd: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+}
+
#if HAVE_NBD_DEVICE
static void *show_parts(void *arg)
{
static void *nbd_client_thread(void *arg)
{
- char *device = arg;
- NBDExportInfo info = { .request_sizes = false, .name = g_strdup("") };
+ struct NbdClientOpts *opts = arg;
+ /* TODO: Revisit this if nbd.ko ever gains support for structured reply */
+ NBDExportInfo info = { .request_sizes = false, .name = g_strdup(""),
+ .mode = NBD_MODE_SIMPLE };
QIOChannelSocket *sioc;
int fd = -1;
int ret = EXIT_FAILURE;
sioc = qio_channel_socket_new();
if (qio_channel_socket_connect_sync(sioc,
- saddr,
+ opts->saddr,
&local_error) < 0) {
error_report_err(local_error);
goto out;
}
- if (nbd_receive_negotiate(NULL, QIO_CHANNEL(sioc),
- NULL, NULL, NULL, &info, &local_error) < 0) {
+ if (nbd_receive_negotiate(QIO_CHANNEL(sioc), NULL, NULL, NULL,
+ &info, &local_error) < 0) {
if (local_error) {
error_report_err(local_error);
}
goto out;
}
- fd = open(device, O_RDWR);
+ fd = open(opts->device, O_RDWR);
if (fd < 0) {
/* Linux-only, we can use %m in printf. */
- error_report("Failed to open %s: %m", device);
+ error_report("Failed to open %s: %m", opts->device);
goto out;
}
}
/* update partition table */
- pthread_create(&show_parts_thread, NULL, show_parts, device);
+ pthread_create(&show_parts_thread, NULL, show_parts, opts->device);
- if (verbose) {
+ if (opts->verbose && !opts->fork_process) {
fprintf(stderr, "NBD device %s is now connected to %s\n",
- device, srcpath);
+ opts->device, opts->srcpath);
} else {
- /* Close stderr so that the qemu-nbd process exits. */
- dup2(STDOUT_FILENO, STDERR_FILENO);
+ nbd_client_release_pipe(opts->old_stderr);
}
if (nbd_client(fd) < 0) {
const char *sockpath,
const char *address,
const char *port,
+ const char *selinux,
bool list)
{
if (device != NULL) {
return "TCP port number can't be set when using socket activation";
}
+ if (selinux != NULL) {
+ return "SELinux label can't be set when using socket activation";
+ }
+
if (list) {
return "List mode is incompatible with socket activation";
}
const char *bindto = NULL;
const char *port = NULL;
char *sockpath = NULL;
- char *device = NULL;
QemuOpts *sn_opts = NULL;
const char *sn_id_or_name = NULL;
const char *sopt = "hVb:o:p:rsnc:dvk:e:f:tl:x:T:D:AB:L";
{ "export-name", required_argument, NULL, 'x' },
{ "description", required_argument, NULL, 'D' },
{ "tls-creds", required_argument, NULL, QEMU_NBD_OPT_TLSCREDS },
+ { "tls-hostname", required_argument, NULL, QEMU_NBD_OPT_TLSHOSTNAME },
{ "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 },
{ "pid-file", required_argument, NULL, QEMU_NBD_OPT_PID_FILE },
+ { "selinux-label", required_argument, NULL,
+ QEMU_NBD_OPT_SELINUX_LABEL },
{ NULL, 0, NULL, 0 }
};
int ch;
QDict *options = NULL;
const char *export_name = NULL; /* defaults to "" later for server mode */
const char *export_description = NULL;
- strList *bitmaps = NULL;
+ BlockDirtyBitmapOrStrList *bitmaps = NULL;
bool alloc_depth = false;
const char *tlscredsid = NULL;
+ const char *tlshostname = NULL;
bool imageOpts = false;
bool writethrough = false; /* Client will flush as needed. */
- bool fork_process = false;
bool list = false;
- int old_stderr = -1;
unsigned socket_activation;
const char *pid_file_name = NULL;
+ const char *selinux_label = NULL;
BlockExportOptions *export_opts;
+ struct NbdClientOpts opts = {
+ .fork_process = false,
+ .verbose = false,
+ .device = NULL,
+ .srcpath = NULL,
+ .saddr = NULL,
+ .old_stderr = STDOUT_FILENO,
+ };
#ifdef CONFIG_POSIX
os_setup_early_signal_handling();
alloc_depth = true;
break;
case 'B':
- QAPI_LIST_PREPEND(bitmaps, g_strdup(optarg));
+ {
+ BlockDirtyBitmapOrStr *el = g_new(BlockDirtyBitmapOrStr, 1);
+ *el = (BlockDirtyBitmapOrStr) {
+ .type = QTYPE_QSTRING,
+ .u.local = g_strdup(optarg),
+ };
+ QAPI_LIST_PREPEND(bitmaps, el);
+ }
break;
case 'k':
sockpath = optarg;
disconnect = true;
break;
case 'c':
- device = optarg;
+ opts.device = optarg;
break;
case 'e':
if (qemu_strtoi(optarg, NULL, 0, &shared) < 0 ||
}
break;
case 'v':
- verbose = 1;
+ opts.verbose = true;
break;
case 'V':
version(argv[0]);
case QEMU_NBD_OPT_TLSCREDS:
tlscredsid = optarg;
break;
+ case QEMU_NBD_OPT_TLSHOSTNAME:
+ tlshostname = optarg;
+ break;
case QEMU_NBD_OPT_IMAGE_OPTS:
imageOpts = true;
break;
tlsauthz = optarg;
break;
case QEMU_NBD_OPT_FORK:
- fork_process = true;
+ opts.fork_process = true;
break;
case 'L':
list = true;
case QEMU_NBD_OPT_PID_FILE:
pid_file_name = optarg;
break;
+ case QEMU_NBD_OPT_SELINUX_LABEL:
+ selinux_label = optarg;
+ break;
}
}
exit(EXIT_FAILURE);
}
if (export_name || export_description || dev_offset ||
- device || disconnect || fmt || sn_id_or_name || bitmaps ||
+ opts.device || disconnect || fmt || sn_id_or_name || bitmaps ||
alloc_depth || seen_aio || seen_discard || seen_cache) {
error_report("List mode is incompatible with per-device settings");
exit(EXIT_FAILURE);
}
- if (fork_process) {
+ if (opts.fork_process) {
error_report("List mode is incompatible with forking");
exit(EXIT_FAILURE);
}
exit(1);
}
trace_init_file();
- qemu_set_log(LOG_TRACE);
+ qemu_set_log(LOG_TRACE, &error_fatal);
socket_activation = check_socket_activation();
if (socket_activation == 0) {
- setup_address_and_port(&bindto, &port);
+ if (!sockpath) {
+ setup_address_and_port(&bindto, &port);
+ }
} else {
/* Using socket activation - check user didn't use -p etc. */
- const char *err_msg = socket_activation_validate_opts(device, sockpath,
+ const char *err_msg = socket_activation_validate_opts(opts.device,
+ sockpath,
bindto, port,
+ selinux_label,
list);
if (err_msg != NULL) {
error_report("%s", err_msg);
}
if (tlscredsid) {
- if (sockpath) {
- error_report("TLS is only supported with IPv4/IPv6");
- exit(EXIT_FAILURE);
- }
- if (device) {
+ if (opts.device) {
error_report("TLS is not supported with a host device");
exit(EXIT_FAILURE);
}
error_report("TLS authorization is incompatible with export list");
exit(EXIT_FAILURE);
}
+ if (tlshostname && !list) {
+ error_report("TLS hostname is only supported with export list");
+ exit(EXIT_FAILURE);
+ }
tlscreds = nbd_get_tls_creds(tlscredsid, list, &local_err);
if (local_err) {
error_reportf_err(local_err, "Failed to get TLS creds: ");
error_report("--tls-authz is not permitted without --tls-creds");
exit(EXIT_FAILURE);
}
+ if (tlshostname) {
+ error_report("--tls-hostname is not permitted without --tls-creds");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (selinux_label) {
+#ifdef CONFIG_SELINUX
+ if (sockpath == NULL && opts.device == NULL) {
+ error_report("--selinux-label is not permitted without --socket");
+ exit(EXIT_FAILURE);
+ }
+#else
+ error_report("SELinux support not enabled in this binary");
+ exit(EXIT_FAILURE);
+#endif
}
if (list) {
- saddr = nbd_build_socket_address(sockpath, bindto, port);
- return qemu_nbd_client_list(saddr, tlscreds, bindto);
+ opts.saddr = nbd_build_socket_address(sockpath, bindto, port);
+ return qemu_nbd_client_list(opts.saddr, tlscreds,
+ tlshostname ? tlshostname : bindto);
}
#if !HAVE_NBD_DEVICE
- if (disconnect || device) {
+ if (disconnect || opts.device) {
error_report("Kernel /dev/nbdN support not available");
exit(EXIT_FAILURE);
}
}
#endif
- if ((device && !verbose) || fork_process) {
+ if ((opts.device && !opts.verbose) || opts.fork_process) {
#ifndef WIN32
+ g_autoptr(GError) err = NULL;
int stderr_fd[2];
pid_t pid;
- int ret;
- if (qemu_pipe(stderr_fd) < 0) {
+ if (!g_unix_open_pipe(stderr_fd, FD_CLOEXEC, &err)) {
error_report("Error setting up communication pipe: %s",
- strerror(errno));
+ err->message);
exit(EXIT_FAILURE);
}
error_report("Failed to fork: %s", strerror(errno));
exit(EXIT_FAILURE);
} else if (pid == 0) {
+ int saved_errno;
+
close(stderr_fd[0]);
/* Remember parent's stderr if we will be restoring it. */
- if (fork_process) {
- old_stderr = dup(STDERR_FILENO);
+ if (opts.verbose /* fork_process is set */) {
+ opts.old_stderr = dup(STDERR_FILENO);
+ if (opts.old_stderr < 0) {
+ error_report("Could not dup original stderr: %s",
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
}
ret = qemu_daemon(1, 0);
+ saved_errno = errno; /* dup2 will overwrite error below */
/* Temporarily redirect stderr to the parent's pipe... */
- dup2(stderr_fd[1], STDERR_FILENO);
+ if (dup2(stderr_fd[1], STDERR_FILENO) < 0) {
+ char str[256];
+ snprintf(str, sizeof(str),
+ "%s: Failed to link stderr to the pipe: %s\n",
+ g_get_prgname(), strerror(errno));
+ /*
+ * We are unable to use error_report() here as we need to get
+ * stderr pointed to the parent's pipe. Write to that pipe
+ * manually.
+ */
+ ret = write(stderr_fd[1], str, strlen(str));
+ exit(EXIT_FAILURE);
+ }
+
if (ret < 0) {
- error_report("Failed to daemonize: %s", strerror(errno));
+ error_report("Failed to daemonize: %s", strerror(saved_errno));
exit(EXIT_FAILURE);
}
#endif /* WIN32 */
}
- if (device != NULL && sockpath == NULL) {
+ if (opts.device != NULL && sockpath == NULL) {
sockpath = g_malloc(128);
- snprintf(sockpath, 128, SOCKET_PATH, basename(device));
+ snprintf(sockpath, 128, SOCKET_PATH, basename(opts.device));
}
server = qio_net_listener_new();
} else {
backlog = MIN(shared, SOMAXCONN);
}
- saddr = nbd_build_socket_address(sockpath, bindto, port);
- if (qio_net_listener_open_sync(server, saddr, backlog,
+#ifdef CONFIG_SELINUX
+ if (selinux_label && setsockcreatecon_raw(selinux_label) == -1) {
+ error_report("Cannot set SELinux socket create context to %s: %s",
+ selinux_label, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+#endif
+ opts.saddr = nbd_build_socket_address(sockpath, bindto, port);
+ if (qio_net_listener_open_sync(server, opts.saddr, backlog,
&local_err) < 0) {
object_unref(OBJECT(server));
error_report_err(local_err);
exit(EXIT_FAILURE);
}
+#ifdef CONFIG_SELINUX
+ if (selinux_label && setsockcreatecon_raw(NULL) == -1) {
+ error_report("Cannot clear SELinux socket create context: %s",
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+#endif
} else {
size_t i;
/* See comment in check_socket_activation above. */
bdrv_init();
atexit(qemu_nbd_shutdown);
- srcpath = argv[optind];
+ opts.srcpath = argv[optind];
if (imageOpts) {
- QemuOpts *opts;
+ QemuOpts *o;
if (fmt) {
error_report("--image-opts and -f are mutually exclusive");
exit(EXIT_FAILURE);
}
- opts = qemu_opts_parse_noisily(&file_opts, srcpath, true);
- if (!opts) {
+ o = qemu_opts_parse_noisily(&file_opts, opts.srcpath, true);
+ if (!o) {
qemu_opts_reset(&file_opts);
exit(EXIT_FAILURE);
}
- options = qemu_opts_to_qdict(opts, NULL);
+ options = qemu_opts_to_qdict(o, NULL);
qemu_opts_reset(&file_opts);
blk = blk_new_open(NULL, NULL, options, flags, &local_err);
} else {
options = qdict_new();
qdict_put_str(options, "driver", fmt);
}
- blk = blk_new_open(srcpath, NULL, options, flags, &local_err);
+ blk = blk_new_open(opts.srcpath, NULL, options, flags, &local_err);
}
if (!blk) {
qdict_put_str(raw_opts, "driver", "raw");
qdict_put_str(raw_opts, "file", bs->node_name);
qdict_put_int(raw_opts, "offset", dev_offset);
+
+ aio_context_acquire(qemu_get_aio_context());
bs = bdrv_open(NULL, NULL, raw_opts, flags, &error_fatal);
+ aio_context_release(qemu_get_aio_context());
+
blk_remove_bs(blk);
blk_insert_bs(blk, bs, &error_fatal);
bdrv_unref(bs);
bs->detect_zeroes = detect_zeroes;
- nbd_server_is_qemu_nbd(true);
+ nbd_server_is_qemu_nbd(shared);
export_opts = g_new(BlockExportOptions, 1);
*export_opts = (BlockExportOptions) {
.has_writable = true,
.writable = !readonly,
.u.nbd = {
- .has_name = true,
.name = g_strdup(export_name),
- .has_description = !!export_description,
.description = g_strdup(export_description),
.has_bitmaps = !!bitmaps,
.bitmaps = bitmaps,
blk_exp_add(export_opts, &error_fatal);
qapi_free_BlockExportOptions(export_opts);
- if (device) {
+ if (opts.device) {
#if HAVE_NBD_DEVICE
- int ret;
-
- ret = pthread_create(&client_thread, NULL, nbd_client_thread, device);
+ ret = pthread_create(&client_thread, NULL, nbd_client_thread, &opts);
if (ret != 0) {
error_report("Failed to create client thread: %s", strerror(ret));
exit(EXIT_FAILURE);
exit(EXIT_FAILURE);
}
- if (fork_process) {
- dup2(old_stderr, STDERR_FILENO);
- close(old_stderr);
+ if (opts.fork_process) {
+ nbd_client_release_pipe(opts.old_stderr);
}
state = RUNNING;
qemu_opts_del(sn_opts);
- if (device) {
- void *ret;
- pthread_join(client_thread, &ret);
- exit(ret != NULL);
+ if (opts.device) {
+ void *result;
+ pthread_join(client_thread, &result);
+ ret = (intptr_t)result;
+ exit(ret);
} else {
exit(EXIT_SUCCESS);
}