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