changeset 13055:8c712bbb18ea

PSARC 2010/105 zfs diff 6425091 want 'zfs diff' to list files that have changed between snapshots
author Tim Haley <Tim.Haley@Sun.COM>
date Sat, 07 Aug 2010 19:27:15 -0600
parents d149b2034cfc
children 233e20cbda69
files usr/src/cmd/truss/codes.c usr/src/cmd/zfs/zfs_main.c usr/src/cmd/ztest/ztest.c usr/src/common/xattr/xattr_common.c usr/src/common/zfs/zfs_deleg.c usr/src/common/zfs/zfs_deleg.h usr/src/lib/libzfs/Makefile.com usr/src/lib/libzfs/common/libzfs.h usr/src/lib/libzfs/common/libzfs_dataset.c usr/src/lib/libzfs/common/libzfs_diff.c usr/src/lib/libzfs/common/libzfs_impl.h usr/src/lib/libzfs/common/libzfs_util.c usr/src/lib/libzfs/common/mapfile-vers usr/src/lib/pyzfs/common/allow.py usr/src/uts/common/Makefile.files usr/src/uts/common/fs/xattr.c usr/src/uts/common/fs/zfs/dmu_diff.c usr/src/uts/common/fs/zfs/dmu_objset.c usr/src/uts/common/fs/zfs/dmu_traverse.c usr/src/uts/common/fs/zfs/dsl_dataset.c usr/src/uts/common/fs/zfs/dsl_deleg.c usr/src/uts/common/fs/zfs/sys/dmu.h usr/src/uts/common/fs/zfs/sys/dmu_objset.h usr/src/uts/common/fs/zfs/sys/dmu_traverse.h usr/src/uts/common/fs/zfs/sys/dsl_dataset.h usr/src/uts/common/fs/zfs/sys/dsl_deleg.h usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h usr/src/uts/common/fs/zfs/sys/zfs_stat.h usr/src/uts/common/fs/zfs/sys/zfs_znode.h usr/src/uts/common/fs/zfs/zfs_ctldir.c usr/src/uts/common/fs/zfs/zfs_ioctl.c usr/src/uts/common/fs/zfs/zfs_vnops.c usr/src/uts/common/fs/zfs/zfs_znode.c usr/src/uts/common/sys/attr.h usr/src/uts/common/sys/fs/zfs.h usr/src/uts/common/sys/vnode.h
diffstat 36 files changed, 1786 insertions(+), 116 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/truss/codes.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/cmd/truss/codes.c	Sat Aug 07 19:27:15 2010 -0600
@@ -1237,6 +1237,14 @@
 		"zfs_cmd_t" },
 	{ (uint_t)ZFS_IOC_VDEV_SPLIT,		"ZFS_IOC_VDEV_SPLIT",
 		"zfs_cmd_t" },
