2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A/*
2N/A * zfs diff support
2N/A */
2N/A#include <ctype.h>
2N/A#include <errno.h>
2N/A#include <libintl.h>
2N/A#include <string.h>
2N/A#include <sys/types.h>
2N/A#include <sys/stat.h>
2N/A#include <fcntl.h>
2N/A#include <attr.h>
2N/A#include <stddef.h>
2N/A#include <unistd.h>
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <stropts.h>
2N/A#include <pthread.h>
2N/A#include <sys/zfs_ioctl.h>
2N/A#include <strings.h>
2N/A#include <libzfs.h>
2N/A#include "libzfs_impl.h"
2N/A
2N/A#define ZDIFF_SNAPDIR "/.zfs/snapshot/"
2N/A#define ZDIFF_SHARESDIR "/.zfs/shares/"
2N/A#define ZDIFF_PREFIX "zfs-diff-%d"
2N/A
2N/Atypedef struct differ_info {
2N/A zfs_handle_t *zhp;
2N/A char *fromsnap;
2N/A char *frommnt;
2N/A char *tosnap;
2N/A char *tomnt;
2N/A char *ds;
2N/A char *dsmnt;
2N/A char *tmpsnap;
2N/A int zerr;
2N/A boolean_t isclone;
2N/A boolean_t enumerate_only;
2N/A boolean_t frombase;
2N/A boolean_t needname;
2N/A boolean_t needoldname;
2N/A uint64_t shares;
2N/A int cleanupfd;
2N/A int outputfd;
2N/A int datafd;
2N/A diff_scan_cb_t *cb_func;
2N/A void *cb_arg;
2N/A char errbuf[1024];
2N/A} differ_info_t;
2N/A
2N/A/*
2N/A * Given a {dsname, object id}, get the object path
2N/A */
2N/Astatic int
2N/Aget_path_for_obj(differ_info_t *di, const char *dsname, uint64_t obj,
2N/A char *pn, int maxlen, zfs_stat_t *zs_rec, zfs_stat_t *zs_base)
2N/A{
2N/A zfs_cmd_t zc = { 0 };
2N/A int error;
2N/A char *name;
2N/A
2N/A if ((error = zs_rec->zs_nameerr) == 0) {
2N/A ASSERT(zs_rec->zs_nameoff != 0);
2N/A name = (char *)((uintptr_t)zs_base +
2N/A (uintptr_t)zs_rec->zs_nameoff);
2N/A } else if (error == ZFS_NAMEERR_NO_NAME_REQUESTED) {
2N/A (void) strlcpy(zc.zc_name, dsname, sizeof (zc.zc_name));
2N/A zc.zc_obj = obj;
2N/A if (ioctl(di->zhp->zfs_hdl->libzfs_fd, ZFS_IOC_OBJ_TO_PATH,
2N/A &zc) != 0) {
2N/A error = errno;
2N/A }
2N/A name = zc.zc_value;
2N/A }
2N/A di->zerr = error;
2N/A
2N/A if (error == 0) {
2N/A (void) strlcpy(pn, name, maxlen);
2N/A return (0);
2N/A }
2N/A
2N/A if (error == ESRCH) {
2N/A (void) sprintf(pn, "/\?\?\?<object#%llu>", (longlong_t)obj);
2N/A di->zerr = 0;
2N/A return (0);
2N/A }
2N/A if (error == EPERM) {
2N/A (void) snprintf(di->errbuf, sizeof (di->errbuf),
2N/A dgettext(TEXT_DOMAIN,
2N/A "The sys_config privilege or diff delegated permission "
2N/A "is needed\nto discover path names"));
2N/A return (-1);
2N/A } else {
2N/A (void) snprintf(di->errbuf, sizeof (di->errbuf),
2N/A dgettext(TEXT_DOMAIN,
2N/A "Unable to determine path for "
2N/A "object %lld in %s"), obj, dsname);
2N/A return (-1);
2N/A }
2N/A}
2N/A
2N/A
2N/A
2N/Astatic int
2N/Awrite_inuse_diffs_zero(differ_info_t *di, uint64_t dobj,
2N/A int fobjerr, char *fobjname, zfs_stat_t *fsp,
2N/A int tobjerr, char *tobjname, zfs_stat_t *tsp)
2N/A{
2N/A mode_t fmode, tmode;
2N/A int change;
2N/A
2N/A /*
2N/A * Unallocated object sharing the same meta dnode block
2N/A */
2N/A if (fobjerr && tobjerr) {
2N/A di->zerr = 0;
2N/A return (0);
2N/A }
2N/A
2N/A di->zerr = 0; /* negate get_stats_for_obj() from side that failed */
2N/A
2N/A if (di->frombase) {
2N/A ASSERT(di->fromsnap == NULL);
2N/A (void) memcpy(fsp, tsp, sizeof (*tsp));
2N/A }
2N/A
2N/A fmode = fsp->zs_mode & S_IFMT;
2N/A tmode = tsp->zs_mode & S_IFMT;
2N/A if (fmode == S_IFDIR || tmode == S_IFDIR || fsp->zs_links == 0 ||
2N/A tsp->zs_links == 0)
2N/A change = 0;
2N/A else
2N/A change = tsp->zs_links - fsp->zs_links;
2N/A if (fobjerr) {
2N/A (*di->cb_func) (di->dsmnt, dobj, ZDIFF_ADDED,
2N/A NULL, tobjname, NULL, tsp, 0, di->cb_arg);
2N/A return (0);
2N/A } else if (tobjerr) {
2N/A /* Account for removing this link itself */
2N/A if (fmode == S_IFDIR)
2N/A fsp->zs_links = 0;
2N/A else
2N/A fsp->zs_links--;
2N/A if (!di->enumerate_only) {
2N/A (*di->cb_func)(di->dsmnt, dobj, ZDIFF_REMOVED,
2N/A fobjname, NULL, fsp, NULL, 0, di->cb_arg);
2N/A }
2N/A return (0);
2N/A }
2N/A
2N/A if (fmode != tmode && fsp->zs_gen == tsp->zs_gen)
2N/A tsp->zs_gen++; /* Force a generational difference */
2N/A
2N/A /* Simple modification or no change */
2N/A if (fsp->zs_gen == tsp->zs_gen &&
2N/A fsp->zs_ctime[0] == tsp->zs_ctime[0] &&
2N/A fsp->zs_ctime[1] == tsp->zs_ctime[1]) {
2N/A /*
2N/A * No apparent changes. This occurs when this unchanged
2N/A * dnode is included in a dnode of dnodes that has
2N/A * other changes.
2N/A */
2N/A return (0);
2N/A }
2N/A if (!di->enumerate_only && fsp->zs_gen == tsp->zs_gen) {
2N/A (*di->cb_func)(di->dsmnt, dobj, ZDIFF_MODIFIED,
2N/A fobjname, tobjname, tsp, fsp, change, di->cb_arg);
2N/A return (0);
2N/A } else {
2N/A /* file re-created or object re-used */
2N/A if (!di->enumerate_only) {
2N/A (*di->cb_func)(di->dsmnt, dobj, ZDIFF_REMOVED,
2N/A fobjname, NULL, fsp, NULL, 0, di->cb_arg);
2N/A }
2N/A (*di->cb_func)(di->dsmnt, dobj, ZDIFF_ADDED,
2N/A NULL, tobjname, NULL, tsp, 0, di->cb_arg);
2N/A return (0);
2N/A }
2N/A}
2N/A
2N/A
2N/A
2N/Astatic int
2N/Awrite_inuse_diffs_one(differ_info_t *di, uint64_t dobj,
2N/A zfs_stat_t *zs2_from, zfs_stat_t *zs2_frombase,
2N/A zfs_stat_t *zs2_to, zfs_stat_t *zs2_tobase)
2N/A{
2N/A zfs_stat_t fsb, tsb;
2N/A char fobjname[MAXPATHLEN], tobjname[MAXPATHLEN];
2N/A int fobjerr = 0, tobjerr = 0;
2N/A char *tosnap;
2N/A
2N/A if (dobj == di->shares)
2N/A return (0);
2N/A
2N/A tosnap = di->tosnap;
2N/A
2N/A if (di->frombase) {
2N/A ASSERT(di->fromsnap == NULL);
2N/A fobjerr = -1;
2N/A fobjname[0] = '\0';
2N/A } else {
2N/A if (zs2_from) {
2N/A if (di->needoldname) {
2N/A fobjerr = get_path_for_obj(di, di->fromsnap,
2N/A dobj, fobjname, MAXPATHLEN,
2N/A zs2_from, zs2_frombase);
2N/A }
2N/A (void) memcpy(&fsb, zs2_from, sizeof (fsb));
2N/A } else {
2N/A bzero(&fsb, sizeof (fsb));
2N/A fobjerr = -1;
2N/A }
2N/A }
2N/A
2N/A if (zs2_to) {
2N/A if (di->needname) {
2N/A tobjerr = get_path_for_obj(di, tosnap, dobj,
2N/A tobjname, MAXPATHLEN, zs2_to, zs2_tobase);
2N/A }
2N/A (void) memcpy(&tsb, zs2_to, sizeof (tsb));
2N/A } else {
2N/A bzero(&tsb, sizeof (tsb));
2N/A tobjerr = -1;
2N/A }
2N/A
2N/A return (write_inuse_diffs_zero(di, dobj, fobjerr, fobjname, &fsb,
2N/A tobjerr, tobjname, &tsb));
2N/A}
2N/A
2N/A#define NUM_RECORDS 16384
2N/A
2N/Astatic int
2N/Asetup_diffs(differ_info_t *di, zfs_cmd_t *fromzp, zfs_cmd_t *tozp)
2N/A{
2N/A char *tosnap;
2N/A void *buffer_tods, *buffer_fromds;
2N/A
2N/A bzero(fromzp, sizeof (*fromzp));
2N/A bzero(tozp, sizeof (*tozp));
2N/A
2N/A tosnap = di->tosnap;
2N/A
2N/A buffer_tods = malloc(sizeof (zfs_stat_t) * NUM_RECORDS);
2N/A if (buffer_tods == NULL)
2N/A return (ENOMEM);
2N/A
2N/A (void) strlcpy(tozp->zc_name, tosnap, sizeof (tozp->zc_name));
2N/A tozp->zc_stat_buf = (uintptr_t)buffer_tods;
2N/A tozp->zc_stat_buflen = sizeof (zfs_stat_t) * NUM_RECORDS;
2N/A tozp->zc_fromobj = 0;
2N/A tozp->zc_needname = di->needname;
2N/A
2N/A if (! di->frombase) {
2N/A buffer_fromds = malloc(sizeof (zfs_stat_t) * NUM_RECORDS);
2N/A if (buffer_fromds == NULL)
2N/A return (ENOMEM);
2N/A
2N/A (void) strlcpy(fromzp->zc_name, di->fromsnap,
2N/A sizeof (fromzp->zc_name));
2N/A fromzp->zc_stat_buf = (uintptr_t)buffer_fromds;
2N/A fromzp->zc_stat_buflen =
2N/A sizeof (zfs_stat_t) * NUM_RECORDS;
2N/A fromzp->zc_fromobj = 0;
2N/A fromzp->zc_needname = di->needoldname;
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/A/*ARGSUSED*/
2N/Astatic void
2N/Ateardown_diffs(differ_info_t *di, zfs_cmd_t *fromzp, zfs_cmd_t *tozp)
2N/A{
2N/A free((void *)(uintptr_t)fromzp->zc_stat_buf);
2N/A free((void *)(uintptr_t)tozp->zc_stat_buf);
2N/A}
2N/A
2N/Astatic int
2N/Awrite_inuse_diffs(differ_info_t *di, dmu_diff_record_t *dr, zfs_cmd_t *fromzp,
2N/A zfs_cmd_t *tozp)
2N/A{
2N/A zfs_stat_t *zs_from = NULL, *zs_to = NULL;
2N/A void *buffer_tods, *buffer_fromds;
2N/A uint64_t to_o, from_o;
2N/A int err;
2N/A
2N/A if (dr->ddr_first > tozp->zc_fromobj)
2N/A tozp->zc_fromobj = dr->ddr_first;
2N/A tozp->zc_obj = dr->ddr_last;
2N/A tozp->zc_cookie = 0;
2N/A to_o = tozp->zc_fromobj;
2N/A buffer_tods = (void *)(uintptr_t)tozp->zc_stat_buf;
2N/A
2N/A if (! di->frombase) {
2N/A if (dr->ddr_first > fromzp->zc_fromobj)
2N/A fromzp->zc_fromobj = dr->ddr_first;
2N/A fromzp->zc_obj = dr->ddr_last;
2N/A fromzp->zc_cookie = 0;
2N/A from_o = fromzp->zc_fromobj;
2N/A buffer_fromds =
2N/A (void *)(uintptr_t)fromzp->zc_stat_buf;
2N/A } else {
2N/A from_o = dr->ddr_last + 1;
2N/A buffer_fromds = NULL;
2N/A }
2N/A
2N/A /*
2N/A * from_o -- current object in "from" dataset.
2N/A * fromzp->zc_fromobj -- the first object in next "from" chunk.
2N/A * fromzp->zc_cookie -- number of records left in "from" chunk
2N/A * to_o -- current object in "to" dataset.
2N/A * tozp->zc_fromobj -- the first object in next "to" chunk.
2N/A * tozp->zc_cookie - number of records left in "to" chunk
2N/A */
2N/A while (from_o <= dr->ddr_last || to_o <= dr->ddr_last) {
2N/A if (! di->frombase) {
2N/A if ((fromzp->zc_cookie == 0) &&
2N/A (from_o <= dr->ddr_last)) {
2N/A ASSERT(from_o == fromzp->zc_fromobj);
2N/A /* Get next stats chunk from "from" dataset */
2N/A if (err = ioctl(di->zhp->zfs_hdl->libzfs_fd,
2N/A ZFS_IOC_BULK_OBJ_TO_STATS, fromzp)) {
2N/A return (err);
2N/A }
2N/A zs_from =
2N/A (zfs_stat_t *)buffer_fromds;
2N/A from_o = fromzp->zc_cookie == 0 ?
2N/A fromzp->zc_fromobj : zs_from->zs_obj;
2N/A }
2N/A }
2N/A
2N/A if (tozp->zc_cookie == 0 && (to_o <= dr->ddr_last)) {
2N/A /* Get next stats chunk from the "to" dataset */
2N/A ASSERT(to_o == tozp->zc_fromobj);
2N/A if (err = ioctl(di->zhp->zfs_hdl->libzfs_fd,
2N/A ZFS_IOC_BULK_OBJ_TO_STATS, tozp)) {
2N/A return (err);
2N/A }
2N/A zs_to = (zfs_stat_t *)buffer_tods;
2N/A to_o = tozp->zc_cookie == 0 ?
2N/A tozp->zc_fromobj : zs_to->zs_obj;
2N/A }
2N/A
2N/A /*
2N/A * At this point, from_o and to_o refer to the next object in
2N/A * their respective datasets. If the next object in both is
2N/A * past the requested range, then this range is complete. Note
2N/A * that we record the next object number in the zfs_cmd_t
2N/A * structure. We can short-circuit unnecessary stat calls where
2N/A * we already have the answer.
2N/A */
2N/A if (from_o > dr->ddr_last && to_o > dr->ddr_last)
2N/A break;
2N/A
2N/A /*
2N/A * At least one dataset has an object in range.
2N/A * Go tell the user what kind of diff it is.
2N/A */
2N/A if (from_o < to_o) {
2N/A /*
2N/A * Object exists in fromds but not tods.
2N/A * This object has been DELETED.
2N/A */
2N/A ASSERT(fromzp->zc_cookie > 0);
2N/A err = write_inuse_diffs_one(di, zs_from->zs_obj,
2N/A zs_from, (zfs_stat_t *)buffer_fromds,
2N/A NULL, NULL);
2N/A /* Consume one object from the "from" list */
2N/A zs_from++;
2N/A fromzp->zc_cookie--;
2N/A from_o = fromzp->zc_cookie == 0 ?
2N/A fromzp->zc_fromobj : zs_from->zs_obj;
2N/A } else if (from_o > to_o) {
2N/A /*
2N/A * Object exists in tods but not fromds.
2N/A * This object has been ADDED.
2N/A */
2N/A ASSERT(tozp->zc_cookie > 0);
2N/A err = write_inuse_diffs_one(di, zs_to->zs_obj,
2N/A NULL, NULL,
2N/A zs_to, (zfs_stat_t *)buffer_tods);
2N/A /* Consume one object from the "to" list */
2N/A tozp->zc_cookie--;
2N/A zs_to++;
2N/A to_o = tozp->zc_cookie == 0 ?
2N/A tozp->zc_fromobj : zs_to->zs_obj;
2N/A
2N/A } else {
2N/A /*
2N/A * Object exists in both tods and fromds.
2N/A * Either this object has been MODIFIED,
2N/A * or else the object has been DELETED and a new
2N/A * object has been ADDED with the same object number.
2N/A */
2N/A ASSERT(fromzp->zc_cookie > 0 && tozp->zc_cookie > 0);
2N/A err = write_inuse_diffs_one(di, zs_from->zs_obj,
2N/A zs_from, (zfs_stat_t *)buffer_fromds,
2N/A zs_to, (zfs_stat_t *)buffer_tods);
2N/A /* Consume one object from each list */
2N/A zs_from++;
2N/A zs_to++;
2N/A tozp->zc_cookie--;
2N/A fromzp->zc_cookie--;
2N/A from_o = fromzp->zc_cookie == 0 ?
2N/A fromzp->zc_fromobj : zs_from->zs_obj;
2N/A to_o = tozp->zc_cookie == 0 ?
2N/A tozp->zc_fromobj : zs_to->zs_obj;
2N/A }
2N/A
2N/A if (err) {
2N/A return (err);
2N/A }
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Adescribe_free(differ_info_t *di, uint64_t object,
2N/A zfs_stat_t *zs2, zfs_stat_t *zs2_base,
2N/A char *namebuf, int maxlen)
2N/A{
2N/A if (di->needoldname &&
2N/A get_path_for_obj(di, di->fromsnap, object, namebuf,
2N/A maxlen, zs2, zs2_base) != 0) {
2N/A return (-1);
2N/A }
2N/A
2N/A (*di->cb_func)(di->dsmnt, object, ZDIFF_REMOVED,
2N/A namebuf, NULL, zs2, NULL, 0, di->cb_arg);
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Awrite_free_diffs(differ_info_t *di, dmu_diff_record_t *dr, zfs_cmd_t *fromzp)
2N/A{
2N/A zfs_stat_t *zs_from = NULL;
2N/A void * buffer_fromds;
2N/A uint64_t from_o;
2N/A int err;
2N/A char fobjname[MAXPATHLEN];
2N/A
2N/A if (di->enumerate_only) {
2N/A /* -e enumerate mode does not display deletes */
2N/A return (0);
2N/A }
2N/A if (dr->ddr_last < fromzp->zc_fromobj) {
2N/A /* we already know this range is empty */
2N/A return (0);
2N/A }
2N/A
2N/A if (dr->ddr_first > fromzp->zc_fromobj) {
2N/A fromzp->zc_fromobj = dr->ddr_first;
2N/A }
2N/A fromzp->zc_obj = dr->ddr_last;
2N/A fromzp->zc_cookie = 0;
2N/A buffer_fromds = (void *)(uintptr_t)fromzp->zc_stat_buf;
2N/A from_o = fromzp->zc_fromobj;
2N/A
2N/A while (from_o <= dr->ddr_last) {
2N/A if (fromzp->zc_cookie == 0) {
2N/A /* Get next stats chunk from the "from" dataset */
2N/A if (err = ioctl(di->zhp->zfs_hdl->libzfs_fd,
2N/A ZFS_IOC_BULK_OBJ_TO_STATS, fromzp)) {
2N/A return (err);
2N/A }
2N/A zs_from = (zfs_stat_t *)(uintptr_t)buffer_fromds;
2N/A }
2N/A
2N/A if (fromzp->zc_cookie == 0)
2N/A break;
2N/A from_o = zs_from->zs_obj;
2N/A
2N/A err = describe_free(di, zs_from->zs_obj,
2N/A zs_from, (zfs_stat_t *)(uintptr_t)buffer_fromds,
2N/A fobjname, MAXPATHLEN);
2N/A zs_from++;
2N/A fromzp->zc_cookie--;
2N/A
2N/A if (err) {
2N/A return (err);
2N/A }
2N/A }
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic void *
2N/Adiffer(void *arg)
2N/A{
2N/A zfs_cmd_t tozc, fromzc;
2N/A differ_info_t *di = arg;
2N/A dmu_diff_record_t dr;
2N/A int err;
2N/A
2N/A err = setup_diffs(di, &fromzc, &tozc);
2N/A if (err != 0)
2N/A return ((void *)-1);
2N/A
2N/A for (;;) {
2N/A char *cp = (char *)&dr;
2N/A int len = sizeof (dr);
2N/A int rv;
2N/A
2N/A do {
2N/A rv = read(di->datafd, cp, len);
2N/A cp += rv;
2N/A len -= rv;
2N/A } while (len > 0 && rv > 0);
2N/A
2N/A if (rv < 0 || (rv == 0 && len != sizeof (dr))) {
2N/A di->zerr = EPIPE;
2N/A break;
2N/A } else if (rv == 0) {
2N/A /* end of file at a natural breaking point */
2N/A break;
2N/A }
2N/A
2N/A switch (dr.ddr_type) {
2N/A case DDR_FREE:
2N/A err = write_free_diffs(di, &dr, &fromzc);
2N/A break;
2N/A case DDR_INUSE:
2N/A err = write_inuse_diffs(di, &dr, &fromzc, &tozc);
2N/A break;
2N/A default:
2N/A di->zerr = EPIPE;
2N/A break;
2N/A }
2N/A
2N/A if (err || di->zerr)
2N/A break;
2N/A }
2N/A
2N/A teardown_diffs(di, &fromzc, &tozc);
2N/A
2N/A (void) close(di->datafd);
2N/A if (err)
2N/A return ((void *)-1);
2N/A if (di->zerr) {
2N/A ASSERT(di->zerr == EINVAL);
2N/A (void) snprintf(di->errbuf, sizeof (di->errbuf),
2N/A dgettext(TEXT_DOMAIN,
2N/A "Internal error: bad data from diff IOCTL"));
2N/A return ((void *)-1);
2N/A }
2N/A return ((void *)0);
2N/A}
2N/A
2N/Astatic void
2N/Afind_shares_object(differ_info_t *di)
2N/A{
2N/A char fullpath[MAXPATHLEN];
2N/A struct stat64 sb = { 0 };
2N/A
2N/A (void) strlcpy(fullpath, di->dsmnt, MAXPATHLEN);
2N/A (void) strlcat(fullpath, ZDIFF_SHARESDIR, MAXPATHLEN);
2N/A
2N/A if (stat64(fullpath, &sb) == 0)
2N/A di->shares = (uint64_t)sb.st_ino;
2N/A else
2N/A /* some very old pools won't have shares object */
2N/A di->shares = (uint64_t)-1;
2N/A}
2N/A
2N/Astatic int
2N/Amake_temp_snapshot(differ_info_t *di)
2N/A{
2N/A libzfs_handle_t *hdl = di->zhp->zfs_hdl;
2N/A zfs_cmd_t zc = { 0 };
2N/A
2N/A (void) snprintf(zc.zc_value, sizeof (zc.zc_value),
2N/A ZDIFF_PREFIX, getpid());
2N/A (void) strlcpy(zc.zc_name, di->ds, sizeof (zc.zc_name));
2N/A zc.zc_cleanup_fd = di->cleanupfd;
2N/A
2N/A if (ioctl(hdl->libzfs_fd, ZFS_IOC_TMP_SNAPSHOT, &zc) != 0) {
2N/A int err = errno;
2N/A if (err == EPERM) {
2N/A (void) snprintf(di->errbuf, sizeof (di->errbuf),
2N/A dgettext(TEXT_DOMAIN, "The diff delegated "
2N/A "permission is needed in order\nto create a "
2N/A "just-in-time snapshot for diffing\n"));
2N/A return (zfs_error(hdl, EZFS_DIFF, di->errbuf));
2N/A } else {
2N/A (void) snprintf(di->errbuf, sizeof (di->errbuf),
2N/A dgettext(TEXT_DOMAIN, "Cannot create just-in-time "
2N/A "snapshot of '%s'"), zc.zc_name);
2N/A return (zfs_standard_error(hdl, NULL,
2N/A ZFS_TYPE_SNAPSHOT, err, di->errbuf));
2N/A }
2N/A }
2N/A
2N/A di->tmpsnap = zfs_strdup(hdl, zc.zc_value);
2N/A di->tosnap = zfs_asprintf(hdl, "%s@%s", di->ds, di->tmpsnap);
2N/A return (0);
2N/A}
2N/A
2N/Astatic void
2N/Ateardown_differ_info(differ_info_t *di)
2N/A{
2N/A free(di->ds);
2N/A free(di->dsmnt);
2N/A free(di->fromsnap);
2N/A free(di->frommnt);
2N/A free(di->tosnap);
2N/A free(di->tmpsnap);
2N/A free(di->tomnt);
2N/A (void) close(di->cleanupfd);
2N/A}
2N/A
2N/Astatic int
2N/Aget_snapshot_names(differ_info_t *di, const char *fromsnap,
2N/A const char *tosnap)
2N/A{
2N/A libzfs_handle_t *hdl = di->zhp->zfs_hdl;
2N/A char *atptrf = NULL;
2N/A char *atptrt = NULL;
2N/A int fdslen, fsnlen;
2N/A int tdslen, tsnlen;
2N/A
2N/A /*
2N/A * Can accept in the from-epoch case
2N/A * dataset
2N/A * dataset@snap1
2N/A */
2N/A if (fromsnap == NULL) {
2N/A di->fromsnap = NULL;
2N/A atptrt = strchr(tosnap, '@');
2N/A tdslen = atptrt ? atptrt - tosnap : strlen(tosnap);
2N/A di->ds = zfs_strdup(hdl, tosnap);
2N/A di->ds[tdslen] = '\0';
2N/A if (atptrt == NULL) {
2N/A return (make_temp_snapshot(di));
2N/A } else {
2N/A di->tosnap = zfs_strdup(hdl, tosnap);
2N/A return (0);
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * Can accept
2N/A * dataset@snap1
2N/A * dataset@snap1 dataset@snap2
2N/A * dataset@snap1 @snap2
2N/A * dataset@snap1 dataset
2N/A * @snap1 dataset@snap2
2N/A */
2N/A if (tosnap == NULL) {
2N/A /* only a from snapshot given, must be valid */
2N/A (void) snprintf(di->errbuf, sizeof (di->errbuf),
2N/A dgettext(TEXT_DOMAIN,
2N/A "Badly formed snapshot name %s"), fromsnap);
2N/A
2N/A if (!zfs_validate_name(hdl, fromsnap, ZFS_TYPE_SNAPSHOT,
2N/A B_FALSE)) {
2N/A return (zfs_error(hdl, EZFS_INVALIDNAME,
2N/A di->errbuf));
2N/A }
2N/A
2N/A atptrf = strchr(fromsnap, '@');
2N/A ASSERT(atptrf != NULL);
2N/A fdslen = atptrf - fromsnap;
2N/A
2N/A di->fromsnap = zfs_strdup(hdl, fromsnap);
2N/A di->ds = zfs_strdup(hdl, fromsnap);
2N/A di->ds[fdslen] = '\0';
2N/A
2N/A /* the to snap will be a just-in-time snap of the head */
2N/A return (make_temp_snapshot(di));
2N/A }
2N/A
2N/A (void) snprintf(di->errbuf, sizeof (di->errbuf),
2N/A dgettext(TEXT_DOMAIN, "diff error"));
2N/A
2N/A atptrf = strchr(fromsnap, '@');
2N/A atptrt = strchr(tosnap, '@');
2N/A fdslen = atptrf ? atptrf - fromsnap : strlen(fromsnap);
2N/A tdslen = atptrt ? atptrt - tosnap : strlen(tosnap);
2N/A fsnlen = strlen(fromsnap) - fdslen; /* includes @ sign */
2N/A tsnlen = strlen(tosnap) - tdslen; /* includes @ sign */
2N/A
2N/A if (fsnlen <= 1 || tsnlen == 1 || (fdslen == 0 && tdslen == 0) ||
2N/A (fsnlen == 0 && tsnlen == 0)) {
2N/A zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
2N/A "Acceptable requests are"
2N/A "\n\tdataset@snap1\n\tdataset@snap1 dataset@snap2"
2N/A "\n\tdataset@snap1 @snap2\n\tdataset@snap1 dataset"
2N/A "\n\t@snap1 dataset@snap2"));
2N/A return (zfs_error(hdl, EZFS_DIFF, di->errbuf));
2N/A } else if ((fdslen > 0 && tdslen > 0) &&
2N/A ((tdslen != fdslen || strncmp(fromsnap, tosnap, fdslen) != 0))) {
2N/A /*
2N/A * not the same dataset name, might be okay if
2N/A * tosnap is a clone of a fromsnap descendant.
2N/A */
2N/A char origin[ZFS_MAXNAMELEN];
2N/A zprop_source_t src;
2N/A zfs_handle_t *zhp;
2N/A
2N/A libzfs_print_on_error(hdl, B_FALSE);
2N/A
2N/A di->ds = zfs_alloc(di->zhp->zfs_hdl, tdslen + 1);
2N/A (void) strncpy(di->ds, tosnap, tdslen);
2N/A di->ds[tdslen] = '\0';
2N/A
2N/A zhp = zfs_open(hdl, di->ds, ZFS_TYPE_FILESYSTEM);
2N/A while (zhp != NULL) {
2N/A int err;
2N/A err = zfs_prop_get(zhp, ZFS_PROP_ORIGIN,
2N/A origin, sizeof (origin), &src, NULL, 0, B_FALSE);
2N/A
2N/A if (err != 0) {
2N/A zfs_close(zhp);
2N/A zhp = NULL;
2N/A break;
2N/A } else if (strncmp(origin, fromsnap, fsnlen) == 0) {
2N/A break;
2N/A }
2N/A (void) zfs_close(zhp);
2N/A zhp = zfs_open(hdl, origin, ZFS_TYPE_FILESYSTEM);
2N/A }
2N/A
2N/A libzfs_print_on_error(hdl, B_TRUE);
2N/A
2N/A if (zhp == NULL) {
2N/A zfs_error_aux(hdl, dgettext(TEXT_DOMAIN,
2N/A "%s is not a descendant dataset of %.*s\n"),
2N/A di->ds, fdslen, fromsnap);
2N/A return (zfs_error(hdl, EZFS_DIFF, di->errbuf));
2N/A } else {
2N/A (void) zfs_close(zhp);
2N/A }
2N/A
2N/A di->isclone = B_TRUE;
2N/A di->fromsnap = zfs_strdup(hdl, fromsnap);
2N/A if (tsnlen) {
2N/A di->tosnap = zfs_strdup(hdl, tosnap);
2N/A } else {
2N/A return (make_temp_snapshot(di));
2N/A }
2N/A } else {
2N/A int dslen = fdslen ? fdslen : tdslen;
2N/A
2N/A di->ds = zfs_alloc(hdl, dslen + 1);
2N/A (void) strncpy(di->ds, fdslen ? fromsnap : tosnap, dslen);
2N/A di->ds[dslen] = '\0';
2N/A
2N/A di->fromsnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrf);
2N/A if (tsnlen) {
2N/A di->tosnap = zfs_asprintf(hdl, "%s%s", di->ds, atptrt);
2N/A } else {
2N/A return (make_temp_snapshot(di));
2N/A }
2N/A }
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Aget_mountpoint(differ_info_t *di, char *dsnm, char **mntpt)
2N/A{
2N/A boolean_t mounted;
2N/A
2N/A mounted = is_mounted(di->zhp->zfs_hdl, dsnm, mntpt);
2N/A if (mounted == B_FALSE) {
2N/A (void) snprintf(di->errbuf, sizeof (di->errbuf),
2N/A dgettext(TEXT_DOMAIN,
2N/A "Cannot diff an unmounted snapshot"));
2N/A return (zfs_error(di->zhp->zfs_hdl, EZFS_BADTYPE, di->errbuf));
2N/A }
2N/A
2N/A /* Avoid a double slash at the beginning of root-mounted datasets */
2N/A if (**mntpt == '/' && *(*mntpt + 1) == '\0')
2N/A **mntpt = '\0';
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Aget_mountpoints(differ_info_t *di)
2N/A{
2N/A char *strptr;
2N/A char *frommntpt;
2N/A
2N/A /*
2N/A * first get the mountpoint for the parent dataset
2N/A */
2N/A if (get_mountpoint(di, di->ds, &di->dsmnt) != 0)
2N/A return (-1);
2N/A
2N/A strptr = strchr(di->tosnap, '@');
2N/A ASSERT3P(strptr, !=, NULL);
2N/A di->tomnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", di->dsmnt,
2N/A ZDIFF_SNAPDIR, ++strptr);
2N/A
2N/A if (di->frombase)
2N/A return (0);
2N/A
2N/A strptr = strchr(di->fromsnap, '@');
2N/A ASSERT3P(strptr, !=, NULL);
2N/A
2N/A frommntpt = di->dsmnt;
2N/A if (di->isclone) {
2N/A char *mntpt;
2N/A int err;
2N/A
2N/A *strptr = '\0';
2N/A err = get_mountpoint(di, di->fromsnap, &mntpt);
2N/A *strptr = '@';
2N/A if (err != 0)
2N/A return (-1);
2N/A frommntpt = mntpt;
2N/A }
2N/A
2N/A di->frommnt = zfs_asprintf(di->zhp->zfs_hdl, "%s%s%s", frommntpt,
2N/A ZDIFF_SNAPDIR, ++strptr);
2N/A
2N/A if (di->isclone)
2N/A free(frommntpt);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Asetup_differ_info(zfs_handle_t *zhp, const char *fromsnap,
2N/A const char *tosnap, differ_info_t *di)
2N/A{
2N/A di->zhp = zhp;
2N/A
2N/A di->cleanupfd = open(ZFS_DEV, O_RDWR|O_EXCL);
2N/A VERIFY(di->cleanupfd >= 0);
2N/A
2N/A if (get_snapshot_names(di, fromsnap, tosnap) != 0)
2N/A return (-1);
2N/A
2N/A if (get_mountpoints(di) != 0)
2N/A return (-1);
2N/A
2N/A find_shares_object(di);
2N/A
2N/A return (0);
2N/A}
2N/A
2N/Aint
2N/Azfs_scan_diffs(zfs_handle_t *zhp, const char *fromsnap,
2N/A const char *tosnap, int flags, diff_scan_cb_t *cb_func, void *cb_arg)
2N/A{
2N/A zfs_cmd_t zc = { 0 };
2N/A char errbuf[1024];
2N/A differ_info_t di = { 0 };
2N/A pthread_t tid;
2N/A int pipefd[2];
2N/A int iocerr;
2N/A
2N/A (void) snprintf(errbuf, sizeof (errbuf),
2N/A dgettext(TEXT_DOMAIN, "zfs diff failed"));
2N/A
2N/A di.frombase = (flags & ZFS_DIFF_BASE) != 0;
2N/A di.enumerate_only = ((flags & ZFS_DIFF_ENUMERATE) != 0) ||
2N/A di.frombase;
2N/A
2N/A di.needname = ! (flags & ZFS_DIFF_NONAME);
2N/A di.needoldname = ! (flags & ZFS_DIFF_NOOLDNAME);
2N/A
2N/A di.cb_func = cb_func;
2N/A di.cb_arg = cb_arg;
2N/A
2N/A if (setup_differ_info(zhp, fromsnap, tosnap, &di)) {
2N/A teardown_differ_info(&di);
2N/A return (-1);
2N/A }
2N/A
2N/A if (pipe(pipefd)) {
2N/A zfs_error_aux(zhp->zfs_hdl, strerror(errno));
2N/A teardown_differ_info(&di);
2N/A return (zfs_error(zhp->zfs_hdl, EZFS_PIPEFAILED, errbuf));
2N/A }
2N/A
2N/A di.datafd = pipefd[0];
2N/A
2N/A if (pthread_create(&tid, NULL, differ, &di)) {
2N/A zfs_error_aux(zhp->zfs_hdl, strerror(errno));
2N/A (void) close(pipefd[0]);
2N/A (void) close(pipefd[1]);
2N/A teardown_differ_info(&di);
2N/A return (zfs_error(zhp->zfs_hdl,
2N/A EZFS_THREADCREATEFAILED, errbuf));
2N/A }
2N/A
2N/A /* do the ioctl() */
2N/A if (di.fromsnap)
2N/A (void) strlcpy(zc.zc_value, di.fromsnap,
2N/A strlen(di.fromsnap) + 1);
2N/A (void) strlcpy(zc.zc_name, di.tosnap, strlen(di.tosnap) + 1);
2N/A zc.zc_cookie = pipefd[1];
2N/A
2N/A iocerr = ioctl(zhp->zfs_hdl->libzfs_fd, ZFS_IOC_DIFF, &zc);
2N/A if (iocerr != 0) {
2N/A (void) snprintf(errbuf, sizeof (errbuf),
2N/A dgettext(TEXT_DOMAIN, "Unable to obtain diffs"));
2N/A if (errno == EPERM) {
2N/A zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
2N/A "\n The sys_mount privilege or diff delegated "
2N/A "permission is needed\n to execute the "
2N/A "diff ioctl"));
2N/A } else if (errno == EXDEV) {
2N/A zfs_error_aux(zhp->zfs_hdl, dgettext(TEXT_DOMAIN,
2N/A "%s is not a descendant dataset of %s\n"),
2N/A di.tosnap, di.fromsnap);
2N/A } else if (errno != EPIPE || di.zerr == 0) {
2N/A zfs_error_aux(zhp->zfs_hdl, strerror(errno));
2N/A }
2N/A (void) close(pipefd[1]);
2N/A (void) pthread_cancel(tid);
2N/A (void) pthread_join(tid, NULL);
2N/A teardown_differ_info(&di);
2N/A if (di.zerr != 0 && di.zerr != EPIPE) {
2N/A zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr));
2N/A return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, errbuf));
2N/A } else {
2N/A return (zfs_error(zhp->zfs_hdl, EZFS_DIFFDATA, errbuf));
2N/A }
2N/A }
2N/A
2N/A (void) close(pipefd[1]);
2N/A (void) pthread_join(tid, NULL);
2N/A
2N/A if (di.zerr != 0) {
2N/A zfs_error_aux(zhp->zfs_hdl, strerror(di.zerr));
2N/A return (zfs_error(zhp->zfs_hdl, EZFS_DIFF, errbuf));
2N/A }
2N/A teardown_differ_info(&di);
2N/A return (0);
2N/A}
2N/A
2N/A/* The rest of this file is support for zfs_show_diffs */
2N/A
2N/Astatic char *getname(uid_t);
2N/Astatic char *getgroup(gid_t);
2N/A
2N/Atypedef enum {
2N/A ZFIELD_NAME = 0,
2N/A ZFIELD_SIZE,
2N/A ZFIELD_CTIME,
2N/A ZFIELD_ATIME,
2N/A ZFIELD_MTIME,
2N/A ZFIELD_CRTIME,
2N/A ZFIELD_DELTA,
2N/A ZFIELD_UID,
2N/A ZFIELD_GID,
2N/A ZFIELD_LINKS,
2N/A ZFIELD_INODE,
2N/A ZFIELD_PARENT_INODE,
2N/A ZFIELD_OLD_NAME,
2N/A /* last item */
2N/A ZFIELD_MAX_FIELD
2N/A} zdiff_field_t;
2N/A
2N/Achar *zdiff_field_names[] = {
2N/A "name",
2N/A "size",
2N/A "ctime",
2N/A "atime",
2N/A "mtime",
2N/A "crtime",
2N/A "linkschange",
2N/A "user",
2N/A "group",
2N/A "links",
2N/A "object",
2N/A "parent",
2N/A "oldname"
2N/A};
2N/A
2N/Atypedef struct zdiff_info {
2N/A int zerr;
2N/A FILE *pi_fp;
2N/A diff_flags_t zi_flags;
2N/A int nfields;
2N/A zdiff_field_t fields[ZFIELD_MAX_FIELD];
2N/A char errbuf[1024];
2N/A} zdiff_info_t;
2N/A
2N/A/*
2N/A * stream_bytes
2N/A *
2N/A * Prints a file name out a character at a time. If the character is
2N/A * not in the range of what we consider "printable" ASCII, display it
2N/A * as an escaped 3-digit octal value. ASCII values less than a space
2N/A * are all control characters and we declare the upper end as the
2N/A * DELete character. This also is the last 7-bit ASCII character.
2N/A * We choose to treat all 8-bit ASCII as not printable for this
2N/A * application.
2N/A */
2N/Astatic void
2N/Astream_bytes(FILE *fp, const char *string)
2N/A{
2N/A
2N/A
2N/A while (*string) {
2N/A if (*string > ' ' && *string != '\\' && *string < '\177')
2N/A (void) fprintf(fp, "%c", *string++);
2N/A else
2N/A (void) fprintf(fp, "\\%03o", (unsigned char)*string++);
2N/A }
2N/A}
2N/A
2N/Aint
2N/Aget_what(zfs_stat_t *isb)
2N/A{
2N/A mode_t what = isb->zs_mode;
2N/A int symbol;
2N/A
2N/A switch (what & S_IFMT) {
2N/A case S_IFBLK:
2N/A symbol = 'B';
2N/A break;
2N/A case S_IFCHR:
2N/A symbol = 'C';
2N/A break;
2N/A case S_IFDIR:
2N/A symbol = '/';
2N/A break;
2N/A case S_IFDOOR:
2N/A symbol = '>';
2N/A break;
2N/A case S_IFIFO:
2N/A symbol = '|';
2N/A break;
2N/A case S_IFLNK:
2N/A symbol = '@';
2N/A break;
2N/A case S_IFPORT:
2N/A symbol = 'P';
2N/A break;
2N/A case S_IFSOCK:
2N/A symbol = '=';
2N/A break;
2N/A case S_IFREG:
2N/A symbol = 'F';
2N/A break;
2N/A default:
2N/A symbol = '?';
2N/A break;
2N/A }
2N/A return (symbol);
2N/A}
2N/A
2N/Astatic void
2N/Aprint_what(FILE *fp, zfs_stat_t *isb)
2N/A{
2N/A int symbol = get_what(isb);
2N/A
2N/A (void) fprintf(fp, "%c", (char)symbol);
2N/A}
2N/A
2N/Astatic void
2N/Aprint_cmn(FILE *fp, char *dsmnt, const char *file)
2N/A{
2N/A stream_bytes(fp, dsmnt);
2N/A stream_bytes(fp, file);
2N/A}
2N/A
2N/Astatic void
2N/Aprint_fields(FILE *fp, zdiff_info_t *di, char *dsmnt, zfs_stat_t *isb,
2N/A const char *oldfile, const char *file, uint64_t obj, int delta)
2N/A
2N/A{
2N/A int i;
2N/A zdiff_field_t field;
2N/A
2N/A for (i = 0; i < di->nfields; i++) {
2N/A if (i > 0) {
2N/A (void) fprintf(fp, "\t");
2N/A }
2N/A field = di->fields[i];
2N/A switch (field) {
2N/A case ZFIELD_NAME:
2N/A print_cmn(fp, dsmnt, file);
2N/A break;
2N/A case ZFIELD_OLD_NAME:
2N/A if (oldfile)
2N/A print_cmn(fp, dsmnt, oldfile);
2N/A else
2N/A (void) fprintf(fp, "-");
2N/A break;
2N/A case ZFIELD_DELTA:
2N/A (void) fprintf(fp, "%+d", delta);
2N/A break;
2N/A case ZFIELD_INODE:
2N/A (void) fprintf(fp, "%llu", (longlong_t)obj);
2N/A break;
2N/A case ZFIELD_PARENT_INODE:
2N/A (void) fprintf(fp, "%llu", (longlong_t)isb->zs_parent);
2N/A break;
2N/A case ZFIELD_LINKS:
2N/A (void) fprintf(fp, "%llu", (longlong_t)isb->zs_links);
2N/A break;
2N/A case ZFIELD_SIZE:
2N/A (void) fprintf(fp, "%llu", (longlong_t)isb->zs_size);
2N/A break;
2N/A case ZFIELD_UID:
2N/A (void) fprintf(fp, "%s", getname(isb->zs_uid));
2N/A break;
2N/A case ZFIELD_GID:
2N/A (void) fprintf(fp, "%s", getgroup(isb->zs_gid));
2N/A break;
2N/A case ZFIELD_ATIME:
2N/A (void) fprintf(fp, "%10lld.%09lld",
2N/A (longlong_t)isb->zs_atime[0],
2N/A (longlong_t)isb->zs_atime[1]);
2N/A break;
2N/A case ZFIELD_CTIME:
2N/A (void) fprintf(fp, "%10lld.%09lld",
2N/A (longlong_t)isb->zs_ctime[0],
2N/A (longlong_t)isb->zs_ctime[1]);
2N/A break;
2N/A case ZFIELD_MTIME:
2N/A (void) fprintf(fp, "%10lld.%09lld",
2N/A (longlong_t)isb->zs_mtime[0],
2N/A (longlong_t)isb->zs_mtime[1]);
2N/A break;
2N/A case ZFIELD_CRTIME:
2N/A (void) fprintf(fp, "%10lld.%09lld",
2N/A (longlong_t)isb->zs_crtime[0],
2N/A (longlong_t)isb->zs_crtime[1]);
2N/A break;
2N/A }
2N/A }
2N/A}
2N/A
2N/Astatic int
2N/Asetup_fields(zdiff_info_t *di, nvlist_t *fields)
2N/A{
2N/A nvpair_t *elem;
2N/A char *propstr;
2N/A int count, j;
2N/A
2N/A /* Process each field */
2N/A elem = NULL;
2N/A count = 0;
2N/A while ((elem = nvlist_next_nvpair(fields, elem)) != NULL) {
2N/A propstr = nvpair_name(elem);
2N/A for (j = 0; j < ZFIELD_MAX_FIELD; j++) {
2N/A if (0 == strcmp(propstr, zdiff_field_names[j])) {
2N/A /* same name */
2N/A break;
2N/A }
2N/A }
2N/A if (j == ZFIELD_MAX_FIELD || count >= ZFIELD_MAX_FIELD)
2N/A goto failfield;
2N/A di->fields[count] = j;
2N/A count++;
2N/A }
2N/A di->nfields = count;
2N/A return (0);
2N/A
2N/Afailfield:
2N/A di->zerr = EZFS_BADPROP;
2N/A (void) snprintf(di->errbuf, sizeof (di->errbuf),
2N/A dgettext(TEXT_DOMAIN, "illegal field %s"), propstr);
2N/A return (-1);
2N/A}
2N/A
2N/Astatic void
2N/Aprint_rename(FILE *fp, zdiff_info_t *di, char *dsmnt, const char *old,
2N/A const char *new, zfs_stat_t *isb, uint64_t obj, int delta)
2N/A{
2N/A if (di->zi_flags & ZFS_DIFF_TIMESTAMP)
2N/A (void) fprintf(fp, "%10lld.%09lld\t",
2N/A (longlong_t)isb->zs_ctime[0],
2N/A (longlong_t)isb->zs_ctime[1]);
2N/A (void) fprintf(fp, "%c\t", ZDIFF_RENAMED);
2N/A if (di->zi_flags & ZFS_DIFF_CLASSIFY) {
2N/A print_what(fp, isb);
2N/A (void) fprintf(fp, "\t");
2N/A }
2N/A if (di->nfields) {
2N/A print_fields(fp, di, dsmnt, isb, old, new, obj, delta);
2N/A } else {
2N/A print_cmn(fp, dsmnt, old);
2N/A if (di->zi_flags & ZFS_DIFF_PARSEABLE)
2N/A (void) fprintf(fp, "\t");
2N/A else
2N/A (void) fprintf(fp, " -> ");
2N/A print_cmn(fp, dsmnt, new);
2N/A if (delta != 0)
2N/A (void) fprintf(fp, "\t(%+d)", delta);
2N/A }
2N/A (void) fprintf(fp, "\n");
2N/A}
2N/A
2N/Astatic void
2N/Aprint_file(FILE *fp, zdiff_info_t *di, char *dsmnt, char type,
2N/A const char *file, zfs_stat_t *isb, uint64_t obj, int delta)
2N/A{
2N/A if (di->zi_flags & ZFS_DIFF_TIMESTAMP)
2N/A (void) fprintf(fp, "%10lld.%09lld\t",
2N/A (longlong_t)isb->zs_ctime[0],
2N/A (longlong_t)isb->zs_ctime[1]);
2N/A (void) fprintf(fp, "%c\t", type);
2N/A if (di->zi_flags & ZFS_DIFF_CLASSIFY) {
2N/A print_what(fp, isb);
2N/A (void) fprintf(fp, "\t");
2N/A }
2N/A if (di->nfields) {
2N/A print_fields(fp, di, dsmnt, isb, NULL, file, obj, delta);
2N/A } else {
2N/A print_cmn(fp, dsmnt, file);
2N/A if (delta != 0)
2N/A (void) fprintf(fp, "\t(%+d)", delta);
2N/A }
2N/A (void) fprintf(fp, "\n");
2N/A}
2N/A
2N/A/*ARGSUSED*/
2N/Avoid
2N/Azdiff_print_cb(char *dsmnt, uint64_t obj,
2N/A char type, char *fobjname, char *tobjname, zfs_stat_t *fsp, zfs_stat_t *tsp,
2N/A int delta, void *arg)
2N/A{
2N/A
2N/A zdiff_info_t *di = arg;
2N/A FILE *fp = di->pi_fp;
2N/A
2N/A switch (type) {
2N/A case ZDIFF_MODIFIED:
2N/A if (0 == strcmp(fobjname, tobjname)) {
2N/A /* same name */
2N/A print_file(fp, di, dsmnt, ZDIFF_MODIFIED, tobjname,
2N/A tsp, obj, delta);
2N/A } else {
2N/A print_rename(fp, di, dsmnt, fobjname, tobjname,
2N/A tsp, obj, delta);
2N/A }
2N/A break;
2N/A case ZDIFF_REMOVED:
2N/A print_file(fp, di, dsmnt, type, fobjname, fsp, obj, delta);
2N/A break;
2N/A case ZDIFF_ADDED:
2N/A print_file(fp, di, dsmnt, type, tobjname, tsp, obj, delta);
2N/A }
2N/A}
2N/A
2N/Aint
2N/Azfs_show_diffs(zfs_handle_t *zhp, int outfd, const char *fromsnap,
2N/A const char *tosnap, nvlist_t *fields, int flags)
2N/A{
2N/A int rc;
2N/A zdiff_info_t di = {0};
2N/A libzfs_handle_t *hdl = zhp->zfs_hdl;
2N/A
2N/A if ((di.pi_fp = fdopen(outfd, "w")) == NULL) {
2N/A di.zerr = errno;
2N/A (void) snprintf(di.errbuf, sizeof (di.errbuf),
2N/A dgettext(TEXT_DOMAIN, "zfs diff failed"));
2N/A return (zfs_standard_error(hdl, NULL,
2N/A ZFS_TYPE_FILESYSTEM, di.zerr, di.errbuf));
2N/A }
2N/A
2N/A if (fields != NULL && setup_fields(&di, fields) != 0) {
2N/A /* di.errbuf is already set up */
2N/A return (zfs_error(hdl, di.zerr, di.errbuf));
2N/A }
2N/A
2N/A if (flags & (ZFS_DIFF_ENUMERATE | ZFS_DIFF_BASE)) {
2N/A /*
2N/A * In normal (non-enumerate) mode, we always need both names
2N/A * (to distinguish rename from modify). In enumerate mode,
2N/A * we need just the new name. In enumerate mode with specific
2N/A * fields, we only need the names if they ask for it.
2N/A */
2N/A if (di.nfields == 0) {
2N/A flags |= ZFS_DIFF_NOOLDNAME;
2N/A } else {
2N/A int i;
2N/A boolean_t needname = B_FALSE, needoldname = B_FALSE;
2N/A for (i = 0; i < di.nfields; i++) {
2N/A zdiff_field_t field;
2N/A field = di.fields[i];
2N/A if (field == ZFIELD_NAME)
2N/A needname = B_TRUE;
2N/A if (field == ZFIELD_OLD_NAME)
2N/A needoldname = B_TRUE;
2N/A }
2N/A if (!needname)
2N/A flags |= ZFS_DIFF_NONAME;
2N/A if (!needoldname)
2N/A flags |= ZFS_DIFF_NOOLDNAME;
2N/A }
2N/A }
2N/A
2N/A di.zi_flags = flags;
2N/A rc = zfs_scan_diffs(zhp, fromsnap, tosnap, flags, zdiff_print_cb, &di);
2N/A
2N/A (void) fclose(di.pi_fp);
2N/A return (rc);
2N/A}
2N/A
2N/A#include <pwd.h>
2N/A#include <grp.h>
2N/A#include <utmpx.h>
2N/A
2N/Astruct utmpx utmp;
2N/A
2N/Astatic uid_t lastuid = (uid_t)-1;
2N/Astatic gid_t lastgid = (gid_t)-1;
2N/Astatic char *lastuname = NULL;
2N/Astatic char *lastgname = NULL;
2N/A
2N/A#define NMAX (sizeof (utmp.ut_name))
2N/A#define SCPYN(a, b) (void) strncpy(a, b, NMAX)
2N/A
2N/A
2N/Astruct cachenode { /* this struct must be zeroed before using */
2N/A struct cachenode *lesschild; /* subtree whose entries < val */
2N/A struct cachenode *grtrchild; /* subtree whose entries > val */
2N/A long val; /* the uid or gid of this entry */
2N/A int initted; /* name has been filled in */
2N/A char name[NMAX+1]; /* the string that val maps to */
2N/A};
2N/Astatic struct cachenode *names, *groups;
2N/A
2N/Astatic struct cachenode *
2N/Afindincache(struct cachenode **head, long val)
2N/A{
2N/A struct cachenode **parent = head;
2N/A struct cachenode *c = *parent;
2N/A
2N/A while (c != NULL) {
2N/A if (val == c->val) {
2N/A /* found it */
2N/A return (c);
2N/A } else if (val < c->val) {
2N/A parent = &c->lesschild;
2N/A c = c->lesschild;
2N/A } else {
2N/A parent = &c->grtrchild;
2N/A c = c->grtrchild;
2N/A }
2N/A }
2N/A
2N/A /* not in the cache, make a new entry for it */
2N/A c = calloc(1, sizeof (struct cachenode));
2N/A if (c == NULL) {
2N/A perror("ls");
2N/A exit(2);
2N/A }
2N/A *parent = c;
2N/A c->val = val;
2N/A return (c);
2N/A}
2N/A
2N/A/*
2N/A * get name from cache, or passwd file for a given uid;
2N/A * lastuid is set to uid.
2N/A */
2N/Astatic char *
2N/Agetname(uid_t uid)
2N/A{
2N/A struct passwd *pwent;
2N/A struct cachenode *c;
2N/A
2N/A if ((uid == lastuid) && lastuname)
2N/A return (lastuname);
2N/A
2N/A c = findincache(&names, uid);
2N/A if (c->initted == 0) {
2N/A if ((pwent = getpwuid(uid)) != NULL) {
2N/A SCPYN(&c->name[0], pwent->pw_name);
2N/A } else {
2N/A (void) sprintf(&c->name[0], "%-8u", (int)uid);
2N/A }
2N/A c->initted = 1;
2N/A }
2N/A lastuid = uid;
2N/A lastuname = &c->name[0];
2N/A return (lastuname);
2N/A}
2N/A
2N/A/*
2N/A * get name from cache, or group file for a given gid;
2N/A * lastgid is set to gid.
2N/A */
2N/Astatic char *
2N/Agetgroup(gid_t gid)
2N/A{
2N/A struct group *grent;
2N/A struct cachenode *c;
2N/A
2N/A if ((gid == lastgid) && lastgname)
2N/A return (lastgname);
2N/A
2N/A c = findincache(&groups, gid);
2N/A if (c->initted == 0) {
2N/A if ((grent = getgrgid(gid)) != NULL) {
2N/A SCPYN(&c->name[0], grent->gr_name);
2N/A } else {
2N/A (void) sprintf(&c->name[0], "%-8u", (int)gid);
2N/A }
2N/A c->initted = 1;
2N/A }
2N/A lastgid = gid;
2N/A lastgname = &c->name[0];
2N/A return (lastgname);
2N/A}