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