diff --git a/pkg/meta/base_test.go b/pkg/meta/base_test.go index fdc6567adaa9..0930f01335ac 100644 --- a/pkg/meta/base_test.go +++ b/pkg/meta/base_test.go @@ -1460,6 +1460,26 @@ func testTrash(t *testing.T, m Meta) { if st := m.Rename(ctx, 1, "f2", 1, "d", 0, &inode, attr); st != 0 { t.Fatalf("rename f2 -> d: %s", st) } + if st := m.Link(ctx, inode, 1, "l", attr); st != 0 || attr.Nlink != 2 { + t.Fatalf("link d -> l1: %s", st) + } + if st := m.Unlink(ctx, 1, "l"); st != 0 { + t.Fatalf("unlink l: %s", st) + } + // hardlink goes to the trash + if st := m.GetAttr(ctx, inode, attr); st != 0 || attr.Nlink != 2 { + t.Fatalf("getattr d(%d): %s, attr %+v", inode, st, attr) + } + if st := m.Link(ctx, inode, 1, "l", attr); st != 0 || attr.Nlink != 3 { + t.Fatalf("link d -> l1: %s", st) + } + if st := m.Unlink(ctx, 1, "l"); st != 0 { + t.Fatalf("unlink l: %s", st) + } + // hardlink is deleted directly + if st := m.GetAttr(ctx, inode, attr); st != 0 || attr.Nlink != 2 { + t.Fatalf("getattr d(%d): %s, attr %+v", inode, st, attr) + } if st := m.Unlink(ctx, 1, "d"); st != 0 { t.Fatalf("unlink d: %s", st) } @@ -1485,7 +1505,7 @@ func testTrash(t *testing.T, m Meta) { if st := m.Readdir(ctx, TrashInode+1, 0, &entries); st != 0 { t.Fatalf("readdir: %s", st) } - if len(entries) != 8 { + if len(entries) != 9 { t.Fatalf("entries: %d", len(entries)) } ctx2 := NewContext(1000, 1, []uint32{1}) diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index 6c6782bdd9b0..92f6c6927e45 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -1405,6 +1405,10 @@ func (m *redisMeta) doUnlink(ctx Context, parent Ino, name string, attr *Attr, s if (attr.Flags&FlagAppend) != 0 || (attr.Flags&FlagImmutable) != 0 { return syscall.EPERM } + if trash > 0 && attr.Nlink > 1 && tx.HExists(ctx, m.entryKey(trash), m.trashEntry(parent, inode, name)).Val() { + trash = 0 + defer func() { m.of.InvalidateChunk(inode, invalidateAttrOnly) }() + } attr.Ctime = now.Unix() attr.Ctimensec = uint32(now.Nanosecond()) if trash == 0 { diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index 8a019ac88750..2f098f9b236e 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -1379,6 +1379,11 @@ func (m *dbMeta) doUnlink(ctx Context, parent Ino, name string, attr *Attr, skip if (n.Flags&FlagAppend) != 0 || (n.Flags&FlagImmutable) != 0 { return syscall.EPERM } + if trash > 0 && n.Nlink > 1 { + if o, e := s.Get(&edge{Parent: trash, Name: []byte(m.trashEntry(parent, e.Inode, string(e.Name))), Inode: e.Inode, Type: e.Type}); e == nil && o { + trash = 0 + } + } n.Ctime = now / 1e3 n.Ctimensec = int16(now % 1e3) if trash == 0 { diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 2398680b0b1d..ba19cf97dce5 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -1254,7 +1254,11 @@ func (m *kvMeta) doUnlink(ctx Context, parent Ino, name string, attr *Attr, skip if _type == TypeDirectory { return syscall.EPERM } - rs := tx.gets(m.inodeKey(parent), m.inodeKey(inode)) + keys := [][]byte{m.inodeKey(parent), m.inodeKey(inode)} + if trash > 0 { + keys = append(keys, m.entryKey(trash, m.trashEntry(parent, inode, name))) + } + rs := tx.gets(keys...) if rs[0] == nil { return syscall.ENOENT } @@ -1279,6 +1283,9 @@ func (m *kvMeta) doUnlink(ctx Context, parent Ino, name string, attr *Attr, skip if (attr.Flags&FlagAppend) != 0 || (attr.Flags&FlagImmutable) != 0 { return syscall.EPERM } + if trash > 0 && attr.Nlink > 1 && rs[2] != nil { + trash = 0 + } attr.Ctime = now.Unix() attr.Ctimensec = uint32(now.Nanosecond()) if trash == 0 {