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