]> git.proxmox.com Git - mirror_zfs-debian.git/blobdiff - lib/libzfs_core/libzfs_core.c
New upstream version 0.7.2
[mirror_zfs-debian.git] / lib / libzfs_core / libzfs_core.c
index b706e6f6be88c106e6630f5a929fbcbe3f1fef00..347d825e2d4ece14bcea9b2b462c0dada1207799 100644 (file)
@@ -22,6 +22,8 @@
 /*
  * Copyright (c) 2012, 2014 by Delphix. All rights reserved.
  * Copyright (c) 2013 Steven Hartland. All rights reserved.
+ * Copyright (c) 2017 Datto Inc.
+ * Copyright 2017 RackTop Systems.
  */
 
 /*
@@ -84,7 +86,7 @@
 #include <sys/stat.h>
 #include <sys/zfs_ioctl.h>
 
-static int g_fd;
+static int g_fd = -1;
 static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
 static int g_refcount;
 
@@ -109,9 +111,14 @@ libzfs_core_fini(void)
 {
        (void) pthread_mutex_lock(&g_lock);
        ASSERT3S(g_refcount, >, 0);
-       g_refcount--;
-       if (g_refcount == 0)
+
+       if (g_refcount > 0)
+               g_refcount--;
+
+       if (g_refcount == 0 && g_fd != -1) {
                (void) close(g_fd);
+               g_fd = -1;
+       }
        (void) pthread_mutex_unlock(&g_lock);
 }
 
@@ -121,16 +128,20 @@ lzc_ioctl(zfs_ioc_t ioc, const char *name,
 {
        zfs_cmd_t zc = {"\0"};
        int error = 0;
-       char *packed;
-       size_t size;
+       char *packed = NULL;
+       size_t size = 0;
 
        ASSERT3S(g_refcount, >, 0);
+       VERIFY3S(g_fd, !=, -1);
 
-       (void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
+       if (name != NULL)
+               (void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
 
-       packed = fnvlist_pack(source, &size);
-       zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed;
-       zc.zc_nvlist_src_size = size;
+       if (source != NULL) {
+               packed = fnvlist_pack(source, &size);
+               zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed;
+               zc.zc_nvlist_src_size = size;
+       }
 
        if (resultp != NULL) {
                *resultp = NULL;
@@ -170,11 +181,11 @@ out:
 }
 
 int
-lzc_create(const char *fsname, dmu_objset_type_t type, nvlist_t *props)
+lzc_create(const char *fsname, enum lzc_dataset_type type, nvlist_t *props)
 {
        int error;
        nvlist_t *args = fnvlist_alloc();
-       fnvlist_add_int32(args, "type", type);
+       fnvlist_add_int32(args, "type", (dmu_objset_type_t)type);
        if (props != NULL)
                fnvlist_add_nvlist(args, "props", props);
        error = lzc_ioctl(ZFS_IOC_CREATE, fsname, args, NULL);
@@ -196,6 +207,28 @@ lzc_clone(const char *fsname, const char *origin,
        return (error);
 }
 
+int
+lzc_promote(const char *fsname, char *snapnamebuf, int snapnamelen)
+{
+       /*
+        * The promote ioctl is still legacy, so we need to construct our
+        * own zfs_cmd_t rather than using lzc_ioctl().
+        */
+       zfs_cmd_t zc = { "\0" };
+
+       ASSERT3S(g_refcount, >, 0);
+       VERIFY3S(g_fd, !=, -1);
+
+       (void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_name));
+       if (ioctl(g_fd, ZFS_IOC_PROMOTE, &zc) != 0) {
+               int error = errno;
+               if (error == EEXIST && snapnamebuf != NULL)
+                       (void) strlcpy(snapnamebuf, zc.zc_string, snapnamelen);
+               return (error);
+       }
+       return (0);
+}
+
 /*
  * Creates snapshots.
  *
@@ -217,7 +250,7 @@ lzc_snapshot(nvlist_t *snaps, nvlist_t *props, nvlist_t **errlist)
        nvpair_t *elem;
        nvlist_t *args;
        int error;
-       char pool[MAXNAMELEN];
+       char pool[ZFS_MAX_DATASET_NAME_LEN];
 
        *errlist = NULL;
 
@@ -269,7 +302,7 @@ lzc_destroy_snaps(nvlist_t *snaps, boolean_t defer, nvlist_t **errlist)
        nvpair_t *elem;
        nvlist_t *args;
        int error;
-       char pool[MAXNAMELEN];
+       char pool[ZFS_MAX_DATASET_NAME_LEN];
 
        /* determine the pool name */
        elem = nvlist_next_nvpair(snaps, NULL);