+	{ (uint_t)ZFS_IOC_NEXT_OBJ,		"ZFS_IOC_NEXT_OBJ",
+		"zfs_cmd_t" },
+	{ (uint_t)ZFS_IOC_DIFF,			"ZFS_IOC_DIFF",
+		"zfs_cmd_t" },
+	{ (uint_t)ZFS_IOC_TMP_SNAPSHOT,		"ZFS_IOC_TMP_SNAPSHOT",
+		"zfs_cmd_t" },
+	{ (uint_t)ZFS_IOC_OBJ_TO_STATS,		"ZFS_IOC_OBJ_TO_STATS",
+		"zfs_cmd_t" },
 
 	/* kssl ioctls */
 	{ (uint_t)KSSL_ADD_ENTRY,		"KSSL_ADD_ENTRY",
--- a/usr/src/cmd/zfs/zfs_main.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/cmd/zfs/zfs_main.c	Sat Aug 07 19:27:15 2010 -0600
@@ -40,6 +40,7 @@
 #include <zone.h>
 #include <grp.h>
 #include <pwd.h>
+#include <signal.h>
 #include <sys/mkdev.h>
 #include <sys/mntent.h>
 #include <sys/mnttab.h>
@@ -84,6 +85,7 @@
 static int zfs_do_python(int argc, char **argv);
 static int zfs_do_hold(int argc, char **argv);
 static int zfs_do_release(int argc, char **argv);
+static int zfs_do_diff(int argc, char **argv);
 
 /*
  * Enable a reasonable set of defaults for libumem debugging on DEBUG builds.
@@ -128,7 +130,8 @@
 	HELP_GROUPSPACE,
 	HELP_HOLD,
 	HELP_HOLDS,
-	HELP_RELEASE
+	HELP_RELEASE,
+	HELP_DIFF
 } zfs_help_t;
 
 typedef struct zfs_command {
@@ -180,6 +183,7 @@
 	{ "hold",	zfs_do_hold,		HELP_HOLD		},
 	{ "holds",	zfs_do_python,		HELP_HOLDS		},
 	{ "release",	zfs_do_release,		HELP_RELEASE		},
+	{ "diff",	zfs_do_diff,		HELP_DIFF		},
 };
 
 #define	NCOMMAND	(sizeof (command_table) / sizeof (command_table[0]))
@@ -283,6 +287,9 @@
 		return (gettext("\tholds [-r] <snapshot> ...\n"));
 	case HELP_RELEASE:
 		return (gettext("\trelease [-r] <tag> <snapshot> ...\n"));
+	case HELP_DIFF:
+		return (gettext("\tdiff [-FHt] <snapshot> "
+		    "[snapshot|filesystem]\n"));
 	}
 
 	abort();
@@ -3974,6 +3981,81 @@
 	return (1);
 }
 
+static int
+zfs_do_diff(int argc, char **argv)
+{
+	zfs_handle_t *zhp;
+	int flags = 0;
+	char *tosnap = NULL;
+	char *fromsnap = NULL;
+	char *atp, *copy;
+	int err;
+	int c;
+
+	while ((c = getopt(argc, argv, "FHt")) != -1) {
+		switch (c) {
+		case 'F':
+			flags |= ZFS_DIFF_CLASSIFY;
+			break;
+		case 'H':
+			flags |= ZFS_DIFF_PARSEABLE;
+			break;
+		case 't':
+			flags |= ZFS_DIFF_TIMESTAMP;
+			break;
+		default:
+			(void) fprintf(stderr,
+			    gettext("invalid option '%c'\n"), optopt);
+			usage(B_FALSE);
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	if (argc < 1) {
+		(void) fprintf(stderr,
+		gettext("must provide at least one snapshot name\n"));
+		usage(B_FALSE);
+	}
+
+	if (argc > 2) {
+		(void) fprintf(stderr, gettext("too many arguments\n"));
+		usage(B_FALSE);
+	}
+
+	fromsnap = argv[0];
+	tosnap = (argc == 2) ? argv[1] : NULL;
+
+	copy = NULL;
+	if (*fromsnap != '@')
+		copy = strdup(fromsnap);
+	else if (tosnap)
+		copy = strdup(tosnap);
+	if (copy == NULL)
+		usage(B_FALSE);
+
+	if (atp = strchr(copy, '@'))
+		*atp = '\0';
+
+	if ((zhp = zfs_open(g_zfs, copy, ZFS_TYPE_FILESYSTEM)) == NULL)
+		return (1);
+
+	free(copy);
+
+	/*
+	 * Ignore SIGPIPE so that the library can give us
+	 * information on any failure
+	 */
+	(void) sigignore(SIGPIPE);
+
+	err = zfs_show_diffs(zhp, STDOUT_FILENO, fromsnap, tosnap, flags);
+
+	zfs_close(zhp);
+
+	return (err != 0);
+}
+
 int
 main(int argc, char **argv)
 {
--- a/usr/src/cmd/ztest/ztest.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/cmd/ztest/ztest.c	Sat Aug 07 19:27:15 2010 -0600
@@ -2878,7 +2878,7 @@
 	    (u_longlong_t)id);
 
 	error = dmu_objset_snapshot(osname, strchr(snapname, '@') + 1,
-	    NULL, B_FALSE);
+	    NULL, NULL, B_FALSE, B_FALSE, -1);
 	if (error == ENOSPC) {
 		ztest_record_enospc(FTAG);
 		return (B_FALSE);
@@ -3083,7 +3083,7 @@
 	(void) snprintf(snap3name, MAXNAMELEN, "%s@s3_%llu", clone1name, id);
 
 	error = dmu_objset_snapshot(osname, strchr(snap1name, '@')+1,
-	    NULL, B_FALSE);
+	    NULL, NULL, B_FALSE, B_FALSE, -1);
 	if (error && error != EEXIST) {
 		if (error == ENOSPC) {
 			ztest_record_enospc(FTAG);
@@ -3107,7 +3107,7 @@
 	}
 
 	error = dmu_objset_snapshot(clone1name, strchr(snap2name, '@')+1,
-	    NULL, B_FALSE);
+	    NULL, NULL, B_FALSE, B_FALSE, -1);
 	if (error && error != EEXIST) {
 		if (error == ENOSPC) {
 			ztest_record_enospc(FTAG);
@@ -3117,7 +3117,7 @@
 	}
 
 	error = dmu_objset_snapshot(clone1name, strchr(snap3name, '@')+1,
-	    NULL, B_FALSE);
+	    NULL, NULL, B_FALSE, B_FALSE, -1);
 	if (error && error != EEXIST) {
 		if (error == ENOSPC) {
 			ztest_record_enospc(FTAG);
@@ -4307,7 +4307,8 @@
 	 * Create snapshot, clone it, mark snap for deferred destroy,
 	 * destroy clone, verify snap was also destroyed.
 	 */
-	error = dmu_objset_snapshot(osname, snapname, NULL, FALSE);
+	error = dmu_objset_snapshot(osname, snapname, NULL, NULL, FALSE,
+	    FALSE, -1);
 	if (error) {
 		if (error == ENOSPC) {
 			ztest_record_enospc("dmu_objset_snapshot");
@@ -4349,7 +4350,8 @@
 	 * destroy a held snapshot, mark for deferred destroy,
 	 * release hold, verify snapshot was destroyed.
 	 */
-	error = dmu_objset_snapshot(osname, snapname, NULL, FALSE);
+	error = dmu_objset_snapshot(osname, snapname, NULL, NULL, FALSE,
+	    FALSE, -1);
 	if (error) {
 		if (error == ENOSPC) {
 			ztest_record_enospc("dmu_objset_snapshot");
--- a/usr/src/common/xattr/xattr_common.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/common/xattr/xattr_common.c	Sat Aug 07 19:27:15 2010 -0600
@@ -19,8 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
 #include <sys/attr.h>
@@ -63,6 +62,7 @@
 	{ A_FSID, O_NONE, XATTR_VIEW_READONLY, DATA_TYPE_UINT64 },
 	{ A_REPARSE_POINT, O_REPARSE_POINT, XATTR_VIEW_READONLY,
 	    DATA_TYPE_BOOLEAN_VALUE },
+	{ A_GEN, O_NONE, XATTR_VIEW_READONLY, DATA_TYPE_UINT64 },
 };
 
 const char *
--- a/usr/src/common/zfs/zfs_deleg.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/common/zfs/zfs_deleg.c	Sat Aug 07 19:27:15 2010 -0600
@@ -19,8 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
 #if defined(_KERNEL)
@@ -69,6 +68,7 @@
 	{ZFS_DELEG_PERM_GROUPUSED, ZFS_DELEG_NOTE_GROUPUSED },
 	{ZFS_DELEG_PERM_HOLD, ZFS_DELEG_NOTE_HOLD },
 	{ZFS_DELEG_PERM_RELEASE, ZFS_DELEG_NOTE_RELEASE },
+	{ZFS_DELEG_PERM_DIFF, ZFS_DELEG_NOTE_DIFF},
 	{NULL, ZFS_DELEG_NOTE_NONE }
 };
 
--- a/usr/src/common/zfs/zfs_deleg.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/common/zfs/zfs_deleg.h	Sat Aug 07 19:27:15 2010 -0600
@@ -19,8 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
 #ifndef	_ZFS_DELEG_H
@@ -63,6 +62,7 @@
 	ZFS_DELEG_NOTE_GROUPUSED,
 	ZFS_DELEG_NOTE_HOLD,
 	ZFS_DELEG_NOTE_RELEASE,
+	ZFS_DELEG_NOTE_DIFF,
 	ZFS_DELEG_NOTE_NONE
 } zfs_deleg_note_t;
 
--- a/usr/src/lib/libzfs/Makefile.com	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/lib/libzfs/Makefile.com	Sat Aug 07 19:27:15 2010 -0600
@@ -19,8 +19,7 @@
 # CDDL HEADER END
 #
 #
-# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
-# Use is subject to license terms.
+# Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
 #
 
 LIBRARY= libzfs.a
@@ -39,6 +38,7 @@
 	libzfs_changelist.o	\
 	libzfs_config.o		\
 	libzfs_dataset.o	\
+	libzfs_diff.o		\
 	libzfs_fru.o		\
 	libzfs_graph.o		\
 	libzfs_import.o		\
@@ -68,7 +68,7 @@
 C99LMODE=	-Xc99=%all
 LDLIBS +=	-lc -lm -ldevid -lgen -lnvpair -luutil -lavl -lefi \
 	-ladm -lidmap -ltsol -lmd -lumem
-CPPFLAGS +=	$(INCS) -D_REENTRANT
+CPPFLAGS +=	$(INCS) -D_LARGEFILE64_SOURCE=1 -D_REENTRANT
 
 SRCS=	$(OBJS_COMMON:%.o=$(SRCDIR)/%.c)	\
 	$(OBJS_SHARED:%.o=$(SRC)/common/zfs/%.c)
--- a/usr/src/lib/libzfs/common/libzfs.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/lib/libzfs/common/libzfs.h	Sat Aug 07 19:27:15 2010 -0600
@@ -120,6 +120,8 @@
 	EZFS_POSTSPLIT_ONLINE,	/* onlining a disk after splitting it */
 	EZFS_SCRUBBING,		/* currently scrubbing */
 	EZFS_NO_SCRUB,		/* no active scrub */
+	EZFS_DIFF,		/* general failure of zfs diff */
+	EZFS_DIFFDATA,		/* bad zfs diff data */
 	EZFS_UNKNOWN
 };
 
@@ -586,6 +588,15 @@
 extern int zfs_receive(libzfs_handle_t *, const char *, recvflags_t,
     int, avl_tree_t *);
 
+typedef enum diff_flags {
+	ZFS_DIFF_PARSEABLE = 0x1,
+	ZFS_DIFF_TIMESTAMP = 0x2,
+	ZFS_DIFF_CLASSIFY = 0x4
+} diff_flags_t;
+
+extern int zfs_show_diffs(zfs_handle_t *, int, const char *, const char *,
+    int);
+
 /*
  * Miscellaneous functions.
  */
--- a/usr/src/lib/libzfs/common/libzfs_dataset.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/lib/libzfs/common/libzfs_dataset.c	Sat Aug 07 19:27:15 2010 -0600
@@ -125,7 +125,7 @@
  * provide a more meaningful error message.  We call zfs_error_aux() to
  * explain exactly why the name was not valid.
  */
-static int
+int
 zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type,
     boolean_t modifying)
 {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/libzfs/common/libzfs_diff.c	Sat Aug 07 19:27:15 2010 -0600
@@ -0,0 +1,826 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+/*
+ * zfs diff support
+ */
+#include <ctype.h>
+#include <errno.h>
+#include <libintl.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <attr.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stropts.h>
+#include <pthread.h>
+#include <sys/zfs_ioctl.h>
+#include <libzfs.h>
+#include "libzfs_impl.h"
+
+#define	ZDIFF_SNAPDIR		"/.zfs/snapshot/"
+#define	ZDIFF_SHARESDIR 	"/.zfs/shares/"
+#define	ZDIFF_PREFIX		"zfs-diff-%d"
+
+#define	ZDIFF_ADDED	'+'
+#define	ZDIFF_MODIFIED	'M'
+#define	ZDIFF_REMOVED	'-'
+#define	ZDIFF_RENAMED	'R'
+
+static boolean_t
+do_name_cmp(const char *fpath, const char *tpath)
+{
+	char *fname, *tname;
+	fname = strrchr(fpath, '/') + 1;
+	tname = strrchr(tpath, '/') + 1;
+	return (strcmp(fname, tname) == 0);
+}
+
+typedef struct differ_info {
+	zfs_handle_t *zhp;
+	char *fromsnap;
+	char *frommnt;
+	char *tosnap;
+	char *tomnt;
+	char *ds;
+	char *dsmnt;
+	char *tmpsnap;
+	char errbuf[1024];
+	boolean_t isclone;
+	boolean_t scripted;
+	boolean_t classify;
+	boolean_t timestamped;
+	uint64_t shares;
+	int zerr;
+	int cleanupfd;
+	int outputfd;
+	int datafd;
+} differ_info_t;
+
+/*
+ * Given a {dsname, object id}, get the object path
+ */
+static int
+get_stats_for_obj(differ_info_t *di, const char *dsname, uint64_t obj,
+    char *pn, int maxlen, zfs_stat_t *sb)
+{
+	zfs_cmd_t zc = { 0 };
+	int error;
+
+	(void) strlcpy(zc.zc_name, dsname, sizeof (zc.zc_name));
+	zc.zc_obj = obj;
+
+	errno = 0;
+	error = ioctl(di->zhp->zfs_hdl->libzfs_fd, ZFS_IOC_OBJ_TO_STATS, &zc);
+	di->zerr = errno;
+
+	/* we can get stats even if we failed to get a path */
+	(void) memcpy(sb, &zc.zc_stat, sizeof (zfs_stat_t));
+	if (error == 0) {
+		ASSERT(di->zerr == 0);
+		(void) strlcpy(pn, zc.zc_value, maxlen);
+		return (0);
+	}
+
+	if (di->zerr == EPERM) {
+		(void) snprintf(di->errbuf, sizeof (di->errbuf),
+		    dgettext(TEXT_DOMAIN,
+		    "The sys_config privilege or diff delegated permission "
+		    "is needed\nto discover path names"));
+		return (-1);
+	} else {
+		(void) snprintf(di->errbuf, sizeof (di->errbuf),
+		    dgettext(TEXT_DOMAIN,
+		    "Unable to determine path or stats for "
+		    "object %lld in %s"), obj, dsname);
+		return (-1);
+	}
+}
+
+/*
+ * stream_bytes
+ *
+ * Prints a file name out a character at a time.  If the character is
+ * not in the range of what we consider "printable" ASCII, display it
+ * as an escaped 3-digit octal value.  ASCII values less than a space
+ * are all control characters and we declare the upper end as the
+ * DELete character.  This also is the last 7-bit ASCII character.
+ * We choose to treat all 8-bit ASCII as not printable for this
+ * application.
+ */
+static void
+stream_bytes(FILE *fp, const char *string)
+{
+	while (*string) {
+		if (*string > ' ' && *string != '\\' && *string < '\177')
+			(void) fprintf(fp, "%c", *string++);
+		else
+			(void) fprintf(fp, "\\%03o", *string++);
+	}
+}
+
+static void
+print_what(FILE *fp, mode_t what)
+{
+	char symbol;
+
+	switch (what & S_IFMT) {
+	case S_IFBLK:
+		symbol = 'B';
+		break;
+	case S_IFCHR:
+		symbol = 'C';
+		break;
+	case S_IFDIR:
+		symbol = '/';
+		break;
+	case S_IFDOOR:
+		symbol = '>';
+		break;
+	case S_IFIFO:
+		symbol = '|';
+		break;
+	case S_IFLNK:
+		symbol = '@';
+		break;
+	case S_IFPORT:
+		symbol = 'P';
+		break;
+	case S_IFSOCK:
+		symbol = '=';
+		break;
+	case S_IFREG:
+		symbol = 'F';
+		break;
+	default:
+		symbol = '?';
+		break;
+	}
+	(void) fprintf(fp, "%c", symbol);
+}
+
+static void
+print_cmn(FILE *fp, differ_info_t *di, const char *file)
+{
+	stream_bytes(fp, di->dsmnt);
+	stream_bytes(fp, file);
+}
+
+static void
+print_rename(FILE *fp, differ_info_t *di, const char *old, const char *new,
+    zfs_stat_t *isb)
+{
+	if (di->timestamped)
+		(void) fprintf(fp, "%10lld.%09lld\t",
+		    (longlong_t)isb->zs_ctime[0],
+		    (longlong_t)isb->zs_ctime[1]);
+	(void) fprintf(fp, "%c\t", ZDIFF_RENAMED);
+	if (di->classify) {
+		print_what(fp, isb->zs_mode);
+		(void) fprintf(fp, "\t");
+	}
+	print_cmn(fp, di, old);
+	if (di->scripted)
+		(void) fprintf(fp, "\t");
+	else
+		(void) fprintf(fp, " -> ");
+	print_cmn(fp, di, new);
+	(void) fprintf(fp, "\n");
+}
+
+static void
+print_link_change(FILE *fp, differ_info_t *di, int delta, const char *file,
+    zfs_stat_t *isb)
+{
+	if (di->timestamped)
+		(void) fprintf(fp, "%10lld.%09lld\t",
+		    (longlong_t)isb->zs_ctime[0],
+		    (longlong_t)isb->zs_ctime[1]);
+	(void) fprintf(fp, "%c\t", ZDIFF_MODIFIED);
+	if (di->classify) {
+		print_what(fp, isb->zs_mode);
+		(void) fprintf(fp, "\t");
+	}
+	print_cmn(fp, di, file);
+	(void) fprintf(fp, "\t(%+d)", delta);
+	(void) fprintf(fp, "\n");
+}
+
+static void
+print_file(FILE *fp, differ_info_t *di, char type, const char *file,
+    zfs_stat_t *isb)
+{
+	if (di->timestamped)
+		(void) fprintf(fp, "%10lld.%09lld\t",
+		    (longlong_t)isb->zs_ctime[0],
+		    (longlong_t)isb->zs_ctime[1]);
+	(void) fprintf(fp, "%c\t", type);
+	if (di->classify) {
+		print_what(fp, isb->zs_mode);
+		(void) fprintf(fp, "\t");
+	}
+	print_cmn(fp, di, file);
+	(void) fprintf(fp, "\n");
+}
+
+static int
+write_inuse_diffs_one(FILE *fp, differ_info_t *di, uint64_t dobj)
+{
+	struct zfs_stat fsb, tsb;
+	boolean_t same_name;
+	mode_t fmode, tmode;
+	char fobjname[MAXPATHLEN], tobjname[MAXPATHLEN];
+	int fobjerr, tobjerr;
+	int change;
+
+	if (dobj == di->shares)
+		return (0);
+
+	/*
+	 * Check the from and to snapshots for info on the object. If
+	 * we get ENOENT, then the object just didn't exist in that
+	 * snapshot.  If we get ENOTSUP, then we tried to get
+	 * info on a non-ZPL object, which we don't care about anyway.
+	 */
+	fobjerr = get_stats_for_obj(di, di->fromsnap, dobj, fobjname,
+	    MAXPATHLEN, &fsb);
+	if (fobjerr && di->zerr != ENOENT && di->zerr != ENOTSUP)
+		return (-1);
+
+	tobjerr = get_stats_for_obj(di, di->tosnap, dobj, tobjname,
+	    MAXPATHLEN, &tsb);
+	if (tobjerr && di->zerr != ENOENT && di->zerr != ENOTSUP)
+		return (-1);
+
+	/*
+	 * Unallocated object sharing the same meta dnode block
+	 */
+	if (fobjerr && tobjerr) {
+		ASSERT(di->zerr == ENOENT || di->zerr == ENOTSUP);
+		di->zerr = 0;
+		return (0);
+	}
+
+	di->zerr = 0; /* negate get_stats_for_obj() from side that failed */
+	fmode = fsb.zs_mode & S_IFMT;
+	tmode = tsb.zs_mode & S_IFMT;
+	if (fmode == S_IFDIR || tmode == S_IFDIR || fsb.zs_links == 0 ||
+	    tsb.zs_links == 0)
+		change = 0;
+	else
+		change = tsb.zs_links - fsb.zs_links;
+
+	if (fobjerr) {
+		if (change) {
+			print_link_change(fp, di, change, tobjname, &tsb);
+			return (0);
+		}
+		print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb);
+		return (0);
+	} else if (tobjerr) {
+		if (change) {
+			print_link_change(fp, di, change, fobjname, &fsb);
+			return (0);
+		}
+		print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb);
+		return (0);
+	}
+
+	if (fmode != tmode && fsb.zs_gen == tsb.zs_gen)
+		tsb.zs_gen++;	/* Force a generational difference */
+	same_name = do_name_cmp(fobjname, tobjname);
+
+	/* Simple modification or no change */
+	if (fsb.zs_gen == tsb.zs_gen) {
+		/* No apparent changes.  Could we assert !this?  */
+		if (fsb.zs_ctime[0] == tsb.zs_ctime[0] &&
+		    fsb.zs_ctime[1] == tsb.zs_ctime[1])
+			return (0);
+		if (change) {
+			print_link_change(fp, di, change,
+			    change > 0 ? fobjname : tobjname, &tsb);
+		} else if (same_name) {
+			print_file(fp, di, ZDIFF_MODIFIED, fobjname, &tsb);
+		} else {
+			print_rename(fp, di, fobjname, tobjname, &tsb);
+		}
+		return (0);
+	} else {
+		/* file re-created or object re-used */
+		print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb);
+		print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb);
+		return (0);
+	}
+}
+
+static int
+write_inuse_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr)
+{
+	uint64_t o;
+	int err;
+
+	for (o = dr->ddr_first; o <= dr->ddr_last; o++) {
+		if (err = write_inuse_diffs_one(fp, di, o))
+			return (err);
+	}
+	return (0);
+}
+
+static int
+describe_free(FILE *fp, differ_info_t *di, uint64_t object, char *namebuf,
+    int maxlen)
+{
+	struct zfs_stat sb;
+
+	if (get_stats_for_obj(di, di->fromsnap, object, namebuf,
+	    maxlen, &sb) != 0) {
+		/* Let it slide, if in the delete queue on from side */
+		if (di->zerr == ENOENT && sb.zs_links == 0) {
+			di->zerr = 0;
+			return (0);
+		}
+		return (-1);
+	}
+
+	print_file(fp, di, ZDIFF_REMOVED, namebuf, &sb);
+	return (0);
+}
+
+static int
+write_free_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr)
+{
+	zfs_cmd_t zc = { 0 };
+	libzfs_handle_t *lhdl = di->zhp->zfs_hdl;
+	char fobjname[MAXPATHLEN];
+
+	(void) strlcpy(zc.zc_name, di->fromsnap, sizeof (zc.zc_name));
+	zc.zc_obj = dr->ddr_first - 1;
+
+	ASSERT(di->zerr == 0);
+
+	while (zc.zc_obj < dr->ddr_last) {
+		int err;
+
+		err = ioctl(lhdl->libzfs_fd, ZFS_IOC_NEXT_OBJ, &zc);
+		if (err == 0) {
+			if (zc.zc_obj == di->shares) {
+				zc.zc_obj++;
+				continue;
+			}
+			if (zc.zc_obj > dr->ddr_last) {
+				break;
+			}
+			err = describe_free(fp, di, zc.zc_obj, fobjname,
+			    MAXPATHLEN);
+			if (err)
+				break;
+		} else if (errno == ESRCH) {
+			break;
+		} else {
+			(void) snprintf(di->errbuf, sizeof (di->errbuf),
+			    dgettext(TEXT_DOMAIN,
+			    "next allocated object (> %lld) find failure"),
+			    zc.zc_obj);
+			di->zerr = errno;
+			break;
+		}
+	}
+	if (di->zerr)
+		return (-1);
+	return (0);
+}
+
+static void *
+differ(void *arg)
+{
+	differ_info_t *di = arg;
+	dmu_diff_record_t dr;
+	FILE *ofp;
+	int err = 0;
+
+	if ((ofp = fdopen(di->outputfd, "w")) == NULL) {
+		di->zerr = errno;
+		(void) strerror_r(errno, di->errbuf, sizeof (di->errbuf));
+		(void) close(di->datafd);
+		return ((void *)-1);
+	}
+
+	for (;;) {
+		char *cp = (char *)&dr;
+		int len = sizeof (dr);
+		int rv;
+
+		do {
+			rv = read(di->datafd, cp, len);
+			cp += rv;
+			len -= rv;
+		} while (len > 0 && rv > 0);
+
+		if (rv < 0 || (rv == 0 && len != sizeof (dr))) {
+			di->zerr = EPIPE;
+			break;
+		} else if (rv == 0) {
+			/* end of file at a natural breaking point */
+			break;
+		}
+
+		switch (dr.ddr_type) {
+		case DDR_FREE:
+			err = write_free_diffs(ofp, di, &dr);
+			break;
+		case DDR_INUSE:
+			err = write_inuse_diffs(ofp, di, &dr);
+			break;
+		default:
+			di->zerr = EPIPE;
+			break;
+		}
+
+		if (err || di->zerr)
+			break;
+	}
+
+	(void) fclose(ofp);
+	(void) close(di->datafd);
+	if (err)
+		return ((void *)-1);
+	if (di->zerr) {
+		ASSERT(di->zerr == EINVAL);
+		(void) snprintf(di->errbuf, sizeof (di->errbuf),
+		    dgettext(TEXT_DOMAIN,
+		    "Internal error: bad data from diff IOCTL"));
+		return ((void *)-1);
+	}
+	return ((void *)0);
+}
+
+static int
+find_shares_object(differ_info_t *di)
+{
+	char fullpath[MAXPATHLEN];
+	struct stat64 sb = { 0 };
+
+	(void) strlcpy(fullpath, di->dsmnt, MAXPATHLEN);
+	(void) strlcat(fullpath, ZDIFF_SHARESDIR, MAXPATHLEN);
+
+	if (stat64(fullpath, &sb) != 0) {
+		(void) snprintf(di->errbuf, sizeof (di->errbuf),
+		    dgettext(TEXT_DOMAIN, "Cannot stat %s"), fullpath);
+		return (zfs_error(di->zhp->zfs_hdl, EZFS_DIFF, di->errbuf));
+	}
+
+	di->shares = (uint64_t)sb.st_ino;
+	return (0);
+}
+
+static int
+make_temp_snapshot(differ_info_t *di)
+{
+	libzfs_handle_t *hdl = di->zhp->zfs_hdl;
+	zfs_cmd_t zc = { 0 };
+
+	(void) snprintf(zc.zc_value, sizeof (zc.zc_value),
+	    ZDIFF_PREFIX, getpid());
+	(void) strlcpy(zc.zc_name, di->ds, sizeof (zc.zc_name));
+	zc.zc_cleanup_fd = di->cleanupfd;
+
+	if (ioctl(hdl->libzfs_fd, ZFS_IOC_TMP_SNAPSHOT, &zc) != 0) {
+		int err = errno;
+		if (err == EPERM) {
+			(void) snprintf(di->errbuf, sizeof (di->errbuf),
+			    dgettext(TEXT_DOMAIN, "The diff delegated "
+			    "permission is needed in order\nto create a "
+			    "just-in-time snapshot for diffing\n"));
+			return (zfs_error(hdl, EZFS_DIFF, di->errbuf));
+		} else {
+			(void) snprintf(di->errbuf, sizeof (di->errbuf),
+			    dgettext(TEXT_DOMAIN, "Cannot create just-in-time "
+			    "snapshot of '%s'"), zc.zc_name);
+			return (zfs_standard_error(hdl, err, di->errbuf));
+		}
+	}
+
+	di->tmpsnap = zfs_strdup(hdl, zc.zc_value);
+	di->tosnap = zfs_asprintf(hdl, "%s@%s", di->ds, di->tmpsnap);
+	return (0);
+}
+
+static void
+teardown_differ_info(differ_info_t *di)
+{
+	free(di->ds);
+	free(di->dsmnt);
+	free(di->fromsnap);
+	free(di->frommnt);
+	free(di->tosnap);
+	free(di->tmpsnap);
+	free(di->tomnt);
+	(void) close(di->cleanupfd);
+}
+
+static int
+get_snapshot_names(differ_info_t *di, const char *fromsnap,
+    const char *tosnap)
+{
+	libzfs_handle_t *hdl = di->zhp->zfs_hdl;
+	char *atptrf = NULL;
+	char *atptrt = NULL;
+	int fdslen, fsnlen;
+	int tdslen, tsnlen;
+
+	/*
+	 * Can accept
+	 *    dataset@snap1
+	 *    dataset@snap1 dataset@snap2
+	 *    dataset@snap1 @snap2
+	 *    dataset@snap1 dataset
+	 *    @snap1 dataset@snap2
+	 */
+	if (tosnap == NULL) {
+		/* only a from snapshot given, must be valid */
+		(void) snprintf(di->errbuf, sizeof (di->errbuf),
+		    dgettext(TEXT_DOMAIN,
+		    "Badly formed snapshot name %s"), fromsnap);
+
+		if (!zfs_validate_name(hdl, fromsnap, ZFS_TYPE_SNAPSHOT,
+		    B_FALSE)) {
+			return (zfs_error(hdl, EZFS_INVALIDNAME,
+			    di->errbuf));
+		}
+
+		atptrf = strchr(fromsnap, '@');
+		ASSERT(atptrf != NULL);
+		fdslen = atptrf - fromsnap;
+
+		di->fromsnap = zfs_strdup(hdl, fromsnap);
+		di->ds = zfs_strdup(hdl, fromsnap);
+		di->ds[fdslen] = '\0';
+
+		/* the to snap will be a just-in-time snap of the head */
+		return (make_temp_snapshot(di));
+	}
+
+	(void) snprintf(di->errbuf, sizeof (di->errbuf),
+	    dgettext(TEXT_DOMAIN,
+	    "Unable to determine which snapshots to compare"));
+
+	atptrf = strchr(fromsnap, '@');
+	atptrt = strchr(tosnap, '@');
+	fdslen = atptrf ? atptrf - fromsnap : strlen(fromsnap);
+	tdslen = atptrt ? atptrt - tosnap : strlen(tosnap);
+	fsnlen = strlen(fromsnap) - fdslen;	/* includes @ sign */
+	tsnlen = strlen(tosnap) - tdslen;	/* includes @ sign */
+
+	if (fsnlen <= 1 || tsnlen == 1 || (fdslen == 0 && tdslen == 0) ||
+	    (fsnlen == 0 && tsnlen == 0)) {
+		return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf));
+	} else if ((fdslen > 0 && tdslen > 0) &&
+	    ((tdslen != fdslen || strncmp(fromsnap, tosnap, fdslen) != 0))) {
+		/*
+		 * not the same dataset name, might be okay if
+		 * tosnap is a clone of a fromsnap descendant.
+		 */
+		char origin[ZFS_MAXNAMELEN];
+		zprop_source_t src;
+		zfs_handle_t *zhp;
+
+		di->ds = zfs_alloc(di->zhp->zfs_hdl, tdslen + 1);
+		(void) strncpy(di->ds, tosnap, tdslen);
+		di->ds[tdslen] = '\0';
+
+		zhp = zfs_open(hdl, di->ds, ZFS_TYPE_FILESYSTEM);
+		while (zhp != NULL) {
+			(void) zfs_prop_get(zhp, ZFS_PROP_ORIGIN,
+			    origin, sizeof (origin), &src, NULL, 0, B_FALSE);
+
+			if (strncmp(origin, fromsnap, fsnlen) == 0)
+				break;
+
+			(void) zfs_close(zhp);
+			zhp = zfs_open(hdl, origin, ZFS_TYPE_FILESYSTEM);
+		}
+
+		if (zhp == NULL) {
+			(void) snprintf(di->errbuf, sizeof (di->errbuf),
+			    dgettext(TEXT_DOMAIN,
+			    "Not an earlier snapshot from the same fs"));
+			return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf));
+		} else {
+			(void) zfs_close(zhp);
+		}
+
+		di->isclone = B_TRUE;
+		di->fromsnap = zfs_strdup(hdl, fromsnap);
+		if (tsnlen) {
+			di->tosnap = zfs_strdup(hdl, tosnap);
+		} else {
+			return (make_temp_snapshot(di));
+		}
+	} else {
+		int dslen = fdslen ? fdslen : tdslen;
+
+		di->ds = zfs_alloc(hdl, dslen + 1);
+		(void) strncpy(di->ds, fdslen ? fromsnap : tosnap, dslen);
+		di->ds[dslen] = '\0';
+
+		di->fromsnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrf);
+		if (tsnlen) {
+			di->tosnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrt);
+		} else {
+			return (make_temp_snapshot(di));
+		}
+	}
+	return (0);
+}
+
+static int
+get_mountpoint(differ_info_t *di, char *dsnm, char **mntpt)
+{
+	boolean_t mounted;
+
+	mounted = is_mounted(di->zhp->zfs_hdl, dsnm, mntpt);
+	if (mounted == B_FALSE) {
+		(void) snprintf(di->errbuf, sizeof (di->errbuf),
+		    dgettext(TEXT_DOMAIN,
+		    "Cannot diff an unmounted snapshot"));
+		return (zfs_error(di->zhp->zfs_hdl, EZFS_BADTYPE, di->errbuf));
+	}
+
+	/* Avoid a double slash at the beginning of root-mounted datasets */
+	if (**mntpt == '/' && *(*mntpt + 1) == '\0')
+		**mntpt = '\0';
+	return (0);
+}
+
+static int
+get_mountpoints(differ_info_t *di)
+{
+	char *strptr;
+	char *frommntpt;
+
+	/*
+	 * first get the mountpoint for the parent dataset
+	 */
+	if (get_mountpoint(di, di->ds, &di->dsmnt) != 0)
+		return (-1);
+
+	strptr = strchr(di->tosnap, '@');
+	ASSERT3P(strptr, !=, NULL);
+	di->tomnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", di->dsmnt,
+	    ZDIFF_SNAPDIR, ++strptr);
+
+	strptr = strchr(di->fromsnap, '@');
+	ASSERT3P(strptr, !=, NULL);
+
+	frommntpt = di->dsmnt;
+	if (di->isclone) {
+		char *mntpt;
+		int err;
+
+		*strptr = '\0';
+		err = get_mountpoint(di, di->fromsnap, &mntpt);
+		*strptr = '@';
+		if (err != 0)
+			return (-1);
+		frommntpt = mntpt;
+	}
+
+	di->frommnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", frommntpt,
+	    ZDIFF_SNAPDIR, ++strptr);
+
+	if (di->isclone)
+		free(frommntpt);
+
+	return (0);
+}
+
+static int
+setup_differ_info(zfs_handle_t *zhp, const char *fromsnap,
+    const char *tosnap, differ_info_t *di)
+{
+	di->zhp = zhp;
+
+	di->cleanupfd = open(ZFS_DEV, O_RDWR|O_EXCL);
+	VERIFY(di->cleanupfd >= 0);
+
+	if (get_snapshot_names(di, fromsnap, tosnap) != 0)
+		return (-1);
+
+	if (get_mountpoints(di) != 0)
+		return (-1);
+
+	if (find_shares_object(di) != 0)
+		return (-1);
+
+	return (0);
+}
+
+int
+zfs_show_diffs(zfs_handle_t *zhp, int outfd, const char *fromsnap,
+    const char *tosnap, int flags)
+{
+	zfs_cmd_t zc = { 0 };
+	char errbuf[1024];
+	differ_info_t di = { 0 };
+	pthread_t tid;
+	int pipefd[2];
+	int iocerr;
+
+	(void) snprintf(errbuf, sizeof (errbuf),
+	    dgettext(TEXT_DOMAIN, "zfs diff failed"));
+
+	if (setup_differ_info(zhp, fromsnap, tosnap, &di)) {
+		teardown_differ_info(&di);
+		return (-1);
+	}
+
+	if (pipe(pipefd)) {
+		zfs_error_aux(zhp->zfs_hdl, strerror(errno));
+		teardown_differ_info(&di);
+		return (zfs_error(zhp->zfs_hdl, EZFS_PIPEFAILED, errbuf));
+	}
+
+	di.scripted = (flags & ZFS_DIFF_PARSEABLE);
+	di.classify = (flags & ZFS_DIFF_CLASSIFY);
+	di.timestamped = (flags & ZFS_DIFF_TIMESTAMP);
+
+	di.outputfd = outfd;
+	di.datafd = pipefd[0];
+
+	if (pthread_create(&tid, NULL, differ, &di)) {
+		zfs_error_aux(zhp->zfs_hdl, strerror(errno));
+		(void) close(pipefd[0]);
+		(void) close(pipefd[1]);
+		teardown_differ_info(&di);
+		return (zfs_error(zhp->zfs_hdl,
+		    EZFS_THREADCREATEFAILED, errbuf));
+	}
+
+	/* do the ioctl() */
+	(void) strlcpy(zc.zc_value, di.fromsnap, strlen(di.fromsnap) + 1);
+	(void) strlcpy(zc.zc_name, di.tosnap, strlen(di.tosnap) + 1);
+	zc.zc_cookie = pipefd[1];
+
+	iocerr = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_DIFF, &zc);
+	if (iocerr != 0) {
+		(void) snprintf(errbuf, sizeof (errbuf),
+		    dgettext(TEXT_DOMAIN, "Unable to obtain diffs"));
+		if (errno == EPERM) {
+			zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+			    "\n   The sys_mount privilege or diff delegated "
+			    "permission is needed\n   to execute the "
+			    "diff ioctl"));
+		} else if (errno == EXDEV) {
+			zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
+			    "\n   Not an earlier snapshot from the same fs"));
+		} else if (errno != EPIPE || di.zerr == 0) {
+			zfs_error_aux(zhp->zfs_hdl, strerror(errno));
+		}
+		(void) close(pipefd[1]);
+		(void) pthread_cancel(tid);
+		(void) pthread_join(tid, NULL);
+		teardown_differ_info(&di);
+		if (di.zerr != 0 && di.zerr != EPIPE) {
+			zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr));
+			return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf));
+		} else {
+			return (zfs_error(zhp->zfs_hdl, EZFS_DIFFDATA, errbuf));
+		}
+	}
+
+	(void) close(pipefd[1]);
+	(void) pthread_join(tid, NULL);
+
+	if (di.zerr != 0) {
+		zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr));
+		return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf));
+	}
+	teardown_differ_info(&di);
+	return (0);
+}
--- a/usr/src/lib/libzfs/common/libzfs_impl.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/lib/libzfs/common/libzfs_impl.h	Sat Aug 07 19:27:15 2010 -0600
@@ -20,8 +20,7 @@
  */
 
 /*
- * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
 #ifndef	_LIBFS_IMPL_H
@@ -69,6 +68,7 @@
 	char libzfs_desc[1024];
 	char *libzfs_log_str;
 	int libzfs_printerr;
+	int libzfs_storeerr; /* stuff error messages into buffer */
 	void *libzfs_sharehdl; /* libshare handle */
 	uint_t libzfs_shareflags;
 	boolean_t libzfs_mnttab_enable;
