diff --git a/include/sys/fs/zfs.h b/include/sys/fs/zfs.h index 830c8455b..87422658c 100644 --- a/include/sys/fs/zfs.h +++ b/include/sys/fs/zfs.h @@ -203,6 +203,7 @@ typedef enum { ZFS_PROP_DEFAULTUSEROBJQUOTA, ZFS_PROP_DEFAULTGROUPOBJQUOTA, ZFS_PROP_DEFAULTPROJECTOBJQUOTA, + ZFS_PROP_SNAPSHOTS_CHANGED_NSECS, ZFS_NUM_PROPS } zfs_prop_t; diff --git a/lib/libzfs/libzfs.abi b/lib/libzfs/libzfs.abi index 5f27b9c6d..a32f2231d 100644 --- a/lib/libzfs/libzfs.abi +++ b/lib/libzfs/libzfs.abi @@ -2311,7 +2311,8 @@ - + + diff --git a/lib/libzfs/libzfs_dataset.c b/lib/libzfs/libzfs_dataset.c index e1b91fc47..b9b76e0bd 100644 --- a/lib/libzfs/libzfs_dataset.c +++ b/lib/libzfs/libzfs_dataset.c @@ -3001,6 +3001,19 @@ zfs_prop_get(zfs_handle_t *zhp, zfs_prop_t prop, char *propbuf, size_t proplen, zcp_check(zhp, prop, val, NULL); break; + case ZFS_PROP_SNAPSHOTS_CHANGED_NSECS: + { + if ((get_numeric_property(zhp, prop, src, &source, + &val) != 0) || val == 0) { + return (-1); + } + + (void) snprintf(propbuf, proplen, "%llu", + (u_longlong_t)val); + } + zcp_check(zhp, prop, val, NULL); + break; + default: switch (zfs_prop_get_type(prop)) { case PROP_TYPE_NUMBER: diff --git a/man/man7/zfsprops.7 b/man/man7/zfsprops.7 index 77e994b91..448a7ec05 100644 --- a/man/man7/zfsprops.7 +++ b/man/man7/zfsprops.7 @@ -535,6 +535,15 @@ This allows us to be more efficient how often we query snapshots. The property is persistent across mount and unmount operations only if the .Sy extensible_dataset feature is enabled. +.It Sy snapshots_changed_nsecs +Specifies the UTC time at which a snapshot for a dataset was last created +or deleted, expressed as the number of nanoseconds since the Unix epoch. +This is a high-precision version of +.Sy snapshots_changed , +representing the same instant with nanosecond instead of second resolution. +The property is persistent across mount and unmount operations only if the +.Sy extensible_dataset +feature is enabled. .It Sy volblocksize For volumes, specifies the block size of the volume. The diff --git a/module/zcommon/zfs_prop.c b/module/zcommon/zfs_prop.c index 2dcc703d3..78d4b0a05 100644 --- a/module/zcommon/zfs_prop.c +++ b/module/zcommon/zfs_prop.c @@ -796,6 +796,12 @@ zfs_prop_init(void) ZFS_TYPE_VOLUME, "", "SNAPSHOTS_CHANGED", B_FALSE, B_TRUE, B_TRUE, NULL, sfeatures); + zprop_register_impl(ZFS_PROP_SNAPSHOTS_CHANGED_NSECS, + "snapshots_changed_nsecs", PROP_TYPE_NUMBER, 0, NULL, + PROP_READONLY, ZFS_TYPE_FILESYSTEM | ZFS_TYPE_VOLUME, "", + "SNAPSHOTS_CHANGED_NSECS", B_FALSE, B_TRUE, B_TRUE, NULL, + sfeatures); + zprop_register_index(ZFS_PROP_LONGNAME, "longname", 0, PROP_INHERIT, ZFS_TYPE_FILESYSTEM, "on | off", "LONGNAME", boolean_table, sfeatures); diff --git a/module/zfs/dsl_dataset.c b/module/zfs/dsl_dataset.c index 1da596427..0d90293d7 100644 --- a/module/zfs/dsl_dataset.c +++ b/module/zfs/dsl_dataset.c @@ -2855,8 +2855,14 @@ dsl_dataset_stats(dsl_dataset_t *ds, nvlist_t *nv) dsl_get_userrefs(ds)); dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_DEFER_DESTROY, dsl_get_defer_destroy(ds)); + inode_timespec_t snap_cmtime = dsl_dir_snap_cmtime(ds->ds_dir); dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_SNAPSHOTS_CHANGED, - dsl_dir_snap_cmtime(ds->ds_dir).tv_sec); + snap_cmtime.tv_sec); + uint64_t snap_cmtime_ns = + ((uint64_t)snap_cmtime.tv_sec * NANOSEC) + + snap_cmtime.tv_nsec; + dsl_prop_nvlist_add_uint64(nv, ZFS_PROP_SNAPSHOTS_CHANGED_NSECS, + snap_cmtime_ns); dsl_dataset_crypt_stats(ds, nv); if (dsl_dataset_phys(ds)->ds_prev_snap_obj != 0) { diff --git a/module/zfs/zcp_get.c b/module/zfs/zcp_get.c index 1ae3a058d..5ab459151 100644 --- a/module/zfs/zcp_get.c +++ b/module/zfs/zcp_get.c @@ -434,6 +434,14 @@ get_special_prop(lua_State *state, dsl_dataset_t *ds, const char *dsname, numval = dsl_dir_snap_cmtime(ds->ds_dir).tv_sec; break; + case ZFS_PROP_SNAPSHOTS_CHANGED_NSECS: { + inode_timespec_t snap_cmtime = + dsl_dir_snap_cmtime(ds->ds_dir); + numval = ((uint64_t)snap_cmtime.tv_sec * NANOSEC) + + snap_cmtime.tv_nsec; + break; + } + default: /* Did not match these props, check in the dsl_dir */ error = get_dsl_dir_prop(ds, zfs_prop, &numval); diff --git a/tests/zfs-tests/tests/functional/snapshot/snapshot_018_pos.ksh b/tests/zfs-tests/tests/functional/snapshot/snapshot_018_pos.ksh index 9591b62fd..db9e268c3 100755 --- a/tests/zfs-tests/tests/functional/snapshot/snapshot_018_pos.ksh +++ b/tests/zfs-tests/tests/functional/snapshot/snapshot_018_pos.ksh @@ -30,22 +30,22 @@ # # DESCRIPTION: -# Verify the functionality of snapshots_changed property +# Verify the functionality of snapshots_changed and snapshots_changed_nsecs properties. # # STRATEGY: # 1. Create a pool -# 2. Verify snapshots_changed property is NULL +# 2. Verify snapshots_changed and snapshots_changed_nsecs properties are NULL # 3. Create a filesystem -# 4. Verify snapshots_changed property is NULL +# 4. Verify snapshots_changed and snapshots_changed_nsecs properties are NULL # 5. Create snapshots for all filesystems -# 6. Verify snapshots_changed property shows correct time +# 6. Verify snapshots_changed property shows correct time and snapshots_changed_nsecs is a valid nanosecond value # 7. Unmount all filesystems # 8. Create a snapshot while unmounted -# 9. Verify snapshots_changed +# 9. Verify snapshots_changed and snapshots_changed_nsecs # 10. Mount the filsystems -# 11. Verify snapshots_changed +# 11. Verify snapshots_changed and snapshots_changed_nsecs # 12. Destroy the snapshots -# 13. Verify snapshots_changed +# 13. Verify snapshots_changed and snapshots_changed_nsecs # function cleanup @@ -55,7 +55,7 @@ function cleanup verify_runnable "both" -log_assert "Verify snapshots_changed property" +log_assert "Verify snapshots_changed and snapshots_changed_nsecs properties" log_onexit cleanup @@ -67,13 +67,25 @@ snapdir=".zfs/snapshot" # Create filesystems and check snapshots_changed is NULL create_pool $TESTPOOL $DISKS snap_changed_testpool=$(zfs get -H -o value -p snapshots_changed $TESTPOOL) +snap_changed_nsecs_testpool=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL) log_must eval "[[ $snap_changed_testpool == - ]]" +log_must eval "[[ $snap_changed_nsecs_testpool == - ]]" +list_changed_testpool=$(zfs list -H -p -o snapshots_changed $TESTPOOL) +list_changed_nsecs_testpool=$(zfs list -H -p -o snapshots_changed_nsecs $TESTPOOL) +log_must eval "[[ $list_changed_testpool == - ]]" +log_must eval "[[ $list_changed_nsecs_testpool == - ]]" tpool_snapdir=$(get_prop mountpoint $TESTPOOL)/$snapdir log_must eval "[[ $(stat_mtime $tpool_snapdir) == 0 ]]" log_must zfs create $TESTPOOL/$TESTFS snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) +snap_changed_nsecs_testfs=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS) log_must eval "[[ $snap_changed_testfs == - ]]" +log_must eval "[[ $snap_changed_nsecs_testfs == - ]]" +list_changed_testfs=$(zfs list -H -p -o snapshots_changed $TESTPOOL/$TESTFS) +list_changed_nsecs_testfs=$(zfs list -H -p -o snapshots_changed_nsecs $TESTPOOL/$TESTFS) +log_must eval "[[ $list_changed_testfs == - ]]" +log_must eval "[[ $list_changed_nsecs_testfs == - ]]" tfs_snapdir=$(get_prop mountpoint $TESTPOOL/$TESTFS)/$snapdir log_must eval "[[ $(stat_mtime $tfs_snapdir) == 0 ]]" @@ -81,48 +93,70 @@ log_must eval "[[ $(stat_mtime $tfs_snapdir) == 0 ]]" curr_time=$(date '+%s') log_must zfs snapshot $snap_testpool snap_changed_testpool=$(zfs get -H -o value -p snapshots_changed $TESTPOOL) +snap_changed_nsecs_testpool=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL) log_must eval "[[ $snap_changed_testpool -ge $curr_time ]]" +log_must eval "[[ $((snap_changed_nsecs_testpool / 1000000000)) == $snap_changed_testpool ]]" +list_changed_testpool=$(zfs list -H -p -o snapshots_changed $TESTPOOL) +list_changed_nsecs_testpool=$(zfs list -H -p -o snapshots_changed_nsecs $TESTPOOL) +log_must eval "[[ $list_changed_testpool == $snap_changed_testpool ]]" +log_must eval "[[ $list_changed_nsecs_testpool == $snap_changed_nsecs_testpool ]]" log_must eval "[[ $(stat_mtime $tpool_snapdir) == $snap_changed_testpool ]]" curr_time=$(date '+%s') log_must zfs snapshot $snap_testfsv1 snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) +snap_changed_nsecs_testfs=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS) log_must eval "[[ $snap_changed_testfs -ge $curr_time ]]" +log_must eval "[[ $((snap_changed_nsecs_testfs / 1000000000)) == $snap_changed_testfs ]]" +list_changed_testfs=$(zfs list -H -p -o snapshots_changed $TESTPOOL/$TESTFS) +list_changed_nsecs_testfs=$(zfs list -H -p -o snapshots_changed_nsecs $TESTPOOL/$TESTFS) +log_must eval "[[ $list_changed_testfs == $snap_changed_testfs ]]" +log_must eval "[[ $list_changed_nsecs_testfs == $snap_changed_nsecs_testfs ]]" log_must eval "[[ $(stat_mtime $tfs_snapdir) == $snap_changed_testfs ]]" # Unmount the filesystems and check snapshots_changed has correct value after unmount log_must zfs unmount $TESTPOOL/$TESTFS log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) == $snap_changed_testfs ]]" +log_must eval "[[ $(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS) == $snap_changed_nsecs_testfs ]]" # Create snapshot while unmounted curr_time=$(date '+%s') log_must zfs snapshot $snap_testfsv2 snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) +snap_changed_nsecs_testfs=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS) log_must eval "[[ $snap_changed_testfs -ge $curr_time ]]" +log_must eval "[[ $((snap_changed_nsecs_testfs / 1000000000)) == $snap_changed_testfs ]]" log_must zfs unmount $TESTPOOL log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL) == $snap_changed_testpool ]]" +log_must eval "[[ $(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL) == $snap_changed_nsecs_testpool ]]" # Mount back the filesystems and check snapshots_changed still has correct value log_must zfs mount $TESTPOOL log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL) == $snap_changed_testpool ]]" +log_must eval "[[ $(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL) == $snap_changed_nsecs_testpool ]]" log_must eval "[[ $(stat_mtime $tpool_snapdir) == $snap_changed_testpool ]]" log_must zfs mount $TESTPOOL/$TESTFS log_must eval "[[ $(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) == $snap_changed_testfs ]]" +log_must eval "[[ $(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS) == $snap_changed_nsecs_testfs ]]" log_must eval "[[ $(stat_mtime $tfs_snapdir) == $snap_changed_testfs ]]" # Destroy the snapshots and check snapshots_changed shows correct time curr_time=$(date '+%s') log_must zfs destroy $snap_testfsv1 snap_changed_testfs=$(zfs get -H -o value -p snapshots_changed $TESTPOOL/$TESTFS) +snap_changed_nsecs_testfs=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL/$TESTFS) log_must eval "[[ $snap_changed_testfs -ge $curr_time ]]" +log_must eval "[[ $((snap_changed_nsecs_testfs / 1000000000)) == $snap_changed_testfs ]]" log_must eval "[[ $(stat_mtime $tfs_snapdir) == $snap_changed_testfs ]]" curr_time=$(date '+%s') log_must zfs destroy $snap_testpool snap_changed_testpool=$(zfs get -H -o value -p snapshots_changed $TESTPOOL) +snap_changed_nsecs_testpool=$(zfs get -H -o value -p snapshots_changed_nsecs $TESTPOOL) log_must eval "[[ $snap_changed_testpool -ge $curr_time ]]" +log_must eval "[[ $((snap_changed_nsecs_testpool / 1000000000)) == $snap_changed_testpool ]]" log_must eval "[[ $(stat_mtime $tpool_snapdir) == $snap_changed_testpool ]]" -log_pass "snapshots_changed property behaves correctly" +log_pass "snapshots_changed and snapshots_changed_nsecs properties behave correctly"