package mount import ( "testing" "time" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" "github.com/seaweedfs/seaweedfs/weed/util" ) // TestFlushFileMetadataPreservesUserMtime is a regression test for issue #9363. // // Before the fix, flushFileMetadata stamped entry.Attributes.Mtime/Ctime with // time.Now() on every flush, clobbering the value SetAttr stored on the entry // when the user ran utimes()/touch -m -d while a file handle was still open. // // The flush has no business inventing timestamps: Write and SetAttr already // maintain mtime/ctime on the entry, and the flush should just persist them. // // The test sets a user-chosen mtime far in the past, runs flushFileMetadata // with no chunks (so it returns before the streamCreateEntry RPC), and asserts // the entry's mtime is unchanged. func TestFlushFileMetadataPreservesUserMtime(t *testing.T) { wfs := &WFS{ inodeToPath: NewInodeToPath(util.FullPath("/"), 0), fhLockTable: util.NewLockTable[FileHandleId](), option: &Option{}, } const inode = uint64(42) fullPath := util.FullPath("/dir/sample.txt") wfs.inodeToPath.Lookup(fullPath, 1, false, false, inode, true) // Use a non-zero nanosecond so the *Ns assertions below catch a regression // that zeroes the field instead of preserving it. userMtime := time.Date(2020, 1, 15, 12, 34, 56, 123456789, time.UTC) entry := &filer_pb.Entry{ Name: "sample.txt", Attributes: &filer_pb.FuseAttributes{ Mtime: userMtime.Unix(), MtimeNs: int32(userMtime.Nanosecond()), Ctime: userMtime.Unix(), CtimeNs: int32(userMtime.Nanosecond()), }, } fh := &FileHandle{ fh: FileHandleId(1), inode: inode, wfs: wfs, entry: &LockedEntry{Entry: entry}, dirtyMetadata: true, } if err := wfs.flushFileMetadata(fh); err != nil { t.Fatalf("flushFileMetadata returned error: %v", err) } if got := entry.Attributes.Mtime; got != userMtime.Unix() { t.Errorf("mtime sec changed: got %d, want %d", got, userMtime.Unix()) } if got := entry.Attributes.MtimeNs; got != int32(userMtime.Nanosecond()) { t.Errorf("mtime ns changed: got %d, want %d", got, userMtime.Nanosecond()) } if got := entry.Attributes.Ctime; got != userMtime.Unix() { t.Errorf("ctime sec changed: got %d, want %d", got, userMtime.Unix()) } if got := entry.Attributes.CtimeNs; got != int32(userMtime.Nanosecond()) { t.Errorf("ctime ns changed: got %d, want %d", got, userMtime.Nanosecond()) } }