@@ -136,6 +136,7 @@
 void zfs_error_aux(libzfs_handle_t *, const char *, ...);
 void *zfs_alloc(libzfs_handle_t *, size_t);
 void *zfs_realloc(libzfs_handle_t *, void *, size_t, size_t);
+char *zfs_asprintf(libzfs_handle_t *, const char *, ...);
 char *zfs_strdup(libzfs_handle_t *, const char *);
 int no_memory(libzfs_handle_t *);
 
@@ -188,6 +189,9 @@
 
 boolean_t zpool_name_valid(libzfs_handle_t *, boolean_t, const char *);
 
+int zfs_validate_name(libzfs_handle_t *hdl, const char *path, int type,
+    boolean_t modifying);
+
 void namespace_clear(libzfs_handle_t *);
 
 /*
--- a/usr/src/lib/libzfs/common/libzfs_util.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/lib/libzfs/common/libzfs_util.c	Sat Aug 07 19:27:15 2010 -0600
@@ -219,6 +219,10 @@
 		    "use 'zpool scrub -s' to cancel current scrub"));
 	case EZFS_NO_SCRUB:
 		return (dgettext(TEXT_DOMAIN, "there is no active scrub"));
+	case EZFS_DIFF:
+		return (dgettext(TEXT_DOMAIN, "unable to generate diffs"));
+	case EZFS_DIFFDATA:
+		return (dgettext(TEXT_DOMAIN, "invalid diff data"));
 	case EZFS_UNKNOWN:
 		return (dgettext(TEXT_DOMAIN, "unknown error"));
 	default:
@@ -494,6 +498,29 @@
 }
 
 /*
+ * A safe form of asprintf() which will die if the allocation fails.
+ */
+/*PRINTFLIKE2*/
+char *
+zfs_asprintf(libzfs_handle_t *hdl, const char *fmt, ...)
+{
+	va_list ap;
+	char *ret;
+	int err;
+
+	va_start(ap, fmt);
+
+	err = vasprintf(&ret, fmt, ap);
+
+	va_end(ap);
+
+	if (err < 0)
+		(void) no_memory(hdl);
+
+	return (ret);
+}
+
+/*
  * A safe form of realloc(), which also zeroes newly allocated space.
  */
 void *
