xref: /onnv-gate/usr/src/lib/libzfs/common/libzfs_diff.c (revision 13043:8c712bbb18ea)
1*13043STim.Haley@Sun.COM /*
2*13043STim.Haley@Sun.COM  * CDDL HEADER START
3*13043STim.Haley@Sun.COM  *
4*13043STim.Haley@Sun.COM  * The contents of this file are subject to the terms of the
5*13043STim.Haley@Sun.COM  * Common Development and Distribution License (the "License").
6*13043STim.Haley@Sun.COM  * You may not use this file except in compliance with the License.
7*13043STim.Haley@Sun.COM  *
8*13043STim.Haley@Sun.COM  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9*13043STim.Haley@Sun.COM  * or http://www.opensolaris.org/os/licensing.
10*13043STim.Haley@Sun.COM  * See the License for the specific language governing permissions
11*13043STim.Haley@Sun.COM  * and limitations under the License.
12*13043STim.Haley@Sun.COM  *
13*13043STim.Haley@Sun.COM  * When distributing Covered Code, include this CDDL HEADER in each
14*13043STim.Haley@Sun.COM  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15*13043STim.Haley@Sun.COM  * If applicable, add the following below this CDDL HEADER, with the
16*13043STim.Haley@Sun.COM  * fields enclosed by brackets "[]" replaced with your own identifying
17*13043STim.Haley@Sun.COM  * information: Portions Copyright [yyyy] [name of copyright owner]
18*13043STim.Haley@Sun.COM  *
19*13043STim.Haley@Sun.COM  * CDDL HEADER END
20*13043STim.Haley@Sun.COM  */
21*13043STim.Haley@Sun.COM 
22*13043STim.Haley@Sun.COM /*
23*13043STim.Haley@Sun.COM  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24*13043STim.Haley@Sun.COM  */
25*13043STim.Haley@Sun.COM 
26*13043STim.Haley@Sun.COM /*
27*13043STim.Haley@Sun.COM  * zfs diff support
28*13043STim.Haley@Sun.COM  */
29*13043STim.Haley@Sun.COM #include <ctype.h>
30*13043STim.Haley@Sun.COM #include <errno.h>
31*13043STim.Haley@Sun.COM #include <libintl.h>
32*13043STim.Haley@Sun.COM #include <string.h>
33*13043STim.Haley@Sun.COM #include <sys/types.h>
34*13043STim.Haley@Sun.COM #include <sys/stat.h>
35*13043STim.Haley@Sun.COM #include <fcntl.h>
36*13043STim.Haley@Sun.COM #include <attr.h>
37*13043STim.Haley@Sun.COM #include <stddef.h>
38*13043STim.Haley@Sun.COM #include <unistd.h>
39*13043STim.Haley@Sun.COM #include <stdio.h>
40*13043STim.Haley@Sun.COM #include <stdlib.h>
41*13043STim.Haley@Sun.COM #include <stropts.h>
42*13043STim.Haley@Sun.COM #include <pthread.h>
43*13043STim.Haley@Sun.COM #include <sys/zfs_ioctl.h>
44*13043STim.Haley@Sun.COM #include <libzfs.h>
45*13043STim.Haley@Sun.COM #include "libzfs_impl.h"
46*13043STim.Haley@Sun.COM 
47*13043STim.Haley@Sun.COM #define	ZDIFF_SNAPDIR		"/.zfs/snapshot/"
48*13043STim.Haley@Sun.COM #define	ZDIFF_SHARESDIR 	"/.zfs/shares/"
49*13043STim.Haley@Sun.COM #define	ZDIFF_PREFIX		"zfs-diff-%d"
50*13043STim.Haley@Sun.COM 
51*13043STim.Haley@Sun.COM #define	ZDIFF_ADDED	'+'
52*13043STim.Haley@Sun.COM #define	ZDIFF_MODIFIED	'M'
53*13043STim.Haley@Sun.COM #define	ZDIFF_REMOVED	'-'
54*13043STim.Haley@Sun.COM #define	ZDIFF_RENAMED	'R'
55*13043STim.Haley@Sun.COM 
56*13043STim.Haley@Sun.COM static boolean_t
do_name_cmp(const char * fpath,const char * tpath)57*13043STim.Haley@Sun.COM do_name_cmp(const char *fpath, const char *tpath)
58*13043STim.Haley@Sun.COM {
59*13043STim.Haley@Sun.COM 	char *fname, *tname;
60*13043STim.Haley@Sun.COM 	fname = strrchr(fpath, '/') + 1;
61*13043STim.Haley@Sun.COM 	tname = strrchr(tpath, '/') + 1;
62*13043STim.Haley@Sun.COM 	return (strcmp(fname, tname) == 0);
63*13043STim.Haley@Sun.COM }
64*13043STim.Haley@Sun.COM 
65*13043STim.Haley@Sun.COM typedef struct differ_info {
66*13043STim.Haley@Sun.COM 	zfs_handle_t *zhp;
67*13043STim.Haley@Sun.COM 	char *fromsnap;
68*13043STim.Haley@Sun.COM 	char *frommnt;
69*13043STim.Haley@Sun.COM 	char *tosnap;
70*13043STim.Haley@Sun.COM 	char *tomnt;
71*13043STim.Haley@Sun.COM 	char *ds;
72*13043STim.Haley@Sun.COM 	char *dsmnt;
73*13043STim.Haley@Sun.COM 	char *tmpsnap;
74*13043STim.Haley@Sun.COM 	char errbuf[1024];
75*13043STim.Haley@Sun.COM 	boolean_t isclone;
76*13043STim.Haley@Sun.COM 	boolean_t scripted;
77*13043STim.Haley@Sun.COM 	boolean_t classify;
78*13043STim.Haley@Sun.COM 	boolean_t timestamped;
79*13043STim.Haley@Sun.COM 	uint64_t shares;
80*13043STim.Haley@Sun.COM 	int zerr;
81*13043STim.Haley@Sun.COM 	int cleanupfd;
82*13043STim.Haley@Sun.COM 	int outputfd;
83*13043STim.Haley@Sun.COM 	int datafd;
84*13043STim.Haley@Sun.COM } differ_info_t;
85*13043STim.Haley@Sun.COM 
86*13043STim.Haley@Sun.COM /*
87*13043STim.Haley@Sun.COM  * Given a {dsname, object id}, get the object path
88*13043STim.Haley@Sun.COM  */
89*13043STim.Haley@Sun.COM static int
get_stats_for_obj(differ_info_t * di,const char * dsname,uint64_t obj,char * pn,int maxlen,zfs_stat_t * sb)90*13043STim.Haley@Sun.COM get_stats_for_obj(differ_info_t *di, const char *dsname, uint64_t obj,
91*13043STim.Haley@Sun.COM     char *pn, int maxlen, zfs_stat_t *sb)
92*13043STim.Haley@Sun.COM {
93*13043STim.Haley@Sun.COM 	zfs_cmd_t zc = { 0 };
94*13043STim.Haley@Sun.COM 	int error;
95*13043STim.Haley@Sun.COM 
96*13043STim.Haley@Sun.COM 	(void) strlcpy(zc.zc_name, dsname, sizeof (zc.zc_name));
97*13043STim.Haley@Sun.COM 	zc.zc_obj = obj;
98*13043STim.Haley@Sun.COM 
99*13043STim.Haley@Sun.COM 	errno = 0;
100*13043STim.Haley@Sun.COM 	error = ioctl(di->zhp->zfs_hdl->libzfs_fd, ZFS_IOC_OBJ_TO_STATS, &zc);
101*13043STim.Haley@Sun.COM 	di->zerr = errno;
102*13043STim.Haley@Sun.COM 
103*13043STim.Haley@Sun.COM 	/* we can get stats even if we failed to get a path */
104*13043STim.Haley@Sun.COM 	(void) memcpy(sb, &zc.zc_stat, sizeof (zfs_stat_t));
105*13043STim.Haley@Sun.COM 	if (error == 0) {
106*13043STim.Haley@Sun.COM 		ASSERT(di->zerr == 0);
107*13043STim.Haley@Sun.COM 		(void) strlcpy(pn, zc.zc_value, maxlen);
108*13043STim.Haley@Sun.COM 		return (0);
109*13043STim.Haley@Sun.COM 	}
110*13043STim.Haley@Sun.COM 
111*13043STim.Haley@Sun.COM 	if (di->zerr == EPERM) {
112*13043STim.Haley@Sun.COM 		(void) snprintf(di->errbuf, sizeof (di->errbuf),
113*13043STim.Haley@Sun.COM 		    dgettext(TEXT_DOMAIN,
114*13043STim.Haley@Sun.COM 		    "The sys_config privilege or diff delegated permission "
115*13043STim.Haley@Sun.COM 		    "is needed\nto discover path names"));
116*13043STim.Haley@Sun.COM 		return (-1);
117*13043STim.Haley@Sun.COM 	} else {
118*13043STim.Haley@Sun.COM 		(void) snprintf(di->errbuf, sizeof (di->errbuf),
119*13043STim.Haley@Sun.COM 		    dgettext(TEXT_DOMAIN,
120*13043STim.Haley@Sun.COM 		    "Unable to determine path or stats for "
121*13043STim.Haley@Sun.COM 		    "object %lld in %s"), obj, dsname);
122*13043STim.Haley@Sun.COM 		return (-1);
123*13043STim.Haley@Sun.COM 	}
124*13043STim.Haley@Sun.COM }
125*13043STim.Haley@Sun.COM 
126*13043STim.Haley@Sun.COM /*
127*13043STim.Haley@Sun.COM  * stream_bytes
128*13043STim.Haley@Sun.COM  *
129*13043STim.Haley@Sun.COM  * Prints a file name out a character at a time.  If the character is
130*13043STim.Haley@Sun.COM  * not in the range of what we consider "printable" ASCII, display it
131*13043STim.Haley@Sun.COM  * as an escaped 3-digit octal value.  ASCII values less than a space
132*13043STim.Haley@Sun.COM  * are all control characters and we declare the upper end as the
133*13043STim.Haley@Sun.COM  * DELete character.  This also is the last 7-bit ASCII character.
134*13043STim.Haley@Sun.COM  * We choose to treat all 8-bit ASCII as not printable for this
135*13043STim.Haley@Sun.COM  * application.
136*13043STim.Haley@Sun.COM  */
137*13043STim.Haley@Sun.COM static void
stream_bytes(FILE * fp,const char * string)138*13043STim.Haley@Sun.COM stream_bytes(FILE *fp, const char *string)
139*13043STim.Haley@Sun.COM {
140*13043STim.Haley@Sun.COM 	while (*string) {
141*13043STim.Haley@Sun.COM 		if (*string > ' ' && *string != '\\' && *string < '\177')
142*13043STim.Haley@Sun.COM 			(void) fprintf(fp, "%c", *string++);
143*13043STim.Haley@Sun.COM 		else
144*13043STim.Haley@Sun.COM 			(void) fprintf(fp, "\\%03o", *string++);
145*13043STim.Haley@Sun.COM 	}
146*13043STim.Haley@Sun.COM }
147*13043STim.Haley@Sun.COM 
148*13043STim.Haley@Sun.COM static void
print_what(FILE * fp,mode_t what)149*13043STim.Haley@Sun.COM print_what(FILE *fp, mode_t what)
150*13043STim.Haley@Sun.COM {
151*13043STim.Haley@Sun.COM 	char symbol;
152*13043STim.Haley@Sun.COM 
153*13043STim.Haley@Sun.COM 	switch (what & S_IFMT) {
154*13043STim.Haley@Sun.COM 	case S_IFBLK:
155*13043STim.Haley@Sun.COM 		symbol = 'B';
156*13043STim.Haley@Sun.COM 		break;
157*13043STim.Haley@Sun.COM 	case S_IFCHR:
158*13043STim.Haley@Sun.COM 		symbol = 'C';
159*13043STim.Haley@Sun.COM 		break;
160*13043STim.Haley@Sun.COM 	case S_IFDIR:
161*13043STim.Haley@Sun.COM 		symbol = '/';
162*13043STim.Haley@Sun.COM 		break;
163*13043STim.Haley@Sun.COM 	case S_IFDOOR:
164*13043STim.Haley@Sun.COM 		symbol = '>';
165*13043STim.Haley@Sun.COM 		break;
166*13043STim.Haley@Sun.COM 	case S_IFIFO:
167*13043STim.Haley@Sun.COM 		symbol = '|';
168*13043STim.Haley@Sun.COM 		break;
169*13043STim.Haley@Sun.COM 	case S_IFLNK:
170*13043STim.Haley@Sun.COM 		symbol = '@';
171*13043STim.Haley@Sun.COM 		break;
172*13043STim.Haley@Sun.COM 	case S_IFPORT:
173*13043STim.Haley@Sun.COM 		symbol = 'P';
174*13043STim.Haley@Sun.COM 		break;
175*13043STim.Haley@Sun.COM 	case S_IFSOCK:
176*13043STim.Haley@Sun.COM 		symbol = '=';
177*13043STim.Haley@Sun.COM 		break;
178*13043STim.Haley@Sun.COM 	case S_IFREG:
179*13043STim.Haley@Sun.COM 		symbol = 'F';
180*13043STim.Haley@Sun.COM 		break;
181*13043STim.Haley@Sun.COM 	default:
182*13043STim.Haley@Sun.COM 		symbol = '?';
183*13043STim.Haley@Sun.COM 		break;
184*13043STim.Haley@Sun.COM 	}
185*13043STim.Haley@Sun.COM 	(void) fprintf(fp, "%c", symbol);
186*13043STim.Haley@Sun.COM }
187*13043STim.Haley@Sun.COM 
188*13043STim.Haley@Sun.COM static void
print_cmn(FILE * fp,differ_info_t * di,const char * file)189*13043STim.Haley@Sun.COM print_cmn(FILE *fp, differ_info_t *di, const char *file)
190*13043STim.Haley@Sun.COM {
191*13043STim.Haley@Sun.COM 	stream_bytes(fp, di->dsmnt);
192*13043STim.Haley@Sun.COM 	stream_bytes(fp, file);
193*13043STim.Haley@Sun.COM }
194*13043STim.Haley@Sun.COM 
195*13043STim.Haley@Sun.COM static void
print_rename(FILE * fp,differ_info_t * di,const char * old,const char * new,zfs_stat_t * isb)196*13043STim.Haley@Sun.COM print_rename(FILE *fp, differ_info_t *di, const char *old, const char *new,
197*13043STim.Haley@Sun.COM     zfs_stat_t *isb)
198*13043STim.Haley@Sun.COM {
199*13043STim.Haley@Sun.COM 	if (di->timestamped)
200*13043STim.Haley@Sun.COM 		(void) fprintf(fp, "%10lld.%09lld\t",
201*13043STim.Haley@Sun.COM 		    (longlong_t)isb->zs_ctime[0],
202*13043STim.Haley@Sun.COM 		    (longlong_t)isb->zs_ctime[1]);
203*13043STim.Haley@Sun.COM 	(void) fprintf(fp, "%c\t", ZDIFF_RENAMED);
204*13043STim.Haley@Sun.COM 	if (di->classify) {
205*13043STim.Haley@Sun.COM 		print_what(fp, isb->zs_mode);
206*13043STim.Haley@Sun.COM 		(void) fprintf(fp, "\t");
207*13043STim.Haley@Sun.COM 	}
208*13043STim.Haley@Sun.COM 	print_cmn(fp, di, old);
209*13043STim.Haley@Sun.COM 	if (di->scripted)
210*13043STim.Haley@Sun.COM 		(void) fprintf(fp, "\t");
211*13043STim.Haley@Sun.COM 	else
212*13043STim.Haley@Sun.COM 		(void) fprintf(fp, " -> ");
213*13043STim.Haley@Sun.COM 	print_cmn(fp, di, new);
214*13043STim.Haley@Sun.COM 	(void) fprintf(fp, "\n");
215*13043STim.Haley@Sun.COM }
216*13043STim.Haley@Sun.COM 
217*13043STim.Haley@Sun.COM static void
print_link_change(FILE * fp,differ_info_t * di,int delta,const char * file,zfs_stat_t * isb)218*13043STim.Haley@Sun.COM print_link_change(FILE *fp, differ_info_t *di, int delta, const char *file,
219*13043STim.Haley@Sun.COM     zfs_stat_t *isb)
220*13043STim.Haley@Sun.COM {
221*13043STim.Haley@Sun.COM 	if (di->timestamped)
222*13043STim.Haley@Sun.COM 		(void) fprintf(fp, "%10lld.%09lld\t",
223*13043STim.Haley@Sun.COM 		    (longlong_t)isb->zs_ctime[0],
224*13043STim.Haley@Sun.COM 		    (longlong_t)isb->zs_ctime[1]);
225*13043STim.Haley@Sun.COM 	(void) fprintf(fp, "%c\t", ZDIFF_MODIFIED);
226*13043STim.Haley@Sun.COM 	if (di->classify) {
227*13043STim.Haley@Sun.COM 		print_what(fp, isb->zs_mode);
228*13043STim.Haley@Sun.COM 		(void) fprintf(fp, "\t");
229*13043STim.Haley@Sun.COM 	}
230*13043STim.Haley@Sun.COM 	print_cmn(fp, di, file);
231*13043STim.Haley@Sun.COM 	(void) fprintf(fp, "\t(%+d)", delta);
232*13043STim.Haley@Sun.COM 	(void) fprintf(fp, "\n");
233*13043STim.Haley@Sun.COM }
234*13043STim.Haley@Sun.COM 
235*13043STim.Haley@Sun.COM static void
print_file(FILE * fp,differ_info_t * di,char type,const char * file,zfs_stat_t * isb)236*13043STim.Haley@Sun.COM print_file(FILE *fp, differ_info_t *di, char type, const char *file,
237*13043STim.Haley@Sun.COM     zfs_stat_t *isb)
238*13043STim.Haley@Sun.COM {
239*13043STim.Haley@Sun.COM 	if (di->timestamped)
240*13043STim.Haley@Sun.COM 		(void) fprintf(fp, "%10lld.%09lld\t",
241*13043STim.Haley@Sun.COM 		    (longlong_t)isb->zs_ctime[0],
242*13043STim.Haley@Sun.COM 		    (longlong_t)isb->zs_ctime[1]);
243*13043STim.Haley@Sun.COM 	(void) fprintf(fp, "%c\t", type);
244*13043STim.Haley@Sun.COM 	if (di->classify) {
245*13043STim.Haley@Sun.COM 		print_what(fp, isb->zs_mode);
246*13043STim.Haley@Sun.COM 		(void) fprintf(fp, "\t");
247*13043STim.Haley@Sun.COM 	}
248*13043STim.Haley@Sun.COM 	print_cmn(fp, di, file);
249*13043STim.Haley@Sun.COM 	(void) fprintf(fp, "\n");
250*13043STim.Haley@Sun.COM }
251*13043STim.Haley@Sun.COM 
252*13043STim.Haley@Sun.COM static int
write_inuse_diffs_one(FILE * fp,differ_info_t * di,uint64_t dobj)253*13043STim.Haley@Sun.COM write_inuse_diffs_one(FILE *fp, differ_info_t *di, uint64_t dobj)
254*13043STim.Haley@Sun.COM {
255*13043STim.Haley@Sun.COM 	struct zfs_stat fsb, tsb;
256*13043STim.Haley@Sun.COM 	boolean_t same_name;
257*13043STim.Haley@Sun.COM 	mode_t fmode, tmode;
258*13043STim.Haley@Sun.COM 	char fobjname[MAXPATHLEN], tobjname[MAXPATHLEN];
259*13043STim.Haley@Sun.COM 	int fobjerr, tobjerr;
260*13043STim.Haley@Sun.COM 	int change;
261*13043STim.Haley@Sun.COM 
262*13043STim.Haley@Sun.COM 	if (dobj == di->shares)
263*13043STim.Haley@Sun.COM 		return (0);
264*13043STim.Haley@Sun.COM 
265*13043STim.Haley@Sun.COM 	/*
266*13043STim.Haley@Sun.COM 	 * Check the from and to snapshots for info on the object. If
267*13043STim.Haley@Sun.COM 	 * we get ENOENT, then the object just didn't exist in that
268*13043STim.Haley@Sun.COM 	 * snapshot.  If we get ENOTSUP, then we tried to get
269*13043STim.Haley@Sun.COM 	 * info on a non-ZPL object, which we don't care about anyway.
270*13043STim.Haley@Sun.COM 	 */
271*13043STim.Haley@Sun.COM 	fobjerr = get_stats_for_obj(di, di->fromsnap, dobj, fobjname,
272*13043STim.Haley@Sun.COM 	    MAXPATHLEN, &fsb);
273*13043STim.Haley@Sun.COM 	if (fobjerr && di->zerr != ENOENT && di->zerr != ENOTSUP)
274*13043STim.Haley@Sun.COM 		return (-1);
275*13043STim.Haley@Sun.COM 
276*13043STim.Haley@Sun.COM 	tobjerr = get_stats_for_obj(di, di->tosnap, dobj, tobjname,
277*13043STim.Haley@Sun.COM 	    MAXPATHLEN, &tsb);
278*13043STim.Haley@Sun.COM 	if (tobjerr && di->zerr != ENOENT && di->zerr != ENOTSUP)
279*13043STim.Haley@Sun.COM 		return (-1);
280*13043STim.Haley@Sun.COM 
281*13043STim.Haley@Sun.COM 	/*
282*13043STim.Haley@Sun.COM 	 * Unallocated object sharing the same meta dnode block
283*13043STim.Haley@Sun.COM 	 */
284*13043STim.Haley@Sun.COM 	if (fobjerr && tobjerr) {
285*13043STim.Haley@Sun.COM 		ASSERT(di->zerr == ENOENT || di->zerr == ENOTSUP);
286*13043STim.Haley@Sun.COM 		di->zerr = 0;
287*13043STim.Haley@Sun.COM 		return (0);
288*13043STim.Haley@Sun.COM 	}
289*13043STim.Haley@Sun.COM 
290*13043STim.Haley@Sun.COM 	di->zerr = 0; /* negate get_stats_for_obj() from side that failed */
291*13043STim.Haley@Sun.COM 	fmode = fsb.zs_mode & S_IFMT;
292*13043STim.Haley@Sun.COM 	tmode = tsb.zs_mode & S_IFMT;
293*13043STim.Haley@Sun.COM 	if (fmode == S_IFDIR || tmode == S_IFDIR || fsb.zs_links == 0 ||
294*13043STim.Haley@Sun.COM 	    tsb.zs_links == 0)
295*13043STim.Haley@Sun.COM 		change = 0;
296*13043STim.Haley@Sun.COM 	else
297*13043STim.Haley@Sun.COM 		change = tsb.zs_links - fsb.zs_links;
298*13043STim.Haley@Sun.COM 
299*13043STim.Haley@Sun.COM 	if (fobjerr) {
300*13043STim.Haley@Sun.COM 		if (change) {
301*13043STim.Haley@Sun.COM 			print_link_change(fp, di, change, tobjname, &tsb);
302*13043STim.Haley@Sun.COM 			return (0);
303*13043STim.Haley@Sun.COM 		}
304*13043STim.Haley@Sun.COM 		print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb);
305*13043STim.Haley@Sun.COM 		return (0);
306*13043STim.Haley@Sun.COM 	} else if (tobjerr) {
307*13043STim.Haley@Sun.COM 		if (change) {
308*13043STim.Haley@Sun.COM 			print_link_change(fp, di, change, fobjname, &fsb);
309*13043STim.Haley@Sun.COM 			return (0);
310*13043STim.Haley@Sun.COM 		}
311*13043STim.Haley@Sun.COM 		print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb);
312*13043STim.Haley@Sun.COM 		return (0);
313*13043STim.Haley@Sun.COM 	}
314*13043STim.Haley@Sun.COM 
315*13043STim.Haley@Sun.COM 	if (fmode != tmode && fsb.zs_gen == tsb.zs_gen)
316*13043STim.Haley@Sun.COM 		tsb.zs_gen++;	/* Force a generational difference */
317*13043STim.Haley@Sun.COM 	same_name = do_name_cmp(fobjname, tobjname);
318*13043STim.Haley@Sun.COM 
319*13043STim.Haley@Sun.COM 	/* Simple modification or no change */
320*13043STim.Haley@Sun.COM 	if (fsb.zs_gen == tsb.zs_gen) {
321*13043STim.Haley@Sun.COM 		/* No apparent changes.  Could we assert !this?  */
322*13043STim.Haley@Sun.COM 		if (fsb.zs_ctime[0] == tsb.zs_ctime[0] &&
323*13043STim.Haley@Sun.COM 		    fsb.zs_ctime[1] == tsb.zs_ctime[1])
324*13043STim.Haley@Sun.COM 			return (0);
325*13043STim.Haley@Sun.COM 		if (change) {
326*13043STim.Haley@Sun.COM 			print_link_change(fp, di, change,
327*13043STim.Haley@Sun.COM 			    change > 0 ? fobjname : tobjname, &tsb);
328*13043STim.Haley@Sun.COM 		} else if (same_name) {
329*13043STim.Haley@Sun.COM 			print_file(fp, di, ZDIFF_MODIFIED, fobjname, &tsb);
330*13043STim.Haley@Sun.COM 		} else {
331*13043STim.Haley@Sun.COM 			print_rename(fp, di, fobjname, tobjname, &tsb);
332*13043STim.Haley@Sun.COM 		}
333*13043STim.Haley@Sun.COM 		return (0);
334*13043STim.Haley@Sun.COM 	} else {
335*13043STim.Haley@Sun.COM 		/* file re-created or object re-used */
336*13043STim.Haley@Sun.COM 		print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb);
337*13043STim.Haley@Sun.COM 		print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb);
338*13043STim.Haley@Sun.COM 		return (0);
339*13043STim.Haley@Sun.COM 	}
340*13043STim.Haley@Sun.COM }
341*13043STim.Haley@Sun.COM 
342*13043STim.Haley@Sun.COM static int
write_inuse_diffs(FILE * fp,differ_info_t * di,dmu_diff_record_t * dr)343*13043STim.Haley@Sun.COM write_inuse_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr)
344*13043STim.Haley@Sun.COM {
345*13043STim.Haley@Sun.COM 	uint64_t o;
346*13043STim.Haley@Sun.COM 	int err;
347*13043STim.Haley@Sun.COM 
348*13043STim.Haley@Sun.COM 	for (o = dr->ddr_first; o <= dr->ddr_last; o++) {
349*13043STim.Haley@Sun.COM 		if (err = write_inuse_diffs_one(fp, di, o))
350*13043STim.Haley@Sun.COM 			return (err);
351*13043STim.Haley@Sun.COM 	}
352*13043STim.Haley@Sun.COM 	return (0);
353*13043STim.Haley@Sun.COM }
354*13043STim.Haley@Sun.COM 
355*13043STim.Haley@Sun.COM static int
describe_free(FILE * fp,differ_info_t * di,uint64_t object,char * namebuf,int maxlen)356*13043STim.Haley@Sun.COM describe_free(FILE *fp, differ_info_t *di, uint64_t object, char *namebuf,
357*13043STim.Haley@Sun.COM     int maxlen)
358*13043STim.Haley@Sun.COM {
359*13043STim.Haley@Sun.COM 	struct zfs_stat sb;
360*13043STim.Haley@Sun.COM 
361*13043STim.Haley@Sun.COM 	if (get_stats_for_obj(di, di->fromsnap, object, namebuf,
362*13043STim.Haley@Sun.COM 	    maxlen, &sb) != 0) {
363*13043STim.Haley@Sun.COM 		/* Let it slide, if in the delete queue on from side */
364*13043STim.Haley@Sun.COM 		if (di->zerr == ENOENT && sb.zs_links == 0) {
365*13043STim.Haley@Sun.COM 			di->zerr = 0;
366*13043STim.Haley@Sun.COM 			return (0);
367*13043STim.Haley@Sun.COM 		}
368*13043STim.Haley@Sun.COM 		return (-1);
369*13043STim.Haley@Sun.COM 	}
370*13043STim.Haley@Sun.COM 
371*13043STim.Haley@Sun.COM 	print_file(fp, di, ZDIFF_REMOVED, namebuf, &sb);
372*13043STim.Haley@Sun.COM 	return (0);
373*13043STim.Haley@Sun.COM }
374*13043STim.Haley@Sun.COM 
375*13043STim.Haley@Sun.COM static int
write_free_diffs(FILE * fp,differ_info_t * di,dmu_diff_record_t * dr)376*13043STim.Haley@Sun.COM write_free_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr)
377*13043STim.Haley@Sun.COM {
378*13043STim.Haley@Sun.COM 	zfs_cmd_t zc = { 0 };
379*13043STim.Haley@Sun.COM 	libzfs_handle_t *lhdl = di->zhp->zfs_hdl;
380*13043STim.Haley@Sun.COM 	char fobjname[MAXPATHLEN];
381*13043STim.Haley@Sun.COM 
382*13043STim.Haley@Sun.COM 	(void) strlcpy(zc.zc_name, di->fromsnap, sizeof (zc.zc_name));
383*13043STim.Haley@Sun.COM 	zc.zc_obj = dr->ddr_first - 1;
384*13043STim.Haley@Sun.COM 
385*13043STim.Haley@Sun.COM 	ASSERT(di->zerr == 0);
386*13043STim.Haley@Sun.COM 
387*13043STim.Haley@Sun.COM 	while (zc.zc_obj < dr->ddr_last) {
388*13043STim.Haley@Sun.COM 		int err;
389*13043STim.Haley@Sun.COM 
390*13043STim.Haley@Sun.COM 		err = ioctl(lhdl->libzfs_fd, ZFS_IOC_NEXT_OBJ, &zc);
391*13043STim.Haley@Sun.COM 		if (err == 0) {
392*13043STim.Haley@Sun.COM 			if (zc.zc_obj == di->shares) {
393*13043STim.Haley@Sun.COM 				zc.zc_obj++;
394*13043STim.Haley@Sun.COM 				continue;
395*13043STim.Haley@Sun.COM 			}
396*13043STim.Haley@Sun.COM 			if (zc.zc_obj > dr->ddr_last) {
397*13043STim.Haley@Sun.COM 				break;
398*13043STim.Haley@Sun.COM 			}
399*13043STim.Haley@Sun.COM 			err = describe_free(fp, di, zc.zc_obj, fobjname,
400*13043STim.Haley@Sun.COM 			    MAXPATHLEN);
401*13043STim.Haley@Sun.COM 			if (err)
402*13043STim.Haley@Sun.COM 				break;
403*13043STim.Haley@Sun.COM 		} else if (errno == ESRCH) {
404*13043STim.Haley@Sun.COM 			break;
405*13043STim.Haley@Sun.COM 		} else {
406*13043STim.Haley@Sun.COM 			(void) snprintf(di->errbuf, sizeof (di->errbuf),
407*13043STim.Haley@Sun.COM 			    dgettext(TEXT_DOMAIN,
408*13043STim.Haley@Sun.COM 			    "next allocated object (> %lld) find failure"),
409*13043STim.Haley@Sun.COM 			    zc.zc_obj);
410*13043STim.Haley@Sun.COM 			di->zerr = errno;
411*13043STim.Haley@Sun.COM 			break;
412*13043STim.Haley@Sun.COM 		}
413*13043STim.Haley@Sun.COM 	}
414*13043STim.Haley@Sun.COM 	if (di->zerr)
415*13043STim.Haley@Sun.COM 		return (-1);
416*13043STim.Haley@Sun.COM 	return (0);
417*13043STim.Haley@Sun.COM }
418*13043STim.Haley@Sun.COM 
419*13043STim.Haley@Sun.COM static void *
differ(void * arg)420*13043STim.Haley@Sun.COM differ(void *arg)
421*13043STim.Haley@Sun.COM {
422*13043STim.Haley@Sun.COM 	differ_info_t *di = arg;
423*13043STim.Haley@Sun.COM 	dmu_diff_record_t dr;
424*13043STim.Haley@Sun.COM 	FILE *ofp;
425*13043STim.Haley@Sun.COM 	int err = 0;
426*13043STim.Haley@Sun.COM 
427*13043STim.Haley@Sun.COM 	if ((ofp = fdopen(di->outputfd, "w")) == NULL) {
428*13043STim.Haley@Sun.COM 		di->zerr = errno;
429*13043STim.Haley@Sun.COM 		(void) strerror_r(errno, di->errbuf, sizeof (di->errbuf));
430*13043STim.Haley@Sun.COM 		(void) close(di->datafd);
431*13043STim.Haley@Sun.COM 		return ((void *)-1);
432*13043STim.Haley@Sun.COM 	}
433*13043STim.Haley@Sun.COM 
434*13043STim.Haley@Sun.COM 	for (;;) {
435*13043STim.Haley@Sun.COM 		char *cp = (char *)&dr;
436*13043STim.Haley@Sun.COM 		int len = sizeof (dr);
437*13043STim.Haley@Sun.COM 		int rv;
438*13043STim.Haley@Sun.COM 
439*13043STim.Haley@Sun.COM 		do {
440*13043STim.Haley@Sun.COM 			rv = read(di->datafd, cp, len);
441*13043STim.Haley@Sun.COM 			cp += rv;
442*13043STim.Haley@Sun.COM 			len -= rv;
443*13043STim.Haley@Sun.COM 		} while (len > 0 && rv > 0);
444*13043STim.Haley@Sun.COM 
445*13043STim.Haley@Sun.COM 		if (rv < 0 || (rv == 0 && len != sizeof (dr))) {
446*13043STim.Haley@Sun.COM 			di->zerr = EPIPE;
447*13043STim.Haley@Sun.COM 			break;
448*13043STim.Haley@Sun.COM 		} else if (rv == 0) {
449*13043STim.Haley@Sun.COM 			/* end of file at a natural breaking point */
450*13043STim.Haley@Sun.COM 			break;
451*13043STim.Haley@Sun.COM 		}
452*13043STim.Haley@Sun.COM 
453*13043STim.Haley@Sun.COM 		switch (dr.ddr_type) {
454*13043STim.Haley@Sun.COM 		case DDR_FREE:
455*13043STim.Haley@Sun.COM 			err = write_free_diffs(ofp, di, &dr);
456*13043STim.Haley@Sun.COM 			break;
457*13043STim.Haley@Sun.COM 		case DDR_INUSE:
458*13043STim.Haley@Sun.COM 			err = write_inuse_diffs(ofp, di, &dr);
459*13043STim.Haley@Sun.COM 			break;
460*13043STim.Haley@Sun.COM 		default:
461*13043STim.Haley@Sun.COM 			di->zerr = EPIPE;
462*13043STim.Haley@Sun.COM 			break;
463*13043STim.Haley@Sun.COM 		}
464*13043STim.Haley@Sun.COM 
465*13043STim.Haley@Sun.COM 		if (err || di->zerr)
466*13043STim.Haley@Sun.COM 			break;
467*13043STim.Haley@Sun.COM 	}
468*13043STim.Haley@Sun.COM 
469*13043STim.Haley@Sun.COM 	(void) fclose(ofp);
470*13043STim.Haley@Sun.COM 	(void) close(di->datafd);
471*13043STim.Haley@Sun.COM 	if (err)
472*13043STim.Haley@Sun.COM 		return ((void *)-1);
473*13043STim.Haley@Sun.COM 	if (di->zerr) {
474*13043STim.Haley@Sun.COM 		ASSERT(di->zerr == EINVAL);
475*13043STim.Haley@Sun.COM 		(void) snprintf(di->errbuf, sizeof (di->errbuf),
476*13043STim.Haley@Sun.COM 		    dgettext(TEXT_DOMAIN,
477*13043STim.Haley@Sun.COM 		    "Internal error: bad data from diff IOCTL"));
478*13043STim.Haley@Sun.COM 		return ((void *)-1);
479*13043STim.Haley@Sun.COM 	}
480*13043STim.Haley@Sun.COM 	return ((void *)0);
481*13043STim.Haley@Sun.COM }
482*13043STim.Haley@Sun.COM 
483*13043STim.Haley@Sun.COM static int
find_shares_object(differ_info_t * di)484*13043STim.Haley@Sun.COM find_shares_object(differ_info_t *di)
485*13043STim.Haley@Sun.COM {
486*13043STim.Haley@Sun.COM 	char fullpath[MAXPATHLEN];
487*13043STim.Haley@Sun.COM 	struct stat64 sb = { 0 };
488*13043STim.Haley@Sun.COM 
489*13043STim.Haley@Sun.COM 	(void) strlcpy(fullpath, di->dsmnt, MAXPATHLEN);
490*13043STim.Haley@Sun.COM 	(void) strlcat(fullpath, ZDIFF_SHARESDIR, MAXPATHLEN);
491*13043STim.Haley@Sun.COM 
492*13043STim.Haley@Sun.COM 	if (stat64(fullpath, &sb) != 0) {
493*13043STim.Haley@Sun.COM 		(void) snprintf(di->errbuf, sizeof (di->errbuf),
494*13043STim.Haley@Sun.COM 		    dgettext(TEXT_DOMAIN, "Cannot stat %s"), fullpath);
495*13043STim.Haley@Sun.COM 		return (zfs_error(di->zhp->zfs_hdl, EZFS_DIFF, di->errbuf));
496*13043STim.Haley@Sun.COM 	}
497*13043STim.Haley@Sun.COM 
498*13043STim.Haley@Sun.COM 	di->shares = (uint64_t)sb.st_ino;
499*13043STim.Haley@Sun.COM 	return (0);
500*13043STim.Haley@Sun.COM }
501*13043STim.Haley@Sun.COM 
502*13043STim.Haley@Sun.COM static int
make_temp_snapshot(differ_info_t * di)503*13043STim.Haley@Sun.COM make_temp_snapshot(differ_info_t *di)
504*13043STim.Haley@Sun.COM {
505*13043STim.Haley@Sun.COM 	libzfs_handle_t *hdl = di->zhp->zfs_hdl;
506*13043STim.Haley@Sun.COM 	zfs_cmd_t zc = { 0 };
507*13043STim.Haley@Sun.COM 
508*13043STim.Haley@Sun.COM 	(void) snprintf(zc.zc_value, sizeof (zc.zc_value),
509*13043STim.Haley@Sun.COM 	    ZDIFF_PREFIX, getpid());
510*13043STim.Haley@Sun.COM 	(void) strlcpy(zc.zc_name, di->ds, sizeof (zc.zc_name));
511*13043STim.Haley@Sun.COM 	zc.zc_cleanup_fd = di->cleanupfd;
512*13043STim.Haley@Sun.COM 
513*13043STim.Haley@Sun.COM 	if (ioctl(hdl->libzfs_fd, ZFS_IOC_TMP_SNAPSHOT, &zc) != 0) {
514*13043STim.Haley@Sun.COM 		int err = errno;
515*13043STim.Haley@Sun.COM 		if (err == EPERM) {
516*13043STim.Haley@Sun.COM 			(void) snprintf(di->errbuf, sizeof (di->errbuf),
517*13043STim.Haley@Sun.COM 			    dgettext(TEXT_DOMAIN, "The diff delegated "
518*13043STim.Haley@Sun.COM 			    "permission is needed in order\nto create a "
519*13043STim.Haley@Sun.COM 			    "just-in-time snapshot for diffing\n"));
520*13043STim.Haley@Sun.COM 			return (zfs_error(hdl, EZFS_DIFF, di->errbuf));
521*13043STim.Haley@Sun.COM 		} else {
522*13043STim.Haley@Sun.COM 			(void) snprintf(di->errbuf, sizeof (di->errbuf),
523*13043STim.Haley@Sun.COM 			    dgettext(TEXT_DOMAIN, "Cannot create just-in-time "
524*13043STim.Haley@Sun.COM 			    "snapshot of '%s'"), zc.zc_name);
525*13043STim.Haley@Sun.COM 			return (zfs_standard_error(hdl, err, di->errbuf));
526*13043STim.Haley@Sun.COM 		}
527*13043STim.Haley@Sun.COM 	}
528*13043STim.Haley@Sun.COM 
529*13043STim.Haley@Sun.COM 	di->tmpsnap = zfs_strdup(hdl, zc.zc_value);
530*13043STim.Haley@Sun.COM 	di->tosnap = zfs_asprintf(hdl, "%s@%s", di->ds, di->tmpsnap);
531*13043STim.Haley@Sun.COM 	return (0);
532*13043STim.Haley@Sun.COM }
533*13043STim.Haley@Sun.COM 
534*13043STim.Haley@Sun.COM static void
teardown_differ_info(differ_info_t * di)535*13043STim.Haley@Sun.COM teardown_differ_info(differ_info_t *di)
536*13043STim.Haley@Sun.COM {
537*13043STim.Haley@Sun.COM 	free(di->ds);
538*13043STim.Haley@Sun.COM 	free(di->dsmnt);
539*13043STim.Haley@Sun.COM 	free(di->fromsnap);
540*13043STim.Haley@Sun.COM 	free(di->frommnt);
541*13043STim.Haley@Sun.COM 	free(di->tosnap);
542*13043STim.Haley@Sun.COM 	free(di->tmpsnap);
543*13043STim.Haley@Sun.COM 	free(di->tomnt);
544*13043STim.Haley@Sun.COM 	(void) close(di->cleanupfd);
545*13043STim.Haley@Sun.COM }
546*13043STim.Haley@Sun.COM 
547*13043STim.Haley@Sun.COM static int
get_snapshot_names(differ_info_t * di,const char * fromsnap,const char * tosnap)548*13043STim.Haley@Sun.COM get_snapshot_names(differ_info_t *di, const char *fromsnap,
549*13043STim.Haley@Sun.COM     const char *tosnap)
550*13043STim.Haley@Sun.COM {
551*13043STim.Haley@Sun.COM 	libzfs_handle_t *hdl = di->zhp->zfs_hdl;
552*13043STim.Haley@Sun.COM 	char *atptrf = NULL;
553*13043STim.Haley@Sun.COM 	char *atptrt = NULL;
554*13043STim.Haley@Sun.COM 	int fdslen, fsnlen;
555*13043STim.Haley@Sun.COM 	int tdslen, tsnlen;
556*13043STim.Haley@Sun.COM 
557*13043STim.Haley@Sun.COM 	/*
558*13043STim.Haley@Sun.COM 	 * Can accept
559*13043STim.Haley@Sun.COM 	 *    dataset@snap1
560*13043STim.Haley@Sun.COM 	 *    dataset@snap1 dataset@snap2
561*13043STim.Haley@Sun.COM 	 *    dataset@snap1 @snap2
562*13043STim.Haley@Sun.COM 	 *    dataset@snap1 dataset
563*13043STim.Haley@Sun.COM 	 *    @snap1 dataset@snap2
564*13043STim.Haley@Sun.COM 	 */
565*13043STim.Haley@Sun.COM 	if (tosnap == NULL) {
566*13043STim.Haley@Sun.COM 		/* only a from snapshot given, must be valid */
567*13043STim.Haley@Sun.COM 		(void) snprintf(di->errbuf, sizeof (di->errbuf),
568*13043STim.Haley@Sun.COM 		    dgettext(TEXT_DOMAIN,
569*13043STim.Haley@Sun.COM 		    "Badly formed snapshot name %s"), fromsnap);
570*13043STim.Haley@Sun.COM 
571*13043STim.Haley@Sun.COM 		if (!zfs_validate_name(hdl, fromsnap, ZFS_TYPE_SNAPSHOT,
572*13043STim.Haley@Sun.COM 		    B_FALSE)) {
573*13043STim.Haley@Sun.COM 			return (zfs_error(hdl, EZFS_INVALIDNAME,
574*13043STim.Haley@Sun.COM 			    di->errbuf));
575*13043STim.Haley@Sun.COM 		}
576*13043STim.Haley@Sun.COM 
577*13043STim.Haley@Sun.COM 		atptrf = strchr(fromsnap, '@');
578*13043STim.Haley@Sun.COM 		ASSERT(atptrf != NULL);
579*13043STim.Haley@Sun.COM 		fdslen = atptrf - fromsnap;
580*13043STim.Haley@Sun.COM 
581*13043STim.Haley@Sun.COM 		di->fromsnap = zfs_strdup(hdl, fromsnap);
582*13043STim.Haley@Sun.COM 		di->ds = zfs_strdup(hdl, fromsnap);
583*13043STim.Haley@Sun.COM 		di->ds[fdslen] = '\0';
584*13043STim.Haley@Sun.COM 
585*13043STim.Haley@Sun.COM 		/* the to snap will be a just-in-time snap of the head */
586*13043STim.Haley@Sun.COM 		return (make_temp_snapshot(di));
587*13043STim.Haley@Sun.COM 	}
588*13043STim.Haley@Sun.COM 
589*13043STim.Haley@Sun.COM 	(void) snprintf(di->errbuf, sizeof (di->errbuf),
590*13043STim.Haley@Sun.COM 	    dgettext(TEXT_DOMAIN,
591*13043STim.Haley@Sun.COM 	    "Unable to determine which snapshots to compare"));
592*13043STim.Haley@Sun.COM 
593*13043STim.Haley@Sun.COM 	atptrf = strchr(fromsnap, '@');
594*13043STim.Haley@Sun.COM 	atptrt = strchr(tosnap, '@');
595*13043STim.Haley@Sun.COM 	fdslen = atptrf ? atptrf - fromsnap : strlen(fromsnap);
596*13043STim.Haley@Sun.COM 	tdslen = atptrt ? atptrt - tosnap : strlen(tosnap);
597*13043STim.Haley@Sun.COM 	fsnlen = strlen(fromsnap) - fdslen;	/* includes @ sign */
598*13043STim.Haley@Sun.COM 	tsnlen = strlen(tosnap) - tdslen;	/* includes @ sign */
599*13043STim.Haley@Sun.COM 
600*13043STim.Haley@Sun.COM 	if (fsnlen <= 1 || tsnlen == 1 || (fdslen == 0 && tdslen == 0) ||
601*13043STim.Haley@Sun.COM 	    (fsnlen == 0 && tsnlen == 0)) {
602*13043STim.Haley@Sun.COM 		return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf));
603*13043STim.Haley@Sun.COM 	} else if ((fdslen > 0 && tdslen > 0) &&
604*13043STim.Haley@Sun.COM 	    ((tdslen != fdslen || strncmp(fromsnap, tosnap, fdslen) != 0))) {
605*13043STim.Haley@Sun.COM 		/*
606*13043STim.Haley@Sun.COM 		 * not the same dataset name, might be okay if
607*13043STim.Haley@Sun.COM 		 * tosnap is a clone of a fromsnap descendant.
608*13043STim.Haley@Sun.COM 		 */
609*13043STim.Haley@Sun.COM 		char origin[ZFS_MAXNAMELEN];
610*13043STim.Haley@Sun.COM 		zprop_source_t src;
611*13043STim.Haley@Sun.COM 		zfs_handle_t *zhp;
612*13043STim.Haley@Sun.COM 
613*13043STim.Haley@Sun.COM 		di->ds = zfs_alloc(di->zhp->zfs_hdl, tdslen + 1);
614*13043STim.Haley@Sun.COM 		(void) strncpy(di->ds, tosnap, tdslen);
615*13043STim.Haley@Sun.COM 		di->ds[tdslen] = '\0';
616*13043STim.Haley@Sun.COM 
617*13043STim.Haley@Sun.COM 		zhp = zfs_open(hdl, di->ds, ZFS_TYPE_FILESYSTEM);
618*13043STim.Haley@Sun.COM 		while (zhp != NULL) {
619*13043STim.Haley@Sun.COM 			(void) zfs_prop_get(zhp, ZFS_PROP_ORIGIN,
620*13043STim.Haley@Sun.COM 			    origin, sizeof (origin), &src, NULL, 0, B_FALSE);
621*13043STim.Haley@Sun.COM 
622*13043STim.Haley@Sun.COM 			if (strncmp(origin, fromsnap, fsnlen) == 0)
623*13043STim.Haley@Sun.COM 				break;
624*13043STim.Haley@Sun.COM 
625*13043STim.Haley@Sun.COM 			(void) zfs_close(zhp);
626*13043STim.Haley@Sun.COM 			zhp = zfs_open(hdl, origin, ZFS_TYPE_FILESYSTEM);
627*13043STim.Haley@Sun.COM 		}
628*13043STim.Haley@Sun.COM 
629*13043STim.Haley@Sun.COM 		if (zhp == NULL) {
630*13043STim.Haley@Sun.COM 			(void) snprintf(di->errbuf, sizeof (di->errbuf),
631*13043STim.Haley@Sun.COM 			    dgettext(TEXT_DOMAIN,
632*13043STim.Haley@Sun.COM 			    "Not an earlier snapshot from the same fs"));
633*13043STim.Haley@Sun.COM 			return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf));
634*13043STim.Haley@Sun.COM 		} else {
635*13043STim.Haley@Sun.COM 			(void) zfs_close(zhp);
636*13043STim.Haley@Sun.COM 		}
637*13043STim.Haley@Sun.COM 
638*13043STim.Haley@Sun.COM 		di->isclone = B_TRUE;
639*13043STim.Haley@Sun.COM 		di->fromsnap = zfs_strdup(hdl, fromsnap);
640*13043STim.Haley@Sun.COM 		if (tsnlen) {
641*13043STim.Haley@Sun.COM 			di->tosnap = zfs_strdup(hdl, tosnap);
642*13043STim.Haley@Sun.COM 		} else {
643*13043STim.Haley@Sun.COM 			return (make_temp_snapshot(di));
644*13043STim.Haley@Sun.COM 		}
645*13043STim.Haley@Sun.COM 	} else {
646*13043STim.Haley@Sun.COM 		int dslen = fdslen ? fdslen : tdslen;
647*13043STim.Haley@Sun.COM 
648*13043STim.Haley@Sun.COM 		di->ds = zfs_alloc(hdl, dslen + 1);
649*13043STim.Haley@Sun.COM 		(void) strncpy(di->ds, fdslen ? fromsnap : tosnap, dslen);
650*13043STim.Haley@Sun.COM 		di->ds[dslen] = '\0';
651*13043STim.Haley@Sun.COM 
652*13043STim.Haley@Sun.COM 		di->fromsnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrf);
653*13043STim.Haley@Sun.COM 		if (tsnlen) {
654*13043STim.Haley@Sun.COM 			di->tosnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrt);
655*13043STim.Haley@Sun.COM 		} else {
656*13043STim.Haley@Sun.COM 			return (make_temp_snapshot(di));
657*13043STim.Haley@Sun.COM 		}
658*13043STim.Haley@Sun.COM 	}
659*13043STim.Haley@Sun.COM 	return (0);
660*13043STim.Haley@Sun.COM }
661*13043STim.Haley@Sun.COM 
662*13043STim.Haley@Sun.COM static int
get_mountpoint(differ_info_t * di,char * dsnm,char ** mntpt)663*13043STim.Haley@Sun.COM get_mountpoint(differ_info_t *di, char *dsnm, char **mntpt)
664*13043STim.Haley@Sun.COM {
665*13043STim.Haley@Sun.COM 	boolean_t mounted;
666*13043STim.Haley@Sun.COM 
667*13043STim.Haley@Sun.COM 	mounted = is_mounted(di->zhp->zfs_hdl, dsnm, mntpt);
668*13043STim.Haley@Sun.COM 	if (mounted == B_FALSE) {
669*13043STim.Haley@Sun.COM 		(void) snprintf(di->errbuf, sizeof (di->errbuf),
670*13043STim.Haley@Sun.COM 		    dgettext(TEXT_DOMAIN,
671*13043STim.Haley@Sun.COM 		    "Cannot diff an unmounted snapshot"));
672*13043STim.Haley@Sun.COM 		return (zfs_error(di->zhp->zfs_hdl, EZFS_BADTYPE, di->errbuf));
673*13043STim.Haley@Sun.COM 	}
674*13043STim.Haley@Sun.COM 
675*13043STim.Haley@Sun.COM 	/* Avoid a double slash at the beginning of root-mounted datasets */
676*13043STim.Haley@Sun.COM 	if (**mntpt == '/' && *(*mntpt + 1) == '\0')
677*13043STim.Haley@Sun.COM 		**mntpt = '\0';
678*13043STim.Haley@Sun.COM 	return (0);
679*13043STim.Haley@Sun.COM }
680*13043STim.Haley@Sun.COM 
681*13043STim.Haley@Sun.COM static int
get_mountpoints(differ_info_t * di)682*13043STim.Haley@Sun.COM get_mountpoints(differ_info_t *di)
683*13043STim.Haley@Sun.COM {
684*13043STim.Haley@Sun.COM 	char *strptr;
685*13043STim.Haley@Sun.COM 	char *frommntpt;
686*13043STim.Haley@Sun.COM 
687*13043STim.Haley@Sun.COM 	/*
688*13043STim.Haley@Sun.COM 	 * first get the mountpoint for the parent dataset
689*13043STim.Haley@Sun.COM 	 */
690*13043STim.Haley@Sun.COM 	if (get_mountpoint(di, di->ds, &di->dsmnt) != 0)
691*13043STim.Haley@Sun.COM 		return (-1);
692*13043STim.Haley@Sun.COM 
693*13043STim.Haley@Sun.COM 	strptr = strchr(di->tosnap, '@');
694*13043STim.Haley@Sun.COM 	ASSERT3P(strptr, !=, NULL);
695*13043STim.Haley@Sun.COM 	di->tomnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", di->dsmnt,
696*13043STim.Haley@Sun.COM 	    ZDIFF_SNAPDIR, ++strptr);
697*13043STim.Haley@Sun.COM 
698*13043STim.Haley@Sun.COM 	strptr = strchr(di->fromsnap, '@');
699*13043STim.Haley@Sun.COM 	ASSERT3P(strptr, !=, NULL);
700*13043STim.Haley@Sun.COM 
701*13043STim.Haley@Sun.COM 	frommntpt = di->dsmnt;
702*13043STim.Haley@Sun.COM 	if (di->isclone) {
703*13043STim.Haley@Sun.COM 		char *mntpt;
704*13043STim.Haley@Sun.COM 		int err;
705*13043STim.Haley@Sun.COM 
706*13043STim.Haley@Sun.COM 		*strptr = '\0';
707*13043STim.Haley@Sun.COM 		err = get_mountpoint(di, di->fromsnap, &mntpt);
708*13043STim.Haley@Sun.COM 		*strptr = '@';
709*13043STim.Haley@Sun.COM 		if (err != 0)
710*13043STim.Haley@Sun.COM 			return (-1);
711*13043STim.Haley@Sun.COM 		frommntpt = mntpt;
712*13043STim.Haley@Sun.COM 	}
713*13043STim.Haley@Sun.COM 
714*13043STim.Haley@Sun.COM 	di->frommnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", frommntpt,
715*13043STim.Haley@Sun.COM 	    ZDIFF_SNAPDIR, ++strptr);
716*13043STim.Haley@Sun.COM 
717*13043STim.Haley@Sun.COM 	if (di->isclone)
718*13043STim.Haley@Sun.COM 		free(frommntpt);
719*13043STim.Haley@Sun.COM 
720*13043STim.Haley@Sun.COM 	return (0);
721*13043STim.Haley@Sun.COM }
722*13043STim.Haley@Sun.COM 
723*13043STim.Haley@Sun.COM static int
setup_differ_info(zfs_handle_t * zhp,const char * fromsnap,const char * tosnap,differ_info_t * di)724*13043STim.Haley@Sun.COM setup_differ_info(zfs_handle_t *zhp, const char *fromsnap,
725*13043STim.Haley@Sun.COM     const char *tosnap, differ_info_t *di)
726*13043STim.Haley@Sun.COM {
727*13043STim.Haley@Sun.COM 	di->zhp = zhp;
728*13043STim.Haley@Sun.COM 
729*13043STim.Haley@Sun.COM 	di->cleanupfd = open(ZFS_DEV, O_RDWR|O_EXCL);
730*13043STim.Haley@Sun.COM 	VERIFY(di->cleanupfd >= 0);
731*13043STim.Haley@Sun.COM 
732*13043STim.Haley@Sun.COM 	if (get_snapshot_names(di, fromsnap, tosnap) != 0)
733*13043STim.Haley@Sun.COM 		return (-1);
734*13043STim.Haley@Sun.COM 
735*13043STim.Haley@Sun.COM 	if (get_mountpoints(di) != 0)
736*13043STim.Haley@Sun.COM 		return (-1);
737*13043STim.Haley@Sun.COM 
738*13043STim.Haley@Sun.COM 	if (find_shares_object(di) != 0)
739*13043STim.Haley@Sun.COM 		return (-1);
740*13043STim.Haley@Sun.COM 
741*13043STim.Haley@Sun.COM 	return (0);
742*13043STim.Haley@Sun.COM }
743*13043STim.Haley@Sun.COM 
744*13043STim.Haley@Sun.COM int
zfs_show_diffs(zfs_handle_t * zhp,int outfd,const char * fromsnap,const char * tosnap,int flags)745*13043STim.Haley@Sun.COM zfs_show_diffs(zfs_handle_t *zhp, int outfd, const char *fromsnap,
746*13043STim.Haley@Sun.COM     const char *tosnap, int flags)
747*13043STim.Haley@Sun.COM {
748*13043STim.Haley@Sun.COM 	zfs_cmd_t zc = { 0 };
749*13043STim.Haley@Sun.COM 	char errbuf[1024];
750*13043STim.Haley@Sun.COM 	differ_info_t di = { 0 };
751*13043STim.Haley@Sun.COM 	pthread_t tid;
752*13043STim.Haley@Sun.COM 	int pipefd[2];
753*13043STim.Haley@Sun.COM 	int iocerr;
754*13043STim.Haley@Sun.COM 
755*13043STim.Haley@Sun.COM 	(void) snprintf(errbuf, sizeof (errbuf),
756*13043STim.Haley@Sun.COM 	    dgettext(TEXT_DOMAIN, "zfs diff failed"));
757*13043STim.Haley@Sun.COM 
758*13043STim.Haley@Sun.COM 	if (setup_differ_info(zhp, fromsnap, tosnap, &di)) {
759*13043STim.Haley@Sun.COM 		teardown_differ_info(&di);
760*13043STim.Haley@Sun.COM 		return (-1);
761*13043STim.Haley@Sun.COM 	}
762*13043STim.Haley@Sun.COM 
763*13043STim.Haley@Sun.COM 	if (pipe(pipefd)) {
764*13043STim.Haley@Sun.COM 		zfs_error_aux(zhp->zfs_hdl, strerror(errno));
765*13043STim.Haley@Sun.COM 		teardown_differ_info(&di);
766*13043STim.Haley@Sun.COM 		return (zfs_error(zhp->zfs_hdl, EZFS_PIPEFAILED, errbuf));
767*13043STim.Haley@Sun.COM 	}
768*13043STim.Haley@Sun.COM 
769*13043STim.Haley@Sun.COM 	di.scripted = (flags & ZFS_DIFF_PARSEABLE);
770*13043STim.Haley@Sun.COM 	di.classify = (flags & ZFS_DIFF_CLASSIFY);
771*13043STim.Haley@Sun.COM 	di.timestamped = (flags & ZFS_DIFF_TIMESTAMP);
772*13043STim.Haley@Sun.COM 
773*13043STim.Haley@Sun.COM 	di.outputfd = outfd;
774*13043STim.Haley@Sun.COM 	di.datafd = pipefd[0];
775*13043STim.Haley@Sun.COM 
776*13043STim.Haley@Sun.COM 	if (pthread_create(&tid, NULL, differ, &di)) {
777*13043STim.Haley@Sun.COM 		zfs_error_aux(zhp->zfs_hdl, strerror(errno));
778*13043STim.Haley@Sun.COM 		(void) close(pipefd[0]);
779*13043STim.Haley@Sun.COM 		(void) close(pipefd[1]);
780*13043STim.Haley@Sun.COM 		teardown_differ_info(&di);
781*13043STim.Haley@Sun.COM 		return (zfs_error(zhp->zfs_hdl,
782*13043STim.Haley@Sun.COM 		    EZFS_THREADCREATEFAILED, errbuf));
783*13043STim.Haley@Sun.COM 	}
784*13043STim.Haley@Sun.COM 
785*13043STim.Haley@Sun.COM 	/* do the ioctl() */
786*13043STim.Haley@Sun.COM 	(void) strlcpy(zc.zc_value, di.fromsnap, strlen(di.fromsnap) + 1);
787*13043STim.Haley@Sun.COM 	(void) strlcpy(zc.zc_name, di.tosnap, strlen(di.tosnap) + 1);
788*13043STim.Haley@Sun.COM 	zc.zc_cookie = pipefd[1];
789*13043STim.Haley@Sun.COM 
790*13043STim.Haley@Sun.COM 	iocerr = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_DIFF, &zc);
791*13043STim.Haley@Sun.COM 	if (iocerr != 0) {
792*13043STim.Haley@Sun.COM 		(void) snprintf(errbuf, sizeof (errbuf),
793*13043STim.Haley@Sun.COM 		    dgettext(TEXT_DOMAIN, "Unable to obtain diffs"));
794*13043STim.Haley@Sun.COM 		if (errno == EPERM) {
795*13043STim.Haley@Sun.COM 			zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
796*13043STim.Haley@Sun.COM 			    "\n   The sys_mount privilege or diff delegated "
797*13043STim.Haley@Sun.COM 			    "permission is needed\n   to execute the "
798*13043STim.Haley@Sun.COM 			    "diff ioctl"));
799*13043STim.Haley@Sun.COM 		} else if (errno == EXDEV) {
800*13043STim.Haley@Sun.COM 			zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
801*13043STim.Haley@Sun.COM 			    "\n   Not an earlier snapshot from the same fs"));
802*13043STim.Haley@Sun.COM 		} else if (errno != EPIPE || di.zerr == 0) {
803*13043STim.Haley@Sun.COM 			zfs_error_aux(zhp->zfs_hdl, strerror(errno));
804*13043STim.Haley@Sun.COM 		}
805*13043STim.Haley@Sun.COM 		(void) close(pipefd[1]);
806*13043STim.Haley@Sun.COM 		(void) pthread_cancel(tid);
807*13043STim.Haley@Sun.COM 		(void) pthread_join(tid, NULL);
808*13043STim.Haley@Sun.COM 		teardown_differ_info(&di);
809*13043STim.Haley@Sun.COM 		if (di.zerr != 0 && di.zerr != EPIPE) {
810*13043STim.Haley@Sun.COM 			zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr));
811*13043STim.Haley@Sun.COM 			return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf));
812*13043STim.Haley@Sun.COM 		} else {
813*13043STim.Haley@Sun.COM 			return (zfs_error(zhp->zfs_hdl, EZFS_DIFFDATA, errbuf));
814*13043STim.Haley@Sun.COM 		}
815*13043STim.Haley@Sun.COM 	}
816*13043STim.Haley@Sun.COM 
817*13043STim.Haley@Sun.COM 	(void) close(pipefd[1]);
818*13043STim.Haley@Sun.COM 	(void) pthread_join(tid, NULL);
819*13043STim.Haley@Sun.COM 
820*13043STim.Haley@Sun.COM 	if (di.zerr != 0) {
821*13043STim.Haley@Sun.COM 		zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr));
822*13043STim.Haley@Sun.COM 		return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf));
823*13043STim.Haley@Sun.COM 	}
824*13043STim.Haley@Sun.COM 	teardown_differ_info(&di);
825*13043STim.Haley@Sun.COM 	return (0);
826*13043STim.Haley@Sun.COM }
827