diff --git a/include/libzfs_core.h b/include/libzfs_core.h
index 7acc03fc71bb..b826e94c4c18 100644
--- a/include/libzfs_core.h
+++ b/include/libzfs_core.h
@@ -86,6 +86,7 @@ enum lzc_send_flags {
LZC_SEND_FLAG_SAVED = 1 << 4,
};
+_LIBZFS_CORE_H int lzc_send_wrapper(int (*)(int, void *), int, void *);
_LIBZFS_CORE_H int lzc_send(const char *, const char *, int,
enum lzc_send_flags);
_LIBZFS_CORE_H int lzc_send_resume(const char *, const char *, int,
diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c
index 4e491fd3beb4..29abb736d88a 100644
--- a/lib/libzfs/libzfs_sendrecv.c
+++ b/lib/libzfs/libzfs_sendrecv.c
@@ -1682,7 +1682,7 @@ lzc_flags_from_resume_nvl(nvlist_t *resume_nvl)
}
static int
-zfs_send_resume_impl(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
+zfs_send_resume_impl_real(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
nvlist_t *resume_nvl)
{
char errbuf[1024];
@@ -1893,6 +1893,32 @@ zfs_send_resume_impl(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
return (error);
}
+struct zfs_send_resume_impl {
+ libzfs_handle_t *hdl;
+ sendflags_t *flags;
+ nvlist_t *resume_nvl;
+};
+
+static int
+zfs_send_resume_impl_dispatch(int outfd, void *arg)
+{
+ struct zfs_send_resume_impl *zsri = arg;
+ return (zfs_send_resume_impl_real(zsri->hdl, zsri->flags, outfd,
+ zsri->resume_nvl));
+}
+
+static int
+zfs_send_resume_impl(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
+ nvlist_t *resume_nvl)
+{
+ struct zfs_send_resume_impl zsri = {
+ .hdl = hdl,
+ .flags = flags,
+ .resume_nvl = resume_nvl,
+ };
+ return (lzc_send_wrapper(zfs_send_resume_impl_dispatch, outfd, &zsri));
+}
+
int
zfs_send_resume(libzfs_handle_t *hdl, sendflags_t *flags, int outfd,
const char *resume_token)
@@ -2170,9 +2196,11 @@ send_prelim_records(zfs_handle_t *zhp, const char *from, int fd,
* if "replicate" is set. If "doall" is set, dump all the intermediate
* snapshots. The DMU_COMPOUNDSTREAM header is used in the "doall"
* case too. If "props" is set, send properties.
+ *
+ * Pre-wrapped (cf. lzc_send_wrapper()).
*/
-int
-zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
+static int
+zfs_send_real(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
sendflags_t *flags, int outfd, snapfilter_cb_t filter_func,
void *cb_arg, nvlist_t **debugnvp)
{
@@ -2374,6 +2402,42 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
return (err);
}
+struct zfs_send {
+ zfs_handle_t *zhp;
+ const char *fromsnap;
+ const char *tosnap;
+ sendflags_t *flags;
+ snapfilter_cb_t *filter_func;
+ void *cb_arg;
+ nvlist_t **debugnvp;
+};
+
+static int
+zfs_send_dispatch(int outfd, void *arg)
+{
+ struct zfs_send *zs = arg;
+ return (zfs_send_real(zs->zhp, zs->fromsnap, zs->tosnap, zs->flags,
+ outfd, zs->filter_func, zs->cb_arg, zs->debugnvp));
+}
+
+int
+zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
+ sendflags_t *flags, int outfd, snapfilter_cb_t filter_func,
+ void *cb_arg, nvlist_t **debugnvp)
+{
+ struct zfs_send arg = {
+ .zhp = zhp,
+ .fromsnap = fromsnap,
+ .tosnap = tosnap,
+ .flags = flags,
+ .filter_func = filter_func,
+ .cb_arg = cb_arg,
+ .debugnvp = debugnvp,
+ };
+ return (lzc_send_wrapper(zfs_send_dispatch, outfd, &arg));
+}
+
+
static zfs_handle_t *
name_to_dir_handle(libzfs_handle_t *hdl, const char *snapname)
{
@@ -2450,10 +2514,12 @@ snapshot_is_before(zfs_handle_t *earlier, zfs_handle_t *later)
* The "zhp" argument is the handle of the dataset to send (typically a
* snapshot). The "from" argument is the full name of the snapshot or
* bookmark that is the incremental source.
+ *
+ * Pre-wrapped (cf. lzc_send_wrapper()).
*/
-int
-zfs_send_one(zfs_handle_t *zhp, const char *from, int fd, sendflags_t *flags,
- const char *redactbook)
+static int
+zfs_send_one_real(zfs_handle_t *zhp, const char *from, int fd,
+ sendflags_t *flags, const char *redactbook)
{
int err;
libzfs_handle_t *hdl = zhp->zfs_hdl;
@@ -2642,6 +2708,34 @@ zfs_send_one(zfs_handle_t *zhp, const char *from, int fd, sendflags_t *flags,
return (err != 0);
}
+struct zfs_send_one {
+ zfs_handle_t *zhp;
+ const char *from;
+ sendflags_t *flags;
+ const char *redactbook;
+};
+
+static int
+zfs_send_one_dispatch(int fd, void *arg)
+{
+ struct zfs_send_one *zso = arg;
+ return (zfs_send_one_real(zso->zhp, zso->from, fd, zso->flags,
+ zso->redactbook));
+}
+
+int
+zfs_send_one(zfs_handle_t *zhp, const char *from, int fd, sendflags_t *flags,
+ const char *redactbook)
+{
+ struct zfs_send_one zso = {
+ .zhp = zhp,
+ .from = from,
+ .flags = flags,
+ .redactbook = redactbook,
+ };
+ return (lzc_send_wrapper(zfs_send_one_dispatch, fd, &zso));
+}
+
/*
* Routines specific to "zfs recv"
*/
diff --git a/lib/libzfs_core/libzfs_core.abi b/lib/libzfs_core/libzfs_core.abi
index 111287d3ef56..266007e4dcad 100644
--- a/lib/libzfs_core/libzfs_core.abi
+++ b/lib/libzfs_core/libzfs_core.abi
@@ -6,8 +6,6 @@
-
-
@@ -195,6 +193,7 @@
+
@@ -215,14 +214,12 @@
-
-
+
-
+
-
@@ -246,7 +243,6 @@
-
@@ -289,6 +285,11 @@
+
+
+
+
+
@@ -309,7 +310,7 @@
-
+
@@ -334,11 +335,6 @@
-
-
-
-
-
@@ -411,6 +407,11 @@
+
+
+
+
+
@@ -431,7 +432,7 @@
-
+
@@ -456,11 +457,6 @@
-
-
-
-
-
@@ -501,6 +497,12 @@
+
+
+
+
+
+
@@ -525,12 +527,6 @@
-
-
-
-
-
-
@@ -718,6 +714,9 @@
+
+
+
@@ -752,18 +751,66 @@
-
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -832,16 +879,16 @@
-
+
-
+
-
+
-
+
@@ -853,65 +900,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -922,11 +910,16 @@
+
+
+
+
+
@@ -1012,6 +1005,7 @@
+
@@ -1574,10 +1568,12 @@
+
+
@@ -1657,6 +1653,12 @@
+
+
+
+
+
+
@@ -1919,7 +1921,11 @@
-
+
+
+
+
+
diff --git a/lib/libzfs_core/libzfs_core.c b/lib/libzfs_core/libzfs_core.c
index 7608ff24a2d9..e49afe52c0d1 100644
--- a/lib/libzfs_core/libzfs_core.c
+++ b/lib/libzfs_core/libzfs_core.c
@@ -617,6 +617,83 @@ max_pipe_buffer(int infd)
#endif
}
+#if __linux__
+struct send_worker_ctx {
+ int from; /* read end of pipe, with send data; closed on exit */
+ int to; /* original arbitrary output fd; mustn't be a pipe */
+};
+
+static void *
+send_worker(void *arg)
+{
+ struct send_worker_ctx *ctx = arg;
+ unsigned int bufsiz = max_pipe_buffer(ctx->from);
+ ssize_t rd;
+ fprintf(stderr, "send worker :0 %u\n", bufsiz);
+
+ while ((rd = splice(ctx->from, NULL, ctx->to, NULL, bufsiz,
+ SPLICE_F_MOVE | SPLICE_F_MORE)) > 0)
+ ;
+
+ int err = (rd == -1) ? errno : 0;
+ close(ctx->from);
+ return ((void *)(uintptr_t)err);
+}
+#endif
+
+/*
+ * Since Linux 5.10, 4d03e3cc59828c82ee89ea6e27a2f3cdf95aaadf
+ * ("fs: don't allow kernel reads and writes without iter ops"),
+ * ZFS_IOC_SEND* will EINVAL when writing to /dev/null, /dev/zero, &c.
+ *
+ * This wrapper transparently executes func() with a pipe
+ * by spawning a thread to copy from that pipe to the original output
+ * in the background.
+ *
+ * Returns the error from func(), if nonzero,
+ * otherwise the error from the thread.
+ *
+ * No-op if orig_fd is -1, already a pipe, and on not-Linux;
+ * as such, it is safe to wrap/call wrapped functions in a wrapped context.
+ */
+int
+lzc_send_wrapper(int (*func)(int, void *), int orig_fd, void *data)
+{
+#if __linux__
+ struct stat sb;
+ if (orig_fd != -1 && fstat(orig_fd, &sb) == -1)
+ return (errno);
+ if (orig_fd == -1 || S_ISFIFO(sb.st_mode))
+ return (func(orig_fd, data));
+
+ int rw[2];
+ if (pipe2(rw, O_CLOEXEC) == -1)
+ return (errno);
+
+ int err;
+ pthread_t send_thread;
+ struct send_worker_ctx ctx = {.from = rw[0], .to = orig_fd};
+ if ((err = pthread_create(&send_thread, NULL, send_worker, &ctx))
+ != 0) {
+ close(rw[0]);
+ close(rw[1]);
+ return (errno = err);
+ }
+
+ err = func(rw[1], data);
+
+ void *send_err;
+ close(rw[1]);
+ pthread_join(send_thread, &send_err);
+ if (err == 0 && send_err != 0)
+ errno = err = (uintptr_t)send_err;
+
+ return (err);
+#else
+ return (func(orig_fd, data));
+#endif
+}
+
/*
* Generate a zfs send stream for the specified snapshot and write it to
* the specified file descriptor.
@@ -687,9 +764,11 @@ lzc_send_resume(const char *snapname, const char *from, int fd,
* redactnv: nvlist of string -> boolean(ignored) containing the names of all
* the snapshots that we should redact with respect to.
* redactbook: Name of the redaction bookmark to create.
+ *
+ * Pre-wrapped.
*/
-int
-lzc_send_resume_redacted(const char *snapname, const char *from, int fd,
+static int
+lzc_send_resume_redacted_real(const char *snapname, const char *from, int fd,
enum lzc_send_flags flags, uint64_t resumeobj, uint64_t resumeoff,
const char *redactbook)
{
@@ -722,6 +801,40 @@ lzc_send_resume_redacted(const char *snapname, const char *from, int fd,
return (err);
}
+struct lzc_send_resume_redacted {
+ const char *snapname;
+ const char *from;
+ enum lzc_send_flags flags;
+ uint64_t resumeobj;
+ uint64_t resumeoff;
+ const char *redactbook;
+};
+
+static int
+lzc_send_resume_redacted_dispatch(int fd, void *arg)
+{
+ struct lzc_send_resume_redacted *zsrr = arg;
+ return (lzc_send_resume_redacted_real(zsrr->snapname, zsrr->from,
+ fd, zsrr->flags, zsrr->resumeobj, zsrr->resumeoff,
+ zsrr->redactbook));
+}
+
+int
+lzc_send_resume_redacted(const char *snapname, const char *from, int fd,
+ enum lzc_send_flags flags, uint64_t resumeobj, uint64_t resumeoff,
+ const char *redactbook)
+{
+ struct lzc_send_resume_redacted zsrr = {
+ .snapname = snapname,
+ .from = from,
+ .flags = flags,
+ .resumeobj = resumeobj,
+ .resumeoff = resumeoff,
+ .redactbook = redactbook,
+ };
+ return (lzc_send_wrapper(lzc_send_resume_redacted_dispatch, fd, &zsrr));
+}
+
/*
* "from" can be NULL, a snapshot, or a bookmark.
*
@@ -737,9 +850,11 @@ lzc_send_resume_redacted(const char *snapname, const char *from, int fd,
* significantly more I/O and be less efficient than a send space estimation on
* an equivalent snapshot. This process is also used if redact_snaps is
* non-null.
+ *
+ * Pre-wrapped.
*/
-int
-lzc_send_space_resume_redacted(const char *snapname, const char *from,
+static int
+lzc_send_space_resume_redacted_real(const char *snapname, const char *from,
enum lzc_send_flags flags, uint64_t resumeobj, uint64_t resumeoff,
uint64_t resume_bytes, const char *redactbook, int fd, uint64_t *spacep)
{
@@ -776,6 +891,45 @@ lzc_send_space_resume_redacted(const char *snapname, const char *from,
return (err);
}
+struct lzc_send_space_resume_redacted {
+ const char *snapname;
+ const char *from;
+ enum lzc_send_flags flags;
+ uint64_t resumeobj;
+ uint64_t resumeoff;
+ uint64_t resume_bytes;
+ const char *redactbook;
+ uint64_t *spacep;
+};
+
+static int
+lzc_send_space_resume_redacted_dispatch(int fd, void *arg)
+{
+ struct lzc_send_space_resume_redacted *zssrr = arg;
+ return (lzc_send_space_resume_redacted_real(zssrr->snapname,
+ zssrr->from, zssrr->flags, zssrr->resumeobj, zssrr->resumeoff,
+ zssrr->resume_bytes, zssrr->redactbook, fd, zssrr->spacep));
+}
+
+int
+lzc_send_space_resume_redacted(const char *snapname, const char *from,
+ enum lzc_send_flags flags, uint64_t resumeobj, uint64_t resumeoff,
+ uint64_t resume_bytes, const char *redactbook, int fd, uint64_t *spacep)
+{
+ struct lzc_send_space_resume_redacted zssrr = {
+ .snapname = snapname,
+ .from = from,
+ .flags = flags,
+ .resumeobj = resumeobj,
+ .resumeoff = resumeoff,
+ .resume_bytes = resume_bytes,
+ .redactbook = redactbook,
+ .spacep = spacep,
+ };
+ return (lzc_send_wrapper(lzc_send_space_resume_redacted_dispatch,
+ fd, &zssrr));
+}
+
int
lzc_send_space(const char *snapname, const char *from,
enum lzc_send_flags flags, uint64_t *spacep)