Add snapshots_changed_nsecs dataset property

Add a read-only dataset property, snapshots_changed_nsecs, which 
exposes the nanosecond resolution version of snapshots_changed.

Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com>
Signed-off-by: Wolfgang Hoschek <wolfgang.hoschek@mac.com>
Closes #17998
Closes #18031
This commit is contained in:
Wolfgang Hoschek 2026-01-06 12:36:20 -05:00 committed by GitHub
parent 6eef5cdc94
commit c77f17b750
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 89 additions and 11 deletions

View File

@ -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;

View File

@ -2311,7 +2311,8 @@
<enumerator name='ZFS_PROP_DEFAULTUSEROBJQUOTA' value='103'/>
<enumerator name='ZFS_PROP_DEFAULTGROUPOBJQUOTA' value='104'/>
<enumerator name='ZFS_PROP_DEFAULTPROJECTOBJQUOTA' value='105'/>
<enumerator name='ZFS_NUM_PROPS' value='106'/>
<enumerator name='ZFS_PROP_SNAPSHOTS_CHANGED_NSECS' value='106'/>
<enumerator name='ZFS_NUM_PROPS' value='107'/>
</enum-decl>
<typedef-decl name='zfs_prop_t' type-id='4b000d60' id='58603c44'/>
<enum-decl name='zprop_source_t' naming-typedef-id='a2256d42' id='5903f80e'>

View File

@ -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:

View File

@ -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

View File

@ -796,6 +796,12 @@ zfs_prop_init(void)
ZFS_TYPE_VOLUME, "<date>", "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, "<nsec>",
"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);

View File

@ -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) {

View File

@ -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);

View File

@ -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"