@@ -628,6 +628,10 @@ void fh_fill_pre_attrs(struct svc_fh *fhp)
628628 stat .mtime = inode -> i_mtime ;
629629 stat .ctime = inode -> i_ctime ;
630630 stat .size = inode -> i_size ;
631+ if (v4 && IS_I_VERSION (inode )) {
632+ stat .change_cookie = inode_query_iversion (inode );
633+ stat .result_mask |= STATX_CHANGE_COOKIE ;
634+ }
631635 }
632636 if (v4 )
633637 fhp -> fh_pre_change = nfsd4_change_attribute (& stat , inode );
@@ -659,6 +663,10 @@ void fh_fill_post_attrs(struct svc_fh *fhp)
659663 if (err ) {
660664 fhp -> fh_post_saved = false;
661665 fhp -> fh_post_attr .ctime = inode -> i_ctime ;
666+ if (v4 && IS_I_VERSION (inode )) {
667+ fhp -> fh_post_attr .change_cookie = inode_query_iversion (inode );
668+ fhp -> fh_post_attr .result_mask |= STATX_CHANGE_COOKIE ;
669+ }
662670 } else
663671 fhp -> fh_post_saved = true;
664672 if (v4 )
@@ -748,3 +756,37 @@ enum fsid_source fsid_source(const struct svc_fh *fhp)
748756 return FSIDSOURCE_UUID ;
749757 return FSIDSOURCE_DEV ;
750758}
759+
760+ /*
761+ * We could use i_version alone as the change attribute. However, i_version
762+ * can go backwards on a regular file after an unclean shutdown. On its own
763+ * that doesn't necessarily cause a problem, but if i_version goes backwards
764+ * and then is incremented again it could reuse a value that was previously
765+ * used before boot, and a client who queried the two values might incorrectly
766+ * assume nothing changed.
767+ *
768+ * By using both ctime and the i_version counter we guarantee that as long as
769+ * time doesn't go backwards we never reuse an old value. If the filesystem
770+ * advertises STATX_ATTR_CHANGE_MONOTONIC, then this mitigation is not
771+ * needed.
772+ *
773+ * We only need to do this for regular files as well. For directories, we
774+ * assume that the new change attr is always logged to stable storage in some
775+ * fashion before the results can be seen.
776+ */
777+ u64 nfsd4_change_attribute (struct kstat * stat , struct inode * inode )
778+ {
779+ u64 chattr ;
780+
781+ if (stat -> result_mask & STATX_CHANGE_COOKIE ) {
782+ chattr = stat -> change_cookie ;
783+ if (S_ISREG (inode -> i_mode ) &&
784+ !(stat -> attributes & STATX_ATTR_CHANGE_MONOTONIC )) {
785+ chattr += (u64 )stat -> ctime .tv_sec << 30 ;
786+ chattr += stat -> ctime .tv_nsec ;
787+ }
788+ } else {
789+ chattr = time_to_chattr (& stat -> ctime );
790+ }
791+ return chattr ;
792+ }
0 commit comments