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