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"