*/
#include "qemu/osdep.h"
+#include <getopt.h>
+#include <libgen.h>
+#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/util.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 <getopt.h>
-#include <libgen.h>
-#include <pthread.h>
+#include "qemu-version.h"
#define SOCKET_PATH "/var/lock/qemu-nbd-%s"
#define QEMU_NBD_OPT_CACHE 256
#define MBR_SIZE 512
static NBDExport *exp;
-static bool newproto;
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 void usage(const char *name)
" -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"
"General purpose options:\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"
" -T, --trace [[enable=]<pattern>][,events=<file>][,file=<file>]\n"
" specify tracing options\n"
" --fork fork off the server process and exit the parent\n"
" --detect-zeroes=MODE set detect-zeroes mode (off, on, unmap)\n"
" --image-opts treat FILE as a full set of image options\n"
"\n"
+QEMU_HELP_BOTTOM "\n"
, name, NBD_DEFAULT_PORT, "DEVICE");
}
static void version(const char *name)
{
printf(
-"%s version 0.0.1\n"
+"%s " QEMU_FULL_VERSION "\n"
"Written by Anthony Liguori.\n"
"\n"
+QEMU_COPYRIGHT "\n"
"This is free software; see the source for copying conditions. There is NO\n"
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
, name);
static void *nbd_client_thread(void *arg)
{
char *device = arg;
- off_t size;
- uint16_t nbdflags;
+ NBDExportInfo info = { .request_sizes = false, };
QIOChannelSocket *sioc;
int fd;
int ret;
goto out;
}
- ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), NULL, &nbdflags,
- NULL, NULL, NULL,
- &size, &local_error);
+ ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), NULL,
+ NULL, NULL, NULL, &info, &local_error);
if (ret < 0) {
if (local_error) {
error_report_err(local_error);
goto out_socket;
}
- ret = nbd_init(fd, sioc, nbdflags, size);
+ ret = nbd_init(fd, sioc, &info, &local_error);
if (ret < 0) {
+ error_report_err(local_error);
goto out_fd;
}
static int nbd_can_accept(void)
{
- return nb_fds < shared;
+ return state == RUNNING && nb_fds < shared;
}
static void nbd_export_closed(NBDExport *exp)
static void nbd_update_server_watch(void);
-static void nbd_client_closed(NBDClient *client)
+static void nbd_client_closed(NBDClient *client, bool negotiated)
{
nb_fds--;
- if (nb_fds == 0 && !persistent && state == RUNNING) {
+ if (negotiated && nb_fds == 0 && !persistent && state == RUNNING) {
state = TERMINATE;
}
nbd_update_server_watch();
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, NULL, 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);
}
}
saddr = g_new0(SocketAddress, 1);
if (sockpath) {
- saddr->type = SOCKET_ADDRESS_KIND_UNIX;
- saddr->u.q_unix.data = g_new0(UnixSocketAddress, 1);
- saddr->u.q_unix.data->path = g_strdup(sockpath);
+ saddr->type = SOCKET_ADDRESS_TYPE_UNIX;
+ saddr->u.q_unix.path = g_strdup(sockpath);
} else {
InetSocketAddress *inet;
- saddr->type = SOCKET_ADDRESS_KIND_INET;
- inet = saddr->u.inet.data = g_new0(InetSocketAddress, 1);
+ saddr->type = SOCKET_ADDRESS_TYPE_INET;
+ inet = &saddr->u.inet;
inet->host = g_strdup(bindto);
if (port) {
inet->port = g_strdup(port);
}
}
-#define FIRST_SOCKET_ACTIVATION_FD 3 /* defined by systemd ABI */
-
-#ifndef _WIN32
-/*
- * Check if socket activation was requested via use of the
- * LISTEN_FDS and LISTEN_PID environment variables.
- *
- * Returns 0 if no socket activation, or the number of FDs.
- */
-static unsigned int check_socket_activation(void)
-{
- const char *s;
- unsigned long pid;
- unsigned long nr_fds;
- unsigned int i;
- int fd;
- int err;
-
- s = getenv("LISTEN_PID");
- if (s == NULL) {
- return 0;
- }
- err = qemu_strtoul(s, NULL, 10, &pid);
- if (err) {
- if (verbose) {
- fprintf(stderr, "malformed %s environment variable (ignored)\n",
- "LISTEN_PID");
- }
- return 0;
- }
- if (pid != getpid()) {
- if (verbose) {
- fprintf(stderr, "%s was not for us (ignored)\n",
- "LISTEN_PID");
- }
- return 0;
- }
-
- s = getenv("LISTEN_FDS");
- if (s == NULL) {
- return 0;
- }
- err = qemu_strtoul(s, NULL, 10, &nr_fds);
- if (err) {
- if (verbose) {
- fprintf(stderr, "malformed %s environment variable (ignored)\n",
- "LISTEN_FDS");
- }
- return 0;
- }
- assert(nr_fds <= UINT_MAX);
-
- /* A limitation of current qemu-nbd is that it can only listen on
- * a single socket. When that limitation is lifted, we can change
- * this function to allow LISTEN_FDS > 1, and remove the assertion
- * in the main function below.
- */
- if (nr_fds > 1) {
- error_report("qemu-nbd does not support socket activation with %s > 1",
- "LISTEN_FDS");
- exit(EXIT_FAILURE);
- }
-
- /* So these are not passed to any child processes we might start. */
- unsetenv("LISTEN_FDS");
- unsetenv("LISTEN_PID");
-
- /* So the file descriptors don't leak into child processes. */
- for (i = 0; i < nr_fds; ++i) {
- fd = FIRST_SOCKET_ACTIVATION_FD + i;
- if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) {
- /* If we cannot set FD_CLOEXEC then it probably means the file
- * descriptor is invalid, so socket activation has gone wrong
- * and we should exit.
- */
- error_report("Socket activation failed: "
- "invalid file descriptor fd = %d: %m",
- fd);
- exit(EXIT_FAILURE);
- }
- }
-
- return (unsigned int) nr_fds;
-}
-
-#else /* !_WIN32 */
-static unsigned int check_socket_activation(void)
-{
- return 0;
-}
-#endif
-
/*
* Check socket parameters compatibility when socket activation is used.
*/
return NULL;
}
+static void qemu_nbd_shutdown(void)
+{
+ job_cancel_sync_all();
+ bdrv_close_all();
+}
+
int main(int argc, char **argv)
{
BlockBackend *blk;
Error *local_err = NULL;
BlockdevDetectZeroesOptions detect_zeroes = BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF;
QDict *options = NULL;
- const char *export_name = NULL;
+ const char *export_name = ""; /* Default export name */
const char *export_description = NULL;
const char *tlscredsid = NULL;
bool imageOpts = false;
sa_sigterm.sa_handler = termsig_handler;
sigaction(SIGTERM, &sa_sigterm, NULL);
+#ifdef CONFIG_POSIX
+ signal(SIGPIPE, SIG_IGN);
+#endif
+
module_call_init(MODULE_INIT_TRACE);
qcrypto_init(&error_fatal);
break;
case QEMU_NBD_OPT_DETECT_ZEROES:
detect_zeroes =
- qapi_enum_parse(BlockdevDetectZeroesOptions_lookup,
+ qapi_enum_parse(&BlockdevDetectZeroesOptions_lookup,
optarg,
- BLOCKDEV_DETECT_ZEROES_OPTIONS__MAX,
BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF,
&local_err);
if (local_err) {
error_report("%s", err_msg);
exit(EXIT_FAILURE);
}
+
+ /* qemu-nbd can only listen on a single socket. */
+ if (socket_activation > 1) {
+ error_report("qemu-nbd does not support socket activation with %s > 1",
+ "LISTEN_FDS");
+ exit(EXIT_FAILURE);
+ }
}
if (tlscredsid) {
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 = "";
- }
tlscreds = nbd_get_tls_creds(tlscredsid, &local_err);
if (local_err) {
error_report("Failed to get TLS creds %s",
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) {
} else {
if (fmt) {
options = qdict_new();
- qdict_put(options, "driver", qstring_from_str(fmt));
+ qdict_put_str(options, "driver", fmt);
}
blk = blk_new_open(srcpath, NULL, options, flags, &local_err);
}
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);
- }
+ nbd_export_set_name(exp, export_name);
+ nbd_export_set_description(exp, export_description);
if (device) {
int ret;