| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. |
| * Copyright 2015 Nexenta Systems, Inc. All rights reserved. |
| * Copyright (c) 2015, 2018 by Delphix. All rights reserved. |
| * Copyright 2016 Joyent, Inc. |
| * Copyright 2016 Igor Kozhukhov <ikozhukhov@gmail.com> |
| */ |
| |
| /* |
| * zfs diff support |
| */ |
| #include <ctype.h> |
| #include <errno.h> |
| #include <libintl.h> |
| #include <string.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <stddef.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stropts.h> |
| #include <pthread.h> |
| #include <sys/zfs_ioctl.h> |
| #include <libzfs.h> |
| #include <libzutil.h> |
| #include "libzfs_impl.h" |
| |
| #define ZDIFF_SNAPDIR "/.zfs/snapshot/" |
| #define ZDIFF_PREFIX "zfs-diff-%d" |
| |
| #define ZDIFF_ADDED '+' |
| #define ZDIFF_MODIFIED "M" |
| #define ZDIFF_REMOVED '-' |
| #define ZDIFF_RENAMED "R" |
| |
| #define ZDIFF_ADDED_COLOR ANSI_GREEN |
| #define ZDIFF_MODIFIED_COLOR ANSI_YELLOW |
| #define ZDIFF_REMOVED_COLOR ANSI_RED |
| #define ZDIFF_RENAMED_COLOR ANSI_BOLD_BLUE |
| |
| /* |
| * Given a {dsname, object id}, get the object path |
| */ |
| static int |
| get_stats_for_obj(differ_info_t *di, const char *dsname, uint64_t obj, |
| char *pn, int maxlen, zfs_stat_t *sb) |
| { |
| zfs_cmd_t zc = {"\0"}; |
| int error; |
| |
| (void) strlcpy(zc.zc_name, dsname, sizeof (zc.zc_name)); |
| zc.zc_obj = obj; |
| |
| errno = 0; |
| error = zfs_ioctl(di->zhp->zfs_hdl, ZFS_IOC_OBJ_TO_STATS, &zc); |
| di->zerr = errno; |
| |
| /* we can get stats even if we failed to get a path */ |
| (void) memcpy(sb, &zc.zc_stat, sizeof (zfs_stat_t)); |
| if (error == 0) { |
| ASSERT(di->zerr == 0); |
| (void) strlcpy(pn, zc.zc_value, maxlen); |
| return (0); |
| } |
| |
| if (di->zerr == ESTALE) { |
| (void) snprintf(pn, maxlen, "(on_delete_queue)"); |
| return (0); |
| } else if (di->zerr == EPERM) { |
| (void) snprintf(di->errbuf, sizeof (di->errbuf), |
| dgettext(TEXT_DOMAIN, |
| "The sys_config privilege or diff delegated permission " |
| "is needed\nto discover path names")); |
| return (-1); |
| } else if (di->zerr == EACCES) { |
| (void) snprintf(di->errbuf, sizeof (di->errbuf), |
| dgettext(TEXT_DOMAIN, |
| "Key must be loaded to discover path names")); |
| return (-1); |
| } else { |
| (void) snprintf(di->errbuf, sizeof (di->errbuf), |
| dgettext(TEXT_DOMAIN, |
| "Unable to determine path or stats for " |
| "object %lld in %s"), (longlong_t)obj, dsname); |
| return (-1); |
| } |
| } |
| |
| /* |
| * stream_bytes |
| * |
| * Prints a file name out a character at a time. If the character is |
| * not in the range of what we consider "printable" ASCII, display it |
| * as an escaped 4-digit octal value. ASCII values less than a space |
| * are all control characters and we declare the upper end as the |
| * DELete character. This also is the last 7-bit ASCII character. |
| * We choose to treat all 8-bit ASCII as not printable for this |
| * application. |
| */ |
| static void |
| stream_bytes(FILE *fp, const char *string) |
| { |
| char c; |
| |
| while ((c = *string++) != '\0') { |
| if (c > ' ' && c != '\\' && c < '\177') { |
| (void) fputc(c, fp); |
| } else { |
| (void) fprintf(fp, "\\%04hho", (uint8_t)c); |
| } |
| } |
| } |
| |
| /* |
| * Takes the type of change (like `print_file`), outputs the appropriate color |
| */ |
| static const char * |
| type_to_color(char type) |
| { |
| if (type == '+') |
| return (ZDIFF_ADDED_COLOR); |
| else if (type == '-') |
| return (ZDIFF_REMOVED_COLOR); |
| else if (type == 'M') |
| return (ZDIFF_MODIFIED_COLOR); |
| else if (type == 'R') |
| return (ZDIFF_RENAMED_COLOR); |
| else |
| return (NULL); |
| } |
| |
| |
| static char |
| get_what(mode_t what) |
| { |
| switch (what & S_IFMT) { |
| case S_IFBLK: |
| return ('B'); |
| case S_IFCHR: |
| return ('C'); |
| case S_IFDIR: |
| return ('/'); |
| #ifdef S_IFDOOR |
| case S_IFDOOR: |
| return ('>'); |
| #endif |
| case S_IFIFO: |
| return ('|'); |
| case S_IFLNK: |
| return ('@'); |
| #ifdef S_IFPORT |
| case S_IFPORT: |
| return ('P'); |
| #endif |
| case S_IFSOCK: |
| return ('='); |
| case S_IFREG: |
| return ('F'); |
| default: |
| return ('?'); |
| } |
| } |
| |
| static void |
| print_cmn(FILE *fp, differ_info_t *di, const char *file) |
| { |
| if (!di->no_mangle) { |
| stream_bytes(fp, di->dsmnt); |
| stream_bytes(fp, file); |
| } else { |
| (void) fputs(di->dsmnt, fp); |
| (void) fputs(file, fp); |
| } |
| } |
| |
| static void |
| print_rename(FILE *fp, differ_info_t *di, const char *old, const char *new, |
| zfs_stat_t *isb) |
| { |
| if (isatty(fileno(fp))) |
| color_start(ZDIFF_RENAMED_COLOR); |
| if (di->timestamped) |
| (void) fprintf(fp, "%10lld.%09lld\t", |
| (longlong_t)isb->zs_ctime[0], |
| (longlong_t)isb->zs_ctime[1]); |
| (void) fputs(ZDIFF_RENAMED "\t", fp); |
| if (di->classify) |
| (void) fprintf(fp, "%c\t", get_what(isb->zs_mode)); |
| print_cmn(fp, di, old); |
| (void) fputs(di->scripted ? "\t" : " -> ", fp); |
| print_cmn(fp, di, new); |
| (void) fputc('\n', fp); |
| |
| if (isatty(fileno(fp))) |
| color_end(); |
| } |
| |
| static void |
| print_link_change(FILE *fp, differ_info_t *di, int delta, const char *file, |
| zfs_stat_t *isb) |
| { |
| if (isatty(fileno(fp))) |
| color_start(ZDIFF_MODIFIED_COLOR); |
| |
| if (di->timestamped) |
| (void) fprintf(fp, "%10lld.%09lld\t", |
| (longlong_t)isb->zs_ctime[0], |
| (longlong_t)isb->zs_ctime[1]); |
| (void) fputs(ZDIFF_MODIFIED "\t", fp); |
| if (di->classify) |
| (void) fprintf(fp, "%c\t", get_what(isb->zs_mode)); |
| print_cmn(fp, di, file); |
| (void) fprintf(fp, "\t(%+d)\n", delta); |
| if (isatty(fileno(fp))) |
| color_end(); |
| } |
| |
| static void |
| print_file(FILE *fp, differ_info_t *di, char type, const char *file, |
| zfs_stat_t *isb) |
| { |
| if (isatty(fileno(fp))) |
| color_start(type_to_color(type)); |
| |
| if (di->timestamped) |
| (void) fprintf(fp, "%10lld.%09lld\t", |
| (longlong_t)isb->zs_ctime[0], |
| (longlong_t)isb->zs_ctime[1]); |
| (void) fprintf(fp, "%c\t", type); |
| if (di->classify) |
| (void) fprintf(fp, "%c\t", get_what(isb->zs_mode)); |
| print_cmn(fp, di, file); |
| (void) fputc('\n', fp); |
| |
| if (isatty(fileno(fp))) |
| color_end(); |
| } |
| |
| static int |
| write_inuse_diffs_one(FILE *fp, differ_info_t *di, uint64_t dobj) |
| { |
| struct zfs_stat fsb, tsb; |
| mode_t fmode, tmode; |
| char fobjname[MAXPATHLEN], tobjname[MAXPATHLEN]; |
| boolean_t already_logged = B_FALSE; |
| int fobjerr, tobjerr; |
| int change; |
| |
| if (dobj == di->shares) |
| return (0); |
| |
| /* |
| * Check the from and to snapshots for info on the object. If |
| * we get ENOENT, then the object just didn't exist in that |
| * snapshot. If we get ENOTSUP, then we tried to get |
| * info on a non-ZPL object, which we don't care about anyway. |
| * For any other error we print a warning which includes the |
| * errno and continue. |
| */ |
| |
| fobjerr = get_stats_for_obj(di, di->fromsnap, dobj, fobjname, |
| MAXPATHLEN, &fsb); |
| if (fobjerr && di->zerr != ENOTSUP && di->zerr != ENOENT) { |
| zfs_error_aux(di->zhp->zfs_hdl, "%s", strerror(di->zerr)); |
| zfs_error(di->zhp->zfs_hdl, di->zerr, di->errbuf); |
| /* |
| * Let's not print an error for the same object more than |
| * once if it happens in both snapshots |
| */ |
| already_logged = B_TRUE; |
| } |
| |
| tobjerr = get_stats_for_obj(di, di->tosnap, dobj, tobjname, |
| MAXPATHLEN, &tsb); |
| |
| if (tobjerr && di->zerr != ENOTSUP && di->zerr != ENOENT) { |
| if (!already_logged) { |
| zfs_error_aux(di->zhp->zfs_hdl, |
| "%s", strerror(di->zerr)); |
| zfs_error(di->zhp->zfs_hdl, di->zerr, di->errbuf); |
| } |
| } |
| /* |
| * Unallocated object sharing the same meta dnode block |
| */ |
| if (fobjerr && tobjerr) { |
| di->zerr = 0; |
| return (0); |
| } |
| |
| di->zerr = 0; /* negate get_stats_for_obj() from side that failed */ |
| fmode = fsb.zs_mode & S_IFMT; |
| tmode = tsb.zs_mode & S_IFMT; |
| if (fmode == S_IFDIR || tmode == S_IFDIR || fsb.zs_links == 0 || |
| tsb.zs_links == 0) |
| change = 0; |
| else |
| change = tsb.zs_links - fsb.zs_links; |
| |
| if (fobjerr) { |
| if (change) { |
| print_link_change(fp, di, change, tobjname, &tsb); |
| return (0); |
| } |
| print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb); |
| return (0); |
| } else if (tobjerr) { |
| if (change) { |
| print_link_change(fp, di, change, fobjname, &fsb); |
| return (0); |
| } |
| print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb); |
| return (0); |
| } |
| |
| if (fmode != tmode && fsb.zs_gen == tsb.zs_gen) |
| tsb.zs_gen++; /* Force a generational difference */ |
| |
| /* Simple modification or no change */ |
| if (fsb.zs_gen == tsb.zs_gen) { |
| /* No apparent changes. Could we assert !this? */ |
| if (fsb.zs_ctime[0] == tsb.zs_ctime[0] && |
| fsb.zs_ctime[1] == tsb.zs_ctime[1]) |
| return (0); |
| if (change) { |
| print_link_change(fp, di, change, |
| change > 0 ? fobjname : tobjname, &tsb); |
| } else if (strcmp(fobjname, tobjname) == 0) { |
| print_file(fp, di, *ZDIFF_MODIFIED, fobjname, &tsb); |
| } else { |
| print_rename(fp, di, fobjname, tobjname, &tsb); |
| } |
| return (0); |
| } else { |
| /* file re-created or object re-used */ |
| print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb); |
| print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb); |
| return (0); |
| } |
| } |
| |
| static int |
| write_inuse_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr) |
| { |
| uint64_t o; |
| int err; |
| |
| for (o = dr->ddr_first; o <= dr->ddr_last; o++) { |
| if ((err = write_inuse_diffs_one(fp, di, o)) != 0) |
| return (err); |
| } |
| return (0); |
| } |
| |
| static int |
| describe_free(FILE *fp, differ_info_t *di, uint64_t object, char *namebuf, |
| int maxlen) |
| { |
| struct zfs_stat sb; |
| |
| (void) get_stats_for_obj(di, di->fromsnap, object, namebuf, |
| maxlen, &sb); |
| |
| /* Don't print if in the delete queue on from side */ |
| if (di->zerr == ESTALE || di->zerr == ENOENT) { |
| di->zerr = 0; |
| return (0); |
| } |
| |
| print_file(fp, di, ZDIFF_REMOVED, namebuf, &sb); |
| return (0); |
| } |
| |
| static int |
| write_free_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr) |
| { |
| zfs_cmd_t zc = {"\0"}; |
| libzfs_handle_t *lhdl = di->zhp->zfs_hdl; |
| char fobjname[MAXPATHLEN]; |
| |
| (void) strlcpy(zc.zc_name, di->fromsnap, sizeof (zc.zc_name)); |
| zc.zc_obj = dr->ddr_first - 1; |
| |
| ASSERT(di->zerr == 0); |
| |
| while (zc.zc_obj < dr->ddr_last) { |
| int err; |
| |
| err = zfs_ioctl(lhdl, ZFS_IOC_NEXT_OBJ, &zc); |
| if (err == 0) { |
| if (zc.zc_obj == di->shares) { |
| zc.zc_obj++; |
| continue; |
| } |
| if (zc.zc_obj > dr->ddr_last) { |
| break; |
| } |
| err = describe_free(fp, di, zc.zc_obj, fobjname, |
| MAXPATHLEN); |
| } else if (errno == ESRCH) { |
| break; |
| } else { |
| (void) snprintf(di->errbuf, sizeof (di->errbuf), |
| dgettext(TEXT_DOMAIN, |
| "next allocated object (> %lld) find failure"), |
| (longlong_t)zc.zc_obj); |
| di->zerr = errno; |
| break; |
| } |
| } |
| if (di->zerr) |
| return (-1); |
| return (0); |
| } |
| |
| static void * |
| differ(void *arg) |
| { |
| differ_info_t *di = arg; |
| dmu_diff_record_t dr; |
| FILE *ofp; |
| int err = 0; |
| |
| if ((ofp = fdopen(di->outputfd, "w")) == NULL) { |
| di->zerr = errno; |
| strlcpy(di->errbuf, strerror(errno), sizeof (di->errbuf)); |
| (void) close(di->datafd); |
| return ((void *)-1); |
| } |
| |
| for (;;) { |
| char *cp = (char *)&dr; |
| int len = sizeof (dr); |
| int rv; |
| |
| do { |
| rv = read(di->datafd, cp, len); |
| cp += rv; |
| len -= rv; |
| } while (len > 0 && rv > 0); |
| |
| if (rv < 0 || (rv == 0 && len != sizeof (dr))) { |
| di->zerr = EPIPE; |
| break; |
| } else if (rv == 0) { |
| /* end of file at a natural breaking point */ |
| break; |
| } |
| |
| switch (dr.ddr_type) { |
| case DDR_FREE: |
| err = write_free_diffs(ofp, di, &dr); |
| break; |
| case DDR_INUSE: |
| err = write_inuse_diffs(ofp, di, &dr); |
| break; |
| default: |
| di->zerr = EPIPE; |
| break; |
| } |
| |
| if (err || di->zerr) |
| break; |
| } |
| |
| (void) fclose(ofp); |
| (void) close(di->datafd); |
| if (err) |
| return ((void *)-1); |
| if (di->zerr) { |
| ASSERT(di->zerr == EPIPE); |
| (void) snprintf(di->errbuf, sizeof (di->errbuf), |
| dgettext(TEXT_DOMAIN, |
| "Internal error: bad data from diff IOCTL")); |
| return ((void *)-1); |
| } |
| return ((void *)0); |
| } |
| |
| static int |
| make_temp_snapshot(differ_info_t *di) |
| { |
| libzfs_handle_t *hdl = di->zhp->zfs_hdl; |
| zfs_cmd_t zc = {"\0"}; |
| |
| (void) snprintf(zc.zc_value, sizeof (zc.zc_value), |
| ZDIFF_PREFIX, getpid()); |
| (void) strlcpy(zc.zc_name, di->ds, sizeof (zc.zc_name)); |
| zc.zc_cleanup_fd = di->cleanupfd; |
| |
| if (zfs_ioctl(hdl, ZFS_IOC_TMP_SNAPSHOT, &zc) != 0) { |
| int err = errno; |
| if (err == EPERM) { |
| (void) snprintf(di->errbuf, sizeof (di->errbuf), |
| dgettext(TEXT_DOMAIN, "The diff delegated " |
| "permission is needed in order\nto create a " |
| "just-in-time snapshot for diffing\n")); |
| return (zfs_error(hdl, EZFS_DIFF, di->errbuf)); |
| } else { |
| (void) snprintf(di->errbuf, sizeof (di->errbuf), |
| dgettext(TEXT_DOMAIN, "Cannot create just-in-time " |
| "snapshot of '%s'"), zc.zc_name); |
| return (zfs_standard_error(hdl, err, di->errbuf)); |
| } |
| } |
| |
| di->tmpsnap = zfs_strdup(hdl, zc.zc_value); |
| di->tosnap = zfs_asprintf(hdl, "%s@%s", di->ds, di->tmpsnap); |
| return (0); |
| } |
| |
| static void |
| teardown_differ_info(differ_info_t *di) |
| { |
| free(di->ds); |
| free(di->dsmnt); |
| free(di->fromsnap); |
| free(di->frommnt); |
| free(di->tosnap); |
| free(di->tmpsnap); |
| free(di->tomnt); |
| (void) close(di->cleanupfd); |
| } |
| |
| static int |
| get_snapshot_names(differ_info_t *di, const char *fromsnap, |
| const char *tosnap) |
| { |
| libzfs_handle_t *hdl = di->zhp->zfs_hdl; |
| char *atptrf = NULL; |
| char *atptrt = NULL; |
| int fdslen, fsnlen; |
| int tdslen, tsnlen; |
| |
| /* |
| * Can accept |
| * fdslen fsnlen tdslen tsnlen |
| * dataset@snap1 |
| * 0. dataset@snap1 dataset@snap2 >0 >1 >0 >1 |
| * 1. dataset@snap1 @snap2 >0 >1 ==0 >1 |
| * 2. dataset@snap1 dataset >0 >1 >0 ==0 |
| * 3. @snap1 dataset@snap2 ==0 >1 >0 >1 |
| * 4. @snap1 dataset ==0 >1 >0 ==0 |
| */ |
| if (tosnap == NULL) { |
| /* only a from snapshot given, must be valid */ |
| (void) snprintf(di->errbuf, sizeof (di->errbuf), |
| dgettext(TEXT_DOMAIN, |
| "Badly formed snapshot name %s"), fromsnap); |
| |
| if (!zfs_validate_name(hdl, fromsnap, ZFS_TYPE_SNAPSHOT, |
| B_FALSE)) { |
| return (zfs_error(hdl, EZFS_INVALIDNAME, |
| di->errbuf)); |
| } |
| |
| atptrf = strchr(fromsnap, '@'); |
| ASSERT(atptrf != NULL); |
| fdslen = atptrf - fromsnap; |
| |
| di->fromsnap = zfs_strdup(hdl, fromsnap); |
| di->ds = zfs_strdup(hdl, fromsnap); |
| di->ds[fdslen] = '\0'; |
| |
| /* the to snap will be a just-in-time snap of the head */ |
| return (make_temp_snapshot(di)); |
| } |
| |
| (void) snprintf(di->errbuf, sizeof (di->errbuf), |
| dgettext(TEXT_DOMAIN, |
| "Unable to determine which snapshots to compare")); |
| |
| atptrf = strchr(fromsnap, '@'); |
| atptrt = strchr(tosnap, '@'); |
| fdslen = atptrf ? atptrf - fromsnap : strlen(fromsnap); |
| tdslen = atptrt ? atptrt - tosnap : strlen(tosnap); |
| fsnlen = strlen(fromsnap) - fdslen; /* includes @ sign */ |
| tsnlen = strlen(tosnap) - tdslen; /* includes @ sign */ |
| |
| if (fsnlen <= 1 || tsnlen == 1 || (fdslen == 0 && tdslen == 0)) { |
| return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf)); |
| } else if ((fdslen > 0 && tdslen > 0) && |
| ((tdslen != fdslen || strncmp(fromsnap, tosnap, fdslen) != 0))) { |
| /* |
| * not the same dataset name, might be okay if |
| * tosnap is a clone of a fromsnap descendant. |
| */ |
| char origin[ZFS_MAX_DATASET_NAME_LEN]; |
| zprop_source_t src; |
| zfs_handle_t *zhp; |
| |
| di->ds = zfs_alloc(di->zhp->zfs_hdl, tdslen + 1); |
| (void) strncpy(di->ds, tosnap, tdslen); |
| di->ds[tdslen] = '\0'; |
| |
| zhp = zfs_open(hdl, di->ds, ZFS_TYPE_FILESYSTEM); |
| while (zhp != NULL) { |
| if (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin, |
| sizeof (origin), &src, NULL, 0, B_FALSE) != 0) { |
| (void) zfs_close(zhp); |
| zhp = NULL; |
| break; |
| } |
| if (strncmp(origin, fromsnap, fsnlen) == 0) |
| break; |
| |
| (void) zfs_close(zhp); |
| zhp = zfs_open(hdl, origin, ZFS_TYPE_FILESYSTEM); |
| } |
| |
| if (zhp == NULL) { |
| (void) snprintf(di->errbuf, sizeof (di->errbuf), |
| dgettext(TEXT_DOMAIN, |
| "Not an earlier snapshot from the same fs")); |
| return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf)); |
| } else { |
| (void) zfs_close(zhp); |
| } |
| |
| di->isclone = B_TRUE; |
| di->fromsnap = zfs_strdup(hdl, fromsnap); |
| if (tsnlen) { |
| di->tosnap = zfs_strdup(hdl, tosnap); |
| } else { |
| return (make_temp_snapshot(di)); |
| } |
| } else { |
| int dslen = fdslen ? fdslen : tdslen; |
| |
| di->ds = zfs_alloc(hdl, dslen + 1); |
| (void) strncpy(di->ds, fdslen ? fromsnap : tosnap, dslen); |
| di->ds[dslen] = '\0'; |
| |
| di->fromsnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrf); |
| if (tsnlen) { |
| di->tosnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrt); |
| } else { |
| return (make_temp_snapshot(di)); |
| } |
| } |
| return (0); |
| } |
| |
| static int |
| get_mountpoint(differ_info_t *di, char *dsnm, char **mntpt) |
| { |
| boolean_t mounted; |
| |
| mounted = is_mounted(di->zhp->zfs_hdl, dsnm, mntpt); |
| if (mounted == B_FALSE) { |
| (void) snprintf(di->errbuf, sizeof (di->errbuf), |
| dgettext(TEXT_DOMAIN, |
| "Cannot diff an unmounted snapshot")); |
| return (zfs_error(di->zhp->zfs_hdl, EZFS_BADTYPE, di->errbuf)); |
| } |
| |
| /* Avoid a double slash at the beginning of root-mounted datasets */ |
| if (**mntpt == '/' && *(*mntpt + 1) == '\0') |
| **mntpt = '\0'; |
| return (0); |
| } |
| |
| static int |
| get_mountpoints(differ_info_t *di) |
| { |
| char *strptr; |
| char *frommntpt; |
| |
| /* |
| * first get the mountpoint for the parent dataset |
| */ |
| if (get_mountpoint(di, di->ds, &di->dsmnt) != 0) |
| return (-1); |
| |
| strptr = strchr(di->tosnap, '@'); |
| ASSERT3P(strptr, !=, NULL); |
| di->tomnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", di->dsmnt, |
| ZDIFF_SNAPDIR, ++strptr); |
| |
| strptr = strchr(di->fromsnap, '@'); |
| ASSERT3P(strptr, !=, NULL); |
| |
| frommntpt = di->dsmnt; |
| if (di->isclone) { |
| char *mntpt; |
| int err; |
| |
| *strptr = '\0'; |
| err = get_mountpoint(di, di->fromsnap, &mntpt); |
| *strptr = '@'; |
| if (err != 0) |
| return (-1); |
| frommntpt = mntpt; |
| } |
| |
| di->frommnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", frommntpt, |
| ZDIFF_SNAPDIR, ++strptr); |
| |
| if (di->isclone) |
| free(frommntpt); |
| |
| return (0); |
| } |
| |
| static int |
| setup_differ_info(zfs_handle_t *zhp, const char *fromsnap, |
| const char *tosnap, differ_info_t *di) |
| { |
| di->zhp = zhp; |
| |
| di->cleanupfd = open(ZFS_DEV, O_RDWR | O_CLOEXEC); |
| VERIFY(di->cleanupfd >= 0); |
| |
| if (get_snapshot_names(di, fromsnap, tosnap) != 0) |
| return (-1); |
| |
| if (get_mountpoints(di) != 0) |
| return (-1); |
| |
| if (find_shares_object(di) != 0) |
| return (-1); |
| |
| return (0); |
| } |
| |
| int |
| zfs_show_diffs(zfs_handle_t *zhp, int outfd, const char *fromsnap, |
| const char *tosnap, int flags) |
| { |
| zfs_cmd_t zc = {"\0"}; |
| char errbuf[1024]; |
| differ_info_t di = { 0 }; |
| pthread_t tid; |
| int pipefd[2]; |
| int iocerr; |
| |
| (void) snprintf(errbuf, sizeof (errbuf), |
| dgettext(TEXT_DOMAIN, "zfs diff failed")); |
| |
| if (setup_differ_info(zhp, fromsnap, tosnap, &di)) { |
| teardown_differ_info(&di); |
| return (-1); |
| } |
| |
| if (pipe2(pipefd, O_CLOEXEC)) { |
| zfs_error_aux(zhp->zfs_hdl, "%s", strerror(errno)); |
| teardown_differ_info(&di); |
| return (zfs_error(zhp->zfs_hdl, EZFS_PIPEFAILED, errbuf)); |
| } |
| |
| di.scripted = (flags & ZFS_DIFF_PARSEABLE); |
| di.classify = (flags & ZFS_DIFF_CLASSIFY); |
| di.timestamped = (flags & ZFS_DIFF_TIMESTAMP); |
| di.no_mangle = (flags & ZFS_DIFF_NO_MANGLE); |
| |
| di.outputfd = outfd; |
| di.datafd = pipefd[0]; |
| |
| if (pthread_create(&tid, NULL, differ, &di)) { |
| zfs_error_aux(zhp->zfs_hdl, "%s", strerror(errno)); |
| (void) close(pipefd[0]); |
| (void) close(pipefd[1]); |
| teardown_differ_info(&di); |
| return (zfs_error(zhp->zfs_hdl, |
| EZFS_THREADCREATEFAILED, errbuf)); |
| } |
| |
| /* do the ioctl() */ |
| (void) strlcpy(zc.zc_value, di.fromsnap, strlen(di.fromsnap) + 1); |
| (void) strlcpy(zc.zc_name, di.tosnap, strlen(di.tosnap) + 1); |
| zc.zc_cookie = pipefd[1]; |
| |
| iocerr = zfs_ioctl(zhp->zfs_hdl, ZFS_IOC_DIFF, &zc); |
| if (iocerr != 0) { |
| (void) snprintf(errbuf, sizeof (errbuf), |
| dgettext(TEXT_DOMAIN, "Unable to obtain diffs")); |
| if (errno == EPERM) { |
| zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, |
| "\n The sys_mount privilege or diff delegated " |
| "permission is needed\n to execute the " |
| "diff ioctl")); |
| } else if (errno == EXDEV) { |
| zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, |
| "\n Not an earlier snapshot from the same fs")); |
| } else if (errno != EPIPE || di.zerr == 0) { |
| zfs_error_aux(zhp->zfs_hdl, "%s", strerror(errno)); |
| } |
| (void) close(pipefd[1]); |
| (void) pthread_cancel(tid); |
| (void) pthread_join(tid, NULL); |
| teardown_differ_info(&di); |
| if (di.zerr != 0 && di.zerr != EPIPE) { |
| zfs_error_aux(zhp->zfs_hdl, "%s", strerror(di.zerr)); |
| return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf)); |
| } else { |
| return (zfs_error(zhp->zfs_hdl, EZFS_DIFFDATA, errbuf)); |
| } |
| } |
| |
| (void) close(pipefd[1]); |
| (void) pthread_join(tid, NULL); |
| |
| if (di.zerr != 0) { |
| zfs_error_aux(zhp->zfs_hdl, "%s", strerror(di.zerr)); |
| return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf)); |
| } |
| teardown_differ_info(&di); |
| return (0); |
| } |