diff --git a/pkg/meta/base_test.go b/pkg/meta/base_test.go index 6eef8ec6b9e2..9d9793e24454 100644 --- a/pkg/meta/base_test.go +++ b/pkg/meta/base_test.go @@ -1197,6 +1197,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) } @@ -1222,7 +1242,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 481cd6be1262..29ee6b2b9c76 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -1248,6 +1248,10 @@ func (m *redisMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno if ctx.Uid() != 0 && pattr.Mode&01000 != 0 && ctx.Uid() != pattr.Uid && ctx.Uid() != attr.Uid { return syscall.EACCES } + 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, 0xFFFFFFFE) }() + } 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 6c9dc0de3566..c6ab365f52a4 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -1258,6 +1258,11 @@ func (m *dbMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno { if ctx.Uid() != 0 && pn.Mode&01000 != 0 && ctx.Uid() != pn.Uid && ctx.Uid() != n.Uid { return syscall.EACCES } + 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 if trash == 0 { n.Nlink-- diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 58e5833e5bb7..88d145096526 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -1115,7 +1115,11 @@ func (m *kvMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno { 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 } @@ -1130,6 +1134,9 @@ func (m *kvMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno { if ctx.Uid() != 0 && pattr.Mode&01000 != 0 && ctx.Uid() != pattr.Uid && ctx.Uid() != attr.Uid { return syscall.EACCES } + if trash > 0 && attr.Nlink > 1 && rs[2] != nil { + trash = 0 + } attr.Ctime = now.Unix() attr.Ctimensec = uint32(now.Nanosecond()) if trash == 0 {