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