Improve ZVOL queue behavior.
[zfs.git] / lib / libzfs / libzfs_sendrecv.c
index 672e004..7a95de0 100644 (file)
 #include <stddef.h>
 #include <fcntl.h>
 #include <sys/mount.h>
+#include <sys/mntent.h>
+#include <sys/mnttab.h>
+#include <sys/avl.h>
+#include <sys/debug.h>
+#include <stddef.h>
 #include <pthread.h>
 #include <umem.h>
 
 #include "zfs_prop.h"
 #include "zfs_fletcher.h"
 #include "libzfs_impl.h"
-#include <sha2.h>
 #include <sys/zio_checksum.h>
 #include <sys/ddt.h>
+#include <sys/socket.h>
 
 /* in libzfs_dataset.c */
 extern void zfs_setprop_error(libzfs_handle_t *, zfs_prop_t, int, char *);
 
 static int zfs_receive_impl(libzfs_handle_t *, const char *, recvflags_t,
-    int, const char *, nvlist_t *, avl_tree_t *, char **);
+    int, const char *, nvlist_t *, avl_tree_t *, char **, int, uint64_t *);
 
-static const zio_cksum_t zero_cksum = { 0 };
+static const zio_cksum_t zero_cksum = { { 0 } };
 
 typedef struct dedup_arg {
        int     inputfd;
@@ -336,12 +341,11 @@ cksummer(void *arg)
                        if (ZIO_CHECKSUM_EQUAL(drrw->drr_key.ddk_cksum,
                            zero_cksum) ||
                            !DRR_IS_DEDUP_CAPABLE(drrw->drr_checksumflags)) {
-                               SHA256_CTX      ctx;
-                               zio_cksum_t     tmpsha256;
+                               zio_cksum_t tmpsha256;
+
+                               zio_checksum_SHA256(buf,
+                                   drrw->drr_length, &tmpsha256);
 
-                               SHA256Init(&ctx);
-                               SHA256Update(&ctx, buf, drrw->drr_length);
-                               SHA256Final(&tmpsha256, &ctx);
                                drrw->drr_key.ddk_cksum.zc_word[0] =
                                    BE_64(tmpsha256.zc_word[0]);
                                drrw->drr_key.ddk_cksum.zc_word[1] =
@@ -782,14 +786,30 @@ static int
 zfs_sort_snaps(zfs_handle_t *zhp, void *data)
 {
        avl_tree_t *avl = data;
-       zfs_node_t *node = zfs_alloc(zhp->zfs_hdl, sizeof (zfs_node_t));
+       zfs_node_t *node;
+       zfs_node_t search;
 
+       search.zn_handle = zhp;
+       node = avl_find(avl, &search, NULL);
+       if (node) {
+               /*
+                * If this snapshot was renamed while we were creating the
+                * AVL tree, it's possible that we already inserted it under
+                * its old name. Remove the old handle before adding the new
+                * one.
+                */
+               zfs_close(node->zn_handle);
+               avl_remove(avl, node);
+               free(node);
+       }
+
+       node = zfs_alloc(zhp->zfs_hdl, sizeof (zfs_node_t));
        node->zn_handle = zhp;
        avl_add(avl, node);
+
        return (0);
 }
 
-/* ARGSUSED */
 static int
 zfs_snapshot_compare(const void *larg, const void *rarg)
 {
@@ -844,6 +864,7 @@ typedef struct send_dump_data {
        const char *fromsnap;
        const char *tosnap;
        char prevsnap[ZFS_MAXNAMELEN];
+       uint64_t prevsnap_obj;
        boolean_t seenfrom, seento, replicate, doall, fromorigin;
        boolean_t verbose;
        int outfd;
@@ -853,6 +874,8 @@ typedef struct send_dump_data {
        snapfilter_cb_t *filter_cb;
        void *filter_cb_arg;
        nvlist_t *debugnv;
+       char holdtag[ZFS_MAXNAMELEN];
+       int cleanup_fd;
 } send_dump_data_t;
 
 /*
@@ -860,23 +883,21 @@ typedef struct send_dump_data {
  * NULL) to the file descriptor specified by outfd.
  */
 static int
-dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, boolean_t fromorigin,
-    int outfd, boolean_t enoent_ok, boolean_t *got_enoent, nvlist_t *debugnv)
+dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, uint64_t fromsnap_obj,
+    boolean_t fromorigin, int outfd, nvlist_t *debugnv)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        libzfs_handle_t *hdl = zhp->zfs_hdl;
        nvlist_t *thisdbg;
 
        assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
-       assert(fromsnap == NULL || fromsnap[0] == '\0' || !fromorigin);
+       assert(fromsnap_obj == 0 || !fromorigin);
 
        (void) strlcpy(zc.zc_name, zhp->zfs_name, sizeof (zc.zc_name));
-       if (fromsnap)
-               (void) strlcpy(zc.zc_value, fromsnap, sizeof (zc.zc_value));
        zc.zc_cookie = outfd;
        zc.zc_obj = fromorigin;
-
-       *got_enoent = B_FALSE;
+       zc.zc_sendobj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
+       zc.zc_fromobj = fromsnap_obj;
 
        VERIFY(0 == nvlist_alloc(&thisdbg, NV_UNIQUE_NAME, 0));
        if (fromsnap && fromsnap[0] != '\0') {
@@ -904,10 +925,6 @@ dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, boolean_t fromorigin,
                        return (zfs_error(hdl, EZFS_CROSSTARGET, errbuf));
 
                case ENOENT:
-                       if (enoent_ok) {
-                               *got_enoent = B_TRUE;
-                               return (0);
-                       }
                        if (zfs_dataset_exists(hdl, zc.zc_name,
                            ZFS_TYPE_SNAPSHOT)) {
                                zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
@@ -943,12 +960,47 @@ dump_ioctl(zfs_handle_t *zhp, const char *fromsnap, boolean_t fromorigin,
 }
 
 static int
+hold_for_send(zfs_handle_t *zhp, send_dump_data_t *sdd)
+{
+       zfs_handle_t *pzhp;
+       int error = 0;
+       char *thissnap;
+
+       assert(zhp->zfs_type == ZFS_TYPE_SNAPSHOT);
+
+       /*
+        * zfs_send() only opens a cleanup_fd for sends that need it,
+        * e.g. replication and doall.
+        */
+       if (sdd->cleanup_fd == -1)
+               return (0);
+
+       thissnap = strchr(zhp->zfs_name, '@') + 1;
+       *(thissnap - 1) = '\0';
+       pzhp = zfs_open(zhp->zfs_hdl, zhp->zfs_name, ZFS_TYPE_DATASET);
+       *(thissnap - 1) = '@';
+
+       /*
+        * It's OK if the parent no longer exists.  The send code will
+        * handle that error.
+        */
+       if (pzhp) {
+               error = zfs_hold(pzhp, thissnap, sdd->holdtag,
+                   B_FALSE, B_TRUE, B_TRUE, sdd->cleanup_fd,
+                   zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID),
+                   zfs_prop_get_int(zhp, ZFS_PROP_CREATETXG));
+               zfs_close(pzhp);
+       }
+
+       return (error);
+}
+
+static int
 dump_snapshot(zfs_handle_t *zhp, void *arg)
 {
        send_dump_data_t *sdd = arg;
-       const char *thissnap;
+       char *thissnap;
        int err;
-       boolean_t got_enoent;
        boolean_t isfromsnap, istosnap;
        boolean_t exclude = B_FALSE;
 
@@ -957,10 +1009,17 @@ dump_snapshot(zfs_handle_t *zhp, void *arg)
            strcmp(sdd->fromsnap, thissnap) == 0);
 
        if (!sdd->seenfrom && isfromsnap) {
-               sdd->seenfrom = B_TRUE;
-               (void) strcpy(sdd->prevsnap, thissnap);
+               err = hold_for_send(zhp, sdd);
+               if (err == 0) {
+                       sdd->seenfrom = B_TRUE;
+                       (void) strcpy(sdd->prevsnap, thissnap);
+                       sdd->prevsnap_obj = zfs_prop_get_int(zhp,
+                           ZFS_PROP_OBJSETID);
+               } else if (err == ENOENT) {
+                       err = 0;
+               }
                zfs_close(zhp);
-               return (0);
+               return (err);
        }
 
        if (sdd->seento || !sdd->seenfrom) {
@@ -1001,7 +1060,7 @@ dump_snapshot(zfs_handle_t *zhp, void *arg)
            sdd->filter_cb(zhp, sdd->filter_cb_arg) == B_FALSE)) {
                /*
                 * This snapshot is filtered out.  Don't send it, and don't
-                * set prevsnap, so it will be as if this snapshot didn't
+                * set prevsnap_obj, so it will be as if this snapshot didn't
                 * exist, and the next accepted snapshot will be sent as
                 * an incremental from the last accepted one, or as the
                 * first (and full) snapshot in the case of a replication,
@@ -1011,20 +1070,26 @@ dump_snapshot(zfs_handle_t *zhp, void *arg)
                return (0);
        }
 
+       err = hold_for_send(zhp, sdd);
+       if (err) {
+               if (err == ENOENT)
+                       err = 0;
+               zfs_close(zhp);
+               return (err);
+       }
+
        /* send it */
        if (sdd->verbose) {
                (void) fprintf(stderr, "sending from @%s to %s\n",
                    sdd->prevsnap, zhp->zfs_name);
        }
 
-       err = dump_ioctl(zhp, sdd->prevsnap,
+       err = dump_ioctl(zhp, sdd->prevsnap, sdd->prevsnap_obj,
            sdd->prevsnap[0] == '\0' && (sdd->fromorigin || sdd->replicate),
-           sdd->outfd, B_TRUE, &got_enoent, sdd->debugnv);
+           sdd->outfd, sdd->debugnv);
 
-       if (got_enoent)
-               err = 0;
-       else
-               (void) strcpy(sdd->prevsnap, thissnap);
+       (void) strcpy(sdd->prevsnap, thissnap);
+       sdd->prevsnap_obj = zfs_prop_get_int(zhp, ZFS_PROP_OBJSETID);
        zfs_close(zhp);
        return (err);
 }
@@ -1035,7 +1100,7 @@ dump_filesystem(zfs_handle_t *zhp, void *arg)
        int rv = 0;
        send_dump_data_t *sdd = arg;
        boolean_t missingfrom = B_FALSE;
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
 
        (void) snprintf(zc.zc_name, sizeof (zc.zc_name), "%s@%s",
            zhp->zfs_name, sdd->tosnap);
@@ -1064,6 +1129,7 @@ dump_filesystem(zfs_handle_t *zhp, void *arg)
        }
 
        sdd->seenfrom = sdd->seento = sdd->prevsnap[0] = 0;
+       sdd->prevsnap_obj = 0;
        if (sdd->fromsnap == NULL || missingfrom)
                sdd->seenfrom = B_TRUE;
 
@@ -1202,7 +1268,6 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
        int err;
        nvlist_t *fss = NULL;
        avl_tree_t *fsavl = NULL;
-       char holdtag[128];
        static uint64_t holdseq;
        int spa_version;
        boolean_t holdsnaps = B_FALSE;
@@ -1211,14 +1276,6 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
        dedup_arg_t dda = { 0 };
        int featureflags = 0;
 
-       if (zhp->zfs_type == ZFS_TYPE_FILESYSTEM) {
-               uint64_t version;
-               version = zfs_prop_get_int(zhp, ZFS_PROP_VERSION);
-               if (version >= ZPL_VERSION_SA) {
-                       featureflags |= DMU_BACKUP_FEATURE_SA_SPILL;
-               }
-       }
-
        (void) snprintf(errbuf, sizeof (errbuf), dgettext(TEXT_DOMAIN,
            "cannot send '%s'"), zhp->zfs_name);
 
@@ -1228,14 +1285,23 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                return (zfs_error(zhp->zfs_hdl, EZFS_NOENT, errbuf));
        }
 
+       if (zhp->zfs_type == ZFS_TYPE_FILESYSTEM) {
+               uint64_t version;
+               version = zfs_prop_get_int(zhp, ZFS_PROP_VERSION);
+               if (version >= ZPL_VERSION_SA) {
+                       featureflags |= DMU_BACKUP_FEATURE_SA_SPILL;
+               }
+       }
+
        if (zfs_spa_version(zhp, &spa_version) == 0 &&
-           spa_version >= SPA_VERSION_USERREFS)
+           spa_version >= SPA_VERSION_USERREFS &&
+           (flags.doall || flags.replicate))
                holdsnaps = B_TRUE;
 
        if (flags.dedup) {
                featureflags |= (DMU_BACKUP_FEATURE_DEDUP |
                    DMU_BACKUP_FEATURE_DEDUPPROPS);
-               if (err = pipe(pipefd)) {
+               if ((err = socketpair(AF_UNIX, SOCK_STREAM, 0, pipefd))) {
                        zfs_error_aux(zhp->zfs_hdl, strerror(errno));
                        return (zfs_error(zhp->zfs_hdl, EZFS_PIPEFAILED,
                            errbuf));
@@ -1243,7 +1309,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                dda.outputfd = outfd;
                dda.inputfd = pipefd[1];
                dda.dedup_hdl = zhp->zfs_hdl;
-               if (err = pthread_create(&tid, NULL, cksummer, &dda)) {
+               if ((err = pthread_create(&tid, NULL, cksummer, &dda))) {
                        (void) close(pipefd[0]);
                        (void) close(pipefd[1]);
                        zfs_error_aux(zhp->zfs_hdl, strerror(errno));
@@ -1256,18 +1322,7 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                dmu_replay_record_t drr = { 0 };
                char *packbuf = NULL;
                size_t buflen = 0;
-               zio_cksum_t zc = { 0 };
-
-               if (holdsnaps) {
-                       (void) snprintf(holdtag, sizeof (holdtag),
-                           ".send-%d-%llu", getpid(), (u_longlong_t)holdseq);
-                       ++holdseq;
-                       err = zfs_hold_range(zhp, fromsnap, tosnap,
-                           holdtag, flags.replicate, B_TRUE, filter_func,
-                           cb_arg);
-                       if (err)
-                               goto err_out;
-               }
+               zio_cksum_t zc = { { 0 } };
 
                if (flags.replicate || flags.props) {
                        nvlist_t *hdrnv;
@@ -1285,13 +1340,8 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
 
                        err = gather_nvlist(zhp->zfs_hdl, zhp->zfs_name,
                            fromsnap, tosnap, flags.replicate, &fss, &fsavl);
-                       if (err) {
-                               if (holdsnaps) {
-                                       (void) zfs_release_range(zhp, fromsnap,
-                                           tosnap, holdtag, flags.replicate);
-                               }
+                       if (err)
                                goto err_out;
-                       }
                        VERIFY(0 == nvlist_add_nvlist(hdrnv, "fss", fss));
                        err = nvlist_pack(hdrnv, &packbuf, &buflen,
                            NV_ENCODE_XDR, 0);
@@ -1302,10 +1352,6 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                        if (err) {
                                fsavl_destroy(fsavl);
                                nvlist_free(fss);
-                               if (holdsnaps) {
-                                       (void) zfs_release_range(zhp, fromsnap,
-                                           tosnap, holdtag, flags.replicate);
-                               }
                                goto stderr_out;
                        }
                }
@@ -1331,10 +1377,6 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                if (err == -1) {
                        fsavl_destroy(fsavl);
                        nvlist_free(fss);
-                       if (holdsnaps) {
-                               (void) zfs_release_range(zhp, fromsnap, tosnap,
-                                   holdtag, flags.replicate);
-                       }
                        err = errno;
                        goto stderr_out;
                }
@@ -1349,10 +1391,6 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                                fsavl_destroy(fsavl);
                                nvlist_free(fss);
                                err = errno;
-                               if (holdsnaps) {
-                                       (void) zfs_release_range(zhp, fromsnap,
-                                           tosnap, holdtag, flags.replicate);
-                               }
                                goto stderr_out;
                        }
                }
@@ -1375,6 +1413,18 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
        sdd.filter_cb_arg = cb_arg;
        if (debugnvp)
                sdd.debugnv = *debugnvp;
+       if (holdsnaps) {
+               ++holdseq;
+               (void) snprintf(sdd.holdtag, sizeof (sdd.holdtag),
+                   ".send-%d-%llu", getpid(), (u_longlong_t)holdseq);
+               sdd.cleanup_fd = open(ZFS_DEV, O_RDWR);
+               if (sdd.cleanup_fd < 0) {
+                       err = errno;
+                       goto stderr_out;
+               }
+       } else {
+               sdd.cleanup_fd = -1;
+       }
        err = dump_filesystems(zhp, &sdd);
        fsavl_destroy(fsavl);
        nvlist_free(fss);
@@ -1384,6 +1434,11 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                (void) pthread_join(tid, NULL);
        }
 
+       if (sdd.cleanup_fd != -1) {
+               VERIFY(0 == close(sdd.cleanup_fd));
+               sdd.cleanup_fd = -1;
+       }
+
        if (flags.replicate || flags.doall || flags.props) {
                /*
                 * write final end record.  NB: want to do this even if
@@ -1392,10 +1447,6 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
                 */
                dmu_replay_record_t drr = { 0 };
                drr.drr_type = DRR_END;
-               if (holdsnaps) {
-                       (void) zfs_release_range(zhp, fromsnap, tosnap,
-                           holdtag, flags.replicate);
-               }
                if (write(outfd, &drr, sizeof (drr)) == -1) {
                        return (zfs_standard_error(zhp->zfs_hdl,
                            errno, errbuf));
@@ -1407,6 +1458,8 @@ zfs_send(zfs_handle_t *zhp, const char *fromsnap, const char *tosnap,
 stderr_out:
        err = zfs_standard_error(zhp->zfs_hdl, err, errbuf);
 err_out:
+       if (sdd.cleanup_fd != -1)
+               VERIFY(0 == close(sdd.cleanup_fd));
        if (flags.dedup) {
                (void) pthread_cancel(tid);
                (void) pthread_join(tid, NULL);
@@ -1481,7 +1534,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
     int baselen, char *newname, recvflags_t flags)
 {
        static int seq;
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int err;
        prop_changelist_t *clp;
        zfs_handle_t *zhp;
@@ -1522,7 +1575,7 @@ recv_rename(libzfs_handle_t *hdl, const char *name, const char *tryname,
 
                (void) strncpy(newname, name, baselen);
                (void) snprintf(newname+baselen, ZFS_MAXNAMELEN-baselen,
-                   "recv-%u-%u", getpid(), seq);
+                   "recv-%ld-%u", (long) getpid(), seq);
                (void) strlcpy(zc.zc_value, newname, sizeof (zc.zc_value));
 
                if (flags.verbose) {
@@ -1554,7 +1607,7 @@ static int
 recv_destroy(libzfs_handle_t *hdl, const char *name, int baselen,
     char *newname, recvflags_t flags)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        int err = 0;
        prop_changelist_t *clp;
        zfs_handle_t *zhp;
@@ -1782,7 +1835,7 @@ again:
                            stream_originguid, originguid)) {
                        case 1: {
                                /* promote it! */
-                               zfs_cmd_t zc = { 0 };
+                               zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
                                nvlist_t *origin_nvfs;
                                char *origin_fsname;
 
@@ -1854,7 +1907,7 @@ again:
                        if (0 == nvlist_lookup_nvlist(stream_nvfs, "snapprops",
                            &props) && 0 == nvlist_lookup_nvlist(props,
                            stream_snapname, &props)) {
-                               zfs_cmd_t zc = { 0 };
+                               zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
 
                                zc.zc_cookie = B_TRUE; /* received */
                                (void) snprintf(zc.zc_name, sizeof (zc.zc_name),
@@ -1992,7 +2045,7 @@ again:
 static int
 zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
     recvflags_t flags, dmu_replay_record_t *drr, zio_cksum_t *zc,
-    char **top_zfs)
+    char **top_zfs, int cleanup_fd, uint64_t *action_handlep)
 {
        nvlist_t *stream_nv = NULL;
        avl_tree_t *stream_avl = NULL;
@@ -2158,7 +2211,8 @@ zfs_receive_package(libzfs_handle_t *hdl, int fd, const char *destname,
                 * recv_skip() and return 0).
                 */
                error = zfs_receive_impl(hdl, destname, flags, fd,
-                   sendfs, stream_nv, stream_avl, top_zfs);
+                   sendfs, stream_nv, stream_avl, top_zfs, cleanup_fd,
+                   action_handlep);
                if (error == ENODATA) {
                        error = 0;
                        break;
@@ -2281,9 +2335,10 @@ static int
 zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
     recvflags_t flags, dmu_replay_record_t *drr,
     dmu_replay_record_t *drr_noswap, const char *sendfs,
-    nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs)
+    nvlist_t *stream_nv, avl_tree_t *stream_avl, char **top_zfs, int cleanup_fd,
+    uint64_t *action_handlep)
 {
-       zfs_cmd_t zc = { 0 };
+       zfs_cmd_t zc = { "\0", "\0", "\0", "\0", 0 };
        time_t begin_time;
        int ioctl_err, ioctl_errno, err;
        char *cp;
@@ -2416,7 +2471,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
         */
        (void) strcpy(zc.zc_top_ds, tosnap);
        (void) strcpy(zc.zc_value, tosnap);
-       (void) strncat(zc.zc_value, chopprefix, sizeof (zc.zc_value));
+       (void) strlcat(zc.zc_value, chopprefix, sizeof (zc.zc_value));
        free(cp);
        if (!zfs_name_valid(zc.zc_value, ZFS_TYPE_SNAPSHOT)) {
                zcmd_free_nvlists(&zc);
@@ -2439,7 +2494,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                        (void) printf("found clone origin %s\n", zc.zc_string);
        }
 
-       stream_wantsnewfs = (drrb->drr_fromguid == NULL ||
+       stream_wantsnewfs = (drrb->drr_fromguid == 0 ||
            (drrb->drr_flags & DRR_FLAG_CLONE));
 
        if (stream_wantsnewfs) {
@@ -2559,6 +2614,12 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                                return (-1);
                        }
                }
+               if (!flags.dryrun && zhp->zfs_type == ZFS_TYPE_VOLUME &&
+                   zvol_remove_link(hdl, zhp->zfs_name) != 0) {
+                       zfs_close(zhp);
+                       zcmd_free_nvlists(&zc);
+                       return (-1);
+               }
                zfs_close(zhp);
        } else {
                /*
@@ -2609,6 +2670,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
 
        zc.zc_nvlist_dst = (uint64_t)(uintptr_t)prop_errbuf;
        zc.zc_nvlist_dst_size = sizeof (prop_errbuf);
+       zc.zc_cleanup_fd = cleanup_fd;
+       zc.zc_action_handle = *action_handlep;
 
        err = ioctl_err = zfs_ioctl(hdl, ZFS_IOC_RECV, &zc);
        ioctl_errno = errno;
@@ -2649,7 +2712,7 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
        zcmd_free_nvlists(&zc);
 
        if (err == 0 && snapprops_nvlist) {
-               zfs_cmd_t zc2 = { 0 };
+               zfs_cmd_t zc2 = { "\0", "\0", "\0", "\0", 0 };
 
                (void) strcpy(zc2.zc_name, zc.zc_value);
                zc2.zc_cookie = B_TRUE; /* received */
@@ -2762,6 +2825,10 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
                if (h != NULL) {
                        if (h->zfs_type == ZFS_TYPE_VOLUME) {
                                *cp = '@';
+                               err = zvol_create_link(hdl, h->zfs_name);
+                               if (err == 0 && ioctl_err == 0)
+                                       err = zvol_create_link(hdl,
+                                           zc.zc_value);
                        } else if (newfs || stream_avl) {
                                /*
                                 * Track the first/top of hierarchy fs,
@@ -2796,6 +2863,8 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
        if (err || ioctl_err)
                return (-1);
 
+       *action_handlep = zc.zc_action_handle;
+
        if (flags.verbose) {
                char buf1[64];
                char buf2[64];
@@ -2816,13 +2885,13 @@ zfs_receive_one(libzfs_handle_t *hdl, int infd, const char *tosnap,
 static int
 zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, recvflags_t flags,
     int infd, const char *sendfs, nvlist_t *stream_nv, avl_tree_t *stream_avl,
-    char **top_zfs)
+    char **top_zfs, int cleanup_fd, uint64_t *action_handlep)
 {
        int err;
        dmu_replay_record_t drr, drr_noswap;
        struct drr_begin *drrb = &drr.drr_u.drr_begin;
        char errbuf[1024];
-       zio_cksum_t zcksum = { 0 };
+       zio_cksum_t zcksum = { { 0 } };
        uint64_t featureflags;
        int hdrtype;
 
@@ -2909,12 +2978,12 @@ zfs_receive_impl(libzfs_handle_t *hdl, const char *tosnap, recvflags_t flags,
                }
                return (zfs_receive_one(hdl, infd, tosnap, flags,
                    &drr, &drr_noswap, sendfs, stream_nv, stream_avl,
-                   top_zfs));
+                   top_zfs, cleanup_fd, action_handlep));
        } else {
                assert(DMU_GET_STREAM_HDRTYPE(drrb->drr_versioninfo) ==
                    DMU_COMPOUNDSTREAM);
                return (zfs_receive_package(hdl, infd, tosnap, flags,
-                   &drr, &zcksum, top_zfs));
+                   &drr, &zcksum, top_zfs, cleanup_fd, action_handlep));
        }
 }
 
@@ -2930,9 +2999,16 @@ zfs_receive(libzfs_handle_t *hdl, const char *tosnap, recvflags_t flags,
 {
        char *top_zfs = NULL;
        int err;
+       int cleanup_fd;
+       uint64_t action_handle = 0;
+
+       cleanup_fd = open(ZFS_DEV, O_RDWR);
+       VERIFY(cleanup_fd >= 0);
 
        err = zfs_receive_impl(hdl, tosnap, flags, infd, NULL, NULL,
-           stream_avl, &top_zfs);
+           stream_avl, &top_zfs, cleanup_fd, &action_handle);
+
+       VERIFY(0 == close(cleanup_fd));
 
        if (err == 0 && !flags.nomount && top_zfs) {
                zfs_handle_t *zhp;