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