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