@@ -579,7 +606,7 @@
 {
 	libzfs_handle_t *hdl;
 
-	if ((hdl = calloc(sizeof (libzfs_handle_t), 1)) == NULL) {
+	if ((hdl = calloc(1, sizeof (libzfs_handle_t))) == NULL) {
 		return (NULL);
 	}
 
--- a/usr/src/lib/libzfs/common/mapfile-vers	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/lib/libzfs/common/mapfile-vers	Sat Aug 07 19:27:15 2010 -0600
@@ -18,11 +18,8 @@
 #
 # CDDL HEADER END
 #
-#
 # Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
 #
-
-#
 # MAPFILE HEADER START
 #
 # WARNING:  STOP NOW.  DO NOT MODIFY THIS FILE.
@@ -61,6 +58,7 @@
 	libzfs_mnttab_cache;
 	libzfs_print_on_error;
 	zfs_allocatable_devs;
+	zfs_asprintf;
 	zfs_clone;
 	zfs_close;
 	zfs_create;
@@ -129,6 +127,7 @@
 	zfs_shareall;
 	zfs_share_nfs;
 	zfs_share_smb;
+	zfs_show_diffs;
 	zfs_smb_acl_add;
 	zfs_smb_acl_purge;
 	zfs_smb_acl_remove;
--- a/usr/src/lib/pyzfs/common/allow.py	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/lib/pyzfs/common/allow.py	Sat Aug 07 19:27:15 2010 -0600
@@ -218,6 +218,7 @@
     send="",
     hold=_("Allows adding a user hold to a snapshot"),
     release=_("Allows releasing a user hold which\n\t\t\t\tmight destroy the snapshot"),
+    diff=_("Allows lookup of paths within a dataset,\n\t\t\t\tgiven an object number. Ordinary users need this\n\t\t\t\tin order to use zfs diff"),
 )
 
 perms_other = dict(
--- a/usr/src/uts/common/Makefile.files	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/Makefile.files	Sat Aug 07 19:27:15 2010 -0600
@@ -1329,6 +1329,7 @@
 	ddt.o			\
 	ddt_zap.o		\
 	dmu.o			\
+	dmu_diff.o		\
 	dmu_send.o		\
 	dmu_object.o		\
 	dmu_objset.o		\
--- a/usr/src/uts/common/fs/xattr.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/xattr.c	Sat Aug 07 19:27:15 2010 -0600
@@ -225,6 +225,9 @@
 		case F_REPARSE:
 			XVA_SET_REQ(&xvattr, XAT_REPARSE);
 			break;
+		case F_GEN:
+			XVA_SET_REQ(&xvattr, XAT_GEN);
+			break;
 		default:
 			break;
 		}
@@ -312,6 +315,11 @@
 			    attr_to_name(F_REPARSE),
 			    xoap->xoa_reparse) == 0);
 		}
