Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 138 additions & 3 deletions cmd/zfs/zfs_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ get_usage(zfs_help_t idx)
case HELP_RELEASE:
return (gettext("\trelease [-r] <tag> <snapshot> ...\n"));
case HELP_DIFF:
return (gettext("\tdiff [-FHth] <snapshot> "
return (gettext("\tdiff [-FHthj] <snapshot> "
"[snapshot|filesystem]\n"));
case HELP_BOOKMARK:
return (gettext("\tbookmark <snapshot|bookmark> "
Expand Down Expand Up @@ -8064,6 +8064,128 @@ find_command_idx(const char *command, int *idx)
return (1);
}

typedef struct diff_json_cbdata {
boolean_t timestamped;
} diff_json_cbdata_t;

/*
* Populate the "changes" sub-object of a diff JSON entry with the specific
* attributes that changed: rename destination path, nlink {from,to},
* mode {from,to}, and ctime {from,to}. The object is omitted entirely when
* there is nothing to report.
*/
static void
diff_json_add_changes(nvlist_t *nvl, zfs_diff_entry_t *entry,
boolean_t timestamped)
{
if (!timestamped && entry->de_type != ZFS_DIFF_RENAMED &&
entry->de_nlink_from == 0 && entry->de_mode_from == 0)
return;

nvlist_t *changes = fnvlist_alloc();

if (entry->de_type == ZFS_DIFF_RENAMED)
fnvlist_add_string(changes, "path", entry->de_path);
if (entry->de_nlink_from != 0) {
nvlist_t *nlink = fnvlist_alloc();
fnvlist_add_uint64(nlink, "from", entry->de_nlink_from);
fnvlist_add_uint64(nlink, "to", entry->de_nlink);
fnvlist_add_nvlist(changes, "nlink", nlink);
fnvlist_free(nlink);
}
if (entry->de_mode_from != 0) {
nvlist_t *mode = fnvlist_alloc();
fnvlist_add_uint64(mode, "from", entry->de_mode_from);
fnvlist_add_uint64(mode, "to", entry->de_mode);
fnvlist_add_nvlist(changes, "mode", mode);
fnvlist_free(mode);
}
if (timestamped) {
nvlist_t *ctime = fnvlist_alloc();
if (entry->de_ctime_from[0] || entry->de_ctime_from[1])
fnvlist_add_uint64_array(ctime, "from",
entry->de_ctime_from, 2);
if (entry->de_ctime_to[0] || entry->de_ctime_to[1])
fnvlist_add_uint64_array(ctime, "to",
entry->de_ctime_to, 2);
fnvlist_add_nvlist(changes, "ctime", ctime);
fnvlist_free(ctime);
}

fnvlist_add_nvlist(nvl, "changes", changes);
fnvlist_free(changes);
}

static int
diff_json_callback(zfs_diff_entry_t *entry, void *arg)
{
diff_json_cbdata_t *cbd = arg;
nvlist_t *nvl;
const char *type_str;
const char *file_type;
mode_t m = (mode_t)entry->de_mode;

switch (entry->de_type) {
case ZFS_DIFF_ADDED:
type_str = "added";
break;
case ZFS_DIFF_REMOVED:
type_str = "removed";
break;
case ZFS_DIFF_MODIFIED:
type_str = "modified";
break;
case ZFS_DIFF_RENAMED:
type_str = "renamed";
break;
default:
type_str = "unknown";
break;
}

if (S_ISREG(m))
file_type = "regular";
else if (S_ISDIR(m))
file_type = "directory";
else if (S_ISLNK(m))
file_type = "symlink";
else if (S_ISBLK(m))
file_type = "block";
else if (S_ISCHR(m))
file_type = "char";
else if (S_ISFIFO(m))
file_type = "fifo";
else if (S_ISSOCK(m))
file_type = "socket";
#ifdef S_IFDOOR
else if ((m & S_IFMT) == S_IFDOOR)
file_type = "door";
#endif
#ifdef S_IFPORT
else if ((m & S_IFMT) == S_IFPORT)
file_type = "port";
#endif
else
file_type = "unknown";

nvl = fnvlist_alloc();
fnvlist_add_string(nvl, "change_type", type_str);
fnvlist_add_string(nvl, "mountpoint", entry->de_mntpt);
fnvlist_add_uint64(nvl, "inode", entry->de_inode);
fnvlist_add_uint64(nvl, "gen", entry->de_gen);
fnvlist_add_string(nvl, "file_type", file_type);
fnvlist_add_string(nvl, "path",
entry->de_type == ZFS_DIFF_RENAMED ?
entry->de_path2 : entry->de_path);

diff_json_add_changes(nvl, entry, cbd->timestamped);

(void) nvlist_print_json(stdout, nvl);
(void) fputc('\n', stdout);
fnvlist_free(nvl);
return (0);
}

static int
zfs_do_diff(int argc, char **argv)
{
Expand All @@ -8075,8 +8197,9 @@ zfs_do_diff(int argc, char **argv)
int err = 0;
int c;
struct sigaction sa;
boolean_t json = B_FALSE;

while ((c = getopt(argc, argv, "FHth")) != -1) {
while ((c = getopt(argc, argv, "FHthj")) != -1) {
switch (c) {
case 'F':
flags |= ZFS_DIFF_CLASSIFY;
Expand All @@ -8090,6 +8213,9 @@ zfs_do_diff(int argc, char **argv)
case 'h':
flags |= ZFS_DIFF_NO_MANGLE;
break;
case 'j':
json = B_TRUE;
break;
default:
(void) fprintf(stderr,
gettext("invalid option '%c'\n"), optopt);
Expand Down Expand Up @@ -8146,7 +8272,16 @@ zfs_do_diff(int argc, char **argv)
goto out;
}

err = zfs_show_diffs(zhp, STDOUT_FILENO, fromsnap, tosnap, flags);
if (json) {
diff_json_cbdata_t cbd = { 0 };
cbd.timestamped = (flags & ZFS_DIFF_TIMESTAMP) != 0;

err = zfs_iter_diffs(zhp, fromsnap, tosnap, flags,
diff_json_callback, &cbd);
} else {
err = zfs_show_diffs(zhp, STDOUT_FILENO, fromsnap, tosnap,
flags);
}
out:
zfs_close(zhp);

Expand Down
63 changes: 63 additions & 0 deletions include/libzfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -941,8 +941,71 @@ typedef enum diff_flags {
ZFS_DIFF_NO_MANGLE = 1 << 3
} diff_flags_t;

typedef enum zfs_diff_type {
ZFS_DIFF_ADDED = '+',
ZFS_DIFF_REMOVED = '-',
ZFS_DIFF_MODIFIED = 'M',
ZFS_DIFF_RENAMED = 'R',
} zfs_diff_type_t;

typedef struct zfs_diff_entry {
zfs_diff_type_t de_type;

/* ctime [seconds, nanoseconds] in from-snapshot; zero for added */
uint64_t de_ctime_from[2];

/* dataset mount point; prepend to de_path / de_path2 for full path */
const char *de_mntpt;

/* relative path in tosnap (adds/modifies) or fromsnap (removes) */
const char *de_path;

/* relative rename destination; NULL unless ZFS_DIFF_RENAMED */
const char *de_path2;

/* ZFS object number, equals st_ino */
uint64_t de_inode;

/* txg in which the object was created; lower 32 bits equal st_gen */
uint64_t de_gen;

/* full POSIX mode word (type bits + permission bits) */
uint64_t de_mode;

/* hard link count */
uint64_t de_nlink;

/* ctime [seconds, nanoseconds] in to-snapshot; zero for removed */
uint64_t de_ctime_to[2];

/*
* The following fields are set for ZFS_DIFF_MODIFIED entries only.
*/

/* nlink in from-snapshot; 0 if nlink did not change */
uint64_t de_nlink_from;

/* mode in from-snapshot; 0 if mode did not change */
uint64_t de_mode_from;
} zfs_diff_entry_t;

/*
* Callback for zfs_iter_diffs(). Return non-zero to abort iteration.
*
* Some snapshot management operations are blocked for the duration of the
* zfs_iter_diffs() call, so long-running work inside the callback is not
* advisable.
*
* The entry and the strings it points to (de_mntpt, de_path, de_path2) are
* stack-allocated and valid only for the duration of the call. Make a copy
* if any of them need to outlive the callback.
*/
typedef int (*zfs_diff_cb_t)(zfs_diff_entry_t *entry, void *arg);

_LIBZFS_H int zfs_show_diffs(zfs_handle_t *, int, const char *, const char *,
int);
_LIBZFS_H int zfs_iter_diffs(zfs_handle_t *, const char *, const char *,
int, zfs_diff_cb_t, void *);

/*
* Miscellaneous functions.
Expand Down
69 changes: 69 additions & 0 deletions lib/libzfs/libzfs.abi
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
<elf-symbol name='zfs_iter_children_v2' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='zfs_iter_dependents' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='zfs_iter_dependents_v2' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='zfs_iter_diffs' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='zfs_iter_filesystems' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='zfs_iter_filesystems_v2' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
<elf-symbol name='zfs_iter_mounted' type='func-type' binding='global-binding' visibility='default-visibility' is-defined='yes'/>
Expand Down Expand Up @@ -5404,6 +5405,74 @@
<parameter type-id='eaa32e2f'/>
<return type-id='eaa32e2f'/>
</function-type>
<subrange-type id='zdf00001' length='2' type-id='7359adad'/>
<array-type-def dimensions='1' type-id='9c313c2d' size-in-bits='128' id='zdf00002'>
<subrange length='2' type-id='7359adad' id='zdf00001'/>
</array-type-def>
<enum-decl name='zfs_diff_type' id='zdf00003'>
<underlying-type type-id='95e97e5e'/>
<enumerator name='ZFS_DIFF_ADDED' value='43'/>
<enumerator name='ZFS_DIFF_MODIFIED' value='77'/>
<enumerator name='ZFS_DIFF_REMOVED' value='45'/>
<enumerator name='ZFS_DIFF_RENAMED' value='82'/>
</enum-decl>
<typedef-decl name='zfs_diff_type_t' type-id='zdf00003' id='zdf00004'/>
<class-decl name='zfs_diff_entry' size-in-bits='896' is-struct='yes' visibility='default' id='zdf00005'>
<data-member access='public' layout-offset-in-bits='0'>
<var-decl name='de_type' type-id='zdf00004' visibility='default'/>
</data-member>
<data-member access='public' layout-offset-in-bits='64'>
<var-decl name='de_ctime_from' type-id='zdf00002' visibility='default'/>
</data-member>
<data-member access='public' layout-offset-in-bits='192'>
<var-decl name='de_mntpt' type-id='80f4b756' visibility='default'/>
</data-member>
<data-member access='public' layout-offset-in-bits='256'>
<var-decl name='de_path' type-id='80f4b756' visibility='default'/>
</data-member>
<data-member access='public' layout-offset-in-bits='320'>
<var-decl name='de_path2' type-id='80f4b756' visibility='default'/>
</data-member>
<data-member access='public' layout-offset-in-bits='384'>
<var-decl name='de_inode' type-id='9c313c2d' visibility='default'/>
</data-member>
<data-member access='public' layout-offset-in-bits='448'>
<var-decl name='de_gen' type-id='9c313c2d' visibility='default'/>
</data-member>
<data-member access='public' layout-offset-in-bits='512'>
<var-decl name='de_mode' type-id='9c313c2d' visibility='default'/>
</data-member>
<data-member access='public' layout-offset-in-bits='576'>
<var-decl name='de_nlink' type-id='9c313c2d' visibility='default'/>
</data-member>
<data-member access='public' layout-offset-in-bits='640'>
<var-decl name='de_ctime_to' type-id='zdf00002' visibility='default'/>
</data-member>
<data-member access='public' layout-offset-in-bits='768'>
<var-decl name='de_nlink_from' type-id='9c313c2d' visibility='default'/>
</data-member>
<data-member access='public' layout-offset-in-bits='832'>
<var-decl name='de_mode_from' type-id='9c313c2d' visibility='default'/>
</data-member>
</class-decl>
<typedef-decl name='zfs_diff_entry_t' type-id='zdf00005' id='zdf00006'/>
<pointer-type-def type-id='zdf00006' size-in-bits='64' id='zdf0000a'/>
<function-type size-in-bits='64' id='zdf00007'>
<parameter type-id='zdf0000a'/>
<parameter type-id='eaa32e2f'/>
<return type-id='95e97e5e'/>
</function-type>
<pointer-type-def type-id='zdf00007' size-in-bits='64' id='zdf00008'/>
<typedef-decl name='zfs_diff_cb_t' type-id='zdf00008' id='zdf00009'/>
<function-decl name='zfs_iter_diffs' mangled-name='zfs_iter_diffs' visibility='default' binding='global' size-in-bits='64' elf-symbol-id='zfs_iter_diffs'>
<parameter type-id='9200a744' name='zhp'/>
<parameter type-id='80f4b756' name='fromsnap'/>
<parameter type-id='80f4b756' name='tosnap'/>
<parameter type-id='95e97e5e' name='flags'/>
<parameter type-id='zdf00009' name='cb'/>
<parameter type-id='eaa32e2f' name='cbarg'/>
<return type-id='95e97e5e'/>
</function-decl>
</abi-instr>
<abi-instr address-size='64' path='lib/libzfs/libzfs_import.c' language='LANG_C99'>
<array-type-def dimensions='1' type-id='03085adc' size-in-bits='192' id='083f8d58'>
Expand Down
Loading
Loading