@@ -296,7 +329,7 @@ lzc_snaprange_space(const char *firstsnap, const char *lastsnap,
        nvlist_t *args;
        nvlist_t *result;
        int err;
-       char fs[MAXNAMELEN];
+       char fs[ZFS_MAX_DATASET_NAME_LEN];
        char *atp;
 
        /* determine the fs name */
@@ -323,14 +356,29 @@ lzc_exists(const char *dataset)
 {
        /*
         * The objset_stats ioctl is still legacy, so we need to construct our
-        * own zfs_cmd_t rather than using zfsc_ioctl().
+        * own zfs_cmd_t rather than using lzc_ioctl().
         */
        zfs_cmd_t zc = {"\0"};
 
+       ASSERT3S(g_refcount, >, 0);
+       VERIFY3S(g_fd, !=, -1);
+
        (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
        return (ioctl(g_fd, ZFS_IOC_OBJSET_STATS, &zc) == 0);
 }
 
+/*
+ * outnvl is unused.
+ * It was added to preserve the function signature in case it is
+ * needed in the future.
+ */
+/*ARGSUSED*/
+int
+lzc_sync(const char *pool_name, nvlist_t *innvl, nvlist_t **outnvl)
+{
+       return (lzc_ioctl(ZFS_IOC_POOL_SYNC, pool_name, innvl, NULL));
+}
+
 /*
  * Create "user holds" on snapshots.  If there is a hold on a snapshot,
  * the snapshot can not be destroyed.  (However, it can be marked for deletion
@@ -361,7 +409,7 @@ lzc_exists(const char *dataset)
 int
 lzc_hold(nvlist_t *holds, int cleanup_fd, nvlist_t **errlist)
 {
-       char pool[MAXNAMELEN];
+       char pool[ZFS_MAX_DATASET_NAME_LEN];
        nvlist_t *args;
        nvpair_t *elem;
        int error;
@@ -391,7 +439,7 @@ lzc_hold(nvlist_t *holds, int cleanup_fd, nvlist_t **errlist)
  *
  * The keys in the nvlist are snapshot names.
  * The snapshots must all be in the same pool.
- * The value is a nvlist whose keys are the holds to remove.
+ * The value is an nvlist whose keys are the holds to remove.
  *
  * Holds which failed to release because they didn't exist will have an entry
  * added to errlist, but will not cause an overall failure.
@@ -408,7 +456,7 @@ lzc_hold(nvlist_t *holds, int cleanup_fd, nvlist_t **errlist)
 int
 lzc_release(nvlist_t *holds, nvlist_t **errlist)
 {
-       char pool[MAXNAMELEN];
+       char pool[ZFS_MAX_DATASET_NAME_LEN];
        nvpair_t *elem;
 
        /* determine the pool name */
@@ -424,18 +472,14 @@ lzc_release(nvlist_t *holds, nvlist_t **errlist)
 /*
  * Retrieve list of user holds on the specified snapshot.
  *
- * On success, *holdsp will be set to a nvlist which the caller must free.
+ * On success, *holdsp will be set to an nvlist which the caller must free.
  * The keys are the names of the holds, and the value is the creation time
  * of the hold (uint64) in seconds since the epoch.
  */
 int
 lzc_get_holds(const char *snapname, nvlist_t **holdsp)
 {
-       int error;
-       nvlist_t *innvl = fnvlist_alloc();
-       error = lzc_ioctl(ZFS_IOC_GET_HOLDS, snapname, innvl, holdsp);
-       fnvlist_free(innvl);
-       return (error);
+       return (lzc_ioctl(ZFS_IOC_GET_HOLDS, snapname, NULL, holdsp));
 }
 
 /*
@@ -467,6 +511,13 @@ lzc_get_holds(const char *snapname, nvlist_t **holdsp)
 int
 lzc_send(const char *snapname, const char *from, int fd,
     enum lzc_send_flags flags)
+{
+       return (lzc_send_resume(snapname, from, fd, flags, 0, 0));
+}
+
+int
+lzc_send_resume(const char *snapname, const char *from, int fd,
+    enum lzc_send_flags flags, uint64_t resumeobj, uint64_t resumeoff)
 {
        nvlist_t *args;
        int err;
@@ -479,6 +530,12 @@ lzc_send(const char *snapname, const char *from, int fd,
                fnvlist_add_boolean(args, "largeblockok");
        if (flags & LZC_SEND_FLAG_EMBED_DATA)
                fnvlist_add_boolean(args, "embedok");
+       if (flags & LZC_SEND_FLAG_COMPRESS)
+               fnvlist_add_boolean(args, "compressok");
+       if (resumeobj != 0 || resumeoff != 0) {
+               fnvlist_add_uint64(args, "resume_object", resumeobj);
+               fnvlist_add_uint64(args, "resume_offset", resumeoff);
+       }
        err = lzc_ioctl(ZFS_IOC_SEND_NEW, snapname, args, NULL);
        nvlist_free(args);
        return (err);
@@ -500,7 +557,8 @@ lzc_send(const char *snapname, const char *from, int fd,
  * an equivalent snapshot.
  */
 int
-lzc_send_space(const char *snapname, const char *from, uint64_t *spacep)
+lzc_send_space(const char *snapname, const char *from,
+    enum lzc_send_flags flags, uint64_t *spacep)
 {
        nvlist_t *args;
        nvlist_t *result;
@@ -509,6 +567,12 @@ lzc_send_space(const char *snapname, const char *from, uint64_t *spacep)
        args = fnvlist_alloc();
        if (from != NULL)
                fnvlist_add_string(args, "from", from);
+       if (flags & LZC_SEND_FLAG_LARGE_BLOCK)
+               fnvlist_add_boolean(args, "largeblockok");
+       if (flags & LZC_SEND_FLAG_EMBED_DATA)
+               fnvlist_add_boolean(args, "embedok");
+       if (flags & LZC_SEND_FLAG_COMPRESS)
+               fnvlist_add_boolean(args, "compressok");
        err = lzc_ioctl(ZFS_IOC_SEND_SPACE, snapname, args, &result);
        nvlist_free(args);
        if (err == 0)
@@ -537,95 +601,296 @@ recv_read(int fd, void *buf, int ilen)
 }
 
 /*
- * The simplest receive case: receive from the specified fd, creating the
- * specified snapshot.  Apply the specified properties a "received" properties
- * (which can be overridden by locally-set properties).  If the stream is a
- * clone, its origin snapshot must be specified by 'origin'.  The 'force'
- * flag will cause the target filesystem to be rolled back or destroyed if
- * necessary to receive.
+ * Linux adds ZFS_IOC_RECV_NEW for resumable streams and preserves the legacy
+ * ZFS_IOC_RECV user/kernel interface.  The new interface supports all stream
+ * options but is currently only used for resumable streams.  This way updated
+ * user space utilities will interoperate with older kernel modules.
  *
- * Return 0 on success or an errno on failure.
- *
- * Note: this interface does not work on dedup'd streams
- * (those with DMU_BACKUP_FEATURE_DEDUP).
+ * Non-Linux OpenZFS platforms have opted to modify the legacy interface.
  */
-int
-lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
-    boolean_t force, int fd)
+static int
+recv_impl(const char *snapname, nvlist_t *recvdprops, nvlist_t *localprops,
+    const char *origin, boolean_t force, boolean_t resumable, int input_fd,
+    const dmu_replay_record_t *begin_record, int cleanup_fd,
+    uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
+    nvlist_t **errors)
 {
-       /*
-        * The receive ioctl is still legacy, so we need to construct our own
-        * zfs_cmd_t rather than using zfsc_ioctl().
-        */
-       zfs_cmd_t zc = {"\0"};
-       char *atp;
-       char *packed = NULL;
-       size_t size;
        dmu_replay_record_t drr;
+       char fsname[MAXPATHLEN];
+       char *atp;
        int error;
 
        ASSERT3S(g_refcount, >, 0);
+       VERIFY3S(g_fd, !=, -1);
 
-       /* zc_name is name of containing filesystem */
-       (void) strlcpy(zc.zc_name, snapname, sizeof (zc.zc_name));
-       atp = strchr(zc.zc_name, '@');
+       /* Set 'fsname' to the name of containing filesystem */
+       (void) strlcpy(fsname, snapname, sizeof (fsname));
+       atp = strchr(fsname, '@');
        if (atp == NULL)
                return (EINVAL);
        *atp = '\0';
 
-       /* if the fs does not exist, try its parent. */
-       if (!lzc_exists(zc.zc_name)) {
-               char *slashp = strrchr(zc.zc_name, '/');
+       /* If the fs does not exist, try its parent. */
+       if (!lzc_exists(fsname)) {
+               char *slashp = strrchr(fsname, '/');
                if (slashp == NULL)
                        return (ENOENT);
                *slashp = '\0';
+       }
 
+       /*
+        * The begin_record is normally a non-byteswapped BEGIN record.
+        * For resumable streams it may be set to any non-byteswapped
+        * dmu_replay_record_t.
+        */
+       if (begin_record == NULL) {
+               error = recv_read(input_fd, &drr, sizeof (drr));
+               if (error != 0)
+                       return (error);
+       } else {
+               drr = *begin_record;
        }
 
-       /* zc_value is full name of the snapshot to create */
-       (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
+       if (resumable) {
+               nvlist_t *outnvl = NULL;
+               nvlist_t *innvl = fnvlist_alloc();
 
-       if (props != NULL) {
-               /* zc_nvlist_src is props to set */
-               packed = fnvlist_pack(props, &size);
-               zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed;
-               zc.zc_nvlist_src_size = size;
-       }
+               fnvlist_add_string(innvl, "snapname", snapname);
 
-       /* zc_string is name of clone origin (if DRR_FLAG_CLONE) */
-       if (origin != NULL)
-               (void) strlcpy(zc.zc_string, origin, sizeof (zc.zc_string));
+               if (recvdprops != NULL)
+                       fnvlist_add_nvlist(innvl, "props", recvdprops);
 
-       /* zc_begin_record is non-byteswapped BEGIN record */
-       error = recv_read(fd, &drr, sizeof (drr));
-       if (error != 0)
-               goto out;
-       zc.zc_begin_record = drr.drr_u.drr_begin;
+               if (localprops != NULL)
+                       fnvlist_add_nvlist(innvl, "localprops", localprops);
 
-       /* zc_cookie is fd to read from */
-       zc.zc_cookie = fd;
+               if (origin != NULL && strlen(origin))
+                       fnvlist_add_string(innvl, "origin", origin);
 
-       /* zc guid is force flag */
-       zc.zc_guid = force;
+               fnvlist_add_byte_array(innvl, "begin_record",
+                   (uchar_t *)&drr, sizeof (drr));
 
-       /* zc_cleanup_fd is unused */
-       zc.zc_cleanup_fd = -1;
+               fnvlist_add_int32(innvl, "input_fd", input_fd);
 
-       error = ioctl(g_fd, ZFS_IOC_RECV, &zc);
-       if (error != 0)
-               error = errno;
+               if (force)
+                       fnvlist_add_boolean(innvl, "force");
+
+               if (resumable)
+                       fnvlist_add_boolean(innvl, "resumable");
+
+               if (cleanup_fd >= 0)
+                       fnvlist_add_int32(innvl, "cleanup_fd", cleanup_fd);
+
+               if (action_handle != NULL)
+                       fnvlist_add_uint64(innvl, "action_handle",
+                           *action_handle);
+
+               error = lzc_ioctl(ZFS_IOC_RECV_NEW, fsname, innvl, &outnvl);
+
+               if (error == 0 && read_bytes != NULL)
+                       error = nvlist_lookup_uint64(outnvl, "read_bytes",
+                           read_bytes);
+
+               if (error == 0 && errflags != NULL)
+                       error = nvlist_lookup_uint64(outnvl, "error_flags",
+                           errflags);
+
+               if (error == 0 && action_handle != NULL)
+                       error = nvlist_lookup_uint64(outnvl, "action_handle",
+                           action_handle);
+
+               if (error == 0 && errors != NULL) {
+                       nvlist_t *nvl;
+                       error = nvlist_lookup_nvlist(outnvl, "errors", &nvl);
+                       if (error == 0)
+                               *errors = fnvlist_dup(nvl);
+               }
+
+               fnvlist_free(innvl);
+               fnvlist_free(outnvl);
+       } else {
+               zfs_cmd_t zc = {"\0"};
+               char *packed = NULL;
+               size_t size;
+
+               ASSERT3S(g_refcount, >, 0);
+
+               (void) strlcpy(zc.zc_name, fsname, sizeof (zc.zc_value));
+               (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
+
+               if (recvdprops != NULL) {
+                       packed = fnvlist_pack(recvdprops, &size);
+                       zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed;
+                       zc.zc_nvlist_src_size = size;
+               }
+
+               if (localprops != NULL) {
+                       packed = fnvlist_pack(localprops, &size);
+                       zc.zc_nvlist_conf = (uint64_t)(uintptr_t)packed;
+                       zc.zc_nvlist_conf_size = size;
+               }
+
+               if (origin != NULL)
+                       (void) strlcpy(zc.zc_string, origin,
+                           sizeof (zc.zc_string));
+
+               ASSERT3S(drr.drr_type, ==, DRR_BEGIN);
+               zc.zc_begin_record = drr.drr_u.drr_begin;
+               zc.zc_guid = force;
+               zc.zc_cookie = input_fd;
+               zc.zc_cleanup_fd = -1;
+               zc.zc_action_handle = 0;
+
+               if (cleanup_fd >= 0)
+                       zc.zc_cleanup_fd = cleanup_fd;
+
+               if (action_handle != NULL)
+                       zc.zc_action_handle = *action_handle;
+
+               zc.zc_nvlist_dst_size = 128 * 1024;
+               zc.zc_nvlist_dst = (uint64_t)(uintptr_t)
+                   malloc(zc.zc_nvlist_dst_size);
+
+               error = ioctl(g_fd, ZFS_IOC_RECV, &zc);
+               if (error != 0) {
+                       error = errno;
+               } else {
+                       if (read_bytes != NULL)
+                               *read_bytes = zc.zc_cookie;
+
+                       if (errflags != NULL)
+                               *errflags = zc.zc_obj;
+
+                       if (action_handle != NULL)
+                               *action_handle = zc.zc_action_handle;
+
+                       if (errors != NULL)
+                               VERIFY0(nvlist_unpack(
+                                   (void *)(uintptr_t)zc.zc_nvlist_dst,
+                                   zc.zc_nvlist_dst_size, errors, KM_SLEEP));
+               }
+
+               if (packed != NULL)
+                       fnvlist_pack_free(packed, size);
+               free((void *)(uintptr_t)zc.zc_nvlist_dst);
+       }
 
-out:
-       if (packed != NULL)
-               fnvlist_pack_free(packed, size);
-       free((void*)(uintptr_t)zc.zc_nvlist_dst);
        return (error);
 }
 
+/*
+ * The simplest receive case: receive from the specified fd, creating the
+ * specified snapshot.  Apply the specified properties as "received" properties
+ * (which can be overridden by locally-set properties).  If the stream is a
+ * clone, its origin snapshot must be specified by 'origin'.  The 'force'
+ * flag will cause the target filesystem to be rolled back or destroyed if
+ * necessary to receive.
+ *
+ * Return 0 on success or an errno on failure.
+ *
+ * Note: this interface does not work on dedup'd streams
+ * (those with DMU_BACKUP_FEATURE_DEDUP).
+ */
+int
+lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
+    boolean_t force, int fd)
+{
+       return (recv_impl(snapname, props, NULL, origin, force, B_FALSE, fd,
+           NULL, -1, NULL, NULL, NULL, NULL));
+}
+
+/*
+ * Like lzc_receive, but if the receive fails due to premature stream
+ * termination, the intermediate state will be preserved on disk.  In this
+ * case, ECKSUM will be returned.  The receive may subsequently be resumed
+ * with a resuming send stream generated by lzc_send_resume().
+ */
+int
+lzc_receive_resumable(const char *snapname, nvlist_t *props, const char *origin,
+    boolean_t force, int fd)
+{
+       return (recv_impl(snapname, props, NULL, origin, force, B_TRUE, fd,
+           NULL, -1, NULL, NULL, NULL, NULL));
+}
+
+/*
+ * Like lzc_receive, but allows the caller to read the begin record and then to
+ * pass it in.  That could be useful if the caller wants to derive, for example,
+ * the snapname or the origin parameters based on the information contained in
+ * the begin record.
+ * The begin record must be in its original form as read from the stream,
+ * in other words, it should not be byteswapped.
+ *
+ * The 'resumable' parameter allows to obtain the same behavior as with
+ * lzc_receive_resumable.
+ */
+int
+lzc_receive_with_header(const char *snapname, nvlist_t *props,
+    const char *origin, boolean_t force, boolean_t resumable, int fd,
+    const dmu_replay_record_t *begin_record)
+{
+       if (begin_record == NULL)
+               return (EINVAL);
+       return (recv_impl(snapname, props, NULL, origin, force, resumable, fd,
+           begin_record, -1, NULL, NULL, NULL, NULL));
+}
+
+/*
+ * Like lzc_receive, but allows the caller to pass all supported arguments
+ * and retrieve all values returned.  The only additional input parameter
+ * is 'cleanup_fd' which is used to set a cleanup-on-exit file descriptor.
+ *
+ * The following parameters all provide return values.  Several may be set
+ * in the failure case and will contain additional information.
+ *
+ * The 'read_bytes' value will be set to the total number of bytes read.
+ *
+ * The 'errflags' value will contain zprop_errflags_t flags which are
+ * used to describe any failures.
+ *
+ * The 'action_handle' is used to pass the handle for this guid/ds mapping.
+ * It should be set to zero on first call and will contain an updated handle
+ * on success, it should be passed in subsequent calls.
+ *
+ * The 'errors' nvlist contains an entry for each unapplied received
+ * property.  Callers are responsible for freeing this nvlist.
+ */
+int lzc_receive_one(const char *snapname, nvlist_t *props,
+    const char *origin, boolean_t force, boolean_t resumable, int input_fd,
+    const dmu_replay_record_t *begin_record, int cleanup_fd,
+    uint64_t *read_bytes, uint64_t *errflags, uint64_t *action_handle,
+    nvlist_t **errors)
+{
+       return (recv_impl(snapname, props, NULL, origin, force, resumable,
+           input_fd, begin_record, cleanup_fd, read_bytes, errflags,
+           action_handle, errors));
+}
+
+/*
+ * Like lzc_receive_one, but allows the caller to pass an additional 'cmdprops'
+ * argument.
+ *
+ * The 'cmdprops' nvlist contains both override ('zfs receive -o') and
+ * exclude ('zfs receive -x') properties. Callers are responsible for freeing
+ * this nvlist
+ */
+int lzc_receive_with_cmdprops(const char *snapname, nvlist_t *props,
+    nvlist_t *cmdprops, const char *origin, boolean_t force,
+    boolean_t resumable, int input_fd, const dmu_replay_record_t *begin_record,
+    int cleanup_fd, uint64_t *read_bytes, uint64_t *errflags,
+    uint64_t *action_handle, nvlist_t **errors)
+{
+       return (recv_impl(snapname, props, cmdprops, origin, force, resumable,
+           input_fd, begin_record, cleanup_fd, read_bytes, errflags,
+           action_handle, errors));
+}
+
 /*
  * Roll back this filesystem or volume to its most recent snapshot.
  * If snapnamebuf is not NULL, it will be filled in with the name
  * of the most recent snapshot.
+ * Note that the latest snapshot may change if a new one is concurrently
+ * created or the current one is destroyed.  lzc_rollback_to can be used
+ * to roll back to a specific latest snapshot.
  *
  * Return 0 on success or an errno on failure.
  */
@@ -643,6 +908,29 @@ lzc_rollback(const char *fsname, char *snapnamebuf, int snapnamelen)
                const char *snapname = fnvlist_lookup_string(result, "target");
                (void) strlcpy(snapnamebuf, snapname, snapnamelen);
        }
+       nvlist_free(result);
+
+       return (err);
+}
+
+/*
+ * Roll back this filesystem or volume to the specified snapshot,
+ * if possible.
+ *
+ * Return 0 on success or an errno on failure.
+ */
+int
+lzc_rollback_to(const char *fsname, const char *snapname)
+{
+       nvlist_t *args;
+       nvlist_t *result;
+       int err;
+
+       args = fnvlist_alloc();
+       fnvlist_add_string(args, "target", snapname);
+       err = lzc_ioctl(ZFS_IOC_ROLLBACK, fsname, args, &result);
+       nvlist_free(args);
+       nvlist_free(result);
        return (err);
 }
 
@@ -664,7 +952,7 @@ lzc_bookmark(nvlist_t *bookmarks, nvlist_t **errlist)
 {
        nvpair_t *elem;
        int error;
-       char pool[MAXNAMELEN];
+       char pool[ZFS_MAX_DATASET_NAME_LEN];
 
        /* determine the pool name */
        elem = nvlist_next_nvpair(bookmarks, NULL);
@@ -726,7 +1014,7 @@ lzc_destroy_bookmarks(nvlist_t *bmarks, nvlist_t **errlist)
 {
        nvpair_t *elem;
        int error;
-       char pool[MAXNAMELEN];
+       char pool[ZFS_MAX_DATASET_NAME_LEN];
 
        /* determine the pool name */
        elem = nvlist_next_nvpair(bmarks, NULL);