/*
* 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.
*/
/*
#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;
{
(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);
}
{
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;
}
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);
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.
*
nvpair_t *elem;
nvlist_t *args;
int error;
- char pool[MAXNAMELEN];
+ char pool[ZFS_MAX_DATASET_NAME_LEN];
*errlist = NULL;
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);
nvlist_t *args;
nvlist_t *result;
int err;
- char fs[MAXNAMELEN];
+ char fs[ZFS_MAX_DATASET_NAME_LEN];
char *atp;
/* determine the fs name */
{
/*
* 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
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;
*
* 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.
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 */
/*
* 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));
}
/*
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;
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);
* 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;
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)
}
/*
- * 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.
*/
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);
}
{
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);
{
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);