+		if (XVA_ISSET_RTN(&xvattr, XAT_GEN)) {
+			VERIFY(nvlist_add_uint64(nvlp,
+			    attr_to_name(F_GEN),
+			    xoap->xoa_generation) == 0);
+		}
 	}
 	/*
 	 * Check for optional ownersid/groupsid
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/fs/zfs/dmu_diff.c	Sat Aug 07 19:27:15 2010 -0600
@@ -0,0 +1,221 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include <sys/dmu.h>
+#include <sys/dmu_impl.h>
+#include <sys/dmu_tx.h>
+#include <sys/dbuf.h>
+#include <sys/dnode.h>
+#include <sys/zfs_context.h>
+#include <sys/dmu_objset.h>
+#include <sys/dmu_traverse.h>
+#include <sys/dsl_dataset.h>
+#include <sys/dsl_dir.h>
+#include <sys/dsl_pool.h>
+#include <sys/dsl_synctask.h>
+#include <sys/zfs_ioctl.h>
+#include <sys/zap.h>
+#include <sys/zio_checksum.h>
+#include <sys/zfs_znode.h>
+
+struct diffarg {
+	struct vnode *da_vp;		/* file to which we are reporting */
+	offset_t *da_offp;
+	int da_err;			/* error that stopped diff search */
+	dmu_diff_record_t da_ddr;
+};
+
+static int
+write_record(struct diffarg *da)
+{
+	ssize_t resid; /* have to get resid to get detailed errno */
+
+	if (da->da_ddr.ddr_type == DDR_NONE) {
+		da->da_err = 0;
+		return (0);
+	}
+
+	da->da_err = vn_rdwr(UIO_WRITE, da->da_vp, (caddr_t)&da->da_ddr,
+	    sizeof (da->da_ddr), 0, UIO_SYSSPACE, FAPPEND,
+	    RLIM64_INFINITY, CRED(), &resid);
+	*da->da_offp += sizeof (da->da_ddr);
+	return (da->da_err);
+}
+
+static int
+report_free_dnode_range(struct diffarg *da, uint64_t first, uint64_t last)
+{
+	ASSERT(first <= last);
+	if (da->da_ddr.ddr_type != DDR_FREE ||
+	    first != da->da_ddr.ddr_last + 1) {
+		if (write_record(da) != 0)
+			return (da->da_err);
+		da->da_ddr.ddr_type = DDR_FREE;
+		da->da_ddr.ddr_first = first;
+		da->da_ddr.ddr_last = last;
+		return (0);
+	}
+	da->da_ddr.ddr_last = last;
+	return (0);
+}
+
+static int
+report_dnode(struct diffarg *da, uint64_t object, dnode_phys_t *dnp)
+{
+	ASSERT(dnp != NULL);
+	if (dnp->dn_type == DMU_OT_NONE)
+		return (report_free_dnode_range(da, object, object));
+
+	if (da->da_ddr.ddr_type != DDR_INUSE ||
+	    object != da->da_ddr.ddr_last + 1) {
+		if (write_record(da) != 0)
+			return (da->da_err);
+		da->da_ddr.ddr_type = DDR_INUSE;
+		da->da_ddr.ddr_first = da->da_ddr.ddr_last = object;
+		return (0);
+	}
+	da->da_ddr.ddr_last = object;
+	return (0);
+}
+
+#define	DBP_SPAN(dnp, level)				  \
+	(((uint64_t)dnp->dn_datablkszsec) << (SPA_MINBLOCKSHIFT + \
+	(level) * (dnp->dn_indblkshift - SPA_BLKPTRSHIFT)))
+
+/* ARGSUSED */
+static int
+diff_cb(spa_t *spa, zilog_t *zilog, const blkptr_t *bp, arc_buf_t *pbuf,
+    const zbookmark_t *zb, const dnode_phys_t *dnp, void *arg)
+{
+	struct diffarg *da = arg;
+	int err = 0;
+
+	if (issig(JUSTLOOKING) && issig(FORREAL))
+		return (EINTR);
+
+	if (zb->zb_object != DMU_META_DNODE_OBJECT)
+		return (0);
+
+	if (bp == NULL) {
+		uint64_t span = DBP_SPAN(dnp, zb->zb_level);
+		uint64_t dnobj = (zb->zb_blkid * span) >> DNODE_SHIFT;
+
+		err = report_free_dnode_range(da, dnobj,
+		    dnobj + (span >> DNODE_SHIFT) - 1);
+		if (err)
+			return (err);
+	} else if (zb->zb_level == 0) {
+		dnode_phys_t *blk;
+		arc_buf_t *abuf;
+		uint32_t aflags = ARC_WAIT;
+		int blksz = BP_GET_LSIZE(bp);
+		int i;
+
+		if (dsl_read(NULL, spa, bp, pbuf,
+		    arc_getbuf_func, &abuf, ZIO_PRIORITY_ASYNC_READ,
+		    ZIO_FLAG_CANFAIL, &aflags, zb) != 0)
+			return (EIO);
+
+		blk = abuf->b_data;
+		for (i = 0; i < blksz >> DNODE_SHIFT; i++) {
+			uint64_t dnobj = (zb->zb_blkid <<
+			    (DNODE_BLOCK_SHIFT - DNODE_SHIFT)) + i;
+			err = report_dnode(da, dnobj, blk+i);
+			if (err)
+				break;
+		}
+		(void) arc_buf_remove_ref(abuf, &abuf);
+		if (err)
+			return (err);
+		/* Don't care about the data blocks */
+		return (TRAVERSE_VISIT_NO_CHILDREN);
+	}
+	return (0);
+}
+
+int
+dmu_diff(objset_t *tosnap, objset_t *fromsnap, struct vnode *vp, offset_t *offp)
+{
+	struct diffarg da;
+	dsl_dataset_t *ds = tosnap->os_dsl_dataset;
+	dsl_dataset_t *fromds = fromsnap->os_dsl_dataset;
+	dsl_dataset_t *findds;
+	dsl_dataset_t *relds;
+	int err = 0;
+
+	/* make certain we are looking at snapshots */
+	if (!dsl_dataset_is_snapshot(ds) || !dsl_dataset_is_snapshot(fromds))
+		return (EINVAL);
+
+	/* fromsnap must be earlier and from the same lineage as tosnap */
+	if (fromds->ds_phys->ds_creation_txg >= ds->ds_phys->ds_creation_txg)
+		return (EXDEV);
+
+	relds = NULL;
+	findds = ds;
+
+	while (fromds->ds_dir != findds->ds_dir) {
+		dsl_pool_t *dp = ds->ds_dir->dd_pool;
+
+		if (!dsl_dir_is_clone(findds->ds_dir)) {
+			if (relds)
+				dsl_dataset_rele(relds, FTAG);
+			return (EXDEV);
+		}
+
+		rw_enter(&dp->dp_config_rwlock, RW_READER);
+		err = dsl_dataset_hold_obj(dp,
+		    findds->ds_dir->dd_phys->dd_origin_obj, FTAG, &findds);
+		rw_exit(&dp->dp_config_rwlock);
+
+		if (relds)
+			dsl_dataset_rele(relds, FTAG);
+
+		if (err)
+			return (EXDEV);
+
+		relds = findds;
+	}
+
+	if (relds)
+		dsl_dataset_rele(relds, FTAG);
+
+	da.da_vp = vp;
+	da.da_offp = offp;
+	da.da_ddr.ddr_type = DDR_NONE;
+	da.da_ddr.ddr_first = da.da_ddr.ddr_last = 0;
+	da.da_err = 0;
+
+	err = traverse_dataset(ds, fromds->ds_phys->ds_creation_txg,
+	    TRAVERSE_PRE | TRAVERSE_PREFETCH_METADATA, diff_cb, &da);
+
+	if (err) {
+		da.da_err = err;
+	} else {
+		/* we set the da.da_err we return as side-effect */
+		(void) write_record(&da);
+	}
+
+	return (da.da_err);
+}
--- a/usr/src/uts/common/fs/zfs/dmu_objset.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/dmu_objset.c	Sat Aug 07 19:27:15 2010 -0600
@@ -42,6 +42,7 @@
 #include <sys/dmu_impl.h>
 #include <sys/zfs_ioctl.h>
 #include <sys/sa.h>
+#include <sys/zfs_onexit.h>
 
 /*
  * Needed to close a window in dnode_move() that allows the objset to be freed
@@ -801,10 +802,14 @@
 struct snaparg {
 	dsl_sync_task_group_t *dstg;
 	char *snapname;
+	char *htag;
 	char failed[MAXPATHLEN];
 	boolean_t recursive;
 	boolean_t needsuspend;
+	boolean_t temporary;
 	nvlist_t *props;
+	struct dsl_ds_holdarg *ha;	/* only needed in the temporary case */
+	dsl_dataset_t *newds;
 };
 
 static int
@@ -812,11 +817,41 @@
 {
 	objset_t *os = arg1;
 	struct snaparg *sn = arg2;
+	int error;
 
 	/* The props have already been checked by zfs_check_userprops(). */
 
-	return (dsl_dataset_snapshot_check(os->os_dsl_dataset,
-	    sn->snapname, tx));
+	error = dsl_dataset_snapshot_check(os->os_dsl_dataset,
+	    sn->snapname, tx);
+	if (error)
+		return (error);
+
+	if (sn->temporary) {
+		/*
+		 * Ideally we would just call
+		 * dsl_dataset_user_hold_check() and
+		 * dsl_dataset_destroy_check() here.  However the
+		 * dataset we want to hold and destroy is the snapshot
+		 * that we just confirmed we can create, but it won't
+		 * exist until after these checks are run.  Do any
+		 * checks we can here and if more checks are added to
+		 * those routines in the future, similar checks may be
+		 * necessary here.
+		 */
+		if (spa_version(os->os_spa) < SPA_VERSION_USERREFS)
+			return (ENOTSUP);
+		/*
+		 * Not checking number of tags because the tag will be
+		 * unique, as it will be the only tag.
+		 */
+		if (strlen(sn->htag) + MAX_TAG_PREFIX_LEN >= MAXNAMELEN)
+			return (E2BIG);
+
+		sn->ha = kmem_alloc(sizeof (struct dsl_ds_holdarg), KM_SLEEP);
+		sn->ha->temphold = B_TRUE;
+		sn->ha->htag = sn->htag;
+	}
+	return (error);
 }
 
 static void
@@ -834,6 +869,19 @@
 		pa.pa_source = ZPROP_SRC_LOCAL;
 		dsl_props_set_sync(ds->ds_prev, &pa, tx);
 	}
+
+	if (sn->temporary) {
+		struct dsl_ds_destroyarg da;
+
+		dsl_dataset_user_hold_sync(ds->ds_prev, sn->ha, tx);
+		kmem_free(sn->ha, sizeof (struct dsl_ds_holdarg));
+		sn->ha = NULL;
+		sn->newds = ds->ds_prev;
+
+		da.ds = ds->ds_prev;
+		da.defer = B_TRUE;
+		dsl_dataset_destroy_sync(&da, FTAG, tx);
+	}
 }
 
 static int
@@ -893,12 +941,13 @@
 }
 
 int
