From 3f368466424e5ad1c00c5a31e5310a53a88ce8a3 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Fri, 10 Apr 2026 11:23:52 -0700 Subject: [PATCH] fix(filer): update hard link ctime when nlink changes on unlink (#9018) * fix(filer): update hard link ctime when nlink changes on unlink When a hard link is unlinked, POSIX requires that the remaining links' ctime is updated because the inode's nlink count changed. The filer's DeleteHardLink() decremented the counter in the KV store but did not update the ctime field. Set ctime to time.Now() on the KV entry before writing it back when the hard link counter is decremented but still > 0. Remove tests/unlink/00.t from known_failures.txt (all 112 subtests now pass). * style: use time.Now().UTC() for ctime in DeleteHardLink --- test/pjdfstest/known_failures.txt | 5 +---- weed/filer/filerstore_hardlink.go | 4 ++++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/pjdfstest/known_failures.txt b/test/pjdfstest/known_failures.txt index a15ddfdc0..e6459933f 100644 --- a/test/pjdfstest/known_failures.txt +++ b/test/pjdfstest/known_failures.txt @@ -38,12 +38,9 @@ tests/unlink/02.t tests/unlink/03.t # ── Hard link nlink/ctime tracking (requires filer changes) ──────────── -# The filer does not update ctime on remaining hard link entries when one -# link is removed, and nlink counts are not correctly maintained across -# rename operations. +# nlink counts are not correctly maintained across rename operations. tests/rename/23.t tests/rename/24.t -tests/unlink/00.t # ── Parent directory mtime/ctime on deferred file create ─────────────── # When file creation is deferred (not flushed to filer immediately), diff --git a/weed/filer/filerstore_hardlink.go b/weed/filer/filerstore_hardlink.go index bfac44a71..ae5ff78a0 100644 --- a/weed/filer/filerstore_hardlink.go +++ b/weed/filer/filerstore_hardlink.go @@ -5,6 +5,7 @@ import ( "context" "errors" "fmt" + "time" "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" @@ -113,6 +114,9 @@ func (fsw *FilerStoreWrapper) DeleteHardLink(ctx context.Context, hardLinkId Har return fsw.KvDelete(ctx, key) } + // POSIX: update ctime when nlink changes (a hard link was removed). + entry.Attr.Ctime = time.Now().UTC() + newBlob, encodeErr := entry.EncodeAttributesAndChunks() if encodeErr != nil { return encodeErr