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 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 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 * 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 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 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 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 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 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 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 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 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 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 * 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 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 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 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 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 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 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 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