-dmu_objset_snapshot(char *fsname, char *snapname,
-    nvlist_t *props, boolean_t recursive)
+dmu_objset_snapshot(char *fsname, char *snapname, char *tag,
+    nvlist_t *props, boolean_t recursive, boolean_t temporary, int cleanup_fd)
 {
 	dsl_sync_task_t *dst;
 	struct snaparg sn;
 	spa_t *spa;
+	minor_t minor;
 	int err;
 
 	(void) strcpy(sn.failed, fsname);
@@ -907,11 +956,26 @@
 	if (err)
 		return (err);
 
+	if (temporary) {
+		if (cleanup_fd < 0) {
+			spa_close(spa, FTAG);
+			return (EINVAL);
+		}
+		if ((err = zfs_onexit_fd_hold(cleanup_fd, &minor)) != 0) {
+			spa_close(spa, FTAG);
+			return (err);
+		}
+	}
+
 	sn.dstg = dsl_sync_task_group_create(spa_get_dsl(spa));
 	sn.snapname = snapname;
+	sn.htag = tag;
 	sn.props = props;
 	sn.recursive = recursive;
 	sn.needsuspend = (spa_version(spa) < SPA_VERSION_FAST_SNAP);
+	sn.temporary = temporary;
+	sn.ha = NULL;
+	sn.newds = NULL;
 
 	if (recursive) {
 		err = dmu_objset_find(fsname,
@@ -927,8 +991,11 @@
 	    dst = list_next(&sn.dstg->dstg_tasks, dst)) {
 		objset_t *os = dst->dst_arg1;
 		dsl_dataset_t *ds = os->os_dsl_dataset;
-		if (dst->dst_err)
+		if (dst->dst_err) {
 			dsl_dataset_name(ds, sn.failed);
+		} else if (temporary) {
+			dsl_register_onexit_hold_cleanup(sn.newds, tag, minor);
+		}
 		if (sn.needsuspend)
 			zil_resume(dmu_objset_zil(os));
 		dmu_objset_rele(os, &sn);
@@ -936,6 +1003,8 @@
 
 	if (err)
 		(void) strcpy(fsname, sn.failed);
+	if (temporary)
+		zfs_onexit_fd_rele(cleanup_fd);
 	dsl_sync_task_group_destroy(sn.dstg);
 	spa_close(spa, FTAG);
 	return (err);
--- a/usr/src/uts/common/fs/zfs/dmu_traverse.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/dmu_traverse.c	Sat Aug 07 19:27:15 2010 -0600
@@ -162,6 +162,8 @@
 	if (td->td_flags & TRAVERSE_PRE) {
 		err = td->td_func(td->td_spa, NULL, bp, pbuf, zb, dnp,
 		    td->td_arg);
+		if (err == TRAVERSE_VISIT_NO_CHILDREN)
+			return (0);
 		if (err)
 			return (err);
 	}
--- a/usr/src/uts/common/fs/zfs/dsl_dataset.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/dsl_dataset.c	Sat Aug 07 19:27:15 2010 -0600
@@ -1375,6 +1375,11 @@
 	return (0);
 }
 
+/*
+ * If you add new checks here, you may need to add
+ * additional checks to the "temporary" case in
+ * snapshot_check() in dmu_objset.c.
+ */
 /* ARGSUSED */
 int
 dsl_dataset_destroy_check(void *arg1, void *arg2, dmu_tx_t *tx)
@@ -1616,21 +1621,23 @@
 	dsl_pool_t *dp = ds->ds_dir->dd_pool;
 	objset_t *mos = dp->dp_meta_objset;
 	dsl_dataset_t *ds_prev = NULL;
+	boolean_t wont_destroy;
 	uint64_t obj;
 
-	ASSERT(ds->ds_owner);
+	wont_destroy = (dsda->defer &&
+	    (ds->ds_userrefs > 0 || ds->ds_phys->ds_num_children > 1));
+
+	ASSERT(ds->ds_owner || wont_destroy);
 	ASSERT(dsda->defer || ds->ds_phys->ds_num_children <= 1);
 	ASSERT(ds->ds_prev == NULL ||
 	    ds->ds_prev->ds_phys->ds_next_snap_obj != ds->ds_object);
 	ASSERT3U(ds->ds_phys->ds_bp.blk_birth, <=, tx->tx_txg);
 
-	if (dsda->defer) {
+	if (wont_destroy) {
 		ASSERT(spa_version(dp->dp_spa) >= SPA_VERSION_USERREFS);
-		if (ds->ds_userrefs > 0 || ds->ds_phys->ds_num_children > 1) {
-			dmu_buf_will_dirty(ds->ds_dbuf, tx);
-			ds->ds_phys->ds_flags |= DS_FLAG_DEFER_DESTROY;
-			return;
-		}
+		dmu_buf_will_dirty(ds->ds_dbuf, tx);
+		ds->ds_phys->ds_flags |= DS_FLAG_DEFER_DESTROY;
+		return;
 	}
 
 	/* signal any waiters that this dataset is going away */
@@ -3452,16 +3459,6 @@
 	return (err);
 }
 
-struct dsl_ds_holdarg {
-	dsl_sync_task_group_t *dstg;
-	char *htag;
-	char *snapname;
-	boolean_t recursive;
-	boolean_t gotone;
-	boolean_t temphold;
-	char failed[MAXPATHLEN];
-};
-
 typedef struct zfs_hold_cleanup_arg {
 	dsl_pool_t *dp;
 	uint64_t dsobj;
@@ -3493,11 +3490,10 @@
 }
 
 /*
- * The max length of a temporary tag prefix is the number of hex digits
- * required to express UINT64_MAX plus one for the hyphen.
+ * If you add new checks here, you may need to add
+ * additional checks to the "temporary" case in
+ * snapshot_check() in dmu_objset.c.
  */
-#define	MAX_TAG_PREFIX_LEN	17
-
 static int
 dsl_dataset_user_hold_check(void *arg1, void *arg2, dmu_tx_t *tx)
 {
@@ -3532,7 +3528,7 @@
 	return (error);
 }
 
-static void
+void
 dsl_dataset_user_hold_sync(void *arg1, void *arg2, dmu_tx_t *tx)
 {
 	dsl_dataset_t *ds = arg1;
--- a/usr/src/uts/common/fs/zfs/dsl_deleg.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/dsl_deleg.c	Sat Aug 07 19:27:15 2010 -0600
@@ -19,7 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
 /*
--- a/usr/src/uts/common/fs/zfs/sys/dmu.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/dmu.h	Sat Aug 07 19:27:15 2010 -0600
@@ -192,8 +192,8 @@
     uint64_t flags);
 int dmu_objset_destroy(const char *name, boolean_t defer);
 int dmu_snapshots_destroy(char *fsname, char *snapname, boolean_t defer);
-int dmu_objset_snapshot(char *fsname, char *snapname, struct nvlist *props,
-    boolean_t recursive);
+int dmu_objset_snapshot(char *fsname, char *snapname, char *tag,
+    struct nvlist *props, boolean_t recursive, boolean_t temporary, int fd);
 int dmu_objset_rename(const char *name, const char *newname,
     boolean_t recursive);
 int dmu_objset_find(char *name, int func(const char *, void *), void *arg,
@@ -726,6 +726,9 @@
     int cleanup_fd, uint64_t *action_handlep);
 int dmu_recv_end(dmu_recv_cookie_t *drc);
 
+int dmu_diff(objset_t *tosnap, objset_t *fromsnap, struct vnode *vp,
+    offset_t *off);
+
 /* CRC64 table */
 #define	ZFS_CRC64_POLY	0xC96C5795D7870F42ULL	/* ECMA-182, reflected form */
 extern uint64_t zfs_crc64_table[256];
--- a/usr/src/uts/common/fs/zfs/sys/dmu_objset.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/dmu_objset.h	Sat Aug 07 19:27:15 2010 -0600
@@ -142,8 +142,8 @@
 int dmu_objset_clone(const char *name, struct dsl_dataset *clone_origin,
     uint64_t flags);
 int dmu_objset_destroy(const char *name, boolean_t defer);
-int dmu_objset_snapshot(char *fsname, char *snapname, nvlist_t *props,
-    boolean_t recursive);
+int dmu_objset_snapshot(char *fsname, char *snapname, char *tag,
+    struct nvlist *props, boolean_t recursive, boolean_t temporary, int fd);
 void dmu_objset_stats(objset_t *os, nvlist_t *nv);
 void dmu_objset_fast_stat(objset_t *os, dmu_objset_stats_t *stat);
 void dmu_objset_space(objset_t *os, uint64_t *refdbytesp, uint64_t *availbytesp,
--- a/usr/src/uts/common/fs/zfs/sys/dmu_traverse.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/dmu_traverse.h	Sat Aug 07 19:27:15 2010 -0600
@@ -49,6 +49,9 @@
 #define	TRAVERSE_PREFETCH (TRAVERSE_PREFETCH_METADATA | TRAVERSE_PREFETCH_DATA)
 #define	TRAVERSE_HARD			(1<<4)
 
+/* Special traverse error return value to indicate skipping of children */
+#define	TRAVERSE_VISIT_NO_CHILDREN	-1
+
 int traverse_dataset(struct dsl_dataset *ds,
     uint64_t txg_start, int flags, blkptr_cb_t func, void *arg);
 int traverse_pool(spa_t *spa,
--- a/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h	Sat Aug 07 19:27:15 2010 -0600
@@ -162,6 +162,22 @@
 	boolean_t need_prep;		/* do we need to retry due to EBUSY? */
 };
 
+/*
+ * The max length of a temporary tag prefix is the number of hex digits
+ * required to express UINT64_MAX plus one for the hyphen.
+ */
+#define	MAX_TAG_PREFIX_LEN	17
+
+struct dsl_ds_holdarg {
+	dsl_sync_task_group_t *dstg;
+	char *htag;
+	char *snapname;
+	boolean_t recursive;
+	boolean_t gotone;
+	boolean_t temphold;
+	char failed[MAXPATHLEN];
+};
+
 #define	dsl_dataset_is_snapshot(ds) \
 	((ds)->ds_phys->ds_num_children != 0)
 
@@ -194,6 +210,7 @@
 dsl_syncfunc_t dsl_dataset_destroy_sync;
 dsl_checkfunc_t dsl_dataset_snapshot_check;
 dsl_syncfunc_t dsl_dataset_snapshot_sync;
+dsl_syncfunc_t dsl_dataset_user_hold_sync;
 int dsl_dataset_rename(char *name, const char *newname, boolean_t recursive);
 int dsl_dataset_promote(const char *name, char *conflsnap);
 int dsl_dataset_clone_swap(dsl_dataset_t *clone, dsl_dataset_t *origin_head,
--- a/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h	Sat Aug 07 19:27:15 2010 -0600
@@ -54,6 +54,7 @@
 #define	ZFS_DELEG_PERM_GROUPUSED	"groupused"
 #define	ZFS_DELEG_PERM_HOLD		"hold"
 #define	ZFS_DELEG_PERM_RELEASE		"release"
+#define	ZFS_DELEG_PERM_DIFF		"diff"
 
 /*
  * Note: the names of properties that are marked delegatable are also
--- a/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h	Sat Aug 07 19:27:15 2010 -0600
@@ -30,6 +30,7 @@
 #include <sys/zio.h>
 #include <sys/dsl_deleg.h>
 #include <sys/spa.h>
+#include <sys/zfs_stat.h>
 
 #ifdef _KERNEL
 #include <sys/nvpair.h>
@@ -198,6 +199,22 @@
 	} drr_u;
 } dmu_replay_record_t;
 
+/* diff record range types */
+typedef enum diff_type {
+	DDR_NONE = 0x1,
+	DDR_INUSE = 0x2,
+	DDR_FREE = 0x4
+} diff_type_t;
+
+/*
+ * The diff reports back ranges of free or in-use objects.
+ */
+typedef struct dmu_diff_record {
+	uint64_t ddr_type;
+	uint64_t ddr_first;
+	uint64_t ddr_last;
+} dmu_diff_record_t;
+
 typedef struct zinject_record {
 	uint64_t	zi_objset;
 	uint64_t	zi_object;
@@ -266,10 +283,11 @@
 	boolean_t	zc_temphold;
 	uint64_t	zc_action_handle;
 	int		zc_cleanup_fd;
-	uint8_t		zc_pad[4];
+	uint8_t		zc_pad[4];		/* alignment */
 	uint64_t	zc_sendobj;
 	uint64_t	zc_fromobj;
 	uint64_t	zc_createtxg;
+	zfs_stat_t	zc_stat;
 } zfs_cmd_t;
 
 typedef struct zfs_useracct {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/uts/common/fs/zfs/sys/zfs_stat.h	Sat Aug 07 19:27:15 2010 -0600
@@ -0,0 +1,56 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#ifndef	_SYS_FS_ZFS_STAT_H
+#define	_SYS_FS_ZFS_STAT_H
+
+#ifdef _KERNEL
+#include <sys/isa_defs.h>
+#include <sys/types32.h>
+#include <sys/dmu.h>
+#endif
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+/*
+ * A limited number of zpl level stats are retrievable
+ * with an ioctl.  zfs diff is the current consumer.
+ */
+typedef struct zfs_stat {
+	uint64_t	zs_gen;
+	uint64_t	zs_mode;
+	uint64_t	zs_links;
+	uint64_t	zs_ctime[2];
+} zfs_stat_t;
+
+extern int zfs_obj_to_stats(objset_t *osp, uint64_t obj, zfs_stat_t *sb,
+    char *buf, int len);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* _SYS_FS_ZFS_STAT_H */
--- a/usr/src/uts/common/fs/zfs/sys/zfs_znode.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/sys/zfs_znode.h	Sat Aug 07 19:27:15 2010 -0600
@@ -35,6 +35,7 @@
 #include <sys/zfs_vfsops.h>
 #include <sys/rrwlock.h>
 #include <sys/zfs_sa.h>
+#include <sys/zfs_stat.h>
 #endif
 #include <sys/zfs_acl.h>
 #include <sys/zil.h>
--- a/usr/src/uts/common/fs/zfs/zfs_ctldir.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/zfs_ctldir.c	Sat Aug 07 19:27:15 2010 -0600
@@ -749,7 +749,8 @@
 		return (err);
 
 	if (err == 0) {
-		err = dmu_objset_snapshot(name, dirname, NULL, B_FALSE);
+		err = dmu_objset_snapshot(name, dirname, NULL, NULL,
+		    B_FALSE, B_FALSE, -1);
 		if (err)
 			return (err);
 		err = lookupnameat(dirname, seg, follow, NULL, vpp, dvp);
--- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c	Sat Aug 07 19:27:15 2010 -0600
@@ -854,6 +854,22 @@
 }
 
 /*
+ * Policy for object to name lookups.
+ */
+/* ARGSUSED */
+static int
+zfs_secpolicy_diff(zfs_cmd_t *zc, cred_t *cr)
+{
+	int error;
+
+	if ((error = secpolicy_sys_config(cr, B_FALSE)) == 0)
+		return (0);
+
+	error = zfs_secpolicy_write_perms(zc->zc_name, ZFS_DELEG_PERM_DIFF, cr);
+	return (error);
+}
+
+/*
  * Policy for fault injection.  Requires all privileges.
  */
 /* ARGSUSED */
@@ -944,6 +960,33 @@
 }
 
 /*
+ * Policy for allowing temporary snapshots to be taken or released
+ */
+static int
+zfs_secpolicy_tmp_snapshot(zfs_cmd_t *zc, cred_t *cr)
+{
+	/*
+	 * A temporary snapshot is the same as a snapshot,
+	 * hold, destroy and release all rolled into one.
+	 * Delegated diff alone is sufficient that we allow this.
+	 */
+	int error;
+
+	if ((error = zfs_secpolicy_write_perms(zc->zc_name,
+	    ZFS_DELEG_PERM_DIFF, cr)) == 0)
+		return (0);
+
+	error = zfs_secpolicy_snapshot(zc, cr);
+	if (!error)
+		error = zfs_secpolicy_hold(zc, cr);
+	if (!error)
+		error = zfs_secpolicy_release(zc, cr);
+	if (!error)
+		error = zfs_secpolicy_destroy(zc, cr);
+	return (error);
+}
+
+/*
  * Returns the nvlist as specified by the user in the zfs_cmd_t.
  */
 static int
@@ -1437,6 +1480,35 @@
 	return (error);
 }
 
+/*
+ * inputs:
+ * zc_name		name of filesystem
+ * zc_obj		object to find
+ *
+ * outputs:
+ * zc_stat		stats on object
+ * zc_value		path to object
+ */
+static int
+zfs_ioc_obj_to_stats(zfs_cmd_t *zc)
+{
+	objset_t *os;
+	int error;
+
+	/* XXX reading from objset not owned */
+	if ((error = dmu_objset_hold(zc->zc_name, FTAG, &os)) != 0)
+		return (error);
+	if (dmu_objset_type(os) != DMU_OST_ZFS) {
+		dmu_objset_rele(os, FTAG);
+		return (EINVAL);
+	}
+	error = zfs_obj_to_stats(os, zc->zc_obj, &zc->zc_stat, zc->zc_value,
+	    sizeof (zc->zc_value));
+	dmu_objset_rele(os, FTAG);
+
+	return (error);
+}
+
 static int
 zfs_ioc_vdev_add(zfs_cmd_t *zc)
 {
@@ -2978,8 +3050,8 @@
 		goto out;
 	}
 
-	error = dmu_objset_snapshot(zc->zc_name, zc->zc_value,
-	    nvprops, recursive);
+	error = dmu_objset_snapshot(zc->zc_name, zc->zc_value, NULL,
+	    nvprops, recursive, B_FALSE, -1);
 
 out:
 	nvlist_free(nvprops);
@@ -4167,6 +4239,113 @@
 };
 
 /*
+ * inputs:
+ * zc_name		name of containing filesystem
+ * zc_obj		object # beyond which we want next in-use object #
+ *
+ * outputs:
+ * zc_obj		next in-use object #
+ */
+static int
+zfs_ioc_next_obj(zfs_cmd_t *zc)
+{
+	objset_t *os = NULL;
+	int error;
+
+	error = dmu_objset_hold(zc->zc_name, FTAG, &os);
+	if (error)
+		return (error);
+
+	error = dmu_object_next(os, &zc->zc_obj, B_FALSE,
+	    os->os_dsl_dataset->ds_phys->ds_prev_snap_txg);
+
+	dmu_objset_rele(os, FTAG);
+	return (error);
+}
+
+/*
+ * inputs:
+ * zc_name		name of filesystem
+ * zc_value		prefix name for snapshot
+ * zc_cleanup_fd	cleanup-on-exit file descriptor for calling process
+ *
+ * outputs:
+ */
+static int
+zfs_ioc_tmp_snapshot(zfs_cmd_t *zc)
+{
+	char *snap_name;
+	int error;
+
+	snap_name = kmem_asprintf("%s-%016llx", zc->zc_value,
+	    (u_longlong_t)ddi_get_lbolt64());
+
+	if (strlen(snap_name) >= MAXNAMELEN) {
+		strfree(snap_name);
+		return (E2BIG);
+	}
+
+	error = dmu_objset_snapshot(zc->zc_name, snap_name, snap_name,
+	    NULL, B_FALSE, B_TRUE, zc->zc_cleanup_fd);
+	if (error != 0) {
+		strfree(snap_name);
+		return (error);
+	}
+
+	(void) strcpy(zc->zc_value, snap_name);
+	strfree(snap_name);
+	return (0);
+}
+
+/*
+ * inputs:
+ * zc_name		name of "to" snapshot
+ * zc_value		name of "from" snapshot
+ * zc_cookie		file descriptor to write diff data on
+ *
+ * outputs:
+ * dmu_diff_record_t's to the file descriptor
+ */
+static int
+zfs_ioc_diff(zfs_cmd_t *zc)
+{
+	objset_t *fromsnap;
+	objset_t *tosnap;
+	file_t *fp;
+	offset_t off;
+	int error;
+
+	error = dmu_objset_hold(zc->zc_name, FTAG, &tosnap);
+	if (error)
+		return (error);
+
+	error = dmu_objset_hold(zc->zc_value, FTAG, &fromsnap);
+	if (error) {
+		dmu_objset_rele(tosnap, FTAG);
+		return (error);
+	}
+
+	fp = getf(zc->zc_cookie);
+	if (fp == NULL) {
+		dmu_objset_rele(fromsnap, FTAG);
+		dmu_objset_rele(tosnap, FTAG);
+		return (EBADF);
+	}
+
+	off = fp->f_offset;
+
+	error = dmu_diff(tosnap, fromsnap, fp->f_vnode, &off);
+
+	if (VOP_SEEK(fp->f_vnode, fp->f_offset, &off, NULL) == 0)
+		fp->f_offset = off;
+	releasef(zc->zc_cookie);
+
+	dmu_objset_rele(fromsnap, FTAG);
+	dmu_objset_rele(tosnap, FTAG);
+	return (error);
+}
+
+/*
  * Remove all ACL files in shares dir
  */
 static int
@@ -4510,9 +4689,9 @@
 	    B_TRUE, B_TRUE },
 	{ zfs_ioc_snapshot, zfs_secpolicy_snapshot, DATASET_NAME, B_TRUE,
 	    B_TRUE },
-	{ zfs_ioc_dsobj_to_dsname, zfs_secpolicy_config, POOL_NAME, B_FALSE,
+	{ zfs_ioc_dsobj_to_dsname, zfs_secpolicy_diff, POOL_NAME, B_FALSE,
 	    B_FALSE },
-	{ zfs_ioc_obj_to_path, zfs_secpolicy_config, DATASET_NAME, B_FALSE,
+	{ zfs_ioc_obj_to_path, zfs_secpolicy_diff, DATASET_NAME, B_FALSE,
 	    B_TRUE },
 	{ zfs_ioc_pool_set_props, zfs_secpolicy_config,	POOL_NAME, B_TRUE,
 	    B_TRUE },
@@ -4541,6 +4720,13 @@
 	{ zfs_ioc_objset_recvd_props, zfs_secpolicy_read, DATASET_NAME, B_FALSE,
 	    B_FALSE },
 	{ zfs_ioc_vdev_split, zfs_secpolicy_config, POOL_NAME, B_TRUE,
+	    B_TRUE },
+	{ zfs_ioc_next_obj, zfs_secpolicy_read, DATASET_NAME, B_FALSE,
+	    B_FALSE },
+	{ zfs_ioc_diff, zfs_secpolicy_diff, DATASET_NAME, B_FALSE, B_FALSE },
+	{ zfs_ioc_tmp_snapshot, zfs_secpolicy_tmp_snapshot, DATASET_NAME,
+	    B_FALSE, B_FALSE },
+	{ zfs_ioc_obj_to_stats, zfs_secpolicy_diff, DATASET_NAME, B_FALSE,
 	    B_TRUE }
 };
 
--- a/usr/src/uts/common/fs/zfs/zfs_vnops.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/zfs_vnops.c	Sat Aug 07 19:27:15 2010 -0600
@@ -2536,6 +2536,10 @@
 			xoap->xoa_reparse = ((zp->z_pflags & ZFS_REPARSE) != 0);
 			XVA_SET_RTN(xvap, XAT_REPARSE);
 		}
+		if (XVA_ISSET_REQ(xvap, XAT_GEN)) {
+			xoap->xoa_generation = zp->z_gen;
+			XVA_SET_RTN(xvap, XAT_GEN);
+		}
 	}
 
 	ZFS_TIME_DECODE(&vap->va_atime, zp->z_atime);
--- a/usr/src/uts/common/fs/zfs/zfs_znode.c	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/fs/zfs/zfs_znode.c	Sat Aug 07 19:27:15 2010 -0600
@@ -63,6 +63,7 @@
 #include <sys/zfs_znode.h>
 #include <sys/sa.h>
 #include <sys/zfs_sa.h>
+#include <sys/zfs_stat.h>
 
 #include "zfs_prop.h"
 #include "zfs_comutil.h"
@@ -1879,80 +1880,121 @@
 
 #endif /* _KERNEL */
 
+static int
+zfs_sa_setup(objset_t *osp, sa_attr_type_t **sa_table)
+{
+	uint64_t sa_obj = 0;
+	int error;
+
+	error = zap_lookup(osp, MASTER_NODE_OBJ, ZFS_SA_ATTRS, 8, 1, &sa_obj);
+	if (error != 0 && error != ENOENT)
+		return (error);
+
+	error = sa_setup(osp, sa_obj, zfs_attr_table, ZPL_END, sa_table);
+	return (error);
+}
+
+static int
+zfs_grab_sa_handle(objset_t *osp, uint64_t obj, sa_handle_t **hdlp,
+    dmu_buf_t **db)
+{
+	dmu_object_info_t doi;
+	int error;
+
+	if ((error = sa_buf_hold(osp, obj, FTAG, db)) != 0)
+		return (error);
+
+	dmu_object_info_from_db(*db, &doi);
+	if ((doi.doi_bonus_type != DMU_OT_SA &&
+	    doi.doi_bonus_type != DMU_OT_ZNODE) ||
+	    doi.doi_bonus_type == DMU_OT_ZNODE &&
+	    doi.doi_bonus_size < sizeof (znode_phys_t)) {
+		sa_buf_rele(*db, FTAG);
+		return (ENOTSUP);
+	}
+
+	error = sa_handle_get(osp, obj, NULL, SA_HDL_PRIVATE, hdlp);
+	if (error != 0) {
+		sa_buf_rele(*db, FTAG);
+		return (error);
+	}
+
+	return (0);
+}
+
+void
+zfs_release_sa_handle(sa_handle_t *hdl, dmu_buf_t *db)
+{
+	sa_handle_destroy(hdl);
+	sa_buf_rele(db, FTAG);
+}
+
 /*
  * Given an object number, return its parent object number and whether
  * or not the object is an extended attribute directory.
  */
 static int
-zfs_obj_to_pobj(objset_t *osp, uint64_t obj, uint64_t *pobjp, int *is_xattrdir,
-    sa_attr_type_t *sa_table)
+zfs_obj_to_pobj(sa_handle_t *hdl, sa_attr_type_t *sa_table, uint64_t *pobjp,
+    int *is_xattrdir)
 {
-	dmu_buf_t *db;
-	dmu_object_info_t doi;
-	int error;
 	uint64_t parent;
 	uint64_t pflags;
 	uint64_t mode;
 	sa_bulk_attr_t bulk[3];
-	sa_handle_t *hdl;
 	int count = 0;
+	int error;
 
-	if ((error = sa_buf_hold(osp, obj, FTAG, &db)) != 0)
+	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_PARENT], NULL,
+	    &parent, sizeof (parent));
+	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_FLAGS], NULL,
+	    &pflags, sizeof (pflags));
+	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_MODE], NULL,
+	    &mode, sizeof (mode));
+
+	if ((error = sa_bulk_lookup(hdl, bulk, count)) != 0)
 		return (error);
 
