199d5e173STim Haley /* 299d5e173STim Haley * CDDL HEADER START 399d5e173STim Haley * 499d5e173STim Haley * The contents of this file are subject to the terms of the 599d5e173STim Haley * Common Development and Distribution License (the "License"). 699d5e173STim Haley * You may not use this file except in compliance with the License. 799d5e173STim Haley * 899d5e173STim Haley * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 999d5e173STim Haley * or http://www.opensolaris.org/os/licensing. 1099d5e173STim Haley * See the License for the specific language governing permissions 1199d5e173STim Haley * and limitations under the License. 1299d5e173STim Haley * 1399d5e173STim Haley * When distributing Covered Code, include this CDDL HEADER in each 1499d5e173STim Haley * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 1599d5e173STim Haley * If applicable, add the following below this CDDL HEADER, with the 1699d5e173STim Haley * fields enclosed by brackets "[]" replaced with your own identifying 1799d5e173STim Haley * information: Portions Copyright [yyyy] [name of copyright owner] 1899d5e173STim Haley * 1999d5e173STim Haley * CDDL HEADER END 2099d5e173STim Haley */ 2199d5e173STim Haley 2299d5e173STim Haley /* 2399d5e173STim Haley * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. 2484302789SAlexander Eremin * Copyright 2015 Nexenta Systems, Inc. All rights reserved. 25*40a5c998SMatthew Ahrens * Copyright (c) 2015 by Delphix. All rights reserved. 26b211eb91SJoshua M. Clulow * Copyright 2016 Joyent, Inc. 2799d5e173STim Haley */ 2899d5e173STim Haley 2999d5e173STim Haley /* 3099d5e173STim Haley * zfs diff support 3199d5e173STim Haley */ 3299d5e173STim Haley #include <ctype.h> 3399d5e173STim Haley #include <errno.h> 3499d5e173STim Haley #include <libintl.h> 3599d5e173STim Haley #include <string.h> 3699d5e173STim Haley #include <sys/types.h> 3799d5e173STim Haley #include <sys/stat.h> 3899d5e173STim Haley #include <fcntl.h> 3999d5e173STim Haley #include <attr.h> 4099d5e173STim Haley #include <stddef.h> 4199d5e173STim Haley #include <unistd.h> 4299d5e173STim Haley #include <stdio.h> 4399d5e173STim Haley #include <stdlib.h> 4499d5e173STim Haley #include <stropts.h> 4599d5e173STim Haley #include <pthread.h> 4699d5e173STim Haley #include <sys/zfs_ioctl.h> 4799d5e173STim Haley #include <libzfs.h> 4899d5e173STim Haley #include "libzfs_impl.h" 4999d5e173STim Haley 5099d5e173STim Haley #define ZDIFF_SNAPDIR "/.zfs/snapshot/" 5199d5e173STim Haley #define ZDIFF_SHARESDIR "/.zfs/shares/" 5299d5e173STim Haley #define ZDIFF_PREFIX "zfs-diff-%d" 5399d5e173STim Haley 5499d5e173STim Haley #define ZDIFF_ADDED '+' 5599d5e173STim Haley #define ZDIFF_MODIFIED 'M' 5699d5e173STim Haley #define ZDIFF_REMOVED '-' 5799d5e173STim Haley #define ZDIFF_RENAMED 'R' 5899d5e173STim Haley 5999d5e173STim Haley typedef struct differ_info { 6099d5e173STim Haley zfs_handle_t *zhp; 6199d5e173STim Haley char *fromsnap; 6299d5e173STim Haley char *frommnt; 6399d5e173STim Haley char *tosnap; 6499d5e173STim Haley char *tomnt; 6599d5e173STim Haley char *ds; 6699d5e173STim Haley char *dsmnt; 6799d5e173STim Haley char *tmpsnap; 6899d5e173STim Haley char errbuf[1024]; 6999d5e173STim Haley boolean_t isclone; 7099d5e173STim Haley boolean_t scripted; 7199d5e173STim Haley boolean_t classify; 7299d5e173STim Haley boolean_t timestamped; 7399d5e173STim Haley uint64_t shares; 7499d5e173STim Haley int zerr; 7599d5e173STim Haley int cleanupfd; 7699d5e173STim Haley int outputfd; 7799d5e173STim Haley int datafd; 7899d5e173STim Haley } differ_info_t; 7999d5e173STim Haley 8099d5e173STim Haley /* 8199d5e173STim Haley * Given a {dsname, object id}, get the object path 8299d5e173STim Haley */ 8399d5e173STim Haley static int 8499d5e173STim Haley get_stats_for_obj(differ_info_t *di, const char *dsname, uint64_t obj, 8599d5e173STim Haley char *pn, int maxlen, zfs_stat_t *sb) 8699d5e173STim Haley { 8799d5e173STim Haley zfs_cmd_t zc = { 0 }; 8899d5e173STim Haley int error; 8999d5e173STim Haley 9099d5e173STim Haley (void) strlcpy(zc.zc_name, dsname, sizeof (zc.zc_name)); 9199d5e173STim Haley zc.zc_obj = obj; 9299d5e173STim Haley 9399d5e173STim Haley errno = 0; 9499d5e173STim Haley error = ioctl(di->zhp->zfs_hdl->libzfs_fd, ZFS_IOC_OBJ_TO_STATS, &zc); 9599d5e173STim Haley di->zerr = errno; 9699d5e173STim Haley 9799d5e173STim Haley /* we can get stats even if we failed to get a path */ 9899d5e173STim Haley (void) memcpy(sb, &zc.zc_stat, sizeof (zfs_stat_t)); 9999d5e173STim Haley if (error == 0) { 10099d5e173STim Haley ASSERT(di->zerr == 0); 10199d5e173STim Haley (void) strlcpy(pn, zc.zc_value, maxlen); 10299d5e173STim Haley return (0); 10399d5e173STim Haley } 10499d5e173STim Haley 10599d5e173STim Haley if (di->zerr == EPERM) { 10699d5e173STim Haley (void) snprintf(di->errbuf, sizeof (di->errbuf), 10799d5e173STim Haley dgettext(TEXT_DOMAIN, 10899d5e173STim Haley "The sys_config privilege or diff delegated permission " 10999d5e173STim Haley "is needed\nto discover path names")); 11099d5e173STim Haley return (-1); 11199d5e173STim Haley } else { 11299d5e173STim Haley (void) snprintf(di->errbuf, sizeof (di->errbuf), 11399d5e173STim Haley dgettext(TEXT_DOMAIN, 11499d5e173STim Haley "Unable to determine path or stats for " 11599d5e173STim Haley "object %lld in %s"), obj, dsname); 11699d5e173STim Haley return (-1); 11799d5e173STim Haley } 11899d5e173STim Haley } 11999d5e173STim Haley 12099d5e173STim Haley /* 12199d5e173STim Haley * stream_bytes 12299d5e173STim Haley * 12399d5e173STim Haley * Prints a file name out a character at a time. If the character is 12499d5e173STim Haley * not in the range of what we consider "printable" ASCII, display it 12599d5e173STim Haley * as an escaped 3-digit octal value. ASCII values less than a space 12699d5e173STim Haley * are all control characters and we declare the upper end as the 12799d5e173STim Haley * DELete character. This also is the last 7-bit ASCII character. 12899d5e173STim Haley * We choose to treat all 8-bit ASCII as not printable for this 12999d5e173STim Haley * application. 13099d5e173STim Haley */ 13199d5e173STim Haley static void 13299d5e173STim Haley stream_bytes(FILE *fp, const char *string) 13399d5e173STim Haley { 134b211eb91SJoshua M. Clulow char c; 135b211eb91SJoshua M. Clulow 136b211eb91SJoshua M. Clulow while ((c = *string++) != '\0') { 137b211eb91SJoshua M. Clulow if (c > ' ' && c != '\\' && c < '\177') { 138b211eb91SJoshua M. Clulow (void) fprintf(fp, "%c", c); 139b211eb91SJoshua M. Clulow } else { 140b211eb91SJoshua M. Clulow (void) fprintf(fp, "\\%03o", (uint8_t)c); 141b211eb91SJoshua M. Clulow } 14299d5e173STim Haley } 14399d5e173STim Haley } 14499d5e173STim Haley 14599d5e173STim Haley static void 14699d5e173STim Haley print_what(FILE *fp, mode_t what) 14799d5e173STim Haley { 14899d5e173STim Haley char symbol; 14999d5e173STim Haley 15099d5e173STim Haley switch (what & S_IFMT) { 15199d5e173STim Haley case S_IFBLK: 15299d5e173STim Haley symbol = 'B'; 15399d5e173STim Haley break; 15499d5e173STim Haley case S_IFCHR: 15599d5e173STim Haley symbol = 'C'; 15699d5e173STim Haley break; 15799d5e173STim Haley case S_IFDIR: 15899d5e173STim Haley symbol = '/'; 15999d5e173STim Haley break; 16099d5e173STim Haley case S_IFDOOR: 16199d5e173STim Haley symbol = '>'; 16299d5e173STim Haley break; 16399d5e173STim Haley case S_IFIFO: 16499d5e173STim Haley symbol = '|'; 16599d5e173STim Haley break; 16699d5e173STim Haley case S_IFLNK: 16799d5e173STim Haley symbol = '@'; 16899d5e173STim Haley break; 16999d5e173STim Haley case S_IFPORT: 17099d5e173STim Haley symbol = 'P'; 17199d5e173STim Haley break; 17299d5e173STim Haley case S_IFSOCK: 17399d5e173STim Haley symbol = '='; 17499d5e173STim Haley break; 17599d5e173STim Haley case S_IFREG: 17699d5e173STim Haley symbol = 'F'; 17799d5e173STim Haley break; 17899d5e173STim Haley default: 17999d5e173STim Haley symbol = '?'; 18099d5e173STim Haley break; 18199d5e173STim Haley } 18299d5e173STim Haley (void) fprintf(fp, "%c", symbol); 18399d5e173STim Haley } 18499d5e173STim Haley 18599d5e173STim Haley static void 18699d5e173STim Haley print_cmn(FILE *fp, differ_info_t *di, const char *file) 18799d5e173STim Haley { 18899d5e173STim Haley stream_bytes(fp, di->dsmnt); 18999d5e173STim Haley stream_bytes(fp, file); 19099d5e173STim Haley } 19199d5e173STim Haley 19299d5e173STim Haley static void 19399d5e173STim Haley print_rename(FILE *fp, differ_info_t *di, const char *old, const char *new, 19499d5e173STim Haley zfs_stat_t *isb) 19599d5e173STim Haley { 19699d5e173STim Haley if (di->timestamped) 19799d5e173STim Haley (void) fprintf(fp, "%10lld.%09lld\t", 19899d5e173STim Haley (longlong_t)isb->zs_ctime[0], 19999d5e173STim Haley (longlong_t)isb->zs_ctime[1]); 20099d5e173STim Haley (void) fprintf(fp, "%c\t", ZDIFF_RENAMED); 20199d5e173STim Haley if (di->classify) { 20299d5e173STim Haley print_what(fp, isb->zs_mode); 20399d5e173STim Haley (void) fprintf(fp, "\t"); 20499d5e173STim Haley } 20599d5e173STim Haley print_cmn(fp, di, old); 20699d5e173STim Haley if (di->scripted) 20799d5e173STim Haley (void) fprintf(fp, "\t"); 20899d5e173STim Haley else 20999d5e173STim Haley (void) fprintf(fp, " -> "); 21099d5e173STim Haley print_cmn(fp, di, new); 21199d5e173STim Haley (void) fprintf(fp, "\n"); 21299d5e173STim Haley } 21399d5e173STim Haley 21499d5e173STim Haley static void 21599d5e173STim Haley print_link_change(FILE *fp, differ_info_t *di, int delta, const char *file, 21699d5e173STim Haley zfs_stat_t *isb) 21799d5e173STim Haley { 21899d5e173STim Haley if (di->timestamped) 21999d5e173STim Haley (void) fprintf(fp, "%10lld.%09lld\t", 22099d5e173STim Haley (longlong_t)isb->zs_ctime[0], 22199d5e173STim Haley (longlong_t)isb->zs_ctime[1]); 22299d5e173STim Haley (void) fprintf(fp, "%c\t", ZDIFF_MODIFIED); 22399d5e173STim Haley if (di->classify) { 22499d5e173STim Haley print_what(fp, isb->zs_mode); 22599d5e173STim Haley (void) fprintf(fp, "\t"); 22699d5e173STim Haley } 22799d5e173STim Haley print_cmn(fp, di, file); 22899d5e173STim Haley (void) fprintf(fp, "\t(%+d)", delta); 22999d5e173STim Haley (void) fprintf(fp, "\n"); 23099d5e173STim Haley } 23199d5e173STim Haley 23299d5e173STim Haley static void 23399d5e173STim Haley print_file(FILE *fp, differ_info_t *di, char type, const char *file, 23499d5e173STim Haley zfs_stat_t *isb) 23599d5e173STim Haley { 23699d5e173STim Haley if (di->timestamped) 23799d5e173STim Haley (void) fprintf(fp, "%10lld.%09lld\t", 23899d5e173STim Haley (longlong_t)isb->zs_ctime[0], 23999d5e173STim Haley (longlong_t)isb->zs_ctime[1]); 24099d5e173STim Haley (void) fprintf(fp, "%c\t", type); 24199d5e173STim Haley if (di->classify) { 24299d5e173STim Haley print_what(fp, isb->zs_mode); 24399d5e173STim Haley (void) fprintf(fp, "\t"); 24499d5e173STim Haley } 24599d5e173STim Haley print_cmn(fp, di, file); 24699d5e173STim Haley (void) fprintf(fp, "\n"); 24799d5e173STim Haley } 24899d5e173STim Haley 24999d5e173STim Haley static int 25099d5e173STim Haley write_inuse_diffs_one(FILE *fp, differ_info_t *di, uint64_t dobj) 25199d5e173STim Haley { 25299d5e173STim Haley struct zfs_stat fsb, tsb; 25399d5e173STim Haley mode_t fmode, tmode; 25499d5e173STim Haley char fobjname[MAXPATHLEN], tobjname[MAXPATHLEN]; 25599d5e173STim Haley int fobjerr, tobjerr; 25699d5e173STim Haley int change; 25799d5e173STim Haley 25899d5e173STim Haley if (dobj == di->shares) 25999d5e173STim Haley return (0); 26099d5e173STim Haley 26199d5e173STim Haley /* 26299d5e173STim Haley * Check the from and to snapshots for info on the object. If 26399d5e173STim Haley * we get ENOENT, then the object just didn't exist in that 26499d5e173STim Haley * snapshot. If we get ENOTSUP, then we tried to get 26599d5e173STim Haley * info on a non-ZPL object, which we don't care about anyway. 26699d5e173STim Haley */ 26799d5e173STim Haley fobjerr = get_stats_for_obj(di, di->fromsnap, dobj, fobjname, 26899d5e173STim Haley MAXPATHLEN, &fsb); 26999d5e173STim Haley if (fobjerr && di->zerr != ENOENT && di->zerr != ENOTSUP) 27099d5e173STim Haley return (-1); 27199d5e173STim Haley 27299d5e173STim Haley tobjerr = get_stats_for_obj(di, di->tosnap, dobj, tobjname, 27399d5e173STim Haley MAXPATHLEN, &tsb); 27499d5e173STim Haley if (tobjerr && di->zerr != ENOENT && di->zerr != ENOTSUP) 27599d5e173STim Haley return (-1); 27699d5e173STim Haley 27799d5e173STim Haley /* 27899d5e173STim Haley * Unallocated object sharing the same meta dnode block 27999d5e173STim Haley */ 28099d5e173STim Haley if (fobjerr && tobjerr) { 28199d5e173STim Haley ASSERT(di->zerr == ENOENT || di->zerr == ENOTSUP); 28299d5e173STim Haley di->zerr = 0; 28399d5e173STim Haley return (0); 28499d5e173STim Haley } 28599d5e173STim Haley 28699d5e173STim Haley di->zerr = 0; /* negate get_stats_for_obj() from side that failed */ 28799d5e173STim Haley fmode = fsb.zs_mode & S_IFMT; 28899d5e173STim Haley tmode = tsb.zs_mode & S_IFMT; 28999d5e173STim Haley if (fmode == S_IFDIR || tmode == S_IFDIR || fsb.zs_links == 0 || 29099d5e173STim Haley tsb.zs_links == 0) 29199d5e173STim Haley change = 0; 29299d5e173STim Haley else 29399d5e173STim Haley change = tsb.zs_links - fsb.zs_links; 29499d5e173STim Haley 29599d5e173STim Haley if (fobjerr) { 29699d5e173STim Haley if (change) { 29799d5e173STim Haley print_link_change(fp, di, change, tobjname, &tsb); 29899d5e173STim Haley return (0); 29999d5e173STim Haley } 30099d5e173STim Haley print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb); 30199d5e173STim Haley return (0); 30299d5e173STim Haley } else if (tobjerr) { 30399d5e173STim Haley if (change) { 30499d5e173STim Haley print_link_change(fp, di, change, fobjname, &fsb); 30599d5e173STim Haley return (0); 30699d5e173STim Haley } 30799d5e173STim Haley print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb); 30899d5e173STim Haley return (0); 30999d5e173STim Haley } 31099d5e173STim Haley 31199d5e173STim Haley if (fmode != tmode && fsb.zs_gen == tsb.zs_gen) 31299d5e173STim Haley tsb.zs_gen++; /* Force a generational difference */ 31399d5e173STim Haley 31499d5e173STim Haley /* Simple modification or no change */ 31599d5e173STim Haley if (fsb.zs_gen == tsb.zs_gen) { 31699d5e173STim Haley /* No apparent changes. Could we assert !this? */ 31799d5e173STim Haley if (fsb.zs_ctime[0] == tsb.zs_ctime[0] && 31899d5e173STim Haley fsb.zs_ctime[1] == tsb.zs_ctime[1]) 31999d5e173STim Haley return (0); 32099d5e173STim Haley if (change) { 32199d5e173STim Haley print_link_change(fp, di, change, 32299d5e173STim Haley change > 0 ? fobjname : tobjname, &tsb); 323aab04418SJoshua M. Clulow } else if (strcmp(fobjname, tobjname) == 0) { 32499d5e173STim Haley print_file(fp, di, ZDIFF_MODIFIED, fobjname, &tsb); 32599d5e173STim Haley } else { 32699d5e173STim Haley print_rename(fp, di, fobjname, tobjname, &tsb); 32799d5e173STim Haley } 32899d5e173STim Haley return (0); 32999d5e173STim Haley } else { 33099d5e173STim Haley /* file re-created or object re-used */ 33199d5e173STim Haley print_file(fp, di, ZDIFF_REMOVED, fobjname, &fsb); 33299d5e173STim Haley print_file(fp, di, ZDIFF_ADDED, tobjname, &tsb); 33399d5e173STim Haley return (0); 33499d5e173STim Haley } 33599d5e173STim Haley } 33699d5e173STim Haley 33799d5e173STim Haley static int 33899d5e173STim Haley write_inuse_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr) 33999d5e173STim Haley { 34099d5e173STim Haley uint64_t o; 34199d5e173STim Haley int err; 34299d5e173STim Haley 34399d5e173STim Haley for (o = dr->ddr_first; o <= dr->ddr_last; o++) { 34499d5e173STim Haley if (err = write_inuse_diffs_one(fp, di, o)) 34599d5e173STim Haley return (err); 34699d5e173STim Haley } 34799d5e173STim Haley return (0); 34899d5e173STim Haley } 34999d5e173STim Haley 35099d5e173STim Haley static int 35199d5e173STim Haley describe_free(FILE *fp, differ_info_t *di, uint64_t object, char *namebuf, 35299d5e173STim Haley int maxlen) 35399d5e173STim Haley { 35499d5e173STim Haley struct zfs_stat sb; 35599d5e173STim Haley 35699d5e173STim Haley if (get_stats_for_obj(di, di->fromsnap, object, namebuf, 35799d5e173STim Haley maxlen, &sb) != 0) { 35899d5e173STim Haley /* Let it slide, if in the delete queue on from side */ 35999d5e173STim Haley if (di->zerr == ENOENT && sb.zs_links == 0) { 36099d5e173STim Haley di->zerr = 0; 36199d5e173STim Haley return (0); 36299d5e173STim Haley } 36399d5e173STim Haley return (-1); 36499d5e173STim Haley } 36599d5e173STim Haley 36699d5e173STim Haley print_file(fp, di, ZDIFF_REMOVED, namebuf, &sb); 36799d5e173STim Haley return (0); 36899d5e173STim Haley } 36999d5e173STim Haley 37099d5e173STim Haley static int 37199d5e173STim Haley write_free_diffs(FILE *fp, differ_info_t *di, dmu_diff_record_t *dr) 37299d5e173STim Haley { 37399d5e173STim Haley zfs_cmd_t zc = { 0 }; 37499d5e173STim Haley libzfs_handle_t *lhdl = di->zhp->zfs_hdl; 37599d5e173STim Haley char fobjname[MAXPATHLEN]; 37699d5e173STim Haley 37799d5e173STim Haley (void) strlcpy(zc.zc_name, di->fromsnap, sizeof (zc.zc_name)); 37899d5e173STim Haley zc.zc_obj = dr->ddr_first - 1; 37999d5e173STim Haley 38099d5e173STim Haley ASSERT(di->zerr == 0); 38199d5e173STim Haley 38299d5e173STim Haley while (zc.zc_obj < dr->ddr_last) { 38399d5e173STim Haley int err; 38499d5e173STim Haley 38599d5e173STim Haley err = ioctl(lhdl->libzfs_fd, ZFS_IOC_NEXT_OBJ, &zc); 38699d5e173STim Haley if (err == 0) { 38799d5e173STim Haley if (zc.zc_obj == di->shares) { 38899d5e173STim Haley zc.zc_obj++; 38999d5e173STim Haley continue; 39099d5e173STim Haley } 39199d5e173STim Haley if (zc.zc_obj > dr->ddr_last) { 39299d5e173STim Haley break; 39399d5e173STim Haley } 39499d5e173STim Haley err = describe_free(fp, di, zc.zc_obj, fobjname, 39599d5e173STim Haley MAXPATHLEN); 39699d5e173STim Haley if (err) 39799d5e173STim Haley break; 39899d5e173STim Haley } else if (errno == ESRCH) { 39999d5e173STim Haley break; 40099d5e173STim Haley } else { 40199d5e173STim Haley (void) snprintf(di->errbuf, sizeof (di->errbuf), 40299d5e173STim Haley dgettext(TEXT_DOMAIN, 40399d5e173STim Haley "next allocated object (> %lld) find failure"), 40499d5e173STim Haley zc.zc_obj); 40599d5e173STim Haley di->zerr = errno; 40699d5e173STim Haley break; 40799d5e173STim Haley } 40899d5e173STim Haley } 40999d5e173STim Haley if (di->zerr) 41099d5e173STim Haley return (-1); 41199d5e173STim Haley return (0); 41299d5e173STim Haley } 41399d5e173STim Haley 41499d5e173STim Haley static void * 41599d5e173STim Haley differ(void *arg) 41699d5e173STim Haley { 41799d5e173STim Haley differ_info_t *di = arg; 41899d5e173STim Haley dmu_diff_record_t dr; 41999d5e173STim Haley FILE *ofp; 42099d5e173STim Haley int err = 0; 42199d5e173STim Haley 42299d5e173STim Haley if ((ofp = fdopen(di->outputfd, "w")) == NULL) { 42399d5e173STim Haley di->zerr = errno; 42499d5e173STim Haley (void) strerror_r(errno, di->errbuf, sizeof (di->errbuf)); 42599d5e173STim Haley (void) close(di->datafd); 42699d5e173STim Haley return ((void *)-1); 42799d5e173STim Haley } 42899d5e173STim Haley 42999d5e173STim Haley for (;;) { 43099d5e173STim Haley char *cp = (char *)&dr; 43199d5e173STim Haley int len = sizeof (dr); 43299d5e173STim Haley int rv; 43399d5e173STim Haley 43499d5e173STim Haley do { 43599d5e173STim Haley rv = read(di->datafd, cp, len); 43699d5e173STim Haley cp += rv; 43799d5e173STim Haley len -= rv; 43899d5e173STim Haley } while (len > 0 && rv > 0); 43999d5e173STim Haley 44099d5e173STim Haley if (rv < 0 || (rv == 0 && len != sizeof (dr))) { 44199d5e173STim Haley di->zerr = EPIPE; 44299d5e173STim Haley break; 44399d5e173STim Haley } else if (rv == 0) { 44499d5e173STim Haley /* end of file at a natural breaking point */ 44599d5e173STim Haley break; 44699d5e173STim Haley } 44799d5e173STim Haley 44899d5e173STim Haley switch (dr.ddr_type) { 44999d5e173STim Haley case DDR_FREE: 45099d5e173STim Haley err = write_free_diffs(ofp, di, &dr); 45199d5e173STim Haley break; 45299d5e173STim Haley case DDR_INUSE: 45399d5e173STim Haley err = write_inuse_diffs(ofp, di, &dr); 45499d5e173STim Haley break; 45599d5e173STim Haley default: 45699d5e173STim Haley di->zerr = EPIPE; 45799d5e173STim Haley break; 45899d5e173STim Haley } 45999d5e173STim Haley 46099d5e173STim Haley if (err || di->zerr) 46199d5e173STim Haley break; 46299d5e173STim Haley } 46399d5e173STim Haley 46499d5e173STim Haley (void) fclose(ofp); 46599d5e173STim Haley (void) close(di->datafd); 46699d5e173STim Haley if (err) 46799d5e173STim Haley return ((void *)-1); 46899d5e173STim Haley if (di->zerr) { 46999d5e173STim Haley ASSERT(di->zerr == EINVAL); 47099d5e173STim Haley (void) snprintf(di->errbuf, sizeof (di->errbuf), 47199d5e173STim Haley dgettext(TEXT_DOMAIN, 47299d5e173STim Haley "Internal error: bad data from diff IOCTL")); 47399d5e173STim Haley return ((void *)-1); 47499d5e173STim Haley } 47599d5e173STim Haley return ((void *)0); 47699d5e173STim Haley } 47799d5e173STim Haley 47899d5e173STim Haley static int 47999d5e173STim Haley find_shares_object(differ_info_t *di) 48099d5e173STim Haley { 48199d5e173STim Haley char fullpath[MAXPATHLEN]; 48299d5e173STim Haley struct stat64 sb = { 0 }; 48399d5e173STim Haley 48499d5e173STim Haley (void) strlcpy(fullpath, di->dsmnt, MAXPATHLEN); 48599d5e173STim Haley (void) strlcat(fullpath, ZDIFF_SHARESDIR, MAXPATHLEN); 48699d5e173STim Haley 48799d5e173STim Haley if (stat64(fullpath, &sb) != 0) { 48899d5e173STim Haley (void) snprintf(di->errbuf, sizeof (di->errbuf), 48999d5e173STim Haley dgettext(TEXT_DOMAIN, "Cannot stat %s"), fullpath); 49099d5e173STim Haley return (zfs_error(di->zhp->zfs_hdl, EZFS_DIFF, di->errbuf)); 49199d5e173STim Haley } 49299d5e173STim Haley 49399d5e173STim Haley di->shares = (uint64_t)sb.st_ino; 49499d5e173STim Haley return (0); 49599d5e173STim Haley } 49699d5e173STim Haley 49799d5e173STim Haley static int 49899d5e173STim Haley make_temp_snapshot(differ_info_t *di) 49999d5e173STim Haley { 50099d5e173STim Haley libzfs_handle_t *hdl = di->zhp->zfs_hdl; 50199d5e173STim Haley zfs_cmd_t zc = { 0 }; 50299d5e173STim Haley 50399d5e173STim Haley (void) snprintf(zc.zc_value, sizeof (zc.zc_value), 50499d5e173STim Haley ZDIFF_PREFIX, getpid()); 50599d5e173STim Haley (void) strlcpy(zc.zc_name, di->ds, sizeof (zc.zc_name)); 50699d5e173STim Haley zc.zc_cleanup_fd = di->cleanupfd; 50799d5e173STim Haley 50899d5e173STim Haley if (ioctl(hdl->libzfs_fd, ZFS_IOC_TMP_SNAPSHOT, &zc) != 0) { 50999d5e173STim Haley int err = errno; 51099d5e173STim Haley if (err == EPERM) { 51199d5e173STim Haley (void) snprintf(di->errbuf, sizeof (di->errbuf), 51299d5e173STim Haley dgettext(TEXT_DOMAIN, "The diff delegated " 51399d5e173STim Haley "permission is needed in order\nto create a " 51499d5e173STim Haley "just-in-time snapshot for diffing\n")); 51599d5e173STim Haley return (zfs_error(hdl, EZFS_DIFF, di->errbuf)); 51699d5e173STim Haley } else { 51799d5e173STim Haley (void) snprintf(di->errbuf, sizeof (di->errbuf), 51899d5e173STim Haley dgettext(TEXT_DOMAIN, "Cannot create just-in-time " 51999d5e173STim Haley "snapshot of '%s'"), zc.zc_name); 52099d5e173STim Haley return (zfs_standard_error(hdl, err, di->errbuf)); 52199d5e173STim Haley } 52299d5e173STim Haley } 52399d5e173STim Haley 52499d5e173STim Haley di->tmpsnap = zfs_strdup(hdl, zc.zc_value); 52599d5e173STim Haley di->tosnap = zfs_asprintf(hdl, "%s@%s", di->ds, di->tmpsnap); 52699d5e173STim Haley return (0); 52799d5e173STim Haley } 52899d5e173STim Haley 52999d5e173STim Haley static void 53099d5e173STim Haley teardown_differ_info(differ_info_t *di) 53199d5e173STim Haley { 53299d5e173STim Haley free(di->ds); 53399d5e173STim Haley free(di->dsmnt); 53499d5e173STim Haley free(di->fromsnap); 53599d5e173STim Haley free(di->frommnt); 53699d5e173STim Haley free(di->tosnap); 53799d5e173STim Haley free(di->tmpsnap); 53899d5e173STim Haley free(di->tomnt); 53999d5e173STim Haley (void) close(di->cleanupfd); 54099d5e173STim Haley } 54199d5e173STim Haley 54299d5e173STim Haley static int 54399d5e173STim Haley get_snapshot_names(differ_info_t *di, const char *fromsnap, 54499d5e173STim Haley const char *tosnap) 54599d5e173STim Haley { 54699d5e173STim Haley libzfs_handle_t *hdl = di->zhp->zfs_hdl; 54799d5e173STim Haley char *atptrf = NULL; 54899d5e173STim Haley char *atptrt = NULL; 54999d5e173STim Haley int fdslen, fsnlen; 55099d5e173STim Haley int tdslen, tsnlen; 55199d5e173STim Haley 55299d5e173STim Haley /* 55399d5e173STim Haley * Can accept 55499d5e173STim Haley * dataset@snap1 55599d5e173STim Haley * dataset@snap1 dataset@snap2 55699d5e173STim Haley * dataset@snap1 @snap2 55799d5e173STim Haley * dataset@snap1 dataset 55899d5e173STim Haley * @snap1 dataset@snap2 55999d5e173STim Haley */ 56099d5e173STim Haley if (tosnap == NULL) { 56199d5e173STim Haley /* only a from snapshot given, must be valid */ 56299d5e173STim Haley (void) snprintf(di->errbuf, sizeof (di->errbuf), 56399d5e173STim Haley dgettext(TEXT_DOMAIN, 56499d5e173STim Haley "Badly formed snapshot name %s"), fromsnap); 56599d5e173STim Haley 56699d5e173STim Haley if (!zfs_validate_name(hdl, fromsnap, ZFS_TYPE_SNAPSHOT, 56799d5e173STim Haley B_FALSE)) { 56899d5e173STim Haley return (zfs_error(hdl, EZFS_INVALIDNAME, 56999d5e173STim Haley di->errbuf)); 57099d5e173STim Haley } 57199d5e173STim Haley 57299d5e173STim Haley atptrf = strchr(fromsnap, '@'); 57399d5e173STim Haley ASSERT(atptrf != NULL); 57499d5e173STim Haley fdslen = atptrf - fromsnap; 57599d5e173STim Haley 57699d5e173STim Haley di->fromsnap = zfs_strdup(hdl, fromsnap); 57799d5e173STim Haley di->ds = zfs_strdup(hdl, fromsnap); 57899d5e173STim Haley di->ds[fdslen] = '\0'; 57999d5e173STim Haley 58099d5e173STim Haley /* the to snap will be a just-in-time snap of the head */ 58199d5e173STim Haley return (make_temp_snapshot(di)); 58299d5e173STim Haley } 58399d5e173STim Haley 58499d5e173STim Haley (void) snprintf(di->errbuf, sizeof (di->errbuf), 58599d5e173STim Haley dgettext(TEXT_DOMAIN, 58699d5e173STim Haley "Unable to determine which snapshots to compare")); 58799d5e173STim Haley 58899d5e173STim Haley atptrf = strchr(fromsnap, '@'); 58999d5e173STim Haley atptrt = strchr(tosnap, '@'); 59099d5e173STim Haley fdslen = atptrf ? atptrf - fromsnap : strlen(fromsnap); 59199d5e173STim Haley tdslen = atptrt ? atptrt - tosnap : strlen(tosnap); 59299d5e173STim Haley fsnlen = strlen(fromsnap) - fdslen; /* includes @ sign */ 59399d5e173STim Haley tsnlen = strlen(tosnap) - tdslen; /* includes @ sign */ 59499d5e173STim Haley 59599d5e173STim Haley if (fsnlen <= 1 || tsnlen == 1 || (fdslen == 0 && tdslen == 0) || 59699d5e173STim Haley (fsnlen == 0 && tsnlen == 0)) { 59799d5e173STim Haley return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf)); 59899d5e173STim Haley } else if ((fdslen > 0 && tdslen > 0) && 59999d5e173STim Haley ((tdslen != fdslen || strncmp(fromsnap, tosnap, fdslen) != 0))) { 60099d5e173STim Haley /* 60199d5e173STim Haley * not the same dataset name, might be okay if 60299d5e173STim Haley * tosnap is a clone of a fromsnap descendant. 60399d5e173STim Haley */ 604*40a5c998SMatthew Ahrens char origin[ZFS_MAX_DATASET_NAME_LEN]; 60599d5e173STim Haley zprop_source_t src; 60699d5e173STim Haley zfs_handle_t *zhp; 60799d5e173STim Haley 60899d5e173STim Haley di->ds = zfs_alloc(di->zhp->zfs_hdl, tdslen + 1); 60999d5e173STim Haley (void) strncpy(di->ds, tosnap, tdslen); 61099d5e173STim Haley di->ds[tdslen] = '\0'; 61199d5e173STim Haley 61299d5e173STim Haley zhp = zfs_open(hdl, di->ds, ZFS_TYPE_FILESYSTEM); 61399d5e173STim Haley while (zhp != NULL) { 61484302789SAlexander Eremin if (zfs_prop_get(zhp, ZFS_PROP_ORIGIN, origin, 61584302789SAlexander Eremin sizeof (origin), &src, NULL, 0, B_FALSE) != 0) { 61684302789SAlexander Eremin (void) zfs_close(zhp); 61784302789SAlexander Eremin zhp = NULL; 61884302789SAlexander Eremin break; 61984302789SAlexander Eremin } 62099d5e173STim Haley if (strncmp(origin, fromsnap, fsnlen) == 0) 62199d5e173STim Haley break; 62299d5e173STim Haley 62399d5e173STim Haley (void) zfs_close(zhp); 62499d5e173STim Haley zhp = zfs_open(hdl, origin, ZFS_TYPE_FILESYSTEM); 62599d5e173STim Haley } 62699d5e173STim Haley 62799d5e173STim Haley if (zhp == NULL) { 62899d5e173STim Haley (void) snprintf(di->errbuf, sizeof (di->errbuf), 62999d5e173STim Haley dgettext(TEXT_DOMAIN, 63099d5e173STim Haley "Not an earlier snapshot from the same fs")); 63199d5e173STim Haley return (zfs_error(hdl, EZFS_INVALIDNAME, di->errbuf)); 63299d5e173STim Haley } else { 63399d5e173STim Haley (void) zfs_close(zhp); 63499d5e173STim Haley } 63599d5e173STim Haley 63699d5e173STim Haley di->isclone = B_TRUE; 63799d5e173STim Haley di->fromsnap = zfs_strdup(hdl, fromsnap); 63899d5e173STim Haley if (tsnlen) { 63999d5e173STim Haley di->tosnap = zfs_strdup(hdl, tosnap); 64099d5e173STim Haley } else { 64199d5e173STim Haley return (make_temp_snapshot(di)); 64299d5e173STim Haley } 64399d5e173STim Haley } else { 64499d5e173STim Haley int dslen = fdslen ? fdslen : tdslen; 64599d5e173STim Haley 64699d5e173STim Haley di->ds = zfs_alloc(hdl, dslen + 1); 64799d5e173STim Haley (void) strncpy(di->ds, fdslen ? fromsnap : tosnap, dslen); 64899d5e173STim Haley di->ds[dslen] = '\0'; 64999d5e173STim Haley 65099d5e173STim Haley di->fromsnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrf); 65199d5e173STim Haley if (tsnlen) { 65299d5e173STim Haley di->tosnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrt); 65399d5e173STim Haley } else { 65499d5e173STim Haley return (make_temp_snapshot(di)); 65599d5e173STim Haley } 65699d5e173STim Haley } 65799d5e173STim Haley return (0); 65899d5e173STim Haley } 65999d5e173STim Haley 66099d5e173STim Haley static int 66199d5e173STim Haley get_mountpoint(differ_info_t *di, char *dsnm, char **mntpt) 66299d5e173STim Haley { 66399d5e173STim Haley boolean_t mounted; 66499d5e173STim Haley 66599d5e173STim Haley mounted = is_mounted(di->zhp->zfs_hdl, dsnm, mntpt); 66699d5e173STim Haley if (mounted == B_FALSE) { 66799d5e173STim Haley (void) snprintf(di->errbuf, sizeof (di->errbuf), 66899d5e173STim Haley dgettext(TEXT_DOMAIN, 66999d5e173STim Haley "Cannot diff an unmounted snapshot")); 67099d5e173STim Haley return (zfs_error(di->zhp->zfs_hdl, EZFS_BADTYPE, di->errbuf)); 67199d5e173STim Haley } 67299d5e173STim Haley 67399d5e173STim Haley /* Avoid a double slash at the beginning of root-mounted datasets */ 67499d5e173STim Haley if (**mntpt == '/' && *(*mntpt + 1) == '\0') 67599d5e173STim Haley **mntpt = '\0'; 67699d5e173STim Haley return (0); 67799d5e173STim Haley } 67899d5e173STim Haley 67999d5e173STim Haley static int 68099d5e173STim Haley get_mountpoints(differ_info_t *di) 68199d5e173STim Haley { 68299d5e173STim Haley char *strptr; 68399d5e173STim Haley char *frommntpt; 68499d5e173STim Haley 68599d5e173STim Haley /* 68699d5e173STim Haley * first get the mountpoint for the parent dataset 68799d5e173STim Haley */ 68899d5e173STim Haley if (get_mountpoint(di, di->ds, &di->dsmnt) != 0) 68999d5e173STim Haley return (-1); 69099d5e173STim Haley 69199d5e173STim Haley strptr = strchr(di->tosnap, '@'); 69299d5e173STim Haley ASSERT3P(strptr, !=, NULL); 69399d5e173STim Haley di->tomnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", di->dsmnt, 69499d5e173STim Haley ZDIFF_SNAPDIR, ++strptr); 69599d5e173STim Haley 69699d5e173STim Haley strptr = strchr(di->fromsnap, '@'); 69799d5e173STim Haley ASSERT3P(strptr, !=, NULL); 69899d5e173STim Haley 69999d5e173STim Haley frommntpt = di->dsmnt; 70099d5e173STim Haley if (di->isclone) { 70199d5e173STim Haley char *mntpt; 70299d5e173STim Haley int err; 70399d5e173STim Haley 70499d5e173STim Haley *strptr = '\0'; 70599d5e173STim Haley err = get_mountpoint(di, di->fromsnap, &mntpt); 70699d5e173STim Haley *strptr = '@'; 70799d5e173STim Haley if (err != 0) 70899d5e173STim Haley return (-1); 70999d5e173STim Haley frommntpt = mntpt; 71099d5e173STim Haley } 71199d5e173STim Haley 71299d5e173STim Haley di->frommnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", frommntpt, 71399d5e173STim Haley ZDIFF_SNAPDIR, ++strptr); 71499d5e173STim Haley 71599d5e173STim Haley if (di->isclone) 71699d5e173STim Haley free(frommntpt); 71799d5e173STim Haley 71899d5e173STim Haley return (0); 71999d5e173STim Haley } 72099d5e173STim Haley 72199d5e173STim Haley static int 72299d5e173STim Haley setup_differ_info(zfs_handle_t *zhp, const char *fromsnap, 72399d5e173STim Haley const char *tosnap, differ_info_t *di) 72499d5e173STim Haley { 72599d5e173STim Haley di->zhp = zhp; 72699d5e173STim Haley 72799d5e173STim Haley di->cleanupfd = open(ZFS_DEV, O_RDWR|O_EXCL); 72899d5e173STim Haley VERIFY(di->cleanupfd >= 0); 72999d5e173STim Haley 73099d5e173STim Haley if (get_snapshot_names(di, fromsnap, tosnap) != 0) 73199d5e173STim Haley return (-1); 73299d5e173STim Haley 73399d5e173STim Haley if (get_mountpoints(di) != 0) 73499d5e173STim Haley return (-1); 73599d5e173STim Haley 73699d5e173STim Haley if (find_shares_object(di) != 0) 73799d5e173STim Haley return (-1); 73899d5e173STim Haley 73999d5e173STim Haley return (0); 74099d5e173STim Haley } 74199d5e173STim Haley 74299d5e173STim Haley int 74399d5e173STim Haley zfs_show_diffs(zfs_handle_t *zhp, int outfd, const char *fromsnap, 74499d5e173STim Haley const char *tosnap, int flags) 74599d5e173STim Haley { 74699d5e173STim Haley zfs_cmd_t zc = { 0 }; 74799d5e173STim Haley char errbuf[1024]; 74899d5e173STim Haley differ_info_t di = { 0 }; 74999d5e173STim Haley pthread_t tid; 75099d5e173STim Haley int pipefd[2]; 75199d5e173STim Haley int iocerr; 75299d5e173STim Haley 75399d5e173STim Haley (void) snprintf(errbuf, sizeof (errbuf), 75499d5e173STim Haley dgettext(TEXT_DOMAIN, "zfs diff failed")); 75599d5e173STim Haley 75699d5e173STim Haley if (setup_differ_info(zhp, fromsnap, tosnap, &di)) { 75799d5e173STim Haley teardown_differ_info(&di); 75899d5e173STim Haley return (-1); 75999d5e173STim Haley } 76099d5e173STim Haley 76199d5e173STim Haley if (pipe(pipefd)) { 76299d5e173STim Haley zfs_error_aux(zhp->zfs_hdl, strerror(errno)); 76399d5e173STim Haley teardown_differ_info(&di); 76499d5e173STim Haley return (zfs_error(zhp->zfs_hdl, EZFS_PIPEFAILED, errbuf)); 76599d5e173STim Haley } 76699d5e173STim Haley 76799d5e173STim Haley di.scripted = (flags & ZFS_DIFF_PARSEABLE); 76899d5e173STim Haley di.classify = (flags & ZFS_DIFF_CLASSIFY); 76999d5e173STim Haley di.timestamped = (flags & ZFS_DIFF_TIMESTAMP); 77099d5e173STim Haley 77199d5e173STim Haley di.outputfd = outfd; 77299d5e173STim Haley di.datafd = pipefd[0]; 77399d5e173STim Haley 77499d5e173STim Haley if (pthread_create(&tid, NULL, differ, &di)) { 77599d5e173STim Haley zfs_error_aux(zhp->zfs_hdl, strerror(errno)); 77699d5e173STim Haley (void) close(pipefd[0]); 77799d5e173STim Haley (void) close(pipefd[1]); 77899d5e173STim Haley teardown_differ_info(&di); 77999d5e173STim Haley return (zfs_error(zhp->zfs_hdl, 78099d5e173STim Haley EZFS_THREADCREATEFAILED, errbuf)); 78199d5e173STim Haley } 78299d5e173STim Haley 78399d5e173STim Haley /* do the ioctl() */ 78499d5e173STim Haley (void) strlcpy(zc.zc_value, di.fromsnap, strlen(di.fromsnap) + 1); 78599d5e173STim Haley (void) strlcpy(zc.zc_name, di.tosnap, strlen(di.tosnap) + 1); 78699d5e173STim Haley zc.zc_cookie = pipefd[1]; 78799d5e173STim Haley 78899d5e173STim Haley iocerr = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_DIFF, &zc); 78999d5e173STim Haley if (iocerr != 0) { 79099d5e173STim Haley (void) snprintf(errbuf, sizeof (errbuf), 79199d5e173STim Haley dgettext(TEXT_DOMAIN, "Unable to obtain diffs")); 79299d5e173STim Haley if (errno == EPERM) { 79399d5e173STim Haley zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, 79499d5e173STim Haley "\n The sys_mount privilege or diff delegated " 79599d5e173STim Haley "permission is needed\n to execute the " 79699d5e173STim Haley "diff ioctl")); 79799d5e173STim Haley } else if (errno == EXDEV) { 79899d5e173STim Haley zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN, 79999d5e173STim Haley "\n Not an earlier snapshot from the same fs")); 80099d5e173STim Haley } else if (errno != EPIPE || di.zerr == 0) { 80199d5e173STim Haley zfs_error_aux(zhp->zfs_hdl, strerror(errno)); 80299d5e173STim Haley } 80399d5e173STim Haley (void) close(pipefd[1]); 80499d5e173STim Haley (void) pthread_cancel(tid); 80599d5e173STim Haley (void) pthread_join(tid, NULL); 80699d5e173STim Haley teardown_differ_info(&di); 80799d5e173STim Haley if (di.zerr != 0 && di.zerr != EPIPE) { 80899d5e173STim Haley zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr)); 80999d5e173STim Haley return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf)); 81099d5e173STim Haley } else { 81199d5e173STim Haley return (zfs_error(zhp->zfs_hdl, EZFS_DIFFDATA, errbuf)); 81299d5e173STim Haley } 81399d5e173STim Haley } 81499d5e173STim Haley 81599d5e173STim Haley (void) close(pipefd[1]); 81699d5e173STim Haley (void) pthread_join(tid, NULL); 81799d5e173STim Haley 81899d5e173STim Haley if (di.zerr != 0) { 81999d5e173STim Haley zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr)); 82099d5e173STim Haley return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, di.errbuf)); 82199d5e173STim Haley } 82299d5e173STim Haley teardown_differ_info(&di); 82399d5e173STim Haley return (0); 82499d5e173STim Haley } 825