mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-15 16:28:30 +01:00
When a mount is rooted at an existing directory, two code paths replace that directory's filer entry with one the mount builds itself: - every mount start re-creates the root with a non-exclusive mkdir, which the filer handles as an update of the whole entry; - creating or removing an entry directly in the root saves the root's entry to touch its mtime, and maybeLoadEntry synthesizes that entry from the mount options instead of reading the filer. The filer keeps only crtime and inode on such an update, so the root loses its mode, owner and extended attributes. When the root is a bucket it also loses the bucket quota, which silently drops to 0 (unlimited), and the settings S3 keeps on the bucket entry. The explicit allowEmptyFolders=false that #11370 meant to keep is replaced by true at every mount start, because the mkdir has wiped it before ensureBucketAllowEmptyFolders looks. Create the root only when a lookup says it is missing, and then exclusively. Leave the root out of the filer-side mtime touch: its attributes come from the mount options (setRootAttr), so the filer copy of its mtime was never visible through the mount anyway. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
2.1 KiB
Go
67 lines
2.1 KiB
Go
package mount
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/filer"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
// savedEntriesFiler records the entries a mount saves.
|
|
type savedEntriesFiler struct {
|
|
filer_pb.UnimplementedSeaweedFilerServer
|
|
mu sync.Mutex
|
|
updates []*filer_pb.UpdateEntryRequest
|
|
}
|
|
|
|
func (s *savedEntriesFiler) UpdateEntry(_ context.Context, req *filer_pb.UpdateEntryRequest) (*filer_pb.UpdateEntryResponse, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.updates = append(s.updates, proto.Clone(req).(*filer_pb.UpdateEntryRequest))
|
|
return &filer_pb.UpdateEntryResponse{}, nil
|
|
}
|
|
|
|
// Creating or removing an entry directly in the mount root must not save the
|
|
// root: its entry is synthesized from the mount options, so saving it would
|
|
// replace the real directory on the filer, wiping a bucket's quota and the
|
|
// settings S3 keeps on it. Directories below the root are still saved.
|
|
func TestTouchDirMtimeCtimeLeavesTheMountRootOnTheFiler(t *testing.T) {
|
|
wfs := newInvalidateTestWFS(t)
|
|
root := util.FullPath("/buckets/b1")
|
|
wfs.option.FilerMountRootPath = string(root)
|
|
fake := &savedEntriesFiler{}
|
|
startFakeFiler(t, wfs, fake)
|
|
|
|
wfs.inodeToPath.Lookup(root, 1, true, false, 0, false)
|
|
wfs.inodeToPath.MarkChildrenCached(root)
|
|
sub := &filer_pb.Entry{
|
|
Name: "sub",
|
|
IsDirectory: true,
|
|
Attributes: &filer_pb.FuseAttributes{FileMode: uint32(os.ModeDir | 0755), Mtime: 1},
|
|
}
|
|
if err := wfs.metaCache.InsertEntry(context.Background(), filer.FromPbEntry(string(root), sub), 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
wfs.touchDirMtimeCtime(root)
|
|
wfs.touchDirMtimeCtime(root.Child("sub"))
|
|
|
|
fake.mu.Lock()
|
|
defer fake.mu.Unlock()
|
|
var saved []string
|
|
for _, req := range fake.updates {
|
|
saved = append(saved, string(util.NewFullPath(req.Directory, req.Entry.Name)))
|
|
}
|
|
if len(saved) != 1 || saved[0] != "/buckets/b1/sub" {
|
|
t.Fatalf("saved %v, want only /buckets/b1/sub", saved)
|
|
}
|
|
if fake.updates[0].Entry.Attributes.Mtime <= 1 {
|
|
t.Errorf("sub mtime = %d, want it touched", fake.updates[0].Entry.Attributes.Mtime)
|
|
}
|
|
}
|