-	dmu_object_info_from_db(db, &doi);
-	if ((doi.doi_bonus_type != DMU_OT_SA &&
-	    doi.doi_bonus_type != DMU_OT_ZNODE) ||
-	    doi.doi_bonus_type == DMU_OT_ZNODE &&
-	    doi.doi_bonus_size < sizeof (znode_phys_t)) {
-		sa_buf_rele(db, FTAG);
-		return (EINVAL);
-	}
-
-	if ((error = sa_handle_get(osp, obj, NULL, SA_HDL_PRIVATE,
-	    &hdl)) != 0) {
-		sa_buf_rele(db, FTAG);
-		return (error);
-	}
-
-	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_PARENT],
-	    NULL, &parent, 8);
-	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_FLAGS], NULL,
-	    &pflags, 8);
-	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_MODE], NULL,
-	    &mode, 8);
-
-	if ((error = sa_bulk_lookup(hdl, bulk, count)) != 0) {
-		sa_buf_rele(db, FTAG);
-		sa_handle_destroy(hdl);
-		return (error);
-	}
 	*pobjp = parent;
 	*is_xattrdir = ((pflags & ZFS_XATTR) != 0) && S_ISDIR(mode);
-	sa_handle_destroy(hdl);
-	sa_buf_rele(db, FTAG);
 
 	return (0);
 }
 
