[Libguestfs] [PATCH libnbd] copy: Refactor ‘struct rw’.
Nir Soffer
nsoffer at redhat.com
Mon Feb 22 17:06:39 UTC 2021
On Mon, Feb 22, 2021 at 5:42 PM Richard W.M. Jones <rjones at redhat.com> wrote:
>
> Make this a (fairly) abstract structure. At least hide the subtype
> fields from the main program. This change is pure refactoring and
> doesn’t change the semantics.
Nicer this way, although a little less type safe.
> ---
> copy/file-ops.c | 164 +++++++++++++++++--
> copy/main.c | 315 ++++++++----------------------------
> copy/multi-thread-copying.c | 104 ++++++------
> copy/nbd-ops.c | 248 +++++++++++++++++++++++++---
> copy/nbdcopy.h | 53 +++---
> copy/null-ops.c | 50 +++++-
> copy/pipe-ops.c | 64 +++++++-
> copy/synch-copying.c | 30 ++--
> 8 files changed, 649 insertions(+), 379 deletions(-)
>
> diff --git a/copy/file-ops.c b/copy/file-ops.c
> index 2a239d0..81b08ce 100644
> --- a/copy/file-ops.c
> +++ b/copy/file-ops.c
> @@ -36,35 +36,159 @@
> #include "isaligned.h"
> #include "nbdcopy.h"
>
> +static struct rw_ops file_ops;
> +
> +struct rw_file {
> + struct rw rw;
> + int fd;
> + struct stat stat;
> + bool seek_hole_supported;
> + int sector_size;
> +};
> +
> +static bool
> +seek_hole_supported (int fd)
> +{
> +#ifndef SEEK_HOLE
> + return false;
> +#else
> + off_t r = lseek (fd, 0, SEEK_HOLE);
> + return r >= 0;
> +#endif
> +}
> +
> +struct rw *
> +file_create (const char *name, const struct stat *stat, int fd)
> +{
> + struct rw_file *rwf = calloc (1, sizeof *rwf);
> + if (rwf == NULL) { perror ("calloc"); exit (EXIT_FAILURE); }
> +
> + rwf->rw.ops = &file_ops;
> + rwf->rw.name = name;
> + rwf->stat = *stat;
> + rwf->fd = fd;
> +
> + if (S_ISBLK (stat->st_mode)) {
> + /* Block device. */
> + rwf->rw.size = lseek (fd, 0, SEEK_END);
> + if (rwf->rw.size == -1) {
> + perror ("lseek");
> + exit (EXIT_FAILURE);
> + }
> + if (lseek (fd, 0, SEEK_SET) == -1) {
> + perror ("lseek");
> + exit (EXIT_FAILURE);
> + }
> + rwf->seek_hole_supported = seek_hole_supported (fd);
> + rwf->sector_size = 4096;
> +#ifdef BLKSSZGET
> + if (ioctl (fd, BLKSSZGET, &rwf->sector_size))
> + fprintf (stderr, "warning: cannot get sector size: %s: %m", name);
> +#endif
> + }
> + else if (S_ISREG (stat->st_mode)) {
> + /* Regular file. */
> + rwf->rw.size = stat->st_size;
> + rwf->seek_hole_supported = seek_hole_supported (fd);
> + }
> + else
> + abort ();
> +
> + return (struct rw *) rwf;
We can avoid the cast here by returning &(rwf->rw)
> +}
> +
> static void
> file_close (struct rw *rw)
> {
> - if (close (rw->u.local.fd) == -1) {
> + struct rw_file *rwf = (struct rw_file *)rw;
> +
> + if (close (rwf->fd) == -1) {
> fprintf (stderr, "%s: close: %m\n", rw->name);
> exit (EXIT_FAILURE);
> }
> + free (rw);
> +}
> +
> +static void
> +file_truncate (struct rw *rw, int64_t size)
> +{
> + struct rw_file *rwf = (struct rw_file *) rw;
> +
> + /* If the destination is an ordinary file then the original file
> + * size doesn't matter. Truncate it to the source size. But
> + * truncate it to zero first so the file is completely empty and
> + * sparse.
> + */
> + if (! S_ISREG (rwf->stat.st_mode))
> + return;
> +
> + if (ftruncate (rwf->fd, 0) == -1 ||
> + ftruncate (rwf->fd, size) == -1) {
> + perror ("truncate");
> + exit (EXIT_FAILURE);
> + }
> + rwf->rw.size = size;
> +
> + /* We can assume the destination is zero. */
> + destination_is_zero = true;
> }
>
> static void
> file_flush (struct rw *rw)
> {
> - if ((S_ISREG (rw->u.local.stat.st_mode) ||
> - S_ISBLK (rw->u.local.stat.st_mode)) &&
> - fsync (rw->u.local.fd) == -1) {
> + struct rw_file *rwf = (struct rw_file *)rw;
> +
> + if ((S_ISREG (rwf->stat.st_mode) ||
> + S_ISBLK (rwf->stat.st_mode)) &&
> + fsync (rwf->fd) == -1) {
> perror (rw->name);
> exit (EXIT_FAILURE);
> }
> }
>
> +static bool
> +file_is_read_only (struct rw *rw)
> +{
> + /* Permissions are hard, and this is only used as an early check
> + * before the copy. Proceed with the copy and fail if it fails.
> + */
> + return false;
> +}
> +
> +static bool
> +file_can_extents (struct rw *rw)
> +{
> +#ifdef SEEK_HOLE
> + return true;
> +#else
> + return false;
> +#endif
> +}
> +
> +static bool
> +file_can_multi_conn (struct rw *rw)
> +{
> + return true;
> +}
> +
> +static void
> +file_start_multi_conn (struct rw *rw)
> +{
> + /* Don't need to do anything for files since we can read/write on a
> + * single file descriptor.
> + */
> +}
> +
> static size_t
> file_synch_read (struct rw *rw,
> void *data, size_t len, uint64_t offset)
> {
> + struct rw_file *rwf = (struct rw_file *)rw;
> size_t n = 0;
> ssize_t r;
>
> while (len > 0) {
> - r = pread (rw->u.local.fd, data, len, offset);
> + r = pread (rwf->fd, data, len, offset);
> if (r == -1) {
> perror (rw->name);
> exit (EXIT_FAILURE);
> @@ -85,10 +209,11 @@ static void
> file_synch_write (struct rw *rw,
> const void *data, size_t len, uint64_t offset)
> {
> + struct rw_file *rwf = (struct rw_file *)rw;
> ssize_t r;
>
> while (len > 0) {
> - r = pwrite (rw->u.local.fd, data, len, offset);
> + r = pwrite (rwf->fd, data, len, offset);
> if (r == -1) {
> perror (rw->name);
> exit (EXIT_FAILURE);
> @@ -103,7 +228,8 @@ static bool
> file_synch_trim (struct rw *rw, uint64_t offset, uint64_t count)
> {
> #ifdef FALLOC_FL_PUNCH_HOLE
> - int fd = rw->u.local.fd;
> + struct rw_file *rwf = (struct rw_file *)rw;
> + int fd = rwf->fd;
> int r;
>
> r = fallocate (fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
> @@ -121,9 +247,11 @@ file_synch_trim (struct rw *rw, uint64_t offset, uint64_t count)
> static bool
> file_synch_zero (struct rw *rw, uint64_t offset, uint64_t count)
> {
> - if (S_ISREG (rw->u.local.stat.st_mode)) {
> + struct rw_file *rwf = (struct rw_file *)rw;
> +
> + if (S_ISREG (rwf->stat.st_mode)) {
> #ifdef FALLOC_FL_ZERO_RANGE
> - int fd = rw->u.local.fd;
> + int fd = rwf->fd;
> int r;
>
> r = fallocate (fd, FALLOC_FL_ZERO_RANGE, offset, count);
> @@ -134,10 +262,10 @@ file_synch_zero (struct rw *rw, uint64_t offset, uint64_t count)
> return true;
> #endif
> }
> - else if (S_ISBLK (rw->u.local.stat.st_mode) &&
> - IS_ALIGNED (offset | count, rw->u.local.sector_size)) {
> + else if (S_ISBLK (rwf->stat.st_mode) &&
> + IS_ALIGNED (offset | count, rwf->sector_size)) {
> #ifdef BLKZEROOUT
> - int fd = rw->u.local.fd;
> + int fd = rwf->fd;
> int r;
> uint64_t range[2] = {offset, count};
>
> @@ -223,11 +351,12 @@ file_get_extents (struct rw *rw, uintptr_t index,
> ret->size = 0;
>
> #ifdef SEEK_HOLE
> + struct rw_file *rwf = (struct rw_file *)rw;
> static pthread_mutex_t lseek_lock = PTHREAD_MUTEX_INITIALIZER;
>
> - if (rw->u.local.seek_hole_supported) {
> + if (rwf->seek_hole_supported) {
> uint64_t end = offset + count;
> - int fd = rw->u.local.fd;
> + int fd = rwf->fd;
> off_t pos;
> struct extent e;
> size_t last;
> @@ -302,9 +431,14 @@ file_get_extents (struct rw *rw, uintptr_t index,
> default_get_extents (rw, index, offset, count, ret);
> }
>
> -struct rw_ops file_ops = {
> +static struct rw_ops file_ops = {
> .ops_name = "file_ops",
> .close = file_close,
> + .is_read_only = file_is_read_only,
> + .can_extents = file_can_extents,
> + .can_multi_conn = file_can_multi_conn,
> + .start_multi_conn = file_start_multi_conn,
> + .truncate = file_truncate,
> .flush = file_flush,
> .synch_read = file_synch_read,
> .synch_write = file_synch_write,
> diff --git a/copy/main.c b/copy/main.c
> index 68a6030..6fdc6fd 100644
> --- a/copy/main.c
> +++ b/copy/main.c
> @@ -53,19 +53,11 @@ int progress_fd = -1; /* --progress=FD */
> unsigned sparse_size = 4096; /* --sparse */
> bool synchronous; /* --synchronous flag */
> unsigned threads; /* --threads */
> -struct rw src, dst; /* The source and destination. */
> +struct rw *src, *dst; /* The source and destination. */
> bool verbose; /* --verbose flag */
>
> static bool is_nbd_uri (const char *s);
> -static bool seek_hole_supported (int fd);
> -static void open_null (struct rw *rw);
> -static int open_local (const char *prog,
> - const char *filename, bool writing, struct rw *rw);
> -static void open_nbd_uri (const char *prog,
> - const char *uri, bool writing, struct rw *rw);
> -static void open_nbd_subprocess (const char *prog,
> - const char **argv, size_t argc,
> - bool writing, struct rw *rw);
> +static struct rw *open_local (const char *filename, bool writing);
> static void print_rw (struct rw *rw, const char *prefix, FILE *fp);
>
> static void __attribute__((noreturn))
> @@ -242,18 +234,18 @@ main (int argc, char *argv[])
>
> found1:
> connections = 1; /* multi-conn not supported */
> - src.name = argv[optind+1];
> - open_nbd_subprocess (argv[0],
> - (const char **) &argv[optind+1], i-optind-1,
> - false, &src);
> + src =
> + nbd_rw_create_subprocess ((const char **) &argv[optind+1], i-optind-1,
> + false);
> optind = i+1;
> }
> else { /* Source is not [...]. */
> - src.name = argv[optind++];
> - if (! is_nbd_uri (src.name))
> - src.u.local.fd = open_local (argv[0], src.name, false, &src);
> + const char *src_name = argv[optind++];
> +
> + if (! is_nbd_uri (src_name))
> + src = open_local (src_name, false);
> else
> - open_nbd_uri (argv[0], src.name, false, &src);
> + src = nbd_rw_create_uri (src_name, src_name, false);
> }
>
> if (optind >= argc)
> @@ -267,48 +259,46 @@ main (int argc, char *argv[])
>
> found2:
> connections = 1; /* multi-conn not supported */
> - dst.name = argv[optind+1];
> - open_nbd_subprocess (argv[0],
> - (const char **) &argv[optind+1], i-optind-1,
> - true, &dst);
> + dst =
> + nbd_rw_create_subprocess ((const char **) &argv[optind+1], i-optind-1,
> + true);
> optind = i+1;
> }
> else { /* Destination is not [...] */
> - dst.name = argv[optind++];
> - if (strcmp (dst.name, "null:") == 0)
> - open_null (&dst);
> - else if (! is_nbd_uri (dst.name))
> - dst.u.local.fd = open_local (argv[0], dst.name, true /* writing */, &dst);
> - else {
> - open_nbd_uri (argv[0], dst.name, true, &dst);
> + const char *dst_name = argv[optind++];
>
> - /* Obviously this is not going to work if the server is
> - * advertising read-only, so fail early with a nice error message.
> - */
> - if (nbd_is_read_only (dst.u.nbd.handles.ptr[0])) {
> - fprintf (stderr, "%s: %s: "
> - "this NBD server is read-only, cannot write to it\n",
> - argv[0], dst.name);
> - exit (EXIT_FAILURE);
> - }
> - }
> + if (strcmp (dst_name, "null:") == 0)
> + dst = null_create (dst_name);
> + else if (! is_nbd_uri (dst_name))
> + dst = open_local (dst_name, true /* writing */);
> + else
> + dst = nbd_rw_create_uri (dst_name, dst_name, true);
> }
>
> /* There must be no extra parameters. */
> if (optind != argc)
> usage (stderr, EXIT_FAILURE);
>
> - /* Check we've set the fields of src and dst. */
> - assert (src.ops);
> - assert (src.name);
> - assert (dst.ops);
> - assert (dst.name);
> + /* Check we've created src and dst and set the expected fields. */
> + assert (src != NULL);
> + assert (dst != NULL);
> + assert (src->ops != NULL);
> + assert (src->name != NULL);
> + assert (dst->ops != NULL);
> + assert (dst->name != NULL);
> +
> + /* Obviously this is not going to work if the destination is
> + * read-only, so fail early with a nice error message.
> + */
> + if (dst->ops->is_read_only (dst)) {
> + fprintf (stderr, "%s: %s: "
> + "the destination is read-only, cannot write to it\n",
> + argv[0], dst->name);
> + exit (EXIT_FAILURE);
> + }
>
> /* If multi-conn is not supported, force connections to 1. */
> - if ((src.ops == &nbd_ops &&
> - ! nbd_can_multi_conn (src.u.nbd.handles.ptr[0])) ||
> - (dst.ops == &nbd_ops &&
> - ! nbd_can_multi_conn (dst.u.nbd.handles.ptr[0])))
> + if (! src->ops->can_multi_conn (src) || ! dst->ops->can_multi_conn (dst))
> connections = 1;
>
> /* Calculate the number of threads from the number of connections. */
> @@ -335,44 +325,17 @@ main (int argc, char *argv[])
> if (threads < connections)
> connections = threads;
>
> - /* Calculate the source and destination sizes. We set these to -1
> - * if the size is not known (because it's a stream). Note that for
> - * local types, open_local set something in *.size already.
> + /* Truncate the destination to the same size as the source. Only
> + * has an effect on regular files.
> */
> - if (src.ops == &nbd_ops) {
> - src.size = nbd_get_size (src.u.nbd.handles.ptr[0]);
> - if (src.size == -1) {
> - fprintf (stderr, "%s: %s: %s\n", argv[0], src.name, nbd_get_error ());
> - exit (EXIT_FAILURE);
> - }
> - }
> - if (dst.ops != &nbd_ops && S_ISREG (dst.u.local.stat.st_mode)) {
> - /* If the destination is an ordinary file then the original file
> - * size doesn't matter. Truncate it to the source size. But
> - * truncate it to zero first so the file is completely empty and
> - * sparse.
> - */
> - dst.size = src.size;
> - if (ftruncate (dst.u.local.fd, 0) == -1 ||
> - ftruncate (dst.u.local.fd, dst.size) == -1) {
> - perror ("truncate");
> - exit (EXIT_FAILURE);
> - }
> - destination_is_zero = true;
> - }
> - else if (dst.ops == &nbd_ops) {
> - dst.size = nbd_get_size (dst.u.nbd.handles.ptr[0]);
> - if (dst.size == -1) {
> - fprintf (stderr, "%s: %s: %s\n", argv[0], dst.name, nbd_get_error ());
> - exit (EXIT_FAILURE);
> - }
> - }
> + if (dst->ops->truncate)
> + dst->ops->truncate (dst, src->size);
>
> /* Check if the source is bigger than the destination, since that
> * would truncate (ie. lose) data. Copying from smaller to larger
> * is OK.
> */
> - if (src.size >= 0 && dst.size >= 0 && src.size > dst.size) {
> + if (src->size >= 0 && dst->size >= 0 && src->size > dst->size) {
> fprintf (stderr,
> "nbdcopy: error: destination size is smaller than source size\n");
> exit (EXIT_FAILURE);
> @@ -383,37 +346,29 @@ main (int argc, char *argv[])
> * settings.
> */
> if (verbose) {
> - print_rw (&src, "nbdcopy: src", stderr);
> - print_rw (&dst, "nbdcopy: dst", stderr);
> + print_rw (src, "nbdcopy: src", stderr);
> + print_rw (dst, "nbdcopy: dst", stderr);
> fprintf (stderr, "nbdcopy: connections=%u requests=%u threads=%u "
> "synchronous=%s\n",
> connections, max_requests, threads,
> synchronous ? "true" : "false");
> }
>
> - /* If #connections > 1 then multi-conn is enabled at both ends and
> - * we need to open further connections.
> + /* If multi-conn is enabled on either side, then at this point we
> + * need to ask the backend to open the extra connections.
> */
> if (connections > 1) {
> assert (threads == connections);
> -
> - if (src.ops == &nbd_ops) {
> - for (i = 1; i < connections; ++i)
> - open_nbd_uri (argv[0], src.name, false, &src);
> - assert (src.u.nbd.handles.size == connections);
> - }
> - if (dst.ops == &nbd_ops) {
> - for (i = 1; i < connections; ++i)
> - open_nbd_uri (argv[0], dst.name, true, &dst);
> - assert (dst.u.nbd.handles.size == connections);
> - }
> + if (src->ops->can_multi_conn (src))
> + src->ops->start_multi_conn (src);
> + if (dst->ops->can_multi_conn (dst))
> + dst->ops->start_multi_conn (dst);
> }
>
> /* If the source is NBD and we couldn't negotiate meta
> * base:allocation then turn off extents.
> */
> - if (src.ops == &nbd_ops &&
> - !nbd_can_meta_context (src.u.nbd.handles.ptr[0], "base:allocation"))
> + if (! src->ops->can_extents (src))
> extents = false;
>
> /* Always set the progress bar to 0% at the start of the copy. */
> @@ -429,12 +384,12 @@ main (int argc, char *argv[])
> progress_bar (1, 1);
>
> /* Shut down the source side. */
> - src.ops->close (&src);
> + src->ops->close (src);
>
> /* Shut down the destination side. */
> if (flush)
> - dst.ops->flush (&dst);
> - dst.ops->close (&dst);
> + dst->ops->flush (dst);
> + dst->ops->close (dst);
>
> exit (EXIT_SUCCESS);
> }
> @@ -452,33 +407,25 @@ is_nbd_uri (const char *s)
> strncmp (s, "nbds+vsock:", 11) == 0;
> }
>
> -/* Open null: (destination only). */
> -static void
> -open_null (struct rw *rw)
> -{
> - rw->ops = &null_ops;
> - rw->size = INT64_MAX;
> -}
> -
> /* Open a local (non-NBD) file, ie. a file, device, or "-" for stdio.
> - * Returns the open file descriptor which the caller must close.
> + * Returns the struct rw * which the caller must close.
> *
> * “writing” is true if this is the destination parameter.
> * “rw->u.local.stat” and “rw->size” return the file stat and size,
> * but size can be returned as -1 if we don't know the size (if it's a
> * pipe or stdio).
> */
> -static int
> -open_local (const char *prog,
> - const char *filename, bool writing, struct rw *rw)
> +static struct rw *
> +open_local (const char *filename, bool writing)
> {
> int flags, fd;
> + struct stat stat;
>
> if (strcmp (filename, "-") == 0) {
> synchronous = true;
> fd = writing ? STDOUT_FILENO : STDIN_FILENO;
> if (writing && isatty (fd)) {
> - fprintf (stderr, "%s: refusing to write to tty\n", prog);
> + fprintf (stderr, "%s: refusing to write to tty\n", "nbdcopy");
Is it intended to replace prog with "nbdcopy"?
> exit (EXIT_FAILURE);
> }
> }
> @@ -502,146 +449,17 @@ open_local (const char *prog,
> }
> }
>
> - if (fstat (fd, &rw->u.local.stat) == -1) {
> + if (fstat (fd, &stat) == -1) {
> perror (filename);
> exit (EXIT_FAILURE);
> }
> - if (S_ISBLK (rw->u.local.stat.st_mode)) {
> - /* Block device. */
> - rw->ops = &file_ops;
> - rw->size = lseek (fd, 0, SEEK_END);
> - if (rw->size == -1) {
> - perror ("lseek");
> - exit (EXIT_FAILURE);
> - }
> - if (lseek (fd, 0, SEEK_SET) == -1) {
> - perror ("lseek");
> - exit (EXIT_FAILURE);
> - }
> - rw->u.local.seek_hole_supported = seek_hole_supported (fd);
> - rw->u.local.sector_size = 4096;
> -#ifdef BLKSSZGET
> - if (ioctl (fd, BLKSSZGET, &rw->u.local.sector_size))
> - fprintf (stderr, "warning: cannot get sector size: %s: %m", rw->name);
> -#endif
> - }
> - else if (S_ISREG (rw->u.local.stat.st_mode)) {
> - /* Regular file. */
> - rw->ops = &file_ops;
> - rw->size = rw->u.local.stat.st_size;
> - rw->u.local.seek_hole_supported = seek_hole_supported (fd);
> - }
> + if (S_ISBLK (stat.st_mode) || S_ISREG (stat.st_mode))
> + return file_create (filename, &stat, fd);
> else {
> - /* Probably stdin/stdout, a pipe or a socket. Set size == -1
> - * which means don't know, and force synchronous mode.
> - */
> - synchronous = true;
> - rw->ops = &pipe_ops;
> - rw->size = -1;
> - rw->u.local.seek_hole_supported = false;
> + /* Probably stdin/stdout, a pipe or a socket. */
> + synchronous = true; /* Force synchronous mode for pipes. */
> + return pipe_create (filename, fd);
> }
> -
> - return fd;
> -}
> -
> -static bool
> -seek_hole_supported (int fd)
> -{
> -#ifndef SEEK_HOLE
> - return false;
> -#else
> - off_t r = lseek (fd, 0, SEEK_HOLE);
> - return r >= 0;
> -#endif
> -}
> -
> -static void
> -open_nbd_uri (const char *prog,
> - const char *uri, bool writing, struct rw *rw)
> -{
> - struct nbd_handle *nbd;
> -
> - rw->ops = &nbd_ops;
> - nbd = nbd_create ();
> - if (nbd == NULL) {
> - fprintf (stderr, "%s: %s\n", prog, nbd_get_error ());
> - exit (EXIT_FAILURE);
> - }
> - nbd_set_debug (nbd, verbose);
> - nbd_set_uri_allow_local_file (nbd, true); /* Allow ?tls-psk-file. */
> - if (extents && !writing &&
> - nbd_add_meta_context (nbd, "base:allocation") == -1) {
> - fprintf (stderr, "%s: %s\n", prog, nbd_get_error ());
> - exit (EXIT_FAILURE);
> - }
> -
> - if (handles_append (&rw->u.nbd.handles, nbd) == -1) {
> - perror ("realloc");
> - exit (EXIT_FAILURE);
> - }
> -
> - if (nbd_connect_uri (nbd, uri) == -1) {
> - fprintf (stderr, "%s: %s: %s\n", prog, uri, nbd_get_error ());
> - exit (EXIT_FAILURE);
> - }
> -
> - /* Cache these. We assume with multi-conn that each handle will act
> - * the same way.
> - */
> - rw->u.nbd.can_trim = nbd_can_trim (nbd) > 0;
> - rw->u.nbd.can_zero = nbd_can_zero (nbd) > 0;
> -}
> -
> -DEFINE_VECTOR_TYPE (const_string_vector, const char *);
> -
> -static void
> -open_nbd_subprocess (const char *prog,
> - const char **argv, size_t argc,
> - bool writing, struct rw *rw)
> -{
> - struct nbd_handle *nbd;
> - const_string_vector copy = empty_vector;
> - size_t i;
> -
> - rw->ops = &nbd_ops;
> - nbd = nbd_create ();
> - if (nbd == NULL) {
> - fprintf (stderr, "%s: %s\n", prog, nbd_get_error ());
> - exit (EXIT_FAILURE);
> - }
> - nbd_set_debug (nbd, verbose);
> - if (extents && !writing &&
> - nbd_add_meta_context (nbd, "base:allocation") == -1) {
> - fprintf (stderr, "%s: %s\n", prog, nbd_get_error ());
> - exit (EXIT_FAILURE);
> - }
> -
> - if (handles_append (&rw->u.nbd.handles, nbd) == -1) {
> - memory_error:
> - perror ("realloc");
> - exit (EXIT_FAILURE);
> - }
> -
> - /* We have to copy the args so we can null-terminate them. */
> - for (i = 0; i < argc; ++i) {
> - if (const_string_vector_append (©, argv[i]) == -1)
> - goto memory_error;
> - }
> - if (const_string_vector_append (©, NULL) == -1)
> - goto memory_error;
> -
> - if (nbd_connect_systemd_socket_activation (nbd, (char **) copy.ptr) == -1) {
> - fprintf (stderr, "%s: %s: %s\n", prog, argv[0], nbd_get_error ());
> - exit (EXIT_FAILURE);
> - }
> -
> - /* Cache these. We assume with multi-conn that each handle will act
> - * the same way.
> - */
> - rw->u.nbd.can_trim = nbd_can_trim (nbd) > 0;
> - rw->u.nbd.can_zero = nbd_can_zero (nbd) > 0;
> -
> - free (copy.ptr);
> }
>
> /* Print an rw struct, used in --verbose mode. */
> @@ -650,7 +468,6 @@ print_rw (struct rw *rw, const char *prefix, FILE *fp)
> {
> fprintf (fp, "%s: %s \"%s\"\n", prefix, rw->ops->ops_name, rw->name);
> fprintf (fp, "%s: size=%" PRIi64 "\n", prefix, rw->size);
> - /* Could print other stuff here, but that's enough for debugging. */
> }
>
> /* Default implementation of rw->ops->get_extents for backends which
> diff --git a/copy/multi-thread-copying.c b/copy/multi-thread-copying.c
> index 98b4056..4f57054 100644
> --- a/copy/multi-thread-copying.c
> +++ b/copy/multi-thread-copying.c
> @@ -50,13 +50,13 @@ get_next_offset (uint64_t *offset, uint64_t *count)
> bool r = false; /* returning false means no more work */
>
> pthread_mutex_lock (&lock);
> - if (next_offset < src.size) {
> + if (next_offset < src->size) {
> *offset = next_offset;
>
> /* Work out how large this range is. The last range may be
> * smaller than THREAD_WORK_SIZE.
> */
> - *count = src.size - *offset;
> + *count = src->size - *offset;
> if (*count > THREAD_WORK_SIZE)
> *count = THREAD_WORK_SIZE;
>
> @@ -69,7 +69,7 @@ get_next_offset (uint64_t *offset, uint64_t *count)
> * are called from threads and not necessarily in monotonic order
> * so the progress bar would move erratically.
> */
> - progress_bar (*offset, dst.size);
> + progress_bar (*offset, dst->size);
> }
> pthread_mutex_unlock (&lock);
> return r;
> @@ -89,11 +89,13 @@ multi_thread_copying (void)
> */
> assert (threads > 0);
> assert (threads == connections);
> +/*
> if (src.ops == &nbd_ops)
> assert (src.u.nbd.handles.size == connections);
> if (dst.ops == &nbd_ops)
> assert (dst.u.nbd.handles.size == connections);
> - assert (src.size != -1);
> +*/
> + assert (src->size != -1);
>
> workers = malloc (sizeof (pthread_t) * threads);
> if (workers == NULL) {
> @@ -147,9 +149,9 @@ worker_thread (void *indexp)
>
> assert (0 < count && count <= THREAD_WORK_SIZE);
> if (extents)
> - src.ops->get_extents (&src, index, offset, count, &exts);
> + src->ops->get_extents (src, index, offset, count, &exts);
> else
> - default_get_extents (&src, index, offset, count, &exts);
> + default_get_extents (src, index, offset, count, &exts);
>
> for (i = 0; i < exts.size; ++i) {
> struct command *command;
> @@ -208,11 +210,11 @@ worker_thread (void *indexp)
> wait_for_request_slots (index);
>
> /* Begin the asynch read operation. */
> - src.ops->asynch_read (&src, command,
> - (nbd_completion_callback) {
> - .callback = finished_read,
> - .user_data = command,
> - });
> + src->ops->asynch_read (src, command,
> + (nbd_completion_callback) {
> + .callback = finished_read,
> + .user_data = command,
> + });
>
> exts.ptr[i].offset += len;
> exts.ptr[i].length -= len;
> @@ -254,7 +256,7 @@ wait_for_request_slots (uintptr_t index)
> static unsigned
> in_flight (uintptr_t index)
> {
> - return src.ops->in_flight (&src, index) + dst.ops->in_flight (&dst, index);
> + return src->ops->in_flight (src, index) + dst->ops->in_flight (dst, index);
> }
>
> /* Poll (optional) NBD src and NBD dst, moving the state machine(s)
> @@ -271,7 +273,7 @@ poll_both_ends (uintptr_t index)
> /* Note: if polling is not supported, this function will
> * set fd == -1 which poll ignores.
> */
> - src.ops->get_polling_fd (&src, index, &fds[0].fd, &direction);
> + src->ops->get_polling_fd (src, index, &fds[0].fd, &direction);
> if (fds[0].fd >= 0) {
> switch (direction) {
> case LIBNBD_AIO_DIRECTION_READ:
> @@ -286,7 +288,7 @@ poll_both_ends (uintptr_t index)
> }
> }
>
> - dst.ops->get_polling_fd (&dst, index, &fds[1].fd, &direction);
> + dst->ops->get_polling_fd (dst, index, &fds[1].fd, &direction);
> if (fds[1].fd >= 0) {
> switch (direction) {
> case LIBNBD_AIO_DIRECTION_READ:
> @@ -311,24 +313,24 @@ poll_both_ends (uintptr_t index)
>
> if (fds[0].fd >= 0) {
> if ((fds[0].revents & (POLLIN | POLLHUP)) != 0)
> - src.ops->asynch_notify_read (&src, index);
> + src->ops->asynch_notify_read (src, index);
> else if ((fds[0].revents & POLLOUT) != 0)
> - src.ops->asynch_notify_write (&src, index);
> + src->ops->asynch_notify_write (src, index);
> else if ((fds[0].revents & (POLLERR | POLLNVAL)) != 0) {
> errno = ENOTCONN;
> - perror (src.name);
> + perror (src->name);
> exit (EXIT_FAILURE);
> }
> }
>
> if (fds[1].fd >= 0) {
> if ((fds[1].revents & (POLLIN | POLLHUP)) != 0)
> - dst.ops->asynch_notify_read (&dst, index);
> + dst->ops->asynch_notify_read (dst, index);
> else if ((fds[1].revents & POLLOUT) != 0)
> - dst.ops->asynch_notify_write (&dst, index);
> + dst->ops->asynch_notify_write (dst, index);
> else if ((fds[1].revents & (POLLERR | POLLNVAL)) != 0) {
> errno = ENOTCONN;
> - perror (dst.name);
> + perror (dst->name);
> exit (EXIT_FAILURE);
> }
> }
> @@ -377,11 +379,11 @@ finished_read (void *vp, int *error)
> /* If sparseness detection (see below) is turned off then we write
> * the whole command.
> */
> - dst.ops->asynch_write (&dst, command,
> - (nbd_completion_callback) {
> - .callback = free_command,
> - .user_data = command,
> - });
> + dst->ops->asynch_write (dst, command,
> + (nbd_completion_callback) {
> + .callback = free_command,
> + .user_data = command,
> + });
> }
> else { /* Sparseness detection. */
> const uint64_t start = command->offset;
> @@ -408,11 +410,11 @@ finished_read (void *vp, int *error)
> newcommand = copy_subcommand (command,
> last_offset, i - last_offset,
> false);
> - dst.ops->asynch_write (&dst, newcommand,
> - (nbd_completion_callback) {
> - .callback = free_command,
> - .user_data = newcommand,
> - });
> + dst->ops->asynch_write (dst, newcommand,
> + (nbd_completion_callback) {
> + .callback = free_command,
> + .user_data = newcommand,
> + });
> }
> /* Start the new hole. */
> last_offset = i;
> @@ -445,11 +447,11 @@ finished_read (void *vp, int *error)
> newcommand = copy_subcommand (command,
> last_offset, i - last_offset,
> false);
> - dst.ops->asynch_write (&dst, newcommand,
> - (nbd_completion_callback) {
> - .callback = free_command,
> - .user_data = newcommand,
> - });
> + dst->ops->asynch_write (dst, newcommand,
> + (nbd_completion_callback) {
> + .callback = free_command,
> + .user_data = newcommand,
> + });
> }
> else {
> newcommand = copy_subcommand (command,
> @@ -462,11 +464,11 @@ finished_read (void *vp, int *error)
> /* There may be an unaligned tail, so write that. */
> if (end - i > 0) {
> newcommand = copy_subcommand (command, i, end - i, false);
> - dst.ops->asynch_write (&dst, newcommand,
> - (nbd_completion_callback) {
> - .callback = free_command,
> - .user_data = newcommand,
> - });
> + dst->ops->asynch_write (dst, newcommand,
> + (nbd_completion_callback) {
> + .callback = free_command,
> + .user_data = newcommand,
> + });
> }
>
> /* Free the original command since it has been split into
> @@ -503,20 +505,20 @@ fill_dst_range_with_zeroes (struct command *command)
>
> if (!allocated) {
> /* Try trimming. */
> - if (dst.ops->asynch_trim (&dst, command,
> - (nbd_completion_callback) {
> - .callback = free_command,
> - .user_data = command,
> - }))
> + if (dst->ops->asynch_trim (dst, command,
> + (nbd_completion_callback) {
> + .callback = free_command,
> + .user_data = command,
> + }))
> return;
> }
>
> /* Try efficient zeroing. */
> - if (dst.ops->asynch_zero (&dst, command,
> - (nbd_completion_callback) {
> - .callback = free_command,
> - .user_data = command,
> - }))
> + if (dst->ops->asynch_zero (dst, command,
> + (nbd_completion_callback) {
> + .callback = free_command,
> + .user_data = command,
> + }))
> return;
>
> /* Fall back to loop writing zeroes. This is going to be slow
> @@ -533,7 +535,7 @@ fill_dst_range_with_zeroes (struct command *command)
> if (len > MAX_REQUEST_SIZE)
> len = MAX_REQUEST_SIZE;
>
> - dst.ops->synch_write (&dst, data, len, command->offset);
> + dst->ops->synch_write (dst, data, len, command->offset);
> command->slice.len -= len;
> command->offset += len;
> }
> diff --git a/copy/nbd-ops.c b/copy/nbd-ops.c
> index 7b48cbc..24970c2 100644
> --- a/copy/nbd-ops.c
> +++ b/copy/nbd-ops.c
> @@ -27,45 +27,221 @@
>
> #include "nbdcopy.h"
>
> +static struct rw_ops nbd_ops;
> +
> +DEFINE_VECTOR_TYPE (handles, struct nbd_handle *)
> +DEFINE_VECTOR_TYPE (const_string_vector, const char *);
> +
> +struct rw_nbd {
> + struct rw rw;
> +
> + /* Because of multi-conn we have to remember enough state in this
> + * handle in order to be able to open another connection with the
> + * same parameters after nbd_rw_create* has been called once.
> + */
> + enum { CREATE_URI, CREATE_SUBPROCESS } create_t;
> + const char *uri; /* For CREATE_URI */
> + const_string_vector argv; /* For CREATE_SUBPROCESS */
> + bool writing;
> +
> + handles handles; /* One handle per connection. */
> + bool can_trim, can_zero; /* Cached nbd_can_trim, nbd_can_zero. */
> +};
> +
> +static void
> +open_one_nbd_handle (struct rw_nbd *rwn)
> +{
> + struct nbd_handle *nbd;
> +
> + nbd = nbd_create ();
> + if (nbd == NULL) {
> + fprintf (stderr, "%s: %s\n", "nbdcopy", nbd_get_error ());
> + exit (EXIT_FAILURE);
> + }
> +
> + nbd_set_debug (nbd, verbose);
> +
> + if (extents && !rwn->writing &&
> + nbd_add_meta_context (nbd, "base:allocation") == -1) {
> + fprintf (stderr, "%s: %s\n", "nbdcopy", nbd_get_error ());
> + exit (EXIT_FAILURE);
> + }
> +
> + switch (rwn->create_t) {
> + case CREATE_URI:
> + nbd_set_uri_allow_local_file (nbd, true); /* Allow ?tls-psk-file. */
> +
> + if (nbd_connect_uri (nbd, rwn->uri) == -1) {
> + fprintf (stderr, "%s: %s: %s\n", "nbdcopy", rwn->uri, nbd_get_error ());
> + exit (EXIT_FAILURE);
> + }
> + break;
> +
> + case CREATE_SUBPROCESS:
> + if (nbd_connect_systemd_socket_activation (nbd,
> + (char **) rwn->argv.ptr)
> + == -1) {
> + fprintf (stderr, "%s: %s: %s\n", "nbdcopy", rwn->argv.ptr[0],
> + nbd_get_error ());
> + exit (EXIT_FAILURE);
> + }
> + }
> +
> + /* Cache these. We assume with multi-conn that each handle will act
> + * the same way.
> + */
> + if (rwn->handles.size == 0) {
> + rwn->can_trim = nbd_can_trim (nbd) > 0;
> + rwn->can_zero = nbd_can_zero (nbd) > 0;
> + rwn->rw.size = nbd_get_size (nbd);
> + if (rwn->rw.size == -1) {
> + fprintf (stderr, "%s: %s: %s\n", "nbdcopy", rwn->rw.name,
> + nbd_get_error ());
> + exit (EXIT_FAILURE);
> + }
> + }
> +
> + if (handles_append (&rwn->handles, nbd) == -1) {
> + perror ("realloc");
> + exit (EXIT_FAILURE);
> + }
> +}
> +
> +struct rw *
> +nbd_rw_create_uri (const char *name, const char *uri, bool writing)
> +{
> + struct rw_nbd *rwn = calloc (1, sizeof *rwn);
> + if (rwn == NULL) { perror ("calloc"); exit (EXIT_FAILURE); }
> +
> + rwn->rw.ops = &nbd_ops;
> + rwn->rw.name = name;
> + rwn->create_t = CREATE_URI;
> + rwn->uri = uri;
> + rwn->writing = writing;
> +
> + open_one_nbd_handle (rwn);
> +
> + return (struct rw *) rwn;
> +}
> +
> +struct rw *
> +nbd_rw_create_subprocess (const char **argv, size_t argc, bool writing)
> +{
> + size_t i;
> + struct rw_nbd *rwn = calloc (1, sizeof *rwn);
> + if (rwn == NULL) { perror ("calloc"); exit (EXIT_FAILURE); }
> +
> + rwn->rw.ops = &nbd_ops;
> + rwn->rw.name = argv[0];
> + rwn->create_t = CREATE_SUBPROCESS;
> + rwn->writing = writing;
> +
> + /* We have to copy the args so we can null-terminate them. */
> + for (i = 0; i < argc; ++i) {
> + if (const_string_vector_append (&rwn->argv, argv[i]) == -1) {
> + memory_error:
> + perror ("realloc");
> + exit (EXIT_FAILURE);
> + }
> + }
> + if (const_string_vector_append (&rwn->argv, NULL) == -1)
> + goto memory_error;
> +
> + open_one_nbd_handle (rwn);
> +
> + return (struct rw *) rwn;
> +}
> +
> static void
> nbd_ops_close (struct rw *rw)
> {
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> size_t i;
>
> - for (i = 0; i < rw->u.nbd.handles.size; ++i) {
> - if (nbd_shutdown (rw->u.nbd.handles.ptr[i], 0) == -1) {
> + for (i = 0; i < rwn->handles.size; ++i) {
> + if (nbd_shutdown (rwn->handles.ptr[i], 0) == -1) {
> fprintf (stderr, "%s: %s\n", rw->name, nbd_get_error ());
> exit (EXIT_FAILURE);
> }
> - nbd_close (rw->u.nbd.handles.ptr[i]);
> + nbd_close (rwn->handles.ptr[i]);
> }
>
> - handles_reset (&rw->u.nbd.handles);
> + handles_reset (&rwn->handles);
> + const_string_vector_reset (&rwn->argv);
> + free (rw);
> }
>
> static void
> nbd_ops_flush (struct rw *rw)
> {
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> size_t i;
>
> - for (i = 0; i < rw->u.nbd.handles.size; ++i) {
> - if (nbd_flush (rw->u.nbd.handles.ptr[i], 0) == -1) {
> + for (i = 0; i < rwn->handles.size; ++i) {
> + if (nbd_flush (rwn->handles.ptr[i], 0) == -1) {
> fprintf (stderr, "%s: %s\n", rw->name, nbd_get_error ());
> exit (EXIT_FAILURE);
> }
> }
> }
>
> +static bool
> +nbd_ops_is_read_only (struct rw *rw)
> +{
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> + if (rwn->handles.size > 0)
> + return nbd_is_read_only (rwn->handles.ptr[0]);
> + else
> + return false;
> +}
> +
> +static bool
> +nbd_ops_can_extents (struct rw *rw)
> +{
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> + if (rwn->handles.size > 0)
> + return nbd_can_meta_context (rwn->handles.ptr[0], "base:allocation");
> + else
> + return false;
> +}
> +
> +static bool
> +nbd_ops_can_multi_conn (struct rw *rw)
> +{
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> + if (rwn->handles.size > 0)
> + return nbd_can_multi_conn (rwn->handles.ptr[0]);
> + else
> + return false;
> +}
> +
> +static void
> +nbd_ops_start_multi_conn (struct rw *rw)
> +{
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> + size_t i;
> +
> + for (i = 1; i < connections; ++i)
> + open_one_nbd_handle (rwn);
> +
> + assert (rwn->handles.size == connections);
> +}
> +
> static size_t
> nbd_ops_synch_read (struct rw *rw,
> void *data, size_t len, uint64_t offset)
> {
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> if (len > rw->size - offset)
> len = rw->size - offset;
> if (len == 0)
> return 0;
>
> - if (nbd_pread (rw->u.nbd.handles.ptr[0], data, len, offset, 0) == -1) {
> + if (nbd_pread (rwn->handles.ptr[0], data, len, offset, 0) == -1) {
> fprintf (stderr, "%s: %s\n", rw->name, nbd_get_error ());
> exit (EXIT_FAILURE);
> }
> @@ -77,7 +253,9 @@ static void
> nbd_ops_synch_write (struct rw *rw,
> const void *data, size_t len, uint64_t offset)
> {
> - if (nbd_pwrite (rw->u.nbd.handles.ptr[0], data, len, offset, 0) == -1) {
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> + if (nbd_pwrite (rwn->handles.ptr[0], data, len, offset, 0) == -1) {
> fprintf (stderr, "%s: %s\n", rw->name, nbd_get_error ());
> exit (EXIT_FAILURE);
> }
> @@ -86,10 +264,12 @@ nbd_ops_synch_write (struct rw *rw,
> static bool
> nbd_ops_synch_trim (struct rw *rw, uint64_t offset, uint64_t count)
> {
> - if (!rw->u.nbd.can_trim)
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> + if (!rwn->can_trim)
> return false;
>
> - if (nbd_trim (rw->u.nbd.handles.ptr[0], count, offset, 0) == -1) {
> + if (nbd_trim (rwn->handles.ptr[0], count, offset, 0) == -1) {
> fprintf (stderr, "%s: %s\n", rw->name, nbd_get_error ());
> exit (EXIT_FAILURE);
> }
> @@ -99,10 +279,12 @@ nbd_ops_synch_trim (struct rw *rw, uint64_t offset, uint64_t count)
> static bool
> nbd_ops_synch_zero (struct rw *rw, uint64_t offset, uint64_t count)
> {
> - if (!rw->u.nbd.can_zero)
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> + if (!rwn->can_zero)
> return false;
>
> - if (nbd_zero (rw->u.nbd.handles.ptr[0],
> + if (nbd_zero (rwn->handles.ptr[0],
> count, offset, LIBNBD_CMD_FLAG_NO_HOLE) == -1) {
> fprintf (stderr, "%s: %s\n", rw->name, nbd_get_error ());
> exit (EXIT_FAILURE);
> @@ -115,7 +297,9 @@ nbd_ops_asynch_read (struct rw *rw,
> struct command *command,
> nbd_completion_callback cb)
> {
> - if (nbd_aio_pread (rw->u.nbd.handles.ptr[command->index],
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> + if (nbd_aio_pread (rwn->handles.ptr[command->index],
> slice_ptr (command->slice),
> command->slice.len, command->offset,
> cb, 0) == -1) {
> @@ -129,7 +313,9 @@ nbd_ops_asynch_write (struct rw *rw,
> struct command *command,
> nbd_completion_callback cb)
> {
> - if (nbd_aio_pwrite (rw->u.nbd.handles.ptr[command->index],
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> + if (nbd_aio_pwrite (rwn->handles.ptr[command->index],
> slice_ptr (command->slice),
> command->slice.len, command->offset,
> cb, 0) == -1) {
> @@ -142,12 +328,14 @@ static bool
> nbd_ops_asynch_trim (struct rw *rw, struct command *command,
> nbd_completion_callback cb)
> {
> - if (!rw->u.nbd.can_trim)
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> + if (!rwn->can_trim)
> return false;
>
> assert (command->slice.len <= UINT32_MAX);
>
> - if (nbd_aio_trim (rw->u.nbd.handles.ptr[command->index],
> + if (nbd_aio_trim (rwn->handles.ptr[command->index],
> command->slice.len, command->offset,
> cb, 0) == -1) {
> fprintf (stderr, "%s: %s\n", rw->name, nbd_get_error ());
> @@ -160,12 +348,14 @@ static bool
> nbd_ops_asynch_zero (struct rw *rw, struct command *command,
> nbd_completion_callback cb)
> {
> - if (!rw->u.nbd.can_zero)
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> + if (!rwn->can_zero)
> return false;
>
> assert (command->slice.len <= UINT32_MAX);
>
> - if (nbd_aio_zero (rw->u.nbd.handles.ptr[command->index],
> + if (nbd_aio_zero (rwn->handles.ptr[command->index],
> command->slice.len, command->offset,
> cb, LIBNBD_CMD_FLAG_NO_HOLE) == -1) {
> fprintf (stderr, "%s: %s\n", rw->name, nbd_get_error ());
> @@ -212,19 +402,22 @@ add_extent (void *vp, const char *metacontext,
> static unsigned
> nbd_ops_in_flight (struct rw *rw, uintptr_t index)
> {
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> +
> /* Since the commands are auto-retired in the callbacks we don't
> * need to count "done" commands.
> */
> - return nbd_aio_in_flight (rw->u.nbd.handles.ptr[index]);
> + return nbd_aio_in_flight (rwn->handles.ptr[index]);
> }
>
> static void
> nbd_ops_get_polling_fd (struct rw *rw, uintptr_t index,
> int *fd, int *direction)
> {
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> struct nbd_handle *nbd;
>
> - nbd = rw->u.nbd.handles.ptr[index];
> + nbd = rwn->handles.ptr[index];
>
> *fd = nbd_aio_get_fd (nbd);
> if (*fd == -1) {
> @@ -240,7 +433,8 @@ nbd_ops_get_polling_fd (struct rw *rw, uintptr_t index,
> static void
> nbd_ops_asynch_notify_read (struct rw *rw, uintptr_t index)
> {
> - if (nbd_aio_notify_read (rw->u.nbd.handles.ptr[index]) == -1) {
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> + if (nbd_aio_notify_read (rwn->handles.ptr[index]) == -1) {
> fprintf (stderr, "%s: %s\n", rw->name, nbd_get_error ());
> exit (EXIT_FAILURE);
> }
> @@ -249,7 +443,8 @@ nbd_ops_asynch_notify_read (struct rw *rw, uintptr_t index)
> static void
> nbd_ops_asynch_notify_write (struct rw *rw, uintptr_t index)
> {
> - if (nbd_aio_notify_write (rw->u.nbd.handles.ptr[index]) == -1) {
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> + if (nbd_aio_notify_write (rwn->handles.ptr[index]) == -1) {
> fprintf (stderr, "%s: %s\n", rw->name, nbd_get_error ());
> exit (EXIT_FAILURE);
> }
> @@ -266,10 +461,11 @@ nbd_ops_get_extents (struct rw *rw, uintptr_t index,
> uint64_t offset, uint64_t count,
> extent_list *ret)
> {
> + struct rw_nbd *rwn = (struct rw_nbd *) rw;
> extent_list exts = empty_vector;
> struct nbd_handle *nbd;
>
> - nbd = rw->u.nbd.handles.ptr[index];
> + nbd = rwn->handles.ptr[index];
>
> ret->size = 0;
>
> @@ -331,9 +527,13 @@ nbd_ops_get_extents (struct rw *rw, uintptr_t index,
> free (exts.ptr);
> }
>
> -struct rw_ops nbd_ops = {
> +static struct rw_ops nbd_ops = {
> .ops_name = "nbd_ops",
> .close = nbd_ops_close,
> + .is_read_only = nbd_ops_is_read_only,
> + .can_extents = nbd_ops_can_extents,
> + .can_multi_conn = nbd_ops_can_multi_conn,
> + .start_multi_conn = nbd_ops_start_multi_conn,
> .flush = nbd_ops_flush,
> .synch_read = nbd_ops_synch_read,
> .synch_write = nbd_ops_synch_write,
> diff --git a/copy/nbdcopy.h b/copy/nbdcopy.h
> index 69fac2a..94fbdeb 100644
> --- a/copy/nbdcopy.h
> +++ b/copy/nbdcopy.h
> @@ -36,8 +36,6 @@
> */
> #define THREAD_WORK_SIZE (128 * 1024 * 1024)
>
> -DEFINE_VECTOR_TYPE (handles, struct nbd_handle *)
> -
> /* Abstracts the input (src) and output (dst) parameters on the
> * command line.
> */
> @@ -45,21 +43,20 @@ struct rw {
> struct rw_ops *ops; /* Operations. */
> const char *name; /* Printable name, for error messages etc. */
> int64_t size; /* May be -1 for streams. */
> - union {
> - struct { /* For files and pipes. */
> - int fd;
> - struct stat stat;
> - bool seek_hole_supported;
> - int sector_size;
> - } local;
> - struct {
> - handles handles; /* For NBD, one handle per connection. */
> - bool can_trim, can_zero; /* Cached nbd_can_trim, nbd_can_zero. */
> - } nbd;
> - } u;
> + /* Followed by private data for the particular subtype. */
> };
>
> -extern struct rw src, dst;
> +extern struct rw *src, *dst;
> +
> +/* Create subtypes. */
> +extern struct rw *file_create (const char *name,
> + const struct stat *stat, int fd);
> +extern struct rw *nbd_rw_create_uri (const char *name,
> + const char *uri, bool writing);
> +extern struct rw *nbd_rw_create_subprocess (const char **argv, size_t argc,
> + bool writing);
> +extern struct rw *null_create (const char *name);
> +extern struct rw *pipe_create (const char *name, int fd);
>
> /* Underlying data buffers. */
> struct buffer {
> @@ -117,6 +114,28 @@ struct rw_ops {
> /* Close the connection and free up associated resources. */
> void (*close) (struct rw *rw);
>
> + /* Return true if this is a read-only connection. */
> + bool (*is_read_only) (struct rw *rw);
> +
> + /* For source only, does it support reading extents? */
> + bool (*can_extents) (struct rw *rw);
> +
> + /* Return true if the connection can do multi-conn. This is true
> + * for files, false for streams, and passed through for NBD.
> + */
> + bool (*can_multi_conn) (struct rw *rw);
> +
> + /* For multi-conn capable backends, before copying we must call this
> + * to begin multi-conn. For NBD this means opening the additional
> + * connections.
> + */
> + void (*start_multi_conn) (struct rw *rw);
> +
> + /* Truncate, only called on output files. This callback can be NULL
> + * for types that don't support this.
> + */
> + void (*truncate) (struct rw *rw, int64_t size);
> +
> /* Flush pending writes to permanent storage. */
> void (*flush) (struct rw *rw);
>
> @@ -188,10 +207,6 @@ struct rw_ops {
> uint64_t offset, uint64_t count,
> extent_list *ret);
> };
> -extern struct rw_ops file_ops;
> -extern struct rw_ops nbd_ops;
> -extern struct rw_ops pipe_ops;
> -extern struct rw_ops null_ops;
>
> extern void default_get_extents (struct rw *rw, uintptr_t index,
> uint64_t offset, uint64_t count,
> diff --git a/copy/null-ops.c b/copy/null-ops.c
> index b2ca66f..3262fb5 100644
> --- a/copy/null-ops.c
> +++ b/copy/null-ops.c
> @@ -30,10 +30,28 @@
> * and fast zeroing.
> */
>
> +static struct rw_ops null_ops;
> +
> +struct rw_null {
> + struct rw rw;
> +};
> +
> +struct rw *
> +null_create (const char *name)
> +{
> + struct rw_null *rw = calloc (1, sizeof *rw);
> + if (rw == NULL) { perror ("calloc"); exit (EXIT_FAILURE); }
> +
> + rw->rw.ops = &null_ops;
> + rw->rw.name = name;
> + rw->rw.size = INT64_MAX;
> + return (struct rw *) rw;
> +}
> +
> static void
> null_close (struct rw *rw)
> {
> - /* nothing */
> + free (rw);
> }
>
> static void
> @@ -42,6 +60,30 @@ null_flush (struct rw *rw)
> /* nothing */
> }
>
> +static bool
> +null_is_read_only (struct rw *rw)
> +{
> + return false;
> +}
> +
> +static bool
> +null_can_extents (struct rw *rw)
> +{
> + return false;
> +}
> +
> +static bool
> +null_can_multi_conn (struct rw *rw)
> +{
> + return true;
> +}
> +
> +static void
> +null_start_multi_conn (struct rw *rw)
> +{
> + /* nothing */
> +}
> +
> static size_t
> null_synch_read (struct rw *rw,
> void *data, size_t len, uint64_t offset)
> @@ -126,9 +168,13 @@ null_get_extents (struct rw *rw, uintptr_t index,
> abort ();
> }
>
> -struct rw_ops null_ops = {
> +static struct rw_ops null_ops = {
> .ops_name = "null_ops",
> .close = null_close,
> + .is_read_only = null_is_read_only,
> + .can_extents = null_can_extents,
> + .can_multi_conn = null_can_multi_conn,
> + .start_multi_conn = null_start_multi_conn,
> .flush = null_flush,
> .synch_read = null_synch_read,
> .synch_write = null_synch_write,
> diff --git a/copy/pipe-ops.c b/copy/pipe-ops.c
> index d127dad..286e6c0 100644
> --- a/copy/pipe-ops.c
> +++ b/copy/pipe-ops.c
> @@ -26,10 +26,33 @@
>
> #include "nbdcopy.h"
>
> +static struct rw_ops pipe_ops;
> +
> +struct rw_pipe {
> + struct rw rw;
> + int fd;
> +};
> +
> +struct rw *
> +pipe_create (const char *name, int fd)
> +{
> + struct rw_pipe *rwp = calloc (1, sizeof *rwp);
> + if (rwp == NULL) { perror ("calloc"); exit (EXIT_FAILURE); }
> +
> + /* Set size == -1 which means don't know. */
> + rwp->rw.ops = &pipe_ops;
> + rwp->rw.name = name;
> + rwp->rw.size = -1;
> + rwp->fd = fd;
> + return (struct rw *) rwp;
> +}
> +
> static void
> pipe_close (struct rw *rw)
> {
> - if (close (rw->u.local.fd) == -1) {
> + struct rw_pipe *rwp = (struct rw_pipe *) rw;
> +
> + if (close (rwp->fd) == -1) {
> fprintf (stderr, "%s: close: %m\n", rw->name);
> exit (EXIT_FAILURE);
> }
> @@ -43,13 +66,39 @@ pipe_flush (struct rw *rw)
> */
> }
>
> +static bool
> +pipe_is_read_only (struct rw *rw)
> +{
> + return false;
> +}
> +
> +static bool
> +pipe_can_extents (struct rw *rw)
> +{
> + return false;
> +}
> +
> +static bool
> +pipe_can_multi_conn (struct rw *rw)
> +{
> + return false;
> +}
> +
> +static void
> +pipe_start_multi_conn (struct rw *rw)
> +{
> + /* Should never be called. */
> + abort ();
> +}
> +
> static size_t
> pipe_synch_read (struct rw *rw,
> void *data, size_t len, uint64_t offset)
> {
> + struct rw_pipe *rwp = (struct rw_pipe *) rw;
> ssize_t r;
>
> - r = read (rw->u.local.fd, data, len);
> + r = read (rwp->fd, data, len);
> if (r == -1) {
> perror (rw->name);
> exit (EXIT_FAILURE);
> @@ -61,10 +110,11 @@ static void
> pipe_synch_write (struct rw *rw,
> const void *data, size_t len, uint64_t offset)
> {
> + struct rw_pipe *rwp = (struct rw_pipe *) rw;
> ssize_t r;
>
> while (len > 0) {
> - r = write (rw->u.local.fd, data, len);
> + r = write (rwp->fd, data, len);
> if (r == -1) {
> perror (rw->name);
> exit (EXIT_FAILURE);
> @@ -109,10 +159,16 @@ pipe_in_flight (struct rw *rw, uintptr_t index)
> return 0;
> }
>
> -struct rw_ops pipe_ops = {
> +static struct rw_ops pipe_ops = {
> .ops_name = "pipe_ops",
>
> .close = pipe_close,
> +
> + .is_read_only = pipe_is_read_only,
> + .can_extents = pipe_can_extents,
> + .can_multi_conn = pipe_can_multi_conn,
> + .start_multi_conn = pipe_start_multi_conn,
> +
> .flush = pipe_flush,
>
> .synch_read = pipe_synch_read,
> diff --git a/copy/synch-copying.c b/copy/synch-copying.c
> index 2712c10..985f005 100644
> --- a/copy/synch-copying.c
> +++ b/copy/synch-copying.c
> @@ -38,13 +38,13 @@ synch_copying (void)
> /* If the source size is unknown then we copy data and cannot use
> * extent information.
> */
> - if (src.size == -1) {
> + if (src->size == -1) {
> size_t r;
>
> - while ((r = src.ops->synch_read (&src, buf, sizeof buf, offset)) > 0) {
> - dst.ops->synch_write (&dst, buf, r, offset);
> + while ((r = src->ops->synch_read (src, buf, sizeof buf, offset)) > 0) {
> + dst->ops->synch_write (dst, buf, r, offset);
> offset += r;
> - progress_bar (offset, src.size);
> + progress_bar (offset, src->size);
> }
> }
>
> @@ -52,47 +52,47 @@ synch_copying (void)
> * blocks and use extent information to optimize the case.
> */
> else {
> - while (offset < src.size) {
> + while (offset < src->size) {
> extent_list exts = empty_vector;
> - uint64_t count = src.size - offset;
> + uint64_t count = src->size - offset;
> size_t i, r;
>
> if (count > sizeof buf)
> count = sizeof buf;
>
> if (extents)
> - src.ops->get_extents (&src, 0, offset, count, &exts);
> + src->ops->get_extents (src, 0, offset, count, &exts);
> else
> - default_get_extents (&src, 0, offset, count, &exts);
> + default_get_extents (src, 0, offset, count, &exts);
>
> for (i = 0; i < exts.size; ++i) {
> assert (exts.ptr[i].length <= count);
>
> if (exts.ptr[i].zero) {
> - if (!dst.ops->synch_trim (&dst, offset, exts.ptr[i].length) &&
> - !dst.ops->synch_zero (&dst, offset, exts.ptr[i].length)) {
> + if (!dst->ops->synch_trim (dst, offset, exts.ptr[i].length) &&
> + !dst->ops->synch_zero (dst, offset, exts.ptr[i].length)) {
> /* If neither trimming nor efficient zeroing are possible,
> * write zeroes the hard way.
> */
> memset (buf, 0, exts.ptr[i].length);
> - dst.ops->synch_write (&dst, buf, exts.ptr[i].length, offset);
> + dst->ops->synch_write (dst, buf, exts.ptr[i].length, offset);
> }
> offset += exts.ptr[i].length;
> }
> else /* data */ {
> - r = src.ops->synch_read (&src, buf, exts.ptr[i].length, offset);
> + r = src->ops->synch_read (src, buf, exts.ptr[i].length, offset);
>
> /* These cases should never happen unless the file is
> * truncated underneath us.
> */
> if (r == 0 || r < exts.ptr[i].length) {
> - fprintf (stderr, "%s: unexpected end of file\n", src.name);
> + fprintf (stderr, "%s: unexpected end of file\n", src->name);
> exit (EXIT_FAILURE);
> }
>
> - dst.ops->synch_write (&dst, buf, r, offset);
> + dst->ops->synch_write (dst, buf, r, offset);
> offset += r;
> - progress_bar (offset, src.size);
> + progress_bar (offset, src->size);
> }
> }
>
> --
> 2.29.0.rc2
>
I looked at it briefly, this is a large change, but generally it
looks good.
Nir
More information about the Libguestfs
mailing list