diff options
| author | Tim Haley <Tim.Haley@Sun.COM> | 2010-08-07 19:27:15 -0600 | 
|---|---|---|
| committer | Tim Haley <Tim.Haley@Sun.COM> | 2010-08-07 19:27:15 -0600 | 
| commit | 99d5e173470cf967aa87653364ed614299e7b511 (patch) | |
| tree | a42eb4b1976859f610f60de0b70159efca9c83e3 /usr/src | |
| parent | 84d8c05b8aec0c78c7207612a6378181d130900c (diff) | |
| download | illumos-joyent-99d5e173470cf967aa87653364ed614299e7b511.tar.gz | |
PSARC 2010/105 zfs diff
6425091 want 'zfs diff' to list files that have changed between snapshots
Diffstat (limited to 'usr/src')
36 files changed, 1780 insertions, 110 deletions
| diff --git a/usr/src/cmd/truss/codes.c b/usr/src/cmd/truss/codes.c index c7d036a4a4..313bc2e6d1 100644 --- a/usr/src/cmd/truss/codes.c +++ b/usr/src/cmd/truss/codes.c @@ -1237,6 +1237,14 @@ const struct ioc {  		"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", diff --git a/usr/src/cmd/zfs/zfs_main.c b/usr/src/cmd/zfs/zfs_main.c index 520767d0ad..9516697390 100644 --- a/usr/src/cmd/zfs/zfs_main.c +++ b/usr/src/cmd/zfs/zfs_main.c @@ -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_userspace(int argc, char **argv);  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 @@ typedef enum {  	HELP_GROUPSPACE,  	HELP_HOLD,  	HELP_HOLDS, -	HELP_RELEASE +	HELP_RELEASE, +	HELP_DIFF  } zfs_help_t;  typedef struct zfs_command { @@ -180,6 +183,7 @@ static zfs_command_t command_table[] = {  	{ "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 @@ get_usage(zfs_help_t idx)  		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 @@ find_command_idx(char *command, int *idx)  	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)  { diff --git a/usr/src/cmd/ztest/ztest.c b/usr/src/cmd/ztest/ztest.c index 3ca0280c97..b2d81b5588 100644 --- a/usr/src/cmd/ztest/ztest.c +++ b/usr/src/cmd/ztest/ztest.c @@ -2878,7 +2878,7 @@ ztest_snapshot_create(char *osname, uint64_t id)  	    (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 @@ ztest_dsl_dataset_promote_busy(ztest_ds_t *zd, uint64_t id)  	(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 @@ ztest_dsl_dataset_promote_busy(ztest_ds_t *zd, uint64_t id)  	}  	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 @@ ztest_dsl_dataset_promote_busy(ztest_ds_t *zd, uint64_t id)  	}  	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 @@ ztest_dmu_snapshot_hold(ztest_ds_t *zd, uint64_t id)  	 * 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 @@ ztest_dmu_snapshot_hold(ztest_ds_t *zd, uint64_t id)  	 * 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"); diff --git a/usr/src/common/xattr/xattr_common.c b/usr/src/common/xattr/xattr_common.c index efe782c5f9..b4be1893ca 100644 --- a/usr/src/common/xattr/xattr_common.c +++ b/usr/src/common/xattr/xattr_common.c @@ -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 @@ static xattr_entry_t xattrs[F_ATTR_ALL] = {  	{ 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 * diff --git a/usr/src/common/zfs/zfs_deleg.c b/usr/src/common/zfs/zfs_deleg.c index 35f81b5846..83d9edb213 100644 --- a/usr/src/common/zfs/zfs_deleg.c +++ b/usr/src/common/zfs/zfs_deleg.c @@ -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_tab_t zfs_deleg_perm_tab[] = {  	{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 }  }; diff --git a/usr/src/common/zfs/zfs_deleg.h b/usr/src/common/zfs/zfs_deleg.h index e90cd0d5f4..b4cb8e2b4e 100644 --- a/usr/src/common/zfs/zfs_deleg.h +++ b/usr/src/common/zfs/zfs_deleg.h @@ -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 @@ typedef enum {  	ZFS_DELEG_NOTE_GROUPUSED,  	ZFS_DELEG_NOTE_HOLD,  	ZFS_DELEG_NOTE_RELEASE, +	ZFS_DELEG_NOTE_DIFF,  	ZFS_DELEG_NOTE_NONE  } zfs_deleg_note_t; diff --git a/usr/src/lib/libzfs/Makefile.com b/usr/src/lib/libzfs/Makefile.com index a3248d1a3a..77e2aa4467 100644 --- a/usr/src/lib/libzfs/Makefile.com +++ b/usr/src/lib/libzfs/Makefile.com @@ -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 @@ OBJS_COMMON=			\  	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 @@ C99MODE=	-xc99=%all  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) diff --git a/usr/src/lib/libzfs/common/libzfs.h b/usr/src/lib/libzfs/common/libzfs.h index f1b17cf568..3208797fc0 100644 --- a/usr/src/lib/libzfs/common/libzfs.h +++ b/usr/src/lib/libzfs/common/libzfs.h @@ -120,6 +120,8 @@ enum {  	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 @@ typedef struct recvflags {  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.   */ diff --git a/usr/src/lib/libzfs/common/libzfs_dataset.c b/usr/src/lib/libzfs/common/libzfs_dataset.c index bb3dd9e7da..b7c1360db4 100644 --- a/usr/src/lib/libzfs/common/libzfs_dataset.c +++ b/usr/src/lib/libzfs/common/libzfs_dataset.c @@ -125,7 +125,7 @@ path_to_str(const char *path, int types)   * 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)  { diff --git a/usr/src/lib/libzfs/common/libzfs_diff.c b/usr/src/lib/libzfs/common/libzfs_diff.c new file mode 100644 index 0000000000..888224f3bc --- /dev/null +++ b/usr/src/lib/libzfs/common/libzfs_diff.c @@ -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); +} diff --git a/usr/src/lib/libzfs/common/libzfs_impl.h b/usr/src/lib/libzfs/common/libzfs_impl.h index 89c48c1c03..c9b09a2050 100644 --- a/usr/src/lib/libzfs/common/libzfs_impl.h +++ b/usr/src/lib/libzfs/common/libzfs_impl.h @@ -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 @@ struct libzfs_handle {  	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 @@ int zfs_error_fmt(libzfs_handle_t *, int, const char *, ...);  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 @@ int zpool_open_silent(libzfs_handle_t *, const char *, zpool_handle_t **);  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 *);  /* diff --git a/usr/src/lib/libzfs/common/libzfs_util.c b/usr/src/lib/libzfs/common/libzfs_util.c index e3de88815c..274287f297 100644 --- a/usr/src/lib/libzfs/common/libzfs_util.c +++ b/usr/src/lib/libzfs/common/libzfs_util.c @@ -219,6 +219,10 @@ libzfs_error_description(libzfs_handle_t *hdl)  		    "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 @@ zfs_alloc(libzfs_handle_t *hdl, size_t size)  }  /* + * 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_init(void)  {  	libzfs_handle_t *hdl; -	if ((hdl = calloc(sizeof (libzfs_handle_t), 1)) == NULL) { +	if ((hdl = calloc(1, sizeof (libzfs_handle_t))) == NULL) {  		return (NULL);  	} diff --git a/usr/src/lib/libzfs/common/mapfile-vers b/usr/src/lib/libzfs/common/mapfile-vers index cf09f065a4..eb8e798a0c 100644 --- a/usr/src/lib/libzfs/common/mapfile-vers +++ b/usr/src/lib/libzfs/common/mapfile-vers @@ -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 @@ SYMBOL_VERSION SUNWprivate_1.1 {  	libzfs_mnttab_cache;  	libzfs_print_on_error;  	zfs_allocatable_devs; +	zfs_asprintf;  	zfs_clone;  	zfs_close;  	zfs_create; @@ -129,6 +127,7 @@ SYMBOL_VERSION SUNWprivate_1.1 {  	zfs_shareall;  	zfs_share_nfs;  	zfs_share_smb; +	zfs_show_diffs;  	zfs_smb_acl_add;  	zfs_smb_acl_purge;  	zfs_smb_acl_remove; diff --git a/usr/src/lib/pyzfs/common/allow.py b/usr/src/lib/pyzfs/common/allow.py index 87e6f14b7d..fa8209f697 100644 --- a/usr/src/lib/pyzfs/common/allow.py +++ b/usr/src/lib/pyzfs/common/allow.py @@ -218,6 +218,7 @@ perms_subcmd = dict(      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( diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files index ed1daa8be6..5e1474c286 100644 --- a/usr/src/uts/common/Makefile.files +++ b/usr/src/uts/common/Makefile.files @@ -1329,6 +1329,7 @@ ZFS_COMMON_OBJS +=		\  	ddt.o			\  	ddt_zap.o		\  	dmu.o			\ +	dmu_diff.o		\  	dmu_send.o		\  	dmu_object.o		\  	dmu_objset.o		\ diff --git a/usr/src/uts/common/fs/xattr.c b/usr/src/uts/common/fs/xattr.c index 070f43e5c6..1657f25549 100644 --- a/usr/src/uts/common/fs/xattr.c +++ b/usr/src/uts/common/fs/xattr.c @@ -225,6 +225,9 @@ xattr_fill_nvlist(vnode_t *vp, xattr_view_t xattr_view, nvlist_t *nvlp,  		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 @@ xattr_fill_nvlist(vnode_t *vp, xattr_view_t xattr_view, nvlist_t *nvlp,  			    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 diff --git a/usr/src/uts/common/fs/zfs/dmu_diff.c b/usr/src/uts/common/fs/zfs/dmu_diff.c new file mode 100644 index 0000000000..22340ebc53 --- /dev/null +++ b/usr/src/uts/common/fs/zfs/dmu_diff.c @@ -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); +} diff --git a/usr/src/uts/common/fs/zfs/dmu_objset.c b/usr/src/uts/common/fs/zfs/dmu_objset.c index 2a15b1166b..7caebd979f 100644 --- a/usr/src/uts/common/fs/zfs/dmu_objset.c +++ b/usr/src/uts/common/fs/zfs/dmu_objset.c @@ -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 @@ dmu_objset_destroy(const char *name, boolean_t defer)  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 @@ snapshot_check(void *arg1, void *arg2, dmu_tx_t *tx)  {  	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 @@ snapshot_sync(void *arg1, void *arg2, dmu_tx_t *tx)  		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 @@ dmu_objset_snapshot_one(const char *name, void *arg)  }  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 @@ dmu_objset_snapshot(char *fsname, char *snapname,  	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 @@ dmu_objset_snapshot(char *fsname, char *snapname,  	    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 @@ dmu_objset_snapshot(char *fsname, char *snapname,  	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); diff --git a/usr/src/uts/common/fs/zfs/dmu_traverse.c b/usr/src/uts/common/fs/zfs/dmu_traverse.c index 37fb664ec3..023f90e12e 100644 --- a/usr/src/uts/common/fs/zfs/dmu_traverse.c +++ b/usr/src/uts/common/fs/zfs/dmu_traverse.c @@ -162,6 +162,8 @@ traverse_visitbp(traverse_data_t *td, const dnode_phys_t *dnp,  	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);  	} diff --git a/usr/src/uts/common/fs/zfs/dsl_dataset.c b/usr/src/uts/common/fs/zfs/dsl_dataset.c index 67a6c554da..59ac4a6094 100644 --- a/usr/src/uts/common/fs/zfs/dsl_dataset.c +++ b/usr/src/uts/common/fs/zfs/dsl_dataset.c @@ -1375,6 +1375,11 @@ dsl_dataset_origin_check(struct dsl_ds_destroyarg *dsda, void *tag,  	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_dataset_destroy_sync(void *arg1, void *tag, dmu_tx_t *tx)  	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 @@ dsl_dataset_set_reservation(const char *dsname, zprop_source_t source,  	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 @@ dsl_register_onexit_hold_cleanup(dsl_dataset_t *ds, const char *htag,  }  /* - * 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 @@ dsl_dataset_user_hold_check(void *arg1, void *arg2, dmu_tx_t *tx)  	return (error);  } -static void +void  dsl_dataset_user_hold_sync(void *arg1, void *arg2, dmu_tx_t *tx)  {  	dsl_dataset_t *ds = arg1; diff --git a/usr/src/uts/common/fs/zfs/dsl_deleg.c b/usr/src/uts/common/fs/zfs/dsl_deleg.c index 45cb3a59e2..529fb052fa 100644 --- a/usr/src/uts/common/fs/zfs/dsl_deleg.c +++ b/usr/src/uts/common/fs/zfs/dsl_deleg.c @@ -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.   */  /* diff --git a/usr/src/uts/common/fs/zfs/sys/dmu.h b/usr/src/uts/common/fs/zfs/sys/dmu.h index c504c23310..07f5949ebf 100644 --- a/usr/src/uts/common/fs/zfs/sys/dmu.h +++ b/usr/src/uts/common/fs/zfs/sys/dmu.h @@ -192,8 +192,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_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 dmu_recv_stream(dmu_recv_cookie_t *drc, struct vnode *vp, offset_t *voffp,      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]; diff --git a/usr/src/uts/common/fs/zfs/sys/dmu_objset.h b/usr/src/uts/common/fs/zfs/sys/dmu_objset.h index e516c41115..c6d202e2e8 100644 --- a/usr/src/uts/common/fs/zfs/sys/dmu_objset.h +++ b/usr/src/uts/common/fs/zfs/sys/dmu_objset.h @@ -142,8 +142,8 @@ int dmu_objset_create(const char *name, dmu_objset_type_t type, uint64_t flags,  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, diff --git a/usr/src/uts/common/fs/zfs/sys/dmu_traverse.h b/usr/src/uts/common/fs/zfs/sys/dmu_traverse.h index 844e7f1aeb..5b326cd99c 100644 --- a/usr/src/uts/common/fs/zfs/sys/dmu_traverse.h +++ b/usr/src/uts/common/fs/zfs/sys/dmu_traverse.h @@ -49,6 +49,9 @@ typedef int (blkptr_cb_t)(spa_t *spa, zilog_t *zilog, const blkptr_t *bp,  #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, diff --git a/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h b/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h index c07d3bbd2a..22733d070e 100644 --- a/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h +++ b/usr/src/uts/common/fs/zfs/sys/dsl_dataset.h @@ -162,6 +162,22 @@ struct dsl_ds_destroyarg {  	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_checkfunc_t dsl_dataset_destroy_check;  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, diff --git a/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h b/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h index c3a528af72..73c43bd238 100644 --- a/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h +++ b/usr/src/uts/common/fs/zfs/sys/dsl_deleg.h @@ -54,6 +54,7 @@ extern "C" {  #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 diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h b/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h index 41b0261921..84bf794fe5 100644 --- a/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h +++ b/usr/src/uts/common/fs/zfs/sys/zfs_ioctl.h @@ -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 @@ typedef struct dmu_replay_record {  	} 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 @@ typedef struct zfs_cmd {  	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 { diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_stat.h b/usr/src/uts/common/fs/zfs/sys/zfs_stat.h new file mode 100644 index 0000000000..465aefaa20 --- /dev/null +++ b/usr/src/uts/common/fs/zfs/sys/zfs_stat.h @@ -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 */ diff --git a/usr/src/uts/common/fs/zfs/sys/zfs_znode.h b/usr/src/uts/common/fs/zfs/sys/zfs_znode.h index 5e281a2c62..17b8e16879 100644 --- a/usr/src/uts/common/fs/zfs/sys/zfs_znode.h +++ b/usr/src/uts/common/fs/zfs/sys/zfs_znode.h @@ -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> diff --git a/usr/src/uts/common/fs/zfs/zfs_ctldir.c b/usr/src/uts/common/fs/zfs/zfs_ctldir.c index 07f18f29f0..815f8895e7 100644 --- a/usr/src/uts/common/fs/zfs/zfs_ctldir.c +++ b/usr/src/uts/common/fs/zfs/zfs_ctldir.c @@ -749,7 +749,8 @@ zfsctl_snapdir_mkdir(vnode_t *dvp, char *dirname, vattr_t *vap, vnode_t  **vpp,  		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); diff --git a/usr/src/uts/common/fs/zfs/zfs_ioctl.c b/usr/src/uts/common/fs/zfs/zfs_ioctl.c index 85f0a5e5cb..1dd8a799de 100644 --- a/usr/src/uts/common/fs/zfs/zfs_ioctl.c +++ b/usr/src/uts/common/fs/zfs/zfs_ioctl.c @@ -854,6 +854,22 @@ zfs_secpolicy_config(zfs_cmd_t *zc, cred_t *cr)  }  /* + * 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 @@ zfs_secpolicy_release(zfs_cmd_t *zc, cred_t *cr)  }  /* + * 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 @@ zfs_ioc_obj_to_path(zfs_cmd_t *zc)  	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 @@ zfs_ioc_snapshot(zfs_cmd_t *zc)  		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 @@ ace_t full_access[] = {  };  /* + * 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 @@ static zfs_ioc_vec_t zfs_ioc_vec[] = {  	    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 @@ static zfs_ioc_vec_t zfs_ioc_vec[] = {  	{ 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 }  }; diff --git a/usr/src/uts/common/fs/zfs/zfs_vnops.c b/usr/src/uts/common/fs/zfs/zfs_vnops.c index b578fe7ce8..fffd4108c5 100644 --- a/usr/src/uts/common/fs/zfs/zfs_vnops.c +++ b/usr/src/uts/common/fs/zfs/zfs_vnops.c @@ -2536,6 +2536,10 @@ zfs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr,  			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); diff --git a/usr/src/uts/common/fs/zfs/zfs_znode.c b/usr/src/uts/common/fs/zfs/zfs_znode.c index aabeb6e3ca..d1c58c62e2 100644 --- a/usr/src/uts/common/fs/zfs/zfs_znode.c +++ b/usr/src/uts/common/fs/zfs/zfs_znode.c @@ -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 @@ zfs_create_fs(objset_t *os, cred_t *cr, nvlist_t *zplprops, dmu_tx_t *tx)  #endif /* _KERNEL */ -/* - * 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_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_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; -	if ((error = sa_buf_hold(osp, obj, FTAG, &db)) != 0) +	if ((error = sa_buf_hold(osp, obj, FTAG, db)) != 0)  		return (error); -	dmu_object_info_from_db(db, &doi); +	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); +		sa_buf_rele(*db, FTAG); +		return (ENOTSUP);  	} -	if ((error = sa_handle_get(osp, obj, NULL, SA_HDL_PRIVATE, -	    &hdl)) != 0) { -		sa_buf_rele(db, FTAG); +	error = sa_handle_get(osp, obj, NULL, SA_HDL_PRIVATE, hdlp); +	if (error != 0) { +		sa_buf_rele(*db, FTAG);  		return (error);  	} -	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_PARENT], -	    NULL, &parent, 8); +	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(sa_handle_t *hdl, sa_attr_type_t *sa_table, uint64_t *pobjp, +    int *is_xattrdir) +{ +	uint64_t parent; +	uint64_t pflags; +	uint64_t mode; +	sa_bulk_attr_t bulk[3]; +	int count = 0; +	int error; + +	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, 8); +	    &pflags, sizeof (pflags));  	SA_ADD_BULK_ATTR(bulk, count, sa_table[ZPL_MODE], NULL, -	    &mode, 8); +	    &mode, sizeof (mode)); -	if ((error = sa_bulk_lookup(hdl, bulk, count)) != 0) { -		sa_buf_rele(db, FTAG); -		sa_handle_destroy(hdl); +	if ((error = sa_bulk_lookup(hdl, bulk, count)) != 0)  		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 @@ zfs_obj_to_path(objset_t *osp, uint64_t obj, char *buf, int len)  		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 @@ zfs_obj_to_path(objset_t *osp, uint64_t obj, char *buf, int len)  		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 @@ zfs_obj_to_path(objset_t *osp, uint64_t obj, char *buf, int len)  	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); +} diff --git a/usr/src/uts/common/sys/attr.h b/usr/src/uts/common/sys/attr.h index b312b5a429..2b049d9cc1 100644 --- a/usr/src/uts/common/sys/attr.h +++ b/usr/src/uts/common/sys/attr.h @@ -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 @@ extern "C" {  #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 @@ typedef enum {  	F_GROUPSID,  	F_FSID,  	F_REPARSE, +	F_GEN,  	F_ATTR_ALL  } f_attr_t; diff --git a/usr/src/uts/common/sys/fs/zfs.h b/usr/src/uts/common/sys/fs/zfs.h index a6a3484ffc..68ea235c6a 100644 --- a/usr/src/uts/common/sys/fs/zfs.h +++ b/usr/src/uts/common/sys/fs/zfs.h @@ -769,7 +769,11 @@ typedef enum zfs_ioc {  	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;  /* diff --git a/usr/src/uts/common/sys/vnode.h b/usr/src/uts/common/sys/vnode.h index b34eb6b4ff..1216983e2d 100644 --- a/usr/src/uts/common/sys/vnode.h +++ b/usr/src/uts/common/sys/vnode.h @@ -398,6 +398,7 @@ typedef struct xoptattr {  	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 @@ typedef vattr_t		vattr32_t;  #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 @@ typedef vattr_t		vattr32_t;  #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 | 
