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