X-Git-Url: https://repo.jachan.dev/qemu.git/blobdiff_plain/de9c0cec6c823071b903ebeebf1ee70b394ff46f..7c3843332db39c2f27405b882a505144d62b3664:/qemu-nbd.c diff --git a/qemu-nbd.c b/qemu-nbd.c index ca722ed425..d222512412 100644 --- a/qemu-nbd.c +++ b/qemu-nbd.c @@ -17,8 +17,14 @@ */ #include "qemu-common.h" -#include "block/block.h" +#include "sysemu/block-backend.h" +#include "block/block_int.h" #include "block/nbd.h" +#include "qemu/main-loop.h" +#include "qemu/sockets.h" +#include "qemu/error-report.h" +#include "block/snapshot.h" +#include "qapi/util.h" #include #include @@ -33,10 +39,11 @@ #include #include -#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 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 static NBDExport *exp; static int verbose; @@ -53,37 +60,47 @@ 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" #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" +" -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" #ifdef CONFIG_LINUX_AIO -" --aio=MODE set AIO mode (native or threads)\n" +" --aio=MODE set AIO mode (native or threads)\n" #endif +" --discard=MODE set discard mode (ignore, unmap)\n" +" --detect-zeroes=MODE set detect-zeroes mode (off, on, discard)\n" "\n" "Report bugs to \n" , name, NBD_DEFAULT_PORT, "DEVICE"); @@ -129,7 +146,7 @@ static void read_partition(uint8_t *p, struct partition_record *r) r->nb_sectors_abs = p[12] | p[13] << 8 | p[14] << 16 | p[15] << 24; } -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]; @@ -138,7 +155,7 @@ static int find_partition(BlockDriverState *bs, int partition, int ext_partnum = 4; int ret; - if ((ret = bdrv_read(bs, 0, data, 1)) < 0) { + if ((ret = blk_read(blk, 0, data, 1)) < 0) { errno = -ret; err(EXIT_FAILURE, "error while reading"); } @@ -158,7 +175,7 @@ static int find_partition(BlockDriverState *bs, int partition, uint8_t data1[512]; int j; - if ((ret = bdrv_read(bs, mbr[i].start_sector_abs, data1, 1)) < 0) { + if ((ret = blk_read(blk, mbr[i].start_sector_abs, data1, 1)) < 0) { errno = -ret; err(EXIT_FAILURE, "error while reading"); } @@ -191,6 +208,56 @@ static void termsig_handler(int signum) qemu_notify_event(); } +static void combine_addr(char *buf, size_t len, const char* address, + uint16_t port) +{ + /* If the address-part contains a colon, it's an IPv6 IP so needs [] */ + if (strstr(address, ":")) { + snprintf(buf, len, "[%s]:%u", address, port); + } else { + snprintf(buf, len, "%s:%u", address, port); + } +} + +static int tcp_socket_incoming(const char *address, uint16_t port) +{ + char address_and_port[128]; + Error *local_err = NULL; + + combine_addr(address_and_port, 128, address, port); + int fd = inet_listen(address_and_port, NULL, 0, SOCK_STREAM, 0, &local_err); + + if (local_err != NULL) { + error_report("%s", error_get_pretty(local_err)); + error_free(local_err); + } + return fd; +} + +static int unix_socket_incoming(const char *path) +{ + Error *local_err = NULL; + int fd = unix_listen(path, NULL, 0, &local_err); + + if (local_err != NULL) { + error_report("%s", error_get_pretty(local_err)); + error_free(local_err); + } + return fd; +} + +static int unix_socket_outgoing(const char *path) +{ + Error *local_err = NULL; + int fd = unix_connect(path, &local_err); + + if (local_err != NULL) { + error_report("%s", error_get_pretty(local_err)); + error_free(local_err); + } + return fd; +} + static void *show_parts(void *arg) { char *device = arg; @@ -226,19 +293,19 @@ static void *nbd_client_thread(void *arg) ret = nbd_receive_negotiate(sock, NULL, &nbdflags, &size, &blocksize); if (ret < 0) { - goto out; + 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; + fprintf(stderr, "Failed to open %s: %m\n", device); + goto out_socket; } ret = nbd_init(fd, sock, nbdflags, size, blocksize); if (ret < 0) { - goto out; + goto out_fd; } /* update partition table */ @@ -254,12 +321,16 @@ static void *nbd_client_thread(void *arg) ret = nbd_client(fd); if (ret) { - goto out; + goto out_fd; } close(fd); kill(getpid(), SIGTERM); return (void *) EXIT_SUCCESS; +out_fd: + close(fd); +out_socket: + closesocket(sock); out: kill(getpid(), SIGTERM); return (void *) EXIT_FAILURE; @@ -293,19 +364,29 @@ static void nbd_accept(void *opaque) socklen_t addr_len = sizeof(addr); int fd = accept(server_fd, (struct sockaddr *)&addr, &addr_len); + if (fd < 0) { + perror("accept"); + return; + } + if (state >= TERMINATE) { close(fd); return; } - if (fd >= 0 && nbd_client_new(exp, fd, nbd_client_closed)) { + if (nbd_client_new(exp, fd, nbd_client_closed)) { nb_fds++; + } else { + shutdown(fd, 2); + close(fd); } } int main(int argc, char **argv) { + BlockBackend *blk; BlockDriverState *bs; + BlockDriver *drv; off_t dev_offset = 0; uint32_t nbdflags = 0; bool disconnect = false; @@ -313,7 +394,9 @@ int main(int argc, char **argv) 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:"; struct option lopt[] = { { "help", 0, NULL, 'h' }, { "version", 0, NULL, 'V' }, @@ -326,13 +409,16 @@ 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' }, { NULL, 0, NULL, 0 } @@ -351,6 +437,9 @@ int main(int argc, char **argv) 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; /* The client thread uses SIGTERM to interrupt the server. A signal * handler ensures that "qemu-nbd -v -c" exits with a nice status code. @@ -359,6 +448,7 @@ int main(int argc, char **argv) memset(&sa_sigterm, 0, sizeof(sa_sigterm)); sa_sigterm.sa_handler = termsig_handler; sigaction(SIGTERM, &sa_sigterm, NULL); + qemu_init_exec_dir(argv[0]); while ((ch = getopt_long(argc, argv, sopt, lopt, &opt_ind)) != -1) { switch (ch) { @@ -401,6 +491,23 @@ int main(int argc, char **argv) errx(EXIT_FAILURE, "Invalid discard mode `%s'", optarg); } 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) { + errx(EXIT_FAILURE, "Failed to parse detect_zeroes mode: %s", + error_get_pretty(local_err)); + } + if (detect_zeroes == BLOCKDEV_DETECT_ZEROES_OPTIONS_UNMAP && + !(flags & BDRV_O_UNMAP)) { + errx(EXIT_FAILURE, "setting detect-zeroes to unmap is not allowed " + "without setting discard operation to unmap"); + } + break; case 'b': bindto = optarg; break; @@ -423,21 +530,35 @@ int main(int argc, char **argv) errx(EXIT_FAILURE, "Offset must be positive `%s'", optarg); } break; + case 'l': + if (strstart(optarg, SNAPSHOT_OPT_BASE, NULL)) { + sn_opts = qemu_opts_parse(&internal_snapshot_opts, optarg, 0); + if (!sn_opts) { + errx(EXIT_FAILURE, "Failed in parsing snapshot param `%s'", + optarg); + } + } 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) + if (*end) { errx(EXIT_FAILURE, "Invalid partition `%s'", optarg); - if (partition < 1 || partition > 8) + } + if (partition < 1 || partition > 8) { errx(EXIT_FAILURE, "Invalid partition %d", partition); + } break; case 'k': sockpath = optarg; - if (sockpath[0] != '/') + if (sockpath[0] != '/') { errx(EXIT_FAILURE, "socket path must be absolute\n"); + } break; case 'd': disconnect = true; @@ -454,9 +575,12 @@ int main(int argc, char **argv) errx(EXIT_FAILURE, "Shared device number must be greater than 0\n"); } break; - case 't': - persistent = 1; - break; + case 'f': + fmt = optarg; + break; + case 't': + persistent = 1; + break; case 'v': verbose = 1; break; @@ -491,7 +615,7 @@ int main(int argc, char **argv) printf("%s disconnected\n", argv[optind]); - return 0; + return 0; } if (device && !verbose) { @@ -551,28 +675,62 @@ int main(int argc, char **argv) snprintf(sockpath, 128, SOCKET_PATH, basename(device)); } - qemu_init_main_loop(); + if (qemu_init_main_loop(&local_err)) { + error_report("%s", error_get_pretty(local_err)); + error_free(local_err); + exit(EXIT_FAILURE); + } bdrv_init(); atexit(bdrv_close_all); - bs = bdrv_new("hda"); + if (fmt) { + drv = bdrv_find_format(fmt); + if (!drv) { + errx(EXIT_FAILURE, "Unknown file format '%s'", fmt); + } + } else { + drv = NULL; + } + + blk = blk_new_with_bs("hda", &error_abort); + bs = blk_bs(blk); + srcpath = argv[optind]; - if ((ret = bdrv_open(bs, srcpath, NULL, flags, NULL)) < 0) { + ret = bdrv_open(&bs, srcpath, NULL, NULL, flags, drv, &local_err); + if (ret < 0) { errno = -ret; - err(EXIT_FAILURE, "Failed to bdrv_open '%s'", argv[optind]); + err(EXIT_FAILURE, "Failed to bdrv_open '%s': %s", argv[optind], + error_get_pretty(local_err)); } - fd_size = bdrv_getlength(bs); + 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) { + errno = -ret; + err(EXIT_FAILURE, + "Failed to load snapshot: %s", + error_get_pretty(local_err)); + } + + bs->detect_zeroes = detect_zeroes; + fd_size = blk_getlength(blk); 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); } } - exp = nbd_export_new(bs, dev_offset, fd_size, nbdflags, nbd_export_closed); + exp = nbd_export_new(blk, dev_offset, fd_size, nbdflags, nbd_export_closed); if (sockpath) { fd = unix_socket_incoming(sockpath); @@ -617,11 +775,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);