zfs_ctldir: make .zfs/snapshot/<name> btime the snapshot creation time - #19093
Merged
Merged
Conversation
Before the automount, '.zfs/snapshot/<name>' is a placeholder inode using simple_dir_inode_operations, which has no getattr, so statx(2) reports no birth time. Once mounted, it is the snapshot's root directory and reports that directory's crtime, i.e. the dataset creation time. A consumer therefore gets either no birth time or the dataset creation time, never the snapshot's, and the answer changes when the snapshot is automounted. Report the snapshot creation time as stx_btime consistently in both states, so it can be read with a plain statx(2) on the path. Give the placeholder its own inode_operations with a getattr that returns the creation time zfsctl_inode_alloc() already stores, and override the birth time of a mounted snapshot's root in zfs_getattr_fast() next to the existing inode number override. The STATX_BTIME copy moves from zpl_getattr() into zfs_getattr_fast() so the override is not overwritten. Add snapdir_stat_btime to ZTS. Signed-off-by: Ameer Hamza <ahamza@ixsystems.com>
behlendorf
approved these changes
Sep 10, 2026
Every getattr in this file passes request_mask on, so it is always used. Signed-off-by: Ameer Hamza <ahamza@ixsystems.com>
behlendorf
approved these changes
Sep 12, 2026
amotin
approved these changes
Sep 15, 2026
ixhamza
added a commit
to truenas/zfs
that referenced
this pull request
Sep 16, 2026
Before the automount, '.zfs/snapshot/<name>' is a placeholder inode using simple_dir_inode_operations, which has no getattr, so statx(2) reports no birth time. Once mounted, it is the snapshot's root directory and reports that directory's crtime, i.e. the dataset creation time. A consumer therefore gets either no birth time or the dataset creation time, never the snapshot's, and the answer changes when the snapshot is automounted. Report the snapshot creation time as stx_btime consistently in both states, so it can be read with a plain statx(2) on the path. Give the placeholder its own inode_operations with a getattr that returns the creation time zfsctl_inode_alloc() already stores, and override the birth time of a mounted snapshot's root in zfs_getattr_fast() next to the existing inode number override. The STATX_BTIME copy moves from zpl_getattr() into zfs_getattr_fast() so the override is not overwritten. Add snapdir_stat_btime to ZTS. Backport of the master change. zfs_getattr_fast() has no request_mask on kernels before 6.6 here, so the birth time is filled regardless of the mask on those. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com> Signed-off-by: Ameer Hamza <ahamza@ixsystems.com> Closes openzfs#19093
14 tasks
bugclerk
pushed a commit
to truenas/zfs
that referenced
this pull request
Sep 16, 2026
Before the automount, '.zfs/snapshot/<name>' is a placeholder inode using simple_dir_inode_operations, which has no getattr, so statx(2) reports no birth time. Once mounted, it is the snapshot's root directory and reports that directory's crtime, i.e. the dataset creation time. A consumer therefore gets either no birth time or the dataset creation time, never the snapshot's, and the answer changes when the snapshot is automounted. Report the snapshot creation time as stx_btime consistently in both states, so it can be read with a plain statx(2) on the path. Give the placeholder its own inode_operations with a getattr that returns the creation time zfsctl_inode_alloc() already stores, and override the birth time of a mounted snapshot's root in zfs_getattr_fast() next to the existing inode number override. The STATX_BTIME copy moves from zpl_getattr() into zfs_getattr_fast() so the override is not overwritten. Add snapdir_stat_btime to ZTS. Backport of the master change. zfs_getattr_fast() has no request_mask on kernels before 6.6 here, so the birth time is filled regardless of the mask on those. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com> Signed-off-by: Ameer Hamza <ahamza@ixsystems.com> Closes openzfs#19093 (cherry picked from commit 5276573)
14 tasks
bugclerk
pushed a commit
to truenas/zfs
that referenced
this pull request
Sep 16, 2026
Before the automount, '.zfs/snapshot/<name>' is a placeholder inode using simple_dir_inode_operations, which has no getattr, so statx(2) reports no birth time. Once mounted, it is the snapshot's root directory and reports that directory's crtime, i.e. the dataset creation time. A consumer therefore gets either no birth time or the dataset creation time, never the snapshot's, and the answer changes when the snapshot is automounted. Report the snapshot creation time as stx_btime consistently in both states, so it can be read with a plain statx(2) on the path. Give the placeholder its own inode_operations with a getattr that returns the creation time zfsctl_inode_alloc() already stores, and override the birth time of a mounted snapshot's root in zfs_getattr_fast() next to the existing inode number override. The STATX_BTIME copy moves from zpl_getattr() into zfs_getattr_fast() so the override is not overwritten. Add snapdir_stat_btime to ZTS. Backport of the master change. zfs_getattr_fast() has no request_mask on kernels before 6.6 here, so the birth time is filled regardless of the mask on those. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com> Signed-off-by: Ameer Hamza <ahamza@ixsystems.com> Closes openzfs#19093 (cherry picked from commit 5276573)
14 tasks
creatorcary
pushed a commit
to truenas/zfs
that referenced
this pull request
Sep 16, 2026
#441) Before the automount, '.zfs/snapshot/<name>' is a placeholder inode using simple_dir_inode_operations, which has no getattr, so statx(2) reports no birth time. Once mounted, it is the snapshot's root directory and reports that directory's crtime, i.e. the dataset creation time. A consumer therefore gets either no birth time or the dataset creation time, never the snapshot's, and the answer changes when the snapshot is automounted. Report the snapshot creation time as stx_btime consistently in both states, so it can be read with a plain statx(2) on the path. Give the placeholder its own inode_operations with a getattr that returns the creation time zfsctl_inode_alloc() already stores, and override the birth time of a mounted snapshot's root in zfs_getattr_fast() next to the existing inode number override. The STATX_BTIME copy moves from zpl_getattr() into zfs_getattr_fast() so the override is not overwritten. Add snapdir_stat_btime to ZTS. Backport of the master change. zfs_getattr_fast() has no request_mask on kernels before 6.6 here, so the birth time is filled regardless of the mask on those. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com> Closes openzfs#19093 (cherry picked from commit 5276573) Signed-off-by: Ameer Hamza <ahamza@ixsystems.com> Co-authored-by: Ameer Hamza <ahamza@ixsystems.com>
creatorcary
pushed a commit
to truenas/zfs
that referenced
this pull request
Sep 16, 2026
#442) Before the automount, '.zfs/snapshot/<name>' is a placeholder inode using simple_dir_inode_operations, which has no getattr, so statx(2) reports no birth time. Once mounted, it is the snapshot's root directory and reports that directory's crtime, i.e. the dataset creation time. A consumer therefore gets either no birth time or the dataset creation time, never the snapshot's, and the answer changes when the snapshot is automounted. Report the snapshot creation time as stx_btime consistently in both states, so it can be read with a plain statx(2) on the path. Give the placeholder its own inode_operations with a getattr that returns the creation time zfsctl_inode_alloc() already stores, and override the birth time of a mounted snapshot's root in zfs_getattr_fast() next to the existing inode number override. The STATX_BTIME copy moves from zpl_getattr() into zfs_getattr_fast() so the override is not overwritten. Add snapdir_stat_btime to ZTS. Backport of the master change. zfs_getattr_fast() has no request_mask on kernels before 6.6 here, so the birth time is filled regardless of the mask on those. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com> Closes openzfs#19093 (cherry picked from commit 5276573) Signed-off-by: Ameer Hamza <ahamza@ixsystems.com> Co-authored-by: Ameer Hamza <ahamza@ixsystems.com>
lundman
pushed a commit
to openzfsonwindows/openzfs
that referenced
this pull request
Sep 27, 2026
Before the automount, '.zfs/snapshot/<name>' is a placeholder inode using simple_dir_inode_operations, which has no getattr, so statx(2) reports no birth time. Once mounted, it is the snapshot's root directory and reports that directory's crtime, i.e. the dataset creation time. A consumer therefore gets either no birth time or the dataset creation time, never the snapshot's, and the answer changes when the snapshot is automounted. Report the snapshot creation time as stx_btime consistently in both states, so it can be read with a plain statx(2) on the path. Give the placeholder its own inode_operations with a getattr that returns the creation time zfsctl_inode_alloc() already stores, and override the birth time of a mounted snapshot's root in zfs_getattr_fast() next to the existing inode number override. The STATX_BTIME copy moves from zpl_getattr() into zfs_getattr_fast() so the override is not overwritten. Add snapdir_stat_btime to ZTS. Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov> Reviewed-by: Alexander Motin <alexander.motin@TrueNAS.com> Signed-off-by: Ameer Hamza <ahamza@ixsystems.com> Closes openzfs#19093
This was referenced Sep 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation and Context
.zfs/snapshot/<name>reports a different birth time depending on whether the snapshot is automounted. Before the automount it is a placeholder inode with the kernel'ssimple_dir_inode_operations, which has nogetattr, sostatx(2)returns nostx_btimeat all (a/m/ctime are the snapshot creation time since a21ca18). Once mounted it is the snapshot's root directory and reports that directory'scrtime, i.e. the dataset creation time. So a consumer that wants "when was this snapshot taken" from a plainstatx(2)gets nothing or the dataset creation time, and the answer changes when the snapshot gets mounted. Reporting the snapshot creation time asstx_btimein both states gives one consistent value for the path; birth time is the natural field for it and the one least likely to affect existing consumers.Description
The unmounted entry gets its own
inode_operationstable,zpl_ops_snapdirs, so it has agetattrat all. It reports the creation time thatzfsctl_inode_alloc()already looked up for a/m/ctime, now also kept inz_btime. For the mounted case,zfs_getattr_fast()already fakes the inode number of a snapshot root so NFS sees the same fileid before and after the automount; the birth time is now faked in the same place, fromdsl_get_creation()of the mounted dataset. To make that stick, theSTATX_BTIMEcopy inzpl_getattr()had to move down intozfs_getattr_fast(), otherwise it would overwrite the override on the way out.How Has This Been Tested?
Before the patch:
After the patch:
Also verified that
.zfs,.zfs/snapshotand files inside the snapshot are unaffected, and that the value holds across rename, rollback, promote, send/recv and expiry unmount, including over NFSv4 and SMB.ZTS: snapdir_stat_btime fails on an unpatched module at the first check and passes with the change; the snapdir, snapshot, crtime, stat and ctime groups pass. Also compiled against 6.1 headers.
Types of Changes
Checklist
Signed-off-by.