X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/de148eb79c7894178a1c2da6f2320b9ce6b0daee..3d4b2f9c9449ee1acdb1a488177204ebfdaccd0d:/qemu-nbd.c diff --git a/qemu-nbd.c b/qemu-nbd.c index 80f08d8464..9710a26176 100644 --- a/qemu-nbd.c +++ b/qemu-nbd.c @@ -16,35 +16,44 @@ * along with this program; if not, see . */ +#include "qemu/osdep.h" #include "qemu-common.h" -#include "block.h" -#include "nbd.h" +#include "sysemu/block-backend.h" +#include "block/block_int.h" +#include "block/nbd.h" +#include "qemu/main-loop.h" +#include "qemu/error-report.h" +#include "qemu/config-file.h" +#include "block/snapshot.h" +#include "qapi/util.h" +#include "qapi/qmp/qstring.h" +#include "qom/object_interfaces.h" +#include "io/channel-socket.h" -#include -#include #include -#include #include -#include -#include -#include -#include #include #include #include -#define SOCKET_PATH "/var/lock/qemu-nbd-%s" -#define QEMU_NBD_OPT_CACHE 1 -#define QEMU_NBD_OPT_AIO 2 +#define SOCKET_PATH "/var/lock/qemu-nbd-%s" +#define QEMU_NBD_OPT_CACHE 1 +#define QEMU_NBD_OPT_AIO 2 +#define QEMU_NBD_OPT_DISCARD 3 +#define QEMU_NBD_OPT_DETECT_ZEROES 4 +#define QEMU_NBD_OPT_OBJECT 5 static NBDExport *exp; +static bool newproto; static int verbose; static char *srcpath; -static char *sockpath; +static SocketAddress *saddr; static int persistent = 0; 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 void usage(const char *name) { @@ -52,37 +61,48 @@ static void usage(const char *name) "Usage: %s [OPTIONS] FILE\n" "QEMU Disk Network Block Device Server\n" "\n" -" -h, --help display this help and exit\n" -" -V, --version output version information and exit\n" +" -h, --help display this help and exit\n" +" -V, --version output version information and exit\n" "\n" "Connection properties:\n" -" -p, --port=PORT port to listen on (default `%d')\n" -" -b, --bind=IFACE interface to bind to (default `0.0.0.0')\n" -" -k, --socket=PATH path to the unix socket\n" -" (default '"SOCKET_PATH"')\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" +" -p, --port=PORT port to listen on (default `%d')\n" +" -b, --bind=IFACE interface to bind to (default `0.0.0.0')\n" +" -k, --socket=PATH path to the unix socket\n" +" (default '"SOCKET_PATH"')\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" "\n" "Exposing part of the image:\n" -" -o, --offset=OFFSET offset into the image\n" -" -P, --partition=NUM only expose partition NUM\n" +" -o, --offset=OFFSET offset into the image\n" +" -P, --partition=NUM only expose partition NUM\n" "\n" +"General purpose options:\n" +" --object type,id=ID,... define an object such as 'secret' for providing\n" +" passwords and/or encryption keys\n" #ifdef __linux__ "Kernel NBD client support:\n" -" -c, --connect=DEV connect FILE to the local NBD device DEV\n" -" -d, --disconnect disconnect the specified device\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" -" -r, --read-only export read-only\n" -" -s, --snapshot use snapshot file\n" -" -n, --nocache disable host cache\n" -" --cache=MODE set cache mode (none, writeback, ...)\n" -#ifdef CONFIG_LINUX_AIO -" --aio=MODE set AIO mode (native or threads)\n" -#endif +" -f, --format=FORMAT set image format (raw, qcow2, ...)\n" +" -r, --read-only export read-only\n" +" -s, --snapshot use FILE as an external snapshot, create a temporary\n" +" file with backing_file=FILE, redirect the write to\n" +" the temporary one\n" +" -l, --load-snapshot=SNAPSHOT_PARAM\n" +" load an internal snapshot inside FILE and export it\n" +" as an read-only device, SNAPSHOT_PARAM format is\n" +" 'snapshot.id=[ID],snapshot.name=[NAME]', or\n" +" '[ID_OR_NAME]'\n" +" -n, --nocache disable host cache\n" +" --cache=MODE set cache mode (none, writeback, ...)\n" +" --aio=MODE set AIO mode (native or threads)\n" +" --discard=MODE set discard mode (ignore, unmap)\n" +" --detect-zeroes=MODE set detect-zeroes mode (off, on, unmap)\n" "\n" "Report bugs to \n" , name, NBD_DEFAULT_PORT, "DEVICE"); @@ -124,11 +144,12 @@ static void read_partition(uint8_t *p, struct partition_record *r) r->end_head = p[5]; r->end_cylinder = p[7] | ((p[6] << 2) & 0x300); r->end_sector = p[6] & 0x3f; - r->start_sector_abs = p[8] | p[9] << 8 | p[10] << 16 | p[11] << 24; - r->nb_sectors_abs = p[12] | p[13] << 8 | p[14] << 16 | p[15] << 24; + + r->start_sector_abs = le32_to_cpup((uint32_t *)(p + 8)); + r->nb_sectors_abs = le32_to_cpup((uint32_t *)(p + 12)); } -static int find_partition(BlockDriverState *bs, int partition, +static int find_partition(BlockBackend *blk, int partition, off_t *offset, off_t *size) { struct partition_record mbr[4]; @@ -137,9 +158,9 @@ static int find_partition(BlockDriverState *bs, int partition, int ext_partnum = 4; int ret; - if ((ret = bdrv_read(bs, 0, data, 1)) < 0) { - errno = -ret; - err(EXIT_FAILURE, "error while reading"); + if ((ret = blk_read(blk, 0, data, 1)) < 0) { + error_report("error while reading: %s", strerror(-ret)); + exit(EXIT_FAILURE); } if (data[510] != 0x55 || data[511] != 0xaa) { @@ -149,23 +170,25 @@ static int find_partition(BlockDriverState *bs, int partition, for (i = 0; i < 4; i++) { read_partition(&data[446 + 16 * i], &mbr[i]); - if (!mbr[i].nb_sectors_abs) + if (!mbr[i].system || !mbr[i].nb_sectors_abs) { continue; + } if (mbr[i].system == 0xF || mbr[i].system == 0x5) { struct partition_record ext[4]; uint8_t data1[512]; int j; - if ((ret = bdrv_read(bs, mbr[i].start_sector_abs, data1, 1)) < 0) { - errno = -ret; - err(EXIT_FAILURE, "error while reading"); + if ((ret = blk_read(blk, mbr[i].start_sector_abs, data1, 1)) < 0) { + error_report("error while reading: %s", strerror(-ret)); + exit(EXIT_FAILURE); } for (j = 0; j < 4; j++) { read_partition(&data1[446 + 16 * j], &ext[j]); - if (!ext[j].nb_sectors_abs) + if (!ext[j].system || !ext[j].nb_sectors_abs) { continue; + } if ((ext_partnum + j + 1) == partition) { *offset = (uint64_t)ext[j].start_sector_abs << 9; @@ -190,6 +213,7 @@ static void termsig_handler(int signum) qemu_notify_event(); } + static void *show_parts(void *arg) { char *device = arg; @@ -211,33 +235,40 @@ static void *nbd_client_thread(void *arg) { char *device = arg; off_t size; - size_t blocksize; uint32_t nbdflags; - int fd, sock; + QIOChannelSocket *sioc; + int fd; int ret; pthread_t show_parts_thread; + Error *local_error = NULL; - sock = unix_socket_outgoing(sockpath); - if (sock < 0) { + sioc = qio_channel_socket_new(); + if (qio_channel_socket_connect_sync(sioc, + saddr, + &local_error) < 0) { + error_report_err(local_error); goto out; } - ret = nbd_receive_negotiate(sock, NULL, &nbdflags, - &size, &blocksize); + ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), NULL, &nbdflags, + &size, &local_error); if (ret < 0) { - goto out; + if (local_error) { + error_report_err(local_error); + } + goto out_socket; } fd = open(device, O_RDWR); if (fd < 0) { /* Linux-only, we can use %m in printf. */ - fprintf(stderr, "Failed to open %s: %m", device); - goto out; + error_report("Failed to open %s: %m", device); + goto out_socket; } - ret = nbd_init(fd, sock, nbdflags, size, blocksize); + ret = nbd_init(fd, sioc, nbdflags, size); if (ret < 0) { - goto out; + goto out_fd; } /* update partition table */ @@ -253,18 +284,23 @@ static void *nbd_client_thread(void *arg) ret = nbd_client(fd); if (ret) { - goto out; + goto out_fd; } close(fd); + object_unref(OBJECT(sioc)); kill(getpid(), SIGTERM); return (void *) EXIT_SUCCESS; +out_fd: + close(fd); +out_socket: + object_unref(OBJECT(sioc)); out: kill(getpid(), SIGTERM); return (void *) EXIT_FAILURE; } -static int nbd_can_accept(void *opaque) +static int nbd_can_accept(void) { return nb_fds < shared; } @@ -275,44 +311,110 @@ static void nbd_export_closed(NBDExport *exp) state = TERMINATED; } +static void nbd_update_server_watch(void); + static void nbd_client_closed(NBDClient *client) { nb_fds--; if (nb_fds == 0 && !persistent && state == RUNNING) { state = TERMINATE; } - qemu_notify_event(); + nbd_update_server_watch(); nbd_client_put(client); } -static void nbd_accept(void *opaque) +static gboolean nbd_accept(QIOChannel *ioc, GIOCondition cond, gpointer opaque) { - int server_fd = (uintptr_t) opaque; - struct sockaddr_in addr; - socklen_t addr_len = sizeof(addr); + QIOChannelSocket *cioc; + + cioc = qio_channel_socket_accept(QIO_CHANNEL_SOCKET(ioc), + NULL); + if (!cioc) { + return TRUE; + } - int fd = accept(server_fd, (struct sockaddr *)&addr, &addr_len); if (state >= TERMINATE) { - close(fd); - return; + object_unref(OBJECT(cioc)); + return TRUE; + } + + nb_fds++; + nbd_update_server_watch(); + nbd_client_new(newproto ? NULL : exp, cioc, nbd_client_closed); + object_unref(OBJECT(cioc)); + + return TRUE; +} + +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); + } + } else { + if (server_watch != -1) { + g_source_remove(server_watch); + server_watch = -1; + } } +} + - if (fd >= 0 && nbd_client_new(exp, fd, nbd_client_closed)) { - nb_fds++; +static SocketAddress *nbd_build_socket_address(const char *sockpath, + const char *bindto, + const char *port) +{ + SocketAddress *saddr; + + saddr = g_new0(SocketAddress, 1); + if (sockpath) { + saddr->type = SOCKET_ADDRESS_KIND_UNIX; + saddr->u.q_unix = g_new0(UnixSocketAddress, 1); + saddr->u.q_unix->path = g_strdup(sockpath); + } else { + saddr->type = SOCKET_ADDRESS_KIND_INET; + saddr->u.inet = g_new0(InetSocketAddress, 1); + saddr->u.inet->host = g_strdup(bindto); + if (port) { + saddr->u.inet->port = g_strdup(port); + } else { + saddr->u.inet->port = g_strdup_printf("%d", NBD_DEFAULT_PORT); + } } + + return saddr; } + +static QemuOptsList qemu_object_opts = { + .name = "object", + .implied_opt_name = "qom-type", + .head = QTAILQ_HEAD_INITIALIZER(qemu_object_opts.head), + .desc = { + { } + }, +}; + + int main(int argc, char **argv) { + BlockBackend *blk; BlockDriverState *bs; off_t dev_offset = 0; uint32_t nbdflags = 0; bool disconnect = false; const char *bindto = "0.0.0.0"; + const char *port = NULL; + char *sockpath = NULL; char *device = NULL; - int port = NBD_DEFAULT_PORT; off_t fd_size; - const char *sopt = "hVb:o:p:rsnP:c:dvk:e:t"; + QemuOpts *sn_opts = NULL; + const char *sn_id_or_name = NULL; + const char *sopt = "hVb:o:p:rsnP:c:dvk:e:f:tl:x:"; struct option lopt[] = { { "help", 0, NULL, 'h' }, { "version", 0, NULL, 'V' }, @@ -325,29 +427,35 @@ int main(int argc, char **argv) { "connect", 1, NULL, 'c' }, { "disconnect", 0, NULL, 'd' }, { "snapshot", 0, NULL, 's' }, + { "load-snapshot", 1, NULL, 'l' }, { "nocache", 0, NULL, 'n' }, { "cache", 1, NULL, QEMU_NBD_OPT_CACHE }, -#ifdef CONFIG_LINUX_AIO { "aio", 1, NULL, QEMU_NBD_OPT_AIO }, -#endif + { "discard", 1, NULL, QEMU_NBD_OPT_DISCARD }, + { "detect-zeroes", 1, NULL, QEMU_NBD_OPT_DETECT_ZEROES }, { "shared", 1, NULL, 'e' }, + { "format", 1, NULL, 'f' }, { "persistent", 0, NULL, 't' }, { "verbose", 0, NULL, 'v' }, + { "object", 1, NULL, QEMU_NBD_OPT_OBJECT }, + { "export-name", 1, NULL, 'x' }, { NULL, 0, NULL, 0 } }; int ch; int opt_ind = 0; - int li; char *end; int flags = BDRV_O_RDWR; int partition = -1; - int ret; - int fd; + int ret = 0; bool seen_cache = false; -#ifdef CONFIG_LINUX_AIO + bool seen_discard = false; bool seen_aio = false; -#endif pthread_t client_thread; + const char *fmt = NULL; + Error *local_err = NULL; + BlockdevDetectZeroesOptions detect_zeroes = BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF; + QDict *options = NULL; + const char *export_name = NULL; /* The client thread uses SIGTERM to interrupt the server. A signal * handler ensures that "qemu-nbd -v -c" exits with a nice status code. @@ -356,6 +464,9 @@ int main(int argc, char **argv) memset(&sa_sigterm, 0, sizeof(sa_sigterm)); sa_sigterm.sa_handler = termsig_handler; sigaction(SIGTERM, &sa_sigterm, NULL); + module_call_init(MODULE_INIT_QOM); + qemu_add_opts(&qemu_object_opts); + qemu_init_exec_dir(argv[0]); while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) { switch (ch) { @@ -367,17 +478,19 @@ int main(int argc, char **argv) /* fallthrough */ case QEMU_NBD_OPT_CACHE: if (seen_cache) { - errx(EXIT_FAILURE, "-n and --cache can only be specified once"); + error_report("-n and --cache can only be specified once"); + exit(EXIT_FAILURE); } seen_cache = true; if (bdrv_parse_cache_flags(optarg, &flags) == -1) { - errx(EXIT_FAILURE, "Invalid cache mode `%s'", optarg); + error_report("Invalid cache mode `%s'", optarg); + exit(EXIT_FAILURE); } break; -#ifdef CONFIG_LINUX_AIO case QEMU_NBD_OPT_AIO: if (seen_aio) { - errx(EXIT_FAILURE, "--aio can only be specified once"); + error_report("--aio can only be specified once"); + exit(EXIT_FAILURE); } seen_aio = true; if (!strcmp(optarg, "native")) { @@ -385,47 +498,91 @@ int main(int argc, char **argv) } else if (!strcmp(optarg, "threads")) { /* this is the default */ } else { - errx(EXIT_FAILURE, "invalid aio mode `%s'", optarg); + error_report("invalid aio mode `%s'", optarg); + exit(EXIT_FAILURE); + } + break; + case QEMU_NBD_OPT_DISCARD: + if (seen_discard) { + error_report("--discard can only be specified once"); + exit(EXIT_FAILURE); + } + seen_discard = true; + if (bdrv_parse_discard_flags(optarg, &flags) == -1) { + error_report("Invalid discard mode `%s'", optarg); + exit(EXIT_FAILURE); + } + break; + case QEMU_NBD_OPT_DETECT_ZEROES: + detect_zeroes = + qapi_enum_parse(BlockdevDetectZeroesOptions_lookup, + optarg, + BLOCKDEV_DETECT_ZEROES_OPTIONS__MAX, + BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF, + &local_err); + if (local_err) { + error_reportf_err(local_err, + "Failed to parse detect_zeroes mode: "); + exit(EXIT_FAILURE); + } + if (detect_zeroes == BLOCKDEV_DETECT_ZEROES_OPTIONS_UNMAP && + !(flags & BDRV_O_UNMAP)) { + error_report("setting detect-zeroes to unmap is not allowed " + "without setting discard operation to unmap"); + exit(EXIT_FAILURE); } break; -#endif case 'b': bindto = optarg; break; case 'p': - li = strtol(optarg, &end, 0); - if (*end) { - errx(EXIT_FAILURE, "Invalid port `%s'", optarg); - } - if (li < 1 || li > 65535) { - errx(EXIT_FAILURE, "Port out of range `%s'", optarg); - } - port = (uint16_t)li; + port = optarg; break; case 'o': dev_offset = strtoll (optarg, &end, 0); if (*end) { - errx(EXIT_FAILURE, "Invalid offset `%s'", optarg); + error_report("Invalid offset `%s'", optarg); + exit(EXIT_FAILURE); } if (dev_offset < 0) { - errx(EXIT_FAILURE, "Offset must be positive `%s'", optarg); + error_report("Offset must be positive `%s'", optarg); + exit(EXIT_FAILURE); } break; + case 'l': + if (strstart(optarg, SNAPSHOT_OPT_BASE, NULL)) { + sn_opts = qemu_opts_parse_noisily(&internal_snapshot_opts, + optarg, false); + if (!sn_opts) { + error_report("Failed in parsing snapshot param `%s'", + optarg); + exit(EXIT_FAILURE); + } + } else { + sn_id_or_name = optarg; + } + /* fall through */ case 'r': nbdflags |= NBD_FLAG_READ_ONLY; flags &= ~BDRV_O_RDWR; break; case 'P': partition = strtol(optarg, &end, 0); - if (*end) - errx(EXIT_FAILURE, "Invalid partition `%s'", optarg); - if (partition < 1 || partition > 8) - errx(EXIT_FAILURE, "Invalid partition %d", partition); + if (*end) { + error_report("Invalid partition `%s'", optarg); + exit(EXIT_FAILURE); + } + if (partition < 1 || partition > 8) { + error_report("Invalid partition %d", partition); + exit(EXIT_FAILURE); + } break; case 'k': sockpath = optarg; - if (sockpath[0] != '/') - errx(EXIT_FAILURE, "socket path must be absolute\n"); + if (sockpath[0] != '/') { + error_report("socket path must be absolute"); + exit(EXIT_FAILURE); + } break; case 'd': disconnect = true; @@ -436,15 +593,23 @@ int main(int argc, char **argv) case 'e': shared = strtol(optarg, &end, 0); if (*end) { - errx(EXIT_FAILURE, "Invalid shared device number '%s'", optarg); + error_report("Invalid shared device number '%s'", optarg); + exit(EXIT_FAILURE); } if (shared < 1) { - errx(EXIT_FAILURE, "Shared device number must be greater than 0\n"); + error_report("Shared device number must be greater than 0"); + exit(EXIT_FAILURE); } break; - case 't': - persistent = 1; - break; + case 'f': + fmt = optarg; + break; + case 't': + persistent = 1; + break; + case 'x': + export_name = optarg; + break; case 'v': verbose = 1; break; @@ -457,29 +622,46 @@ int main(int argc, char **argv) exit(0); break; case '?': - errx(EXIT_FAILURE, "Try `%s --help' for more information.", - argv[0]); + error_report("Try `%s --help' for more information.", argv[0]); + exit(EXIT_FAILURE); + case QEMU_NBD_OPT_OBJECT: { + QemuOpts *opts; + opts = qemu_opts_parse_noisily(&qemu_object_opts, + optarg, true); + if (!opts) { + exit(EXIT_FAILURE); + } + } break; } } if ((argc - optind) != 1) { - errx(EXIT_FAILURE, "Invalid number of argument.\n" - "Try `%s --help' for more information.", - argv[0]); + error_report("Invalid number of arguments"); + error_printf("Try `%s --help' for more information.\n", argv[0]); + exit(EXIT_FAILURE); + } + + if (qemu_opts_foreach(&qemu_object_opts, + user_creatable_add_opts_foreach, + NULL, &local_err)) { + error_report_err(local_err); + exit(EXIT_FAILURE); } if (disconnect) { - fd = open(argv[optind], O_RDWR); - if (fd < 0) { - err(EXIT_FAILURE, "Cannot open %s", argv[optind]); + int nbdfd = open(argv[optind], O_RDWR); + if (nbdfd < 0) { + error_report("Cannot open %s: %s", argv[optind], + strerror(errno)); + exit(EXIT_FAILURE); } - nbd_disconnect(fd); + nbd_disconnect(nbdfd); - close(fd); + close(nbdfd); printf("%s disconnected\n", argv[optind]); - return 0; + return 0; } if (device && !verbose) { @@ -488,21 +670,27 @@ int main(int argc, char **argv) int ret; if (qemu_pipe(stderr_fd) < 0) { - err(EXIT_FAILURE, "Error setting up communication pipe"); + error_report("Error setting up communication pipe: %s", + strerror(errno)); + exit(EXIT_FAILURE); } /* Now daemonize, but keep a communication channel open to * print errors and exit with the proper status code. */ pid = fork(); - if (pid == 0) { + if (pid < 0) { + error_report("Failed to fork: %s", strerror(errno)); + exit(EXIT_FAILURE); + } else if (pid == 0) { close(stderr_fd[0]); ret = qemu_daemon(1, 0); /* Temporarily redirect stderr to the parent's pipe... */ dup2(stderr_fd[1], STDERR_FILENO); if (ret < 0) { - err(EXIT_FAILURE, "Failed to daemonize"); + error_report("Failed to daemonize: %s", strerror(errno)); + exit(EXIT_FAILURE); } /* ... close the descriptor we inherited and go on. */ @@ -524,7 +712,9 @@ int main(int argc, char **argv) } } if (ret < 0) { - err(EXIT_FAILURE, "Cannot read from daemon"); + error_report("Cannot read from daemon: %s", + strerror(errno)); + exit(EXIT_FAILURE); } /* Usually the daemon should not print any message. @@ -539,36 +729,75 @@ int main(int argc, char **argv) snprintf(sockpath, 128, SOCKET_PATH, basename(device)); } - qemu_init_main_loop(); + saddr = nbd_build_socket_address(sockpath, bindto, port); + + if (qemu_init_main_loop(&local_err)) { + error_report_err(local_err); + exit(EXIT_FAILURE); + } bdrv_init(); atexit(bdrv_close_all); - bs = bdrv_new("hda"); + if (fmt) { + options = qdict_new(); + qdict_put(options, "driver", qstring_from_str(fmt)); + } + srcpath = argv[optind]; - if ((ret = bdrv_open(bs, srcpath, flags, NULL)) < 0) { - errno = -ret; - err(EXIT_FAILURE, "Failed to bdrv_open '%s'", argv[optind]); + blk = blk_new_open("hda", srcpath, NULL, options, flags, &local_err); + if (!blk) { + error_reportf_err(local_err, "Failed to blk_new_open '%s': ", + argv[optind]); + exit(EXIT_FAILURE); + } + bs = blk_bs(blk); + + if (sn_opts) { + ret = bdrv_snapshot_load_tmp(bs, + qemu_opt_get(sn_opts, SNAPSHOT_OPT_ID), + qemu_opt_get(sn_opts, SNAPSHOT_OPT_NAME), + &local_err); + } else if (sn_id_or_name) { + ret = bdrv_snapshot_load_tmp_by_id_or_name(bs, sn_id_or_name, + &local_err); + } + if (ret < 0) { + error_reportf_err(local_err, "Failed to load snapshot: "); + exit(EXIT_FAILURE); } - fd_size = bdrv_getlength(bs); + bs->detect_zeroes = detect_zeroes; + fd_size = blk_getlength(blk); + if (fd_size < 0) { + error_report("Failed to determine the image length: %s", + strerror(-fd_size)); + exit(EXIT_FAILURE); + } if (partition != -1) { - ret = find_partition(bs, partition, &dev_offset, &fd_size); + ret = find_partition(blk, partition, &dev_offset, &fd_size); if (ret < 0) { - errno = -ret; - err(EXIT_FAILURE, "Could not find partition %d", partition); + error_report("Could not find partition %d: %s", partition, + strerror(-ret)); + exit(EXIT_FAILURE); } } - exp = nbd_export_new(bs, dev_offset, fd_size, nbdflags, nbd_export_closed); - - if (sockpath) { - fd = unix_socket_incoming(sockpath); - } else { - fd = tcp_socket_incoming(bindto, port); + exp = nbd_export_new(blk, dev_offset, fd_size, nbdflags, nbd_export_closed, + &local_err); + if (!exp) { + error_report_err(local_err); + exit(EXIT_FAILURE); + } + if (export_name) { + nbd_export_set_name(exp, export_name); + newproto = true; } - if (fd < 0) { + server_ioc = qio_channel_socket_new(); + if (qio_channel_socket_listen_sync(server_ioc, saddr, &local_err) < 0) { + object_unref(OBJECT(server_ioc)); + error_report_err(local_err); return 1; } @@ -577,21 +806,22 @@ int main(int argc, char **argv) ret = pthread_create(&client_thread, NULL, nbd_client_thread, device); if (ret != 0) { - errx(EXIT_FAILURE, "Failed to create client thread: %s", - strerror(ret)); + error_report("Failed to create client thread: %s", strerror(ret)); + exit(EXIT_FAILURE); } } else { /* Shut up GCC warnings. */ memset(&client_thread, 0, sizeof(client_thread)); } - qemu_set_fd_handler2(fd, nbd_can_accept, nbd_accept, NULL, - (void *)(uintptr_t)fd); + nbd_update_server_watch(); /* now when the initialization is (almost) complete, chdir("/") * to free any busy filesystems */ if (chdir("/") < 0) { - err(EXIT_FAILURE, "Could not chdir to root directory"); + error_report("Could not chdir to root directory: %s", + strerror(errno)); + exit(EXIT_FAILURE); } state = RUNNING; @@ -605,11 +835,13 @@ int main(int argc, char **argv) } } while (state != TERMINATED); - bdrv_close(bs); + blk_unref(blk); if (sockpath) { unlink(sockpath); } + qemu_opts_del(sn_opts); + if (device) { void *ret; pthread_join(client_thread, &ret);