1219089Spjd/*
2219089Spjd * CDDL HEADER START
3219089Spjd *
4219089Spjd * The contents of this file are subject to the terms of the
5219089Spjd * Common Development and Distribution License (the "License").
6219089Spjd * You may not use this file except in compliance with the License.
7219089Spjd *
8219089Spjd * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9219089Spjd * or http://www.opensolaris.org/os/licensing.
10219089Spjd * See the License for the specific language governing permissions
11219089Spjd * and limitations under the License.
12219089Spjd *
13219089Spjd * When distributing Covered Code, include this CDDL HEADER in each
14219089Spjd * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15219089Spjd * If applicable, add the following below this CDDL HEADER, with the
16219089Spjd * fields enclosed by brackets "[]" replaced with your own identifying
17219089Spjd * information: Portions Copyright [yyyy] [name of copyright owner]
18219089Spjd *
19219089Spjd * CDDL HEADER END
20219089Spjd */
21219089Spjd
22219089Spjd/*
23219089Spjd * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24290763Smav * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
25307122Smav * Copyright (c) 2015 by Delphix. All rights reserved.
26297121Smav * Copyright 2016 Joyent, Inc.
27307058Smav * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com>
28219089Spjd */
29219089Spjd
30219089Spjd/*
31219089Spjd * zfs diff support
32219089Spjd */
33219089Spjd#include <ctype.h>
34219089Spjd#include <errno.h>
35219089Spjd#include <libintl.h>
36219089Spjd#include <string.h>
37219089Spjd#include <sys/types.h>
38219089Spjd#include <sys/stat.h>
39219089Spjd#include <fcntl.h>
40219089Spjd#include <stddef.h>
41219089Spjd#include <unistd.h>
42219089Spjd#include <stdio.h>
43219089Spjd#include <stdlib.h>
44219089Spjd#include <pthread.h>
45219089Spjd#include <sys/zfs_ioctl.h>
46219089Spjd#include <libzfs.h>
47219089Spjd#include "libzfs_impl.h"
48219089Spjd
49219089Spjd#define	ZDIFF_SNAPDIR		"/.zfs/snapshot/"
50219089Spjd#define	ZDIFF_SHARESDIR 	"/.zfs/shares/"
51219089Spjd#define	ZDIFF_PREFIX		"zfs-diff-%d"
52219089Spjd
53219089Spjd#define	ZDIFF_ADDED	'+'
54219089Spjd#define	ZDIFF_MODIFIED	'M'
55219089Spjd#define	ZDIFF_REMOVED	'-'
56219089Spjd#define	ZDIFF_RENAMED	'R'
57219089Spjd
58219089Spjdtypedef struct differ_info {
59219089Spjd	zfs_handle_t *zhp;
60219089Spjd	char *fromsnap;
61219089Spjd	char *frommnt;
62219089Spjd	char *tosnap;
63219089Spjd	char *tomnt;
64219089Spjd	char *ds;
65219089Spjd	char *dsmnt;
66219089Spjd	char *tmpsnap;
67219089Spjd	char errbuf[1024];
68219089Spjd	boolean_t isclone;
69219089Spjd	boolean_t scripted;
70219089Spjd	boolean_t classify;
71219089Spjd	boolean_t timestamped;
72219089Spjd	uint64_t shares;
73219089Spjd	int zerr;
74219089Spjd	int cleanupfd;
75219089Spjd	int outputfd;
76219089Spjd	int datafd;
77219089Spjd} differ_info_t;
78219089Spjd
79219089Spjd/*
80219089Spjd * Given a {dsname, object id}, get the object path
81219089Spjd */
82219089Spjdstatic int
83219089Spjdget_stats_for_obj(differ_info_t *di, const char *dsname, uint64_t obj,
84219089Spjd    char *pn, int maxlen, zfs_stat_t *sb)
85219089Spjd{
86219089Spjd	zfs_cmd_t zc = { 0 };
87219089Spjd	int error;
88219089Spjd
89219089Spjd	(void) strlcpy(zc.zc_name, dsname, sizeof (zc.zc_name));
90219089Spjd	zc.zc_obj = obj;
91219089Spjd
92219089Spjd	errno = 0;
93219089Spjd	error = ioctl(di->zhp->zfs_hdl->libzfs_fd, ZFS_IOC_OBJ_TO_STATS, &zc);
94219089Spjd	di->zerr = errno;
95219089Spjd
96219089Spjd	/* we can get stats even if we failed to get a path */
97219089Spjd	(void) memcpy(sb, &zc.zc_stat, sizeof (zfs_stat_t));
98219089Spjd	if (error == 0) {
99219089Spjd		ASSERT(di->zerr == 0);
100219089Spjd		(void) strlcpy(pn, zc.zc_value, maxlen);
101219089Spjd		return (0);
102219089Spjd	}
103219089Spjd
104219089Spjd	if (di->zerr == EPERM) {
105219089Spjd		(void) snprintf(di->errbuf, sizeof (di->errbuf),
106219089Spjd		    dgettext(TEXT_DOMAIN,
107219089Spjd		    "The sys_config privilege or diff delegated permission "
108219089Spjd		    "is needed\nto discover path names"));
109219089Spjd		return (-1);
110219089Spjd	} else {
111219089Spjd		(void) snprintf(di->errbuf, sizeof (di->errbuf),
112219089Spjd		    dgettext(TEXT_DOMAIN,
113219089Spjd		    "Unable to determine path or stats for "
114219089Spjd		    "object %lld in %s"), obj, dsname);
115219089Spjd		return (-1);
116219089Spjd	}
117219089Spjd}
118219089Spjd
119219089Spjd/*
120219089Spjd * stream_bytes
121219089Spjd *
122219089Spjd * Prints a file name out a character at a time.  If the character is
123219089Spjd * not in the range of what we consider "printable" ASCII, display it
124219089Spjd * as an escaped 3-digit octal value.  ASCII values less than a space
125219089Spjd * are all control characters and we declare the upper end as the
126219089Spjd * DELete character.  This also is the last 7-bit ASCII character.
127219089Spjd * We choose to treat all 8-bit ASCII as not printable for this
128219089Spjd * application.
129219089Spjd */
130219089Spjdstatic void
131219089Spjdstream_bytes(FILE *fp, const char *string)
132219089Spjd{
133297121Smav	char c;
134297121Smav
135297121Smav	while ((c = *string++) != '\0') {
136297121Smav		if (c > ' ' && c != '\\' && c < '\177') {
137297121Smav			(void) fprintf(fp, "%c", c);
138297121Smav		} else {
139297121Smav			(void) fprintf(fp, "\\%03o", (uint8_t)c);
140219959Spjd		}
141219089Spjd	}
142219089Spjd}
143219089Spjd
144219089Spjdstatic void
145219089Spjdprint_what(FILE *fp, mode_t what)
146219089Spjd{
147219089Spjd	char symbol;
148219089Spjd
149219089Spjd	switch (what & S_IFMT) {
150219089Spjd	case S_IFBLK:
151219089Spjd		symbol = 'B';
152219089Spjd		break;
153219089Spjd	case S_IFCHR:
154219089Spjd		symbol = 'C';
155219089Spjd		break;
156219089Spjd	case S_IFDIR:
157219089Spjd		symbol = '/';
158219089Spjd		break;
159219089Spjd#ifdef S_IFDOOR
160219089Spjd	case S_IFDOOR:
161219089Spjd		symbol = '>';
162219089Spjd		break;
163219089Spjd#endif
164219089Spjd	case S_IFIFO:
165219089Spjd		symbol = '|';
166219089Spjd		break;
167219089Spjd	case S_IFLNK:
168219089Spjd		symbol = '@';
169219089Spjd		break;
170219089Spjd#ifdef S_IFPORT
171219089Spjd	case S_IFPORT:
172219089Spjd		symbol = 'P';
173219089Spjd		break;
174219089Spjd#endif
175219089Spjd	case S_IFSOCK:
176219089Spjd		symbol = '=';
177219089Spjd		break;
178219089Spjd	case S_IFREG:
179219089Spjd		symbol = 'F';
180219089Spjd		break;
181219089Spjd	default:
182219089Spjd		symbol = '?';
183219089Spjd		break;
184219089Spjd	}
185219089Spjd	(void) fprintf(fp, "%c", symbol);
186219089Spjd}
187219089Spjd
188219089Spjdstatic void
189219089Spjdprint_cmn(FILE *fp, differ_info_t *di, const char *file)
190219089Spjd{
191219089Spjd	stream_bytes(fp, di->dsmnt);
192219089Spjd	stream_bytes(fp, file);
193219089Spjd}
194219089Spjd
195219089Spjdstatic void
196219089Spjdprint_rename(FILE *fp, differ_info_t *di, const char *old, const char *new,
197219089Spjd    zfs_stat_t *isb)
198219089Spjd{
199219089Spjd	if (di->timestamped)
200219089Spjd		(void) fprintf(fp, "%10lld.%09lld\t",
201219089Spjd		    (longlong_t)isb->zs_ctime[0],
202219089Spjd		    (longlong_t)isb->zs_ctime[1]);
203219089Spjd	(void) fprintf(fp, "%c\t", ZDIFF_RENAMED);
204219089Spjd	if (di->classify) {
205219089Spjd		print_what(fp, isb->zs_mode);
206219089Spjd		(void) fprintf(fp, "\t");
207219089Spjd	}
208219089Spjd	print_cmn(fp, di, old);
209219089Spjd	if (di->scripted)
210219089Spjd		(void) fprintf(fp, "\t");
211219089Spjd	else
212219089Spjd		(void) fprintf(fp, " -> ");
213219089Spjd	print_cmn(fp, di, new);
214219089Spjd	(void) fprintf(fp, "\n");
215219089Spjd}
216219089Spjd
217219089Spjdstatic void
218219089Spjdprint_link_change(FILE *fp, differ_info_t *di, int delta, const char *file,
219219089Spjd    zfs_stat_t *isb)
220219089Spjd{
221219089Spjd	if (di->timestamped)
222219089Spjd		(void) fprintf(fp, "%10lld.%09lld\t",
223219089Spjd		    (longlong_t)isb->zs_ctime[0],
224219089Spjd		    (longlong_t)isb->zs_ctime[1]);
225219089Spjd	(void) fprintf(fp, "%c\t", ZDIFF_MODIFIED);
226219089Spjd	if (di->classify) {
227219089Spjd		print_what(fp, isb->zs_mode);
228219089Spjd		(void) fprintf(fp, "\t");
229219089Spjd	}
230219089Spjd	print_cmn(fp, di, file);
231219089Spjd	(void) fprintf(fp, "\t(%+d)", delta);
232219089Spjd	(void) fprintf(fp, "\n");
233219089Spjd}
234219089Spjd
235219089Spjdstatic void
236219089Spjdprint_file(FILE *fp, differ_info_t *di, char type, const char *file,
237219089Spjd    zfs_stat_t *isb)
238219089Spjd{
239219089Spjd	if (di->timestamped)
240219089Spjd		(void) fprintf(fp, "%10lld.%09lld\t",
241219089Spjd		    (longlong_t)isb->zs_ctime[0],
242219089Spjd		    (longlong_t)isb->zs_ctime[1]);
243219089Spjd	(void) fprintf(fp, "%c\t", type);
244219089Spjd	if (di->classify) {
245219089Spjd		print_what(fp, isb->zs_mode);
246219089Spjd		(void) fprintf(fp, "\t");
247219089Spjd	}
248219089Spjd	print_cmn(fp, di, file);
249219089Spjd	(void) fprintf(fp, "\n");
250219089Spjd}
251219089Spjd
252219089Spjdstatic int
253219089Spjdwrite_inuse_diffs_one(FILE *fp, differ_info_t *di, uint64_t dobj)
254219089Spjd{
255219089Spjd	struct zfs_stat fsb, tsb;
256219089Spjd	mode_t fmode, tmode;
257219089Spjd	char fobjname[MAXPATHLEN], tobjname[MAXPATHLEN];
258219089Spjd	int fobjerr, tobjerr;
259219089Spjd	int change;
260219089Spjd
261219089Spjd	if (dobj == di->shares)
262219089Spjd		return (0);
263219089Spjd
264219089Spjd	/*
265219089Spjd	 * Check the from and to snapshots for info on the object. If
266219089Spjd	 * we get ENOENT, then the object just didn't exist in that
267219089Spjd	 * snapshot.  If we get ENOTSUP, then we tried to get
268219089Spjd	 * info on a non-ZPL object, which we don't care about anyway.
269219089Spjd	 */
270219089Spjd	fobjerr = get_stats_for_obj(di, di->fromsnap, dobj, fobjname,
271219089Spjd	    MAXPATHLEN, &fsb);
272219089Spjd	if (fobjerr && di->zerr != ENOENT && di->zerr != ENOTSUP)
273219089Spjd		return (-1);
274219089Spjd
275219089Spjd	tobjerr = get_stats_for_obj(di, di->tosnap, dobj, tobjname,
276219089Spjd	    MAXPATHLEN, &tsb);
277219089Spjd	if (tobjerr && di->zerr != ENOENT && di->zerr != ENOTSUP)
278219089Spjd		return (-1);
279219089Spjd
280219089Spjd	/*
281219089Spjd	 * Unallocated object sharing the same meta dnode block
282219089Spjd	 */
283219089Spjd	if (fobjerr && tobjerr) {
284219089Spjd		ASSERT(di->zerr == ENOENT || di->zerr == ENOTSUP);
285219089Spjd		di->zerr = 0;
286219089Spjd		return (0);
287219089Spjd	}
288219089Spjd
289219089Spjd	di->zerr = 0; /* negate get_stats_for_obj() from side that failed */
290219089Spjd	fmode = fsb.zs_mode & S_IFMT;
291219089Spjd	tmode = tsb.zs_mode & S_IFMT;
292219089Spjd	if (fmode == S_IFDIR || tmode == S_IFDIR || fsb.zs_links == 0 ||
293219089Spjd	    tsb.zs_links == 0)
294219089Spjd		change = 0;
295219089Spjd	else
296219089Spjd		change = tsb.zs_links - fsb.zs_links;
297219089Spjd
298219089Spjd	if (fobjerr) {
299219089Spjd		if (change) {
300219089Spjd			print_link_change(fp, di, change, tobjname, &tsb);
301219089Spjd			return (0);
302219089Spjd		}
303219089Spjd		print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb);
304219089Spjd		return (0);
305219089Spjd	} else if (tobjerr) {
306219089Spjd		if (change) {
307219089Spjd			print_link_change(fp, di, change, fobjname, &fsb);
308219089Spjd			return (0);
309219089Spjd		}
310219089Spjd		print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb);
311219089Spjd		return (0);
312219089Spjd	}
313219089Spjd
314219089Spjd	if (fmode != tmode && fsb.zs_gen == tsb.zs_gen)
315219089Spjd		tsb.zs_gen++;	/* Force a generational difference */
316219089Spjd
317219089Spjd	/* Simple modification or no change */
318219089Spjd	if (fsb.zs_gen == tsb.zs_gen) {
319219089Spjd		/* No apparent changes.  Could we assert !this?  */
320219089Spjd		if (fsb.zs_ctime[0] == tsb.zs_ctime[0] &&
321219089Spjd		    fsb.zs_ctime[1] == tsb.zs_ctime[1])
322219089Spjd			return (0);
323219089Spjd		if (change) {
324219089Spjd			print_link_change(fp, di, change,
325219089Spjd			    change > 0 ? fobjname : tobjname, &tsb);
326325148Savg		} else if (strcmp(fobjname, tobjname) == 0) {
327219089Spjd			print_file(fp, di, ZDIFF_MODIFIED, fobjname, &tsb);
328219089Spjd		} else {
329219089Spjd			print_rename(fp, di, fobjname, tobjname, &tsb);
330219089Spjd		}
331219089Spjd		return (0);
332219089Spjd	} else {
333219089Spjd		/* file re-created or object re-used */
334219089Spjd		print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb);
335219089Spjd		print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb);
336219089Spjd		return (0);
337219089Spjd	}
338219089Spjd}
339219089Spjd
340219089Spjdstatic int
341219089Spjdwrite_inuse_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr)
342219089Spjd{
343219089Spjd	uint64_t o;
344219089Spjd	int err;
345219089Spjd
346219089Spjd	for (o = dr->ddr_first; o <= dr->ddr_last; o++) {
347307058Smav		if ((err = write_inuse_diffs_one(fp, di, o)) != 0)
348219089Spjd			return (err);
349219089Spjd	}
350219089Spjd	return (0);
351219089Spjd}
352219089Spjd
353219089Spjdstatic int
354219089Spjddescribe_free(FILE *fp, differ_info_t *di, uint64_t object, char *namebuf,
355219089Spjd    int maxlen)
356219089Spjd{
357219089Spjd	struct zfs_stat sb;
358219089Spjd
359219089Spjd	if (get_stats_for_obj(di, di->fromsnap, object, namebuf,
360219089Spjd	    maxlen, &sb) != 0) {
361219089Spjd		/* Let it slide, if in the delete queue on from side */
362219089Spjd		if (di->zerr == ENOENT && sb.zs_links == 0) {
363219089Spjd			di->zerr = 0;
364219089Spjd			return (0);
365219089Spjd		}
366219089Spjd		return (-1);
367219089Spjd	}
368219089Spjd
369219089Spjd	print_file(fp, di, ZDIFF_REMOVED, namebuf, &sb);
370219089Spjd	return (0);
371219089Spjd}
372219089Spjd
373219089Spjdstatic int
374219089Spjdwrite_free_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr)
375219089Spjd{
376219089Spjd	zfs_cmd_t zc = { 0 };
377219089Spjd	libzfs_handle_t *lhdl = di->zhp->zfs_hdl;
378219089Spjd	char fobjname[MAXPATHLEN];
379219089Spjd
380219089Spjd	(void) strlcpy(zc.zc_name, di->fromsnap, sizeof (zc.zc_name));
381219089Spjd	zc.zc_obj = dr->ddr_first - 1;
382219089Spjd
383219089Spjd	ASSERT(di->zerr == 0);
384219089Spjd
385219089Spjd	while (zc.zc_obj < dr->ddr_last) {
386219089Spjd		int err;
387219089Spjd
388219089Spjd		err = ioctl(lhdl->libzfs_fd, ZFS_IOC_NEXT_OBJ, &zc);
389219089Spjd		if (err == 0) {
390219089Spjd			if (zc.zc_obj == di->shares) {
391219089Spjd				zc.zc_obj++;
392219089Spjd				continue;
393219089Spjd			}
394219089Spjd			if (zc.zc_obj > dr->ddr_last) {
395219089Spjd				break;
396219089Spjd			}
397219089Spjd			err = describe_free(fp, di, zc.zc_obj, fobjname,
398219089Spjd			    MAXPATHLEN);
399219089Spjd			if (err)
400219089Spjd				break;
401219089Spjd		} else if (errno == ESRCH) {
402219089Spjd			break;
403219089Spjd		} else {
404219089Spjd			(void) snprintf(di->errbuf, sizeof (di->errbuf),
405219089Spjd			    dgettext(TEXT_DOMAIN,
406219089Spjd			    "next allocated object (> %lld) find failure"),
407219089Spjd			    zc.zc_obj);
408219089Spjd			di->zerr = errno;
409219089Spjd			break;
410219089Spjd		}
411219089Spjd	}
412219089Spjd	if (di->zerr)
413219089Spjd		return (-1);
414219089Spjd	return (0);
415219089Spjd}
416219089Spjd
417219089Spjdstatic void *
418219089Spjddiffer(void *arg)
419219089Spjd{
420219089Spjd	differ_info_t *di = arg;
421219089Spjd	dmu_diff_record_t dr;
422219089Spjd	FILE *ofp;
423219089Spjd	int err = 0;
424219089Spjd
425219089Spjd	if ((ofp = fdopen(di->outputfd, "w")) == NULL) {
426219089Spjd		di->zerr = errno;
427219089Spjd		(void) strerror_r(errno, di->errbuf, sizeof (di->errbuf));
428219089Spjd		(void) close(di->datafd);
429219089Spjd		return ((void *)-1);
430219089Spjd	}
431219089Spjd
432219089Spjd	for (;;) {
433219089Spjd		char *cp = (char *)&dr;
434219089Spjd		int len = sizeof (dr);
435219089Spjd		int rv;
436219089Spjd
437219089Spjd		do {
438219089Spjd			rv = read(di->datafd, cp, len);
439219089Spjd			cp += rv;
440219089Spjd			len -= rv;
441219089Spjd		} while (len > 0 && rv > 0);
442219089Spjd
443219089Spjd		if (rv < 0 || (rv == 0 && len != sizeof (dr))) {
444219089Spjd			di->zerr = EPIPE;
445219089Spjd			break;
446219089Spjd		} else if (rv == 0) {
447219089Spjd			/* end of file at a natural breaking point */
448219089Spjd			break;
449219089Spjd		}
450219089Spjd
451219089Spjd		switch (dr.ddr_type) {
452219089Spjd		case DDR_FREE:
453219089Spjd			err = write_free_diffs(ofp, di, &dr);
454219089Spjd			break;
455219089Spjd		case DDR_INUSE:
456219089Spjd			err = write_inuse_diffs(ofp, di, &dr);
457219089Spjd			break;
458219089Spjd		default:
459219089Spjd			di->zerr = EPIPE;
460219089Spjd			break;
461219089Spjd		}
462219089Spjd
463219089Spjd		if (err || di->zerr)
464219089Spjd			break;
465219089Spjd	}
466219089Spjd
467219089Spjd	(void) fclose(ofp);
468219089Spjd	(void) close(di->datafd);
469219089Spjd	if (err)
470219089Spjd		return ((void *)-1);
471219089Spjd	if (di->zerr) {
472219089Spjd		ASSERT(di->zerr == EINVAL);
473219089Spjd		(void) snprintf(di->errbuf, sizeof (di->errbuf),
474219089Spjd		    dgettext(TEXT_DOMAIN,
475219089Spjd		    "Internal error: bad data from diff IOCTL"));
476219089Spjd		return ((void *)-1);
477219089Spjd	}
478219089Spjd	return ((void *)0);
479219089Spjd}
480219089Spjd
481219089Spjdstatic int
482219089Spjdfind_shares_object(differ_info_t *di)
483219089Spjd{
484219089Spjd	char fullpath[MAXPATHLEN];
485219089Spjd	struct stat64 sb = { 0 };
486219089Spjd
487219089Spjd	(void) strlcpy(fullpath, di->dsmnt, MAXPATHLEN);
488219089Spjd	(void) strlcat(fullpath, ZDIFF_SHARESDIR, MAXPATHLEN);
489219089Spjd
490219089Spjd	if (stat64(fullpath, &sb) != 0) {
491297077Smav#ifdef illumos
492219089Spjd		(void) snprintf(di->errbuf, sizeof (di->errbuf),
493219089Spjd		    dgettext(TEXT_DOMAIN, "Cannot stat %s"), fullpath);
494219089Spjd		return (zfs_error(di->zhp->zfs_hdl, EZFS_DIFF, di->errbuf));
495219089Spjd#else
496219089Spjd		return (0);
497219089Spjd#endif
498219089Spjd	}
499219089Spjd
500219089Spjd	di->shares = (uint64_t)sb.st_ino;
501219089Spjd	return (0);
502219089Spjd}
503219089Spjd
504219089Spjdstatic int
505219089Spjdmake_temp_snapshot(differ_info_t *di)
506219089Spjd{
507219089Spjd	libzfs_handle_t *hdl = di->zhp->zfs_hdl;
508219089Spjd	zfs_cmd_t zc = { 0 };
509219089Spjd
510219089Spjd	(void) snprintf(zc.zc_value, sizeof (zc.zc_value),
511219089Spjd	    ZDIFF_PREFIX, getpid());
512219089Spjd	(void) strlcpy(zc.zc_name, di->ds, sizeof (zc.zc_name));
513219089Spjd	zc.zc_cleanup_fd = di->cleanupfd;
514219089Spjd
515219089Spjd	if (ioctl(hdl->libzfs_fd, ZFS_IOC_TMP_SNAPSHOT, &zc) != 0) {
516219089Spjd		int err = errno;
517219089Spjd		if (err == EPERM) {
518219089Spjd			(void) snprintf(di->errbuf, sizeof (di->errbuf),
519219089Spjd			    dgettext(TEXT_DOMAIN, "The diff delegated "
520219089Spjd			    "permission is needed in order\nto create a "
521219089Spjd			    "just-in-time snapshot for diffing\n"));
522219089Spjd			return (zfs_error(hdl, EZFS_DIFF, di->errbuf));
523219089Spjd		} else {
524219089Spjd			(void) snprintf(di->errbuf, sizeof (di->errbuf),
525219089Spjd			    dgettext(TEXT_DOMAIN, "Cannot create just-in-time "
526219089Spjd			    "snapshot of '%s'"), zc.zc_name);
527219089Spjd			return (zfs_standard_error(hdl, err, di->errbuf));
528219089Spjd		}
529219089Spjd	}
530219089Spjd
531219089Spjd	di->tmpsnap = zfs_strdup(hdl, zc.zc_value);
532219089Spjd	di->tosnap = zfs_asprintf(hdl, "%s@%s", di->ds, di->tmpsnap);
533219089Spjd	return (0);
534219089Spjd}
535219089Spjd
536219089Spjdstatic void
537219089Spjdteardown_differ_info(differ_info_t *di)
538219089Spjd{
539219089Spjd	free(di->ds);
540219089Spjd	free(di->dsmnt);
541219089Spjd	free(di->fromsnap);
542219089Spjd	free(di->frommnt);
543219089Spjd	free(di->tosnap);
544219089Spjd	free(di->tmpsnap);
545219089Spjd	free(di->tomnt);
546219089Spjd	(void) close(di->cleanupfd);
547219089Spjd}
548219089Spjd
549219089Spjdstatic int
550219089Spjdget_snapshot_names(differ_info_t *di, const char *fromsnap,
551219089Spjd    const char *tosnap)
552219089Spjd{
553219089Spjd	libzfs_handle_t *hdl = di->zhp->zfs_hdl;
554219089Spjd	char *atptrf = NULL;
555219089Spjd	char *atptrt = NULL;
556219089Spjd	int fdslen, fsnlen;
557219089Spjd	int tdslen, tsnlen;
558219089Spjd
559219089Spjd	/*
560219089Spjd	 * Can accept
561219089Spjd	 *    dataset@snap1
562219089Spjd	 *    dataset@snap1 dataset@snap2
563219089Spjd	 *    dataset@snap1 @snap2
564219089Spjd	 *    dataset@snap1 dataset
565219089Spjd	 *    @snap1 dataset@snap2
566219089Spjd	 */
567219089Spjd	if (tosnap == NULL) {
568219089Spjd		/* only a from snapshot given, must be valid */
569219089Spjd		(void) snprintf(di->errbuf, sizeof (di->errbuf),
570219089Spjd		    dgettext(TEXT_DOMAIN,
571219089Spjd		    "Badly formed snapshot name %s"), fromsnap);
572219089Spjd
573219089Spjd		if (!zfs_validate_name(hdl, fromsnap, ZFS_TYPE_SNAPSHOT,
574219089Spjd		    B_FALSE)) {
575219089Spjd			return (zfs_error(hdl, EZFS_INVALIDNAME,
576219089Spjd			    di->errbuf));
577219089Spjd		}
578219089Spjd
579219089Spjd		atptrf = strchr(fromsnap, '@');
580219089Spjd		ASSERT(atptrf != NULL);
581219089Spjd		fdslen = atptrf - fromsnap;
582219089Spjd
583219089Spjd		di->fromsnap = zfs_strdup(hdl, fromsnap);
584219089Spjd		di->ds = zfs_strdup(hdl, fromsnap);
585219089Spjd		di->ds[fdslen] = '\0';
586219089Spjd
587219089Spjd		/* the to snap will be a just-in-time snap of the head */
588219089Spjd		return (make_temp_snapshot(di));
589219089Spjd	}
590219089Spjd
591219089Spjd	(void) snprintf(di->errbuf, sizeof (di->errbuf),
592219089Spjd	    dgettext(TEXT_DOMAIN,
593219089Spjd	    "Unable to determine which snapshots to compare"));
594219089Spjd
595219089Spjd	atptrf = strchr(fromsnap, '@');
596219089Spjd	atptrt = strchr(tosnap, '@');
597219089Spjd	fdslen = atptrf ? atptrf - fromsnap : strlen(fromsnap);
598219089Spjd	tdslen = atptrt ? atptrt - tosnap : strlen(tosnap);
599219089Spjd	fsnlen = strlen(fromsnap) - fdslen;	/* includes @ sign */
600219089Spjd	tsnlen = strlen(tosnap) - tdslen;	/* includes @ sign */
601219089Spjd
602219089Spjd	if (fsnlen <= 1 || tsnlen == 1 || (fdslen == 0 && tdslen == 0) ||
603219089Spjd	    (fsnlen == 0 && tsnlen == 0)) {
604219089Spjd		return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf));
605219089Spjd	} else if ((fdslen > 0 && tdslen > 0) &&
606219089Spjd	    ((tdslen != fdslen || strncmp(fromsnap, tosnap, fdslen) != 0))) {
607219089Spjd		/*
608219089Spjd		 * not the same dataset name, might be okay if
609219089Spjd		 * tosnap is a clone of a fromsnap descendant.
610219089Spjd		 */
611307122Smav		char origin[ZFS_MAX_DATASET_NAME_LEN];
612219089Spjd		zprop_source_t src;
613219089Spjd		zfs_handle_t *zhp;
614219089Spjd
615219089Spjd		di->ds = zfs_alloc(di->zhp->zfs_hdl, tdslen + 1);
616219089Spjd		(void) strncpy(di->ds, tosnap, tdslen);
617219089Spjd		di->ds[tdslen] = '\0';
618219089Spjd
619219089Spjd		zhp = zfs_open(hdl, di->ds, ZFS_TYPE_FILESYSTEM);
620219089Spjd		while (zhp != NULL) {
621290763Smav			if (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin,
622290763Smav			    sizeof (origin), &src, NULL, 0, B_FALSE) != 0) {
623290763Smav				(void) zfs_close(zhp);
624290763Smav				zhp = NULL;
625290763Smav				break;
626290763Smav			}
627219089Spjd			if (strncmp(origin, fromsnap, fsnlen) == 0)
628219089Spjd				break;
629219089Spjd
630219089Spjd			(void) zfs_close(zhp);
631219089Spjd			zhp = zfs_open(hdl, origin, ZFS_TYPE_FILESYSTEM);
632219089Spjd		}
633219089Spjd
634219089Spjd		if (zhp == NULL) {
635219089Spjd			(void) snprintf(di->errbuf, sizeof (di->errbuf),
636219089Spjd			    dgettext(TEXT_DOMAIN,
637219089Spjd			    "Not an earlier snapshot from the same fs"));
638219089Spjd			return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf));
639219089Spjd		} else {
640219089Spjd			(void) zfs_close(zhp);
641219089Spjd		}
642219089Spjd
643219089Spjd		di->isclone = B_TRUE;
644219089Spjd		di->fromsnap = zfs_strdup(hdl, fromsnap);
645219089Spjd		if (tsnlen) {
646219089Spjd			di->tosnap = zfs_strdup(hdl, tosnap);
647219089Spjd		} else {
648219089Spjd			return (make_temp_snapshot(di));
649219089Spjd		}
650219089Spjd	} else {
651219089Spjd		int dslen = fdslen ? fdslen : tdslen;
652219089Spjd
653219089Spjd		di->ds = zfs_alloc(hdl, dslen + 1);
654219089Spjd		(void) strncpy(di->ds, fdslen ? fromsnap : tosnap, dslen);
655219089Spjd		di->ds[dslen] = '\0';
656219089Spjd
657219089Spjd		di->fromsnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrf);
658219089Spjd		if (tsnlen) {
659219089Spjd			di->tosnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrt);
660219089Spjd		} else {
661219089Spjd			return (make_temp_snapshot(di));
662219089Spjd		}
663219089Spjd	}
664219089Spjd	return (0);
665219089Spjd}
666219089Spjd
667219089Spjdstatic int
668219089Spjdget_mountpoint(differ_info_t *di, char *dsnm, char **mntpt)
669219089Spjd{
670219089Spjd	boolean_t mounted;
671219089Spjd
672219089Spjd	mounted = is_mounted(di->zhp->zfs_hdl, dsnm, mntpt);
673219089Spjd	if (mounted == B_FALSE) {
674219089Spjd		(void) snprintf(di->errbuf, sizeof (di->errbuf),
675219089Spjd		    dgettext(TEXT_DOMAIN,
676219089Spjd		    "Cannot diff an unmounted snapshot"));
677219089Spjd		return (zfs_error(di->zhp->zfs_hdl, EZFS_BADTYPE, di->errbuf));
678219089Spjd	}
679219089Spjd
680219089Spjd	/* Avoid a double slash at the beginning of root-mounted datasets */
681219089Spjd	if (**mntpt == '/' && *(*mntpt + 1) == '\0')
682219089Spjd		**mntpt = '\0';
683219089Spjd	return (0);
684219089Spjd}
685219089Spjd
686219089Spjdstatic int
687219089Spjdget_mountpoints(differ_info_t *di)
688219089Spjd{
689219089Spjd	char *strptr;
690219089Spjd	char *frommntpt;
691219089Spjd
692219089Spjd	/*
693219089Spjd	 * first get the mountpoint for the parent dataset
694219089Spjd	 */
695219089Spjd	if (get_mountpoint(di, di->ds, &di->dsmnt) != 0)
696219089Spjd		return (-1);
697219089Spjd
698219089Spjd	strptr = strchr(di->tosnap, '@');
699219089Spjd	ASSERT3P(strptr, !=, NULL);
700219089Spjd	di->tomnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", di->dsmnt,
701219089Spjd	    ZDIFF_SNAPDIR, ++strptr);
702219089Spjd
703219089Spjd	strptr = strchr(di->fromsnap, '@');
704219089Spjd	ASSERT3P(strptr, !=, NULL);
705219089Spjd
706219089Spjd	frommntpt = di->dsmnt;
707219089Spjd	if (di->isclone) {
708219089Spjd		char *mntpt;
709219089Spjd		int err;
710219089Spjd
711219089Spjd		*strptr = '\0';
712219089Spjd		err = get_mountpoint(di, di->fromsnap, &mntpt);
713219089Spjd		*strptr = '@';
714219089Spjd		if (err != 0)
715219089Spjd			return (-1);
716219089Spjd		frommntpt = mntpt;
717219089Spjd	}
718219089Spjd
719219089Spjd	di->frommnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", frommntpt,
720219089Spjd	    ZDIFF_SNAPDIR, ++strptr);
721219089Spjd
722219089Spjd	if (di->isclone)
723219089Spjd		free(frommntpt);
724219089Spjd
725219089Spjd	return (0);
726219089Spjd}
727219089Spjd
728219089Spjdstatic int
729219089Spjdsetup_differ_info(zfs_handle_t *zhp, const char *fromsnap,
730219089Spjd    const char *tosnap, differ_info_t *di)
731219089Spjd{
732219089Spjd	di->zhp = zhp;
733219089Spjd
734219089Spjd	di->cleanupfd = open(ZFS_DEV, O_RDWR|O_EXCL);
735219089Spjd	VERIFY(di->cleanupfd >= 0);
736219089Spjd
737219089Spjd	if (get_snapshot_names(di, fromsnap, tosnap) != 0)
738219089Spjd		return (-1);
739219089Spjd
740219089Spjd	if (get_mountpoints(di) != 0)
741219089Spjd		return (-1);
742219089Spjd
743219089Spjd	if (find_shares_object(di) != 0)
744219089Spjd		return (-1);
745219089Spjd
746219089Spjd	return (0);
747219089Spjd}
748219089Spjd
749219089Spjdint
750219089Spjdzfs_show_diffs(zfs_handle_t *zhp, int outfd, const char *fromsnap,
751219089Spjd    const char *tosnap, int flags)
752219089Spjd{
753219089Spjd	zfs_cmd_t zc = { 0 };
754219089Spjd	char errbuf[1024];
755219089Spjd	differ_info_t di = { 0 };
756219089Spjd	pthread_t tid;
757219089Spjd	int pipefd[2];
758219089Spjd	int iocerr;
759219089Spjd
760219089Spjd	(void) snprintf(errbuf, sizeof (errbuf),
761219089Spjd	    dgettext(TEXT_DOMAIN, "zfs diff failed"));
762219089Spjd
763219089Spjd	if (setup_differ_info(zhp, fromsnap, tosnap, &di)) {
764219089Spjd		teardown_differ_info(&di);
765219089Spjd		return (-1);
766219089Spjd	}
767219089Spjd
768219089Spjd	if (pipe(pipefd)) {
769219089Spjd		zfs_error_aux(zhp->zfs_hdl, strerror(errno));
770219089Spjd		teardown_differ_info(&di);
771219089Spjd		return (zfs_error(zhp->zfs_hdl, EZFS_PIPEFAILED, errbuf));
772219089Spjd	}
773219089Spjd
774219089Spjd	di.scripted = (flags & ZFS_DIFF_PARSEABLE);
775219089Spjd	di.classify = (flags & ZFS_DIFF_CLASSIFY);
776219089Spjd	di.timestamped = (flags & ZFS_DIFF_TIMESTAMP);
777219089Spjd
778219089Spjd	di.outputfd = outfd;
779219089Spjd	di.datafd = pipefd[0];
780219089Spjd
781219089Spjd	if (pthread_create(&tid, NULL, differ, &di)) {
782219089Spjd		zfs_error_aux(zhp->zfs_hdl, strerror(errno));
783219089Spjd		(void) close(pipefd[0]);
784219089Spjd		(void) close(pipefd[1]);
785219089Spjd		teardown_differ_info(&di);
786219089Spjd		return (zfs_error(zhp->zfs_hdl,
787219089Spjd		    EZFS_THREADCREATEFAILED, errbuf));
788219089Spjd	}
789219089Spjd
790219089Spjd	/* do the ioctl() */
791219089Spjd	(void) strlcpy(zc.zc_value, di.fromsnap, strlen(di.fromsnap) + 1);
792219089Spjd	(void) strlcpy(zc.zc_name, di.tosnap, strlen(di.tosnap) + 1);
793219089Spjd	zc.zc_cookie = pipefd[1];
794219089Spjd
795219089Spjd	iocerr = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_DIFF, &zc);
796219089Spjd	if (iocerr != 0) {
797219089Spjd		(void) snprintf(errbuf, sizeof (errbuf),
798219089Spjd		    dgettext(TEXT_DOMAIN, "Unable to obtain diffs"));
799219089Spjd		if (errno == EPERM) {
800219089Spjd			zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
801219089Spjd			    "\n   The sys_mount privilege or diff delegated "
802219089Spjd			    "permission is needed\n   to execute the "
803219089Spjd			    "diff ioctl"));
804219089Spjd		} else if (errno == EXDEV) {
805219089Spjd			zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
806219089Spjd			    "\n   Not an earlier snapshot from the same fs"));
807219089Spjd		} else if (errno != EPIPE || di.zerr == 0) {
808219089Spjd			zfs_error_aux(zhp->zfs_hdl, strerror(errno));
809219089Spjd		}
810219089Spjd		(void) close(pipefd[1]);
811219089Spjd		(void) pthread_cancel(tid);
812219089Spjd		(void) pthread_join(tid, NULL);
813219089Spjd		teardown_differ_info(&di);
814219089Spjd		if (di.zerr != 0 && di.zerr != EPIPE) {
815219089Spjd			zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr));
816219089Spjd			return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf));
817219089Spjd		} else {
818219089Spjd			return (zfs_error(zhp->zfs_hdl, EZFS_DIFFDATA, errbuf));
819219089Spjd		}
820219089Spjd	}
821219089Spjd
822219089Spjd	(void) close(pipefd[1]);
823219089Spjd	(void) pthread_join(tid, NULL);
824219089Spjd
825219089Spjd	if (di.zerr != 0) {
826219089Spjd		zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr));
827219089Spjd		return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf));
828219089Spjd	}
829219089Spjd	teardown_differ_info(&di);
830219089Spjd	return (0);
831219089Spjd}
832