-int
-zfs_obj_to_path(objset_t *osp, uint64_t obj, char *buf, int len)
+/*
+ * Given an object number, return some zpl level statistics
+ */
+static int
+zfs_obj_to_stats_impl(sa_handle_t *hdl, sa_attr_type_t *sa_table,
+    zfs_stat_t *sb)
 {
+	sa_bulk_attr_t bulk[4];
+	int count = 0;
+
+	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_MODE], NULL,
+	    &sb->zs_mode, sizeof (sb->zs_mode));
+	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_GEN], NULL,
+	    &sb->zs_gen, sizeof (sb->zs_gen));
+	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_LINKS], NULL,
+	    &sb->zs_links, sizeof (sb->zs_links));
+	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_CTIME], NULL,
+	    &sb->zs_ctime, sizeof (sb->zs_ctime));
+
+	return (sa_bulk_lookup(hdl, bulk, count));
+}
+
+static int
+zfs_obj_to_path_impl(objset_t *osp, uint64_t obj, sa_handle_t *hdl,
+    sa_attr_type_t *sa_table, char *buf, int len)
+{
+	sa_handle_t *sa_hdl;
+	sa_handle_t *prevhdl = NULL;
+	dmu_buf_t *prevdb = NULL;
+	dmu_buf_t *sa_db = NULL;
 	char *path = buf + len - 1;
-	sa_attr_type_t *sa_table;
 	int error;
-	uint64_t sa_obj = 0;
 
 	*path = '\0';
-
-	error = zap_lookup(osp, MASTER_NODE_OBJ, ZFS_SA_ATTRS, 8, 1, &sa_obj);
-
-	if (error != 0 && error != ENOENT)
-		return (error);
-
-	if ((error = sa_setup(osp, sa_obj, zfs_attr_table,
-	    ZPL_END, &sa_table)) != 0)
-		return (error);
+	sa_hdl = hdl;
 
 	for (;;) {
 		uint64_t pobj;
@@ -1960,8 +2002,11 @@
 		size_t complen;
 		int is_xattrdir;
 
-		if ((error = zfs_obj_to_pobj(osp, obj, &pobj,
-		    &is_xattrdir, sa_table)) != 0)
+		if (prevdb)
+			zfs_release_sa_handle(prevhdl, prevdb);
+
+		if ((error = zfs_obj_to_pobj(sa_hdl, sa_table, &pobj,
+		    &is_xattrdir)) != 0)
 			break;
 
 		if (pobj == obj) {
@@ -1985,6 +2030,22 @@
 		ASSERT(path >= buf);
 		bcopy(component, path, complen);
 		obj = pobj;
+
+		if (sa_hdl != hdl) {
+			prevhdl = sa_hdl;
+			prevdb = sa_db;
+		}
+		error = zfs_grab_sa_handle(osp, obj, &sa_hdl, &sa_db);
+		if (error != 0) {
+			sa_hdl = prevhdl;
+			sa_db = prevdb;
+			break;
+		}
+	}
+
+	if (sa_hdl != NULL && sa_hdl != hdl) {
+		ASSERT(sa_db != NULL);
+		zfs_release_sa_handle(sa_hdl, sa_db);
 	}
 
 	if (error == 0)
@@ -1992,3 +2053,57 @@
 
 	return (error);
 }
+
+int
+zfs_obj_to_path(objset_t *osp, uint64_t obj, char *buf, int len)
+{
+	sa_attr_type_t *sa_table;
+	sa_handle_t *hdl;
+	dmu_buf_t *db;
+	int error;
+
+	error = zfs_sa_setup(osp, &sa_table);
+	if (error != 0)
+		return (error);
+
+	error = zfs_grab_sa_handle(osp, obj, &hdl, &db);
+	if (error != 0)
+		return (error);
+
+	error = zfs_obj_to_path_impl(osp, obj, hdl, sa_table, buf, len);
+
+	zfs_release_sa_handle(hdl, db);
+	return (error);
+}
+
+int
+zfs_obj_to_stats(objset_t *osp, uint64_t obj, zfs_stat_t *sb,
+    char *buf, int len)
+{
+	char *path = buf + len - 1;
+	sa_attr_type_t *sa_table;
+	sa_handle_t *hdl;
+	dmu_buf_t *db;
+	int error;
+
+	*path = '\0';
+
+	error = zfs_sa_setup(osp, &sa_table);
+	if (error != 0)
+		return (error);
+
+	error = zfs_grab_sa_handle(osp, obj, &hdl, &db);
+	if (error != 0)
+		return (error);
+
+	error = zfs_obj_to_stats_impl(hdl, sa_table, sb);
+	if (error != 0) {
+		zfs_release_sa_handle(hdl, db);
+		return (error);
+	}
+
+	error = zfs_obj_to_path_impl(osp, obj, hdl, sa_table, buf, len);
+
+	zfs_release_sa_handle(hdl, db);
+	return (error);
+}
--- a/usr/src/uts/common/sys/attr.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/sys/attr.h	Sat Aug 07 19:27:15 2010 -0600
@@ -19,8 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
 #ifndef _SYS_ATTR_H
@@ -55,6 +54,7 @@
 #define	A_OWNERSID		"ownersid"
 #define	A_GROUPSID		"groupsid"
 #define	A_REPARSE_POINT		"reparse"
+#define	A_GEN			"generation"
 
 /* Attribute option for utilities */
 #define	O_HIDDEN	 "H"
@@ -93,6 +93,7 @@
 	F_GROUPSID,
 	F_FSID,
 	F_REPARSE,
+	F_GEN,
 	F_ATTR_ALL
 } f_attr_t;
 
--- a/usr/src/uts/common/sys/fs/zfs.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/sys/fs/zfs.h	Sat Aug 07 19:27:15 2010 -0600
@@ -769,7 +769,11 @@
 	ZFS_IOC_RELEASE,
 	ZFS_IOC_GET_HOLDS,
 	ZFS_IOC_OBJSET_RECVD_PROPS,
-	ZFS_IOC_VDEV_SPLIT
+	ZFS_IOC_VDEV_SPLIT,
+	ZFS_IOC_NEXT_OBJ,
+	ZFS_IOC_DIFF,
+	ZFS_IOC_TMP_SNAPSHOT,
+	ZFS_IOC_OBJ_TO_STATS
 } zfs_ioc_t;
 
 /*
--- a/usr/src/uts/common/sys/vnode.h	Sat Aug 07 15:19:49 2010 -0700
+++ b/usr/src/uts/common/sys/vnode.h	Sat Aug 07 19:27:15 2010 -0600
@@ -398,6 +398,7 @@
 	uint8_t		xoa_av_modified;
 	uint8_t		xoa_av_scanstamp[AV_SCANSTAMP_SZ];
 	uint8_t		xoa_reparse;
+	uint64_t	xoa_generation;
 } xoptattr_t;
 
 /*
@@ -577,11 +578,12 @@
 #define	XAT0_AV_MODIFIED	0x00000800	/* anti-virus modified */
 #define	XAT0_AV_SCANSTAMP	0x00001000	/* anti-virus scanstamp */
 #define	XAT0_REPARSE	0x00002000	/* FS reparse point */
+#define	XAT0_GEN	0x00004000	/* object generation number */
 
 #define	XAT0_ALL_ATTRS	(XAT0_CREATETIME|XAT0_ARCHIVE|XAT0_SYSTEM| \
     XAT0_READONLY|XAT0_HIDDEN|XAT0_NOUNLINK|XAT0_IMMUTABLE|XAT0_APPENDONLY| \
     XAT0_NODUMP|XAT0_OPAQUE|XAT0_AV_QUARANTINED| \
-    XAT0_AV_MODIFIED|XAT0_AV_SCANSTAMP|XAT0_REPARSE)
+    XAT0_AV_MODIFIED|XAT0_AV_SCANSTAMP|XAT0_REPARSE|XAT0_GEN)
 
 /* Support for XAT_* optional attributes */
 #define	XVA_MASK		0xffffffff	/* Used to mask off 32 bits */
@@ -615,6 +617,7 @@
 #define	XAT_AV_MODIFIED		((XAT0_INDEX << XVA_SHFT) | XAT0_AV_MODIFIED)
 #define	XAT_AV_SCANSTAMP	((XAT0_INDEX << XVA_SHFT) | XAT0_AV_SCANSTAMP)
 #define	XAT_REPARSE		((XAT0_INDEX << XVA_SHFT) | XAT0_REPARSE)
+#define	XAT_GEN			((XAT0_INDEX << XVA_SHFT) | XAT0_GEN)
 
 /*
  * The returned attribute map array (xva_rtnattrmap[]) is located past the