diff --git a/cmd/zfs/zfs_main.c b/cmd/zfs/zfs_main.c index 73ccf72d263c..977b29fd62e1 100644 --- a/cmd/zfs/zfs_main.c +++ b/cmd/zfs/zfs_main.c @@ -135,7 +135,8 @@ static int zfs_do_unzone(int argc, char **argv); static int zfs_do_help(int argc, char **argv); enum zfs_options { - ZFS_OPTION_JSON_NUMS_AS_INT = 1024 + ZFS_OPTION_JSON_NUMS_AS_INT = 1024, + ZFS_OPTION_FORCE_PRESERVE_OLD = 1025 }; /* @@ -321,11 +322,11 @@ get_usage(zfs_help_t idx) case HELP_PROMOTE: return (gettext("\tpromote \n")); case HELP_RECEIVE: - return (gettext("\treceive [-vMnsFhu] " + return (gettext("\treceive [-vMnshu] [-F [--preserve-old]] " "[-o =] ... [-x ] ...\n" "\t \n" - "\treceive [-vMnsFhu] [-o =] ... " - "[-x ] ... \n" + "\treceive [-vMnshu] [-F [--preserve-old]] " + "[-o =] ... [-x ] ... \n" "\t [-d | -e] \n" "\treceive -A \n")); case HELP_RENAME: @@ -5130,8 +5131,16 @@ zfs_do_receive(int argc, char **argv) if (nvlist_alloc(&props, NV_UNIQUE_NAME, 0) != 0) nomem(); + struct option long_options[] = { + {"force", no_argument, NULL, 'F'}, + {"preserve-old", no_argument, NULL, + ZFS_OPTION_FORCE_PRESERVE_OLD}, + {0, 0, 0, 0} + }; + /* check options */ - while ((c = getopt(argc, argv, ":o:x:dehMnuvFsAc")) != -1) { + while ((c = getopt_long(argc, argv, ":o:x:dehMnuvFsAc", long_options, + NULL)) != -1) { switch (c) { case 'o': if (!parseprop(props, optarg)) { @@ -5184,6 +5193,9 @@ zfs_do_receive(int argc, char **argv) case 'F': flags.force = B_TRUE; break; + case ZFS_OPTION_FORCE_PRESERVE_OLD: + flags.preserveold = B_TRUE; + break; case 'A': abort_resumable = B_TRUE; break; @@ -5219,6 +5231,12 @@ zfs_do_receive(int argc, char **argv) usage(B_FALSE); } + if (!flags.force && flags.preserveold) { + (void) fprintf(stderr, gettext("'--preserve-old' only works" + "'-F' option\n")); + usage(B_FALSE); + } + if (abort_resumable) { if (flags.isprefix || flags.istail || flags.dryrun || flags.resumable || flags.nomount) { diff --git a/include/libzfs.h b/include/libzfs.h index 01d51999f4eb..18209d6db4d1 100644 --- a/include/libzfs.h +++ b/include/libzfs.h @@ -879,6 +879,9 @@ typedef struct recvflags { /* rollback/destroy filesystems as necessary (eg, -F) */ boolean_t force; + /* preserve deletion of old snapshots, used in conjunction with -F */ + boolean_t preserveold; + /* set "canmount=off" on all modified filesystems */ boolean_t canmountoff; diff --git a/lib/libzfs/libzfs_sendrecv.c b/lib/libzfs/libzfs_sendrecv.c index b9780720e5a3..142eb0ce6388 100644 --- a/lib/libzfs/libzfs_sendrecv.c +++ b/lib/libzfs/libzfs_sendrecv.c @@ -3545,6 +3545,7 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs, nvlist_t *stream_nvfs = NULL; nvpair_t *snapelem, *nextsnapelem; uint64_t fromguid = 0; + uint64_t stream_guid = 0; uint64_t originguid = 0; uint64_t stream_originguid = 0; uint64_t parent_fromsnap_guid, stream_parent_fromsnap_guid; @@ -3570,8 +3571,10 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs, thisguid = fnvpair_value_uint64(snapelem); stream_nvfs = fsavl_find(stream_avl, thisguid, NULL); - if (stream_nvfs != NULL) + if (stream_nvfs != NULL) { + stream_guid = thisguid; break; + } } /* check for promote */ @@ -3630,6 +3633,16 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs, if (!flags->force) continue; + /* + * If we have --preserve-old flag set, check + * current snapshot birth with the one found + * in the stream. Only delete recent snapshots + * but preserve older ones. + */ + if (flags->preserveold && created_before(hdl, + local_avl, thisguid, stream_guid) == -1) + continue; + (void) snprintf(name, sizeof (name), "%s@%s", fsname, nvpair_name(snapelem)); @@ -3686,7 +3699,8 @@ recv_incremental_replication(libzfs_handle_t *hdl, const char *tofs, /* check for delete */ if (stream_nvfs == NULL) { - if (!flags->force) + if (!flags->force || nvlist_next_nvpair(snaps, + NULL) == NULL) continue; error = recv_destroy(hdl, fsname, strlen(tofs)+1,