From 2ae0045fecfe6eb5e3e0c1e6431a32a66df9977a Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 4 Mar 2024 21:10:29 +0800 Subject: [PATCH 01/22] feat/acl: add acl support Signed-off-by: jiefeng --- pkg/meta/base.go | 64 ++++++++- pkg/meta/base_test.go | 177 ++++++++++++++++++++++- pkg/meta/interface.go | 11 ++ pkg/meta/redis.go | 274 +++++++++++++++++++++++++++++++++-- pkg/meta/sql.go | 328 ++++++++++++++++++++++++++++++++++++++---- pkg/meta/tkv.go | 238 ++++++++++++++++++++++++++++-- pkg/vfs/vfs.go | 161 +++++++++++++++++++-- 7 files changed, 1188 insertions(+), 65 deletions(-) diff --git a/pkg/meta/base.go b/pkg/meta/base.go index 02b8cc0fa79a..b92175dfa730 100644 --- a/pkg/meta/base.go +++ b/pkg/meta/base.go @@ -32,6 +32,7 @@ import ( "time" "github.com/dustin/go-humanize" + aclAPI "github.com/juicedata/juicefs/pkg/acl" "github.com/juicedata/juicefs/pkg/utils" "github.com/juicedata/juicefs/pkg/version" "github.com/pkg/errors" @@ -162,6 +163,7 @@ type baseMeta struct { reloadCb []func(*Format) umounting bool sesMu sync.Mutex + aclCache aclAPI.Cache dirStatsLock sync.Mutex dirStats map[Ino]dirStat @@ -202,6 +204,7 @@ func newBaseMeta(addr string, conf *Config) *baseMeta { msgCallbacks: &msgCallbacks{ callbacks: make(map[uint32]MsgCallback), }, + aclCache: aclAPI.NewCache(), usedSpaceG: prometheus.NewGauge(prometheus.GaugeOpts{ Name: "used_space", @@ -1160,15 +1163,19 @@ func (m *baseMeta) parseAttr(buf []byte, attr *Attr) { attr.Nlink = rb.Get32() attr.Length = rb.Get64() attr.Rdev = rb.Get32() - if rb.Left() >= 8 { + if rb.Left() >= 16 { attr.Parent = Ino(rb.Get64()) } attr.Full = true + if rb.Left() >= 8 { + attr.AccessACLId = rb.Get32() + attr.DefaultACLId = rb.Get32() + } logger.Tracef("attr: %+v -> %+v", buf, attr) } func (m *baseMeta) marshal(attr *Attr) []byte { - w := utils.NewBuffer(36 + 24 + 4 + 8) + w := utils.NewBuffer(36 + 24 + 4 + 8 + 8) w.Put8(attr.Flags) w.Put16((uint16(attr.Typ) << 12) | (attr.Mode & 0xfff)) w.Put32(attr.Uid) @@ -1183,6 +1190,8 @@ func (m *baseMeta) marshal(attr *Attr) []byte { w.Put64(attr.Length) w.Put32(attr.Rdev) w.Put64(uint64(attr.Parent)) + w.Put32(attr.AccessACLId) + w.Put32(attr.DefaultACLId) logger.Tracef("attr: %+v -> %+v", attr, w.Bytes()) return w.Bytes() } @@ -2696,7 +2705,7 @@ LOOP: return eno } -func (m *baseMeta) mergeAttr(ctx Context, inode Ino, set uint16, cur, attr *Attr, now time.Time) (*Attr, syscall.Errno) { +func (m *baseMeta) mergeAttr(ctx Context, inode Ino, set uint16, cur, attr *Attr, now time.Time, rule *aclAPI.Rule) (*Attr, syscall.Errno) { dirtyAttr := *cur if (set&(SetAttrUID|SetAttrGID)) != 0 && (set&SetAttrMode) != 0 { attr.Mode |= (cur.Mode & 06000) @@ -2731,7 +2740,12 @@ func (m *baseMeta) mergeAttr(ctx Context, inode Ino, set uint16, cur, attr *Attr attr.Mode &= 05777 } } - if attr.Mode != cur.Mode { + + if rule != nil { + rule.SetMode(attr.Mode) + dirtyAttr.Mode = attr.Mode&07000 | rule.GetMode() + changed = true + } else if attr.Mode != cur.Mode { if ctx.Uid() != 0 && ctx.Uid() != cur.Uid && (cur.Mode&01777 != attr.Mode&01777 || attr.Mode&02000 > cur.Mode&02000 || attr.Mode&04000 > cur.Mode&04000) { return nil, syscall.EPERM @@ -2793,6 +2807,46 @@ func (m *baseMeta) CheckSetAttr(ctx Context, inode Ino, set uint16, attr Attr) s if st := m.en.doGetAttr(ctx, inode, &cur); st != 0 { return st } - _, st := m.mergeAttr(ctx, inode, set, &cur, &attr, time.Now()) + _, st := m.mergeAttr(ctx, inode, set, &cur, &attr, time.Now(), nil) return st } + +const ACLCounterName = "acl" + +var errACLNotInCache = errors.New("acl not in cache") + +func (m *baseMeta) getFaclFromCache(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) error { + ino = m.checkRoot(ino) + cAttr := &Attr{} + if m.conf.OpenCache > 0 && m.of.Check(ino, cAttr) { + aclId := getAttrACLId(cAttr, aclType) + if aclId == aclAPI.None { + return ENOATTR + } + + if cRule := m.aclCache.Get(aclId); cRule != nil { + *rule = *cRule + return nil + } + } + return errACLNotInCache +} + +func setAttrACLId(attr *Attr, aclType uint8, id uint32) { + switch aclType { + case aclAPI.TypeAccess: + attr.AccessACLId = id + case aclAPI.TypeDefault: + attr.DefaultACLId = id + } +} + +func getAttrACLId(attr *Attr, aclType uint8) uint32 { + switch aclType { + case aclAPI.TypeAccess: + return attr.AccessACLId + case aclAPI.TypeDefault: + return attr.DefaultACLId + } + return aclAPI.None +} diff --git a/pkg/meta/base_test.go b/pkg/meta/base_test.go index c8ed8e546849..db9951ed9197 100644 --- a/pkg/meta/base_test.go +++ b/pkg/meta/base_test.go @@ -34,9 +34,12 @@ import ( "testing" "time" + "xorm.io/xorm" + + aclAPI "github.com/juicedata/juicefs/pkg/acl" "github.com/juicedata/juicefs/pkg/utils" "github.com/redis/go-redis/v9" - "xorm.io/xorm" + "github.com/stretchr/testify/assert" ) func testConfig() *Config { @@ -148,6 +151,178 @@ func testMeta(t *testing.T, m Meta) { testClone(t, m) base.conf.ReadOnly = true testReadOnly(t, m) + testACL(t, m) +} + +func testACL(t *testing.T, m Meta) { + if err := m.Init(testFormat(), false); err != nil { + t.Fatalf("test acl failed: %s", err) + } + + ctx := Background + testDir := "test_dir" + var testDirIno Ino + attr1 := &Attr{} + + if st := m.Mkdir(ctx, RootInode, testDir, 0644, 0, 0, &testDirIno, attr1); st != 0 { + t.Fatalf("create %s: %s", testDir, st) + } + + rule := &aclAPI.Rule{ + Owner: 7, + Group: 7, + Mask: 7, + Other: 7, + NamedUsers: []aclAPI.Entry{ + { + Id: 1001, + Perm: 4, + }, + }, + NamedGroups: nil, + } + + // case: setfacl + if st := m.SetFacl(ctx, testDirIno, aclAPI.TypeAccess, rule); st != 0 { + t.Fatalf("setfacl error: %s", st) + } + + // case: getfacl + rule2 := &aclAPI.Rule{} + if st := m.GetFacl(ctx, testDirIno, aclAPI.TypeAccess, rule2); st != 0 { + t.Fatalf("getfacl error: %s", st) + } + assert.True(t, rule.IsEqual(rule2)) + + // case: setfacl will sync mode (group class is mask) + attr2 := &Attr{} + if st := m.GetAttr(ctx, testDirIno, attr2); st != 0 { + t.Fatalf("getattr error: %s", st) + } + assert.Equal(t, uint16(0777), attr2.Mode) + + // case: setattr will sync acl + set := uint16(0) | SetAttrMode + attr2 = &Attr{ + Mode: 0555, + } + if st := m.SetAttr(ctx, testDirIno, set, 0, attr2); st != 0 { + t.Fatalf("setattr error: %s", st) + } + + rule3 := &aclAPI.Rule{} + if st := m.GetFacl(ctx, testDirIno, aclAPI.TypeAccess, rule3); st != 0 { + t.Fatalf("getfacl error: %s", st) + } + rule2.Owner = 5 + rule2.Mask = 5 + rule2.Other = 5 + assert.True(t, rule3.IsEqual(rule2)) + + // case: remove acl + rule3.Mask = 0xFFFF + rule3.NamedUsers = nil + rule3.NamedGroups = nil + if st := m.SetFacl(ctx, testDirIno, aclAPI.TypeAccess, rule3); st != 0 { + t.Fatalf("setattr error: %s", st) + } + + st := m.GetFacl(ctx, testDirIno, aclAPI.TypeAccess, nil) + assert.Equal(t, ENOATTR, st) + + attr2 = &Attr{} + if st := m.GetAttr(ctx, testDirIno, attr2); st != 0 { + t.Fatalf("getattr error: %s", st) + } + assert.Equal(t, uint16(0575), attr2.Mode) + + // case: set normal default acl + if st := m.SetFacl(ctx, testDirIno, aclAPI.TypeDefault, rule); st != 0 { + t.Fatalf("setfacl error: %s", st) + } + + // case: get normal default acl + rule2 = &aclAPI.Rule{} + if st := m.GetFacl(ctx, testDirIno, aclAPI.TypeDefault, rule2); st != 0 { + t.Fatalf("getfacl error: %s", st) + } + assert.True(t, rule2.IsEqual(rule)) + + // case: mk subdir with normal default acl + subDir := "sub_dir" + var subDirIno Ino + attr2 = &Attr{} + + mode := uint16(0222) + // cumask will be ignored + if st := m.Mkdir(ctx, testDirIno, subDir, mode, 0022, 0, &subDirIno, attr2); st != 0 { + t.Fatalf("create %s: %s", subDir, st) + } + + // subdir inherit default acl + rule3 = &aclAPI.Rule{} + if st := m.GetFacl(ctx, subDirIno, aclAPI.TypeDefault, rule3); st != 0 { + t.Fatalf("getfacl error: %s", st) + } + assert.True(t, rule3.IsEqual(rule2)) + + // subdir access acl + rule3 = &aclAPI.Rule{} + if st := m.GetFacl(ctx, subDirIno, aclAPI.TypeAccess, rule3); st != 0 { + t.Fatalf("getfacl error: %s", st) + } + rule2.Owner &= (mode >> 6) & 7 + rule2.Mask &= (mode >> 3) & 7 + rule2.Other &= mode & 7 + assert.True(t, rule3.IsEqual(rule2)) + + // case: set minimal default acl + rule = &aclAPI.Rule{ + Owner: 5, + Group: 5, + Mask: 0xFFFF, + Other: 5, + NamedUsers: nil, + NamedGroups: nil, + } + if st := m.SetFacl(ctx, testDirIno, aclAPI.TypeDefault, rule); st != 0 { + t.Fatalf("setfacl error: %s", st) + } + + // case: get minimal default acl + rule2 = &aclAPI.Rule{} + if st := m.GetFacl(ctx, testDirIno, aclAPI.TypeDefault, rule2); st != 0 { + t.Fatalf("getfacl error: %s", st) + } + assert.True(t, rule2.IsEqual(rule)) + + // case: mk subdir with minimal default acl + subDir2 := "sub_dir2" + var subDirIno2 Ino + attr2 = &Attr{} + + mode = uint16(0222) + if st := m.Mkdir(ctx, testDirIno, subDir2, mode, 0022, 0, &subDirIno2, attr2); st != 0 { + t.Fatalf("create %s: %s", subDir, st) + } + + // subdir inherit default acl + rule3 = &aclAPI.Rule{} + if st := m.GetFacl(ctx, subDirIno2, aclAPI.TypeDefault, rule3); st != 0 { + t.Fatalf("getfacl error: %s", st) + } + assert.True(t, rule3.IsEqual(rule2)) + + // subdir have no access acl + rule3 = &aclAPI.Rule{} + st = m.GetFacl(ctx, subDirIno2, aclAPI.TypeAccess, rule3) + assert.Equal(t, ENOATTR, st) + + attr2 = &Attr{} + if st := m.GetAttr(ctx, subDirIno2, attr2); st != 0 { + t.Fatalf("getattr error: %s", st) + } + assert.Equal(t, rule.GetMode(), attr2.Mode) } func testMetaClient(t *testing.T, m Meta) { diff --git a/pkg/meta/interface.go b/pkg/meta/interface.go index ca88fe9f5479..c0485f337c73 100644 --- a/pkg/meta/interface.go +++ b/pkg/meta/interface.go @@ -28,6 +28,7 @@ import ( "syscall" "time" + aclAPI "github.com/juicedata/juicefs/pkg/acl" "github.com/juicedata/juicefs/pkg/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -123,6 +124,10 @@ func (i Ino) IsTrash() bool { return i >= TrashInode } +func (i Ino) IsNormal() bool { + return i >= RootInode && i < TrashInode +} + var TrashName = ".trash" func isTrash(ino Ino) bool { @@ -161,6 +166,9 @@ type Attr struct { Parent Ino // inode of parent; 0 means tracked by parentKey (for hardlinks) Full bool // the attributes are completed or not KeepCache bool // whether to keep the cached page or not + + AccessACLId uint32 // access ACL id (identical ACL rules share the same access ACL ID.) + DefaultACLId uint32 // default ACL id (default ACL and the access ACL share the same cache and store) } func typeToStatType(_type uint8) uint32 { @@ -474,6 +482,9 @@ type Meta interface { // getBase return the base engine. getBase() *baseMeta InitMetrics(registerer prometheus.Registerer) + + SetFacl(ctx Context, ino Ino, aclType uint8, n *aclAPI.Rule) syscall.Errno + GetFacl(ctx Context, ino Ino, aclType uint8, n *aclAPI.Rule) syscall.Errno } type Creator func(driver, addr string, conf *Config) (Meta, error) diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index 053fa5565e95..7174dca4a1b3 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -44,9 +44,9 @@ import ( "syscall" "time" - "github.com/pkg/errors" - + aclAPI "github.com/juicedata/juicefs/pkg/acl" "github.com/juicedata/juicefs/pkg/utils" + "github.com/pkg/errors" "github.com/redis/go-redis/v9" ) @@ -318,7 +318,37 @@ func (m *redisMeta) doInit(format *Format, force bool) error { // root inode attr.Mode = 0777 - return m.rdb.Set(ctx, m.inodeKey(1), m.marshal(attr), 0).Err() + if err = m.rdb.Set(ctx, m.inodeKey(1), m.marshal(attr), 0).Err(); err != nil { + return err + } + + // cache all acls + maxId, err := m.getCounter(ACLCounterName) + if err != nil { + return err + } + + if maxId > 0 { + missKeys := make([]string, maxId) + for i := 0; i < int(maxId); i++ { + missKeys[i] = m.aclKey(uint32(i) + 1) + } + + acls, err := m.rdb.MGet(ctx, missKeys...).Result() + if err != nil { + return err + } + for i, val := range acls { + var tmpRule *aclAPI.Rule + if val != nil { + tmpRule = &aclAPI.Rule{} + tmpRule.Decode(val.([]byte)) + } + // may have empty slot + m.aclCache.Put(uint32(i)+1, tmpRule) + } + } + return nil } func (m *redisMeta) Reset() error { @@ -605,6 +635,10 @@ func (m *redisMeta) totalInodesKey() string { return m.prefix + totalInodes } +func (m *redisMeta) aclKey(id uint32) string { + return fmt.Sprintf("%sacl%d", m.prefix, id) +} + func (m *redisMeta) delfiles() string { return m.prefix + "delfiles" } @@ -799,11 +833,22 @@ func (m *redisMeta) Resolve(ctx Context, parent Ino, path string, inode *Ino, at } func (m *redisMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno { - a, err := m.rdb.Get(ctx, m.inodeKey(inode)).Bytes() - if err == nil { - m.parseAttr(a, attr) - } - return errno(err) + return errno(m.txn(ctx, func(tx *redis.Tx) error { + val, err := tx.Get(ctx, m.inodeKey(inode)).Bytes() + if err != nil { + return err + } + m.parseAttr(val, attr) + + if attr != nil && attr.AccessACLId != aclAPI.None { + rule := &aclAPI.Rule{} + if err := m.getACL(ctx, tx, attr.AccessACLId, rule); err != nil { + return err + } + attr.Mode = (rule.GetMode() & 0777) | (attr.Mode & 07000) + } + return nil + }, m.inodeKey(inode))) } type timeoutError interface { @@ -1141,13 +1186,33 @@ func (m *redisMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode return syscall.EPERM } now := time.Now() - dirtyAttr, st := m.mergeAttr(ctx, inode, set, &cur, attr, now) + + // get acl + var rule *aclAPI.Rule + if cur.AccessACLId != aclAPI.None { + rule = &aclAPI.Rule{} + if err := m.getACL(ctx, tx, cur.AccessACLId, rule); err != nil { + return err + } + } + + dirtyAttr, st := m.mergeAttr(ctx, inode, set, &cur, attr, now, rule) if st != 0 { return st } if dirtyAttr == nil { return nil } + + // set acl + if rule != nil { + aclId, err := m.insertACL(ctx, tx, rule) + if err != nil { + return err + } + setAttrACLId(dirtyAttr, aclAPI.TypeAccess, aclId) + } + dirtyAttr.Ctime = now.Unix() dirtyAttr.Ctimensec = uint32(now.Nanosecond()) _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { @@ -1216,7 +1281,6 @@ func (m *redisMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, m attr = &Attr{} } attr.Typ = _type - attr.Mode = mode & ^cumask attr.Uid = ctx.Uid() attr.Gid = ctx.Gid() if _type == TypeDirectory { @@ -1287,6 +1351,35 @@ func (m *redisMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, m return syscall.EEXIST } + mode &= 07777 + if pattr.DefaultACLId != aclAPI.None && _type != TypeSymlink { + // inherit default acl + if _type == TypeDirectory { + attr.DefaultACLId = pattr.DefaultACLId + } + + // set access acl by parent's default acl + rule := &aclAPI.Rule{} + if err = m.getACL(ctx, tx, pattr.DefaultACLId, rule); err != nil { + return err + } + + if rule.IsMinimal() { + // simple acl as default + attr.Mode = (mode & 0xFE00) | rule.GetMode() + } else { + cRule := rule.ChildAccessACL(mode) + id, err := m.insertACL(ctx, tx, cRule) + if err != nil { + return err + } + attr.AccessACLId = id + attr.Mode = (mode & 0xFE00) | cRule.GetMode() + } + } else { + attr.Mode = mode & ^cumask + } + var updateParent bool now := time.Now() if parent != TrashInode { @@ -4448,3 +4541,164 @@ func (m *redisMeta) doTouchAtime(ctx Context, inode Ino, attr *Attr, now time.Ti }, m.inodeKey(inode)) return updated, err } + +func (m *redisMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { + if aclType != aclAPI.TypeAccess && aclType != aclAPI.TypeDefault { + return syscall.EINVAL + } + + if !ino.IsNormal() { + return syscall.EPERM + } + + now := time.Now() // TODO from context + defer func() { + m.timeit("SetFacl", now) + m.of.InvalidateChunk(ino, invalidateAttrOnly) + }() + + return errno(m.txn(ctx, func(tx *redis.Tx) error { + val, err := tx.Get(ctx, m.inodeKey(ino)).Bytes() + if err != nil { + return err + } + attr := &Attr{} + m.parseAttr(val, attr) + + if ctx.Uid() != 0 && ctx.Uid() != attr.Uid { + return syscall.EPERM + } + + if rule.IsEmpty() { + // remove acl + setAttrACLId(attr, aclType, aclAPI.None) + } else if rule.IsMinimal() && aclType == aclAPI.TypeAccess { + // remove acl + setAttrACLId(attr, aclType, aclAPI.None) + // set mode + attr.Mode &= 07000 + attr.Mode |= ((rule.Owner & 7) << 6) | ((rule.Group & 7) << 3) | (rule.Other & 7) + } else { + // set acl + rule.InheritPerms(attr.Mode) + aclId, err := m.insertACL(ctx, tx, rule) + if err != nil { + return err + } + setAttrACLId(attr, aclType, aclId) + + // set mode + if aclType == aclAPI.TypeAccess { + attr.Mode &= 07000 + attr.Mode |= ((rule.Owner & 7) << 6) | ((rule.Mask & 7) << 3) | (rule.Other & 7) + } + } + + // update attr + attr.Ctime = now.Unix() + attr.Ctimensec = uint32(now.Nanosecond()) + _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { + pipe.Set(ctx, m.inodeKey(ino), m.marshal(attr), 0) + return nil + }) + return err + }, m.inodeKey(ino))) +} + +func (m *redisMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { + var err error + if err = m.getFaclFromCache(ctx, ino, aclType, rule); err == nil { + return 0 + } + + if !errors.Is(err, errACLNotInCache) { + return errno(err) + } + + defer m.timeit("GetFacl", time.Now()) + + return errno(m.txn(ctx, func(tx *redis.Tx) error { + val, err := tx.Get(ctx, m.inodeKey(ino)).Bytes() + if err != nil { + return err + } + attr := &Attr{} + m.parseAttr(val, attr) + m.of.Update(ino, attr) + + aclId := getAttrACLId(attr, aclType) + if aclId == aclAPI.None { + return ENOATTR + } + + return m.getACL(ctx, tx, aclId, rule) + }, m.inodeKey(ino))) +} + +func (m *redisMeta) getACL(ctx Context, tx *redis.Tx, id uint32, rule *aclAPI.Rule) error { + if cRule := m.aclCache.Get(id); cRule != nil { + *rule = *cRule + return nil + } + + cmds, err := tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { + pipe.Get(ctx, m.aclKey(id)) + return nil + }) + if err != nil { + return err + } + + val, err := cmds[0].(*redis.StringCmd).Bytes() + if err != nil { + return err + } + if val == nil { + return ENOATTR + } + + rule.Decode(val) + m.aclCache.Put(id, rule) + return nil +} + +func (m *redisMeta) insertACL(ctx Context, tx *redis.Tx, rule *aclAPI.Rule) (uint32, error) { + var aclId uint32 = aclAPI.None + if aclId = m.aclCache.GetId(rule); aclId == aclAPI.None { + // TODO failures may result in some id wastage. + newId, err := m.incrCounter(ACLCounterName, 1) + if err != nil { + return aclAPI.None, err + } + aclId = uint32(newId) + + if err = tx.Set(ctx, m.aclKey(aclId), rule.Encode(), 0).Err(); err != nil { + return aclAPI.None, err + } + m.aclCache.Put(aclId, rule) + + // try load miss + missIds := m.aclCache.GetMissIds(aclId) + if len(missIds) > 0 { + missKeys := make([]string, len(missIds)) + for i, id := range missIds { + missKeys[i] = m.aclKey(id) + } + + acls, err := tx.MGet(ctx, missKeys...).Result() + if err != nil { + return aclId, nil + } + for i, data := range acls { + var tmpRule *aclAPI.Rule + if data != nil { + tmpRule = &aclAPI.Rule{} + tmpRule.Decode(data.([]byte)) + } + // may have empty slot + m.aclCache.Put(missIds[i], tmpRule) + } + } + } + return aclId, nil +} diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index 3a93bb9b9d35..772f3de61869 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -39,6 +39,7 @@ import ( "xorm.io/xorm/log" "xorm.io/xorm/names" + aclAPI "github.com/juicedata/juicefs/pkg/acl" "github.com/juicedata/juicefs/pkg/utils" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -65,22 +66,68 @@ type edge struct { } type node struct { - Inode Ino `xorm:"pk"` - Type uint8 `xorm:"notnull"` - Flags uint8 `xorm:"notnull"` - Mode uint16 `xorm:"notnull"` - Uid uint32 `xorm:"notnull"` - Gid uint32 `xorm:"notnull"` - Atime int64 `xorm:"notnull"` - Mtime int64 `xorm:"notnull"` - Ctime int64 `xorm:"notnull"` - Atimensec int16 `xorm:"notnull default 0"` - Mtimensec int16 `xorm:"notnull default 0"` - Ctimensec int16 `xorm:"notnull default 0"` - Nlink uint32 `xorm:"notnull"` - Length uint64 `xorm:"notnull"` - Rdev uint32 - Parent Ino + Inode Ino `xorm:"pk"` + Type uint8 `xorm:"notnull"` + Flags uint8 `xorm:"notnull"` + Mode uint16 `xorm:"notnull"` + Uid uint32 `xorm:"notnull"` + Gid uint32 `xorm:"notnull"` + Atime int64 `xorm:"notnull"` + Mtime int64 `xorm:"notnull"` + Ctime int64 `xorm:"notnull"` + Atimensec int16 `xorm:"notnull default 0"` + Mtimensec int16 `xorm:"notnull default 0"` + Ctimensec int16 `xorm:"notnull default 0"` + Nlink uint32 `xorm:"notnull"` + Length uint64 `xorm:"notnull"` + Rdev uint32 + Parent Ino + AccessACLId uint32 `xorm:"'access_acl_id'"` + DefaultACLId uint32 `xorm:"'default_acl_id'"` +} + +func getACLIdColName(aclType uint8) string { + switch aclType { + case aclAPI.TypeAccess: + return "access_acl_id" + case aclAPI.TypeDefault: + return "default_acl_id" + } + return "" +} + +type acl struct { + Id uint32 `xorm:"pk autoincr"` + Owner uint16 + Group uint16 + Mask uint16 + Other uint16 + NamedUsers []byte + NamedGroups []byte +} + +func newSQLAcl(r *aclAPI.Rule) *acl { + a := &acl{ + Owner: r.Owner, + Group: r.Group, + Mask: r.Mask, + Other: r.Other, + } + a.NamedUsers = r.NamedUsers.Encode() + a.NamedGroups = r.NamedGroups.Encode() + return a +} + +func (a *acl) ToRule(r *aclAPI.Rule) { + if r == nil { + *r = aclAPI.Rule{} + } + r.Owner = a.Owner + r.Group = a.Group + r.Other = a.Other + r.Mask = a.Mask + r.NamedUsers.Decode(a.NamedUsers) + r.NamedGroups.Decode(a.NamedGroups) } type namedNode struct { @@ -191,6 +238,8 @@ type dbMeta struct { noReadOnlyTxn bool } +var _ Meta = &dbMeta{} + type dbSnap struct { node map[Ino]*node symlink map[Ino]*symlink @@ -326,6 +375,9 @@ func (m *dbMeta) doInit(format *Format, force bool) error { if err := m.syncTable(new(detachedNode)); err != nil { return fmt.Errorf("create table detachedNode: %s", err) } + if err := m.syncTable(new(acl)); err != nil { + return fmt.Errorf("create table acl: %s", err) + } var s = setting{Name: "format"} var ok bool @@ -410,7 +462,21 @@ func (m *dbMeta) doInit(format *Format, force bool) error { {"totalInodes", 0}, {"nextCleanupSlices", 0}, } - return mustInsert(s, n, &cs) + if err = mustInsert(s, n, &cs); err != nil { + return err + } + + // cache all acls + var acls []acl + if err = s.Find(&acls); err != nil { + return err + } + for _, val := range acls { + tmpRule := &aclAPI.Rule{} + val.ToRule(tmpRule) + m.aclCache.Put(val.Id, tmpRule) + } + return nil }) } @@ -814,6 +880,8 @@ func (m *dbMeta) parseAttr(n *node, attr *Attr) { attr.Rdev = n.Rdev attr.Parent = n.Parent attr.Full = true + attr.AccessACLId = n.AccessACLId + attr.DefaultACLId = n.DefaultACLId } func (m *dbMeta) parseNode(attr *Attr, n *node) { @@ -835,6 +903,8 @@ func (m *dbMeta) parseNode(attr *Attr, n *node) { n.Length = attr.Length n.Rdev = attr.Rdev n.Parent = attr.Parent + n.AccessACLId = attr.AccessACLId + n.DefaultACLId = attr.DefaultACLId } func (m *dbMeta) updateStats(space int64, inodes int64) { @@ -897,12 +967,21 @@ func (m *dbMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno { return errno(m.roTxn(func(s *xorm.Session) error { var n = node{Inode: inode} ok, err := s.Get(&n) - if ok { - m.parseAttr(&n, attr) - } else if err == nil { - err = syscall.ENOENT + if err != nil { + return err + } else if !ok { + return syscall.ENOENT } - return err + m.parseAttr(&n, attr) + + if attr != nil && attr.AccessACLId != aclAPI.None { + rule := &aclAPI.Rule{} + if err = m.getACL(s, attr.AccessACLId, rule); err != nil { + return err + } + attr.Mode = (rule.GetMode() & 0777) | (attr.Mode & 07000) + } + return nil })) } @@ -922,18 +1001,39 @@ func (m *dbMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui return syscall.EPERM } now := time.Now() - dirtyAttr, st := m.mergeAttr(ctx, inode, set, &curAttr, attr, now) + + // get acl + var rule *aclAPI.Rule + if curAttr.AccessACLId != aclAPI.None { + rule = &aclAPI.Rule{} + if err = m.getACL(s, curAttr.AccessACLId, rule); err != nil { + return err + } + } + + dirtyAttr, st := m.mergeAttr(ctx, inode, set, &curAttr, attr, now, rule) if st != 0 { return st } if dirtyAttr == nil { return nil } + + // set acl + if rule != nil { + aclId, err := m.insertACL(s, rule) + if err != nil { + return err + } + setAttrACLId(dirtyAttr, aclAPI.TypeAccess, aclId) + } + var dirtyNode node m.parseNode(dirtyAttr, &dirtyNode) dirtyNode.Ctime = now.UnixNano() / 1e3 dirtyNode.Ctimensec = int16(now.Nanosecond() % 1000) - _, err = s.Cols("flags", "mode", "uid", "gid", "atime", "mtime", "ctime", "atimensec", "mtimensec", "ctimensec"). + _, err = s.Cols("flags", "mode", "uid", "gid", "atime", "mtime", "ctime", + "atimensec", "mtimensec", "ctimensec", "access_acl_id", "default_acl_id"). Update(&dirtyNode, &node{Inode: inode}) if err == nil { m.parseAttr(&dirtyNode, attr) @@ -1210,7 +1310,6 @@ func (m *dbMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode var n node n.Inode = ino n.Type = _type - n.Mode = mode & ^cumask n.Uid = ctx.Uid() n.Gid = ctx.Gid() if _type == TypeDirectory { @@ -1285,6 +1384,35 @@ func (m *dbMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode return syscall.EEXIST } + mode &= 07777 + if pattr.DefaultACLId != aclAPI.None && _type != TypeSymlink { + // inherit default acl + if _type == TypeDirectory { + n.DefaultACLId = pattr.DefaultACLId + } + + // set access acl by parent's default acl + rule := &aclAPI.Rule{} + if err = m.getACL(s, pattr.DefaultACLId, rule); err != nil { + return err + } + + if rule.IsMinimal() { + // simple acl as default + n.Mode = (mode & 0xFE00) | rule.GetMode() + } else { + cRule := rule.ChildAccessACL(mode) + id, err := m.insertACL(s, cRule) + if err != nil { + return err + } + n.AccessACLId = id + n.Mode = (mode & 0xFE00) | cRule.GetMode() + } + } else { + n.Mode = mode & ^cumask + } + var updateParent bool now := time.Now().UnixNano() if parent != TrashInode { @@ -4168,3 +4296,153 @@ func (m *dbMeta) doTouchAtime(ctx Context, inode Ino, attr *Attr, now time.Time) }, inode) return updated, err } + +func (m *dbMeta) insertACL(s *xorm.Session, rule *aclAPI.Rule) (uint32, error) { + var aclId uint32 = aclAPI.None + if aclId = m.aclCache.GetId(rule); aclId == aclAPI.None { + // TODO conflicts from multiple clients are rare and result in only minor duplicates, thus not addressed for now. + val := newSQLAcl(rule) + if _, err := s.Insert(val); err != nil { + return aclAPI.None, err + } + aclId = val.Id + m.aclCache.Put(aclId, rule) + + // try load miss + missIds := m.aclCache.GetMissIds(val.Id) + if len(missIds) > 0 { + var acls []acl + if err := s.In("id", missIds).Find(&acls); err != nil { + return aclId, err + } + + for _, data := range acls { + tmpRule := &aclAPI.Rule{} + data.ToRule(tmpRule) + m.aclCache.Put(data.Id, tmpRule) + } + } + } + return aclId, nil +} + +func (m *dbMeta) getACL(s *xorm.Session, id uint32, rule *aclAPI.Rule) error { + if cRule := m.aclCache.Get(id); cRule != nil { + *rule = *cRule + return nil + } + + var aclVal = &acl{Id: id} + if ok, err := s.Get(aclVal); err != nil { + return err + } else if !ok { + return ENOATTR + } + + aclVal.ToRule(rule) + m.aclCache.Put(id, rule) + return nil +} + +func (m *dbMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { + if aclType != aclAPI.TypeAccess && aclType != aclAPI.TypeDefault { + return syscall.EINVAL + } + + if !ino.IsNormal() { + return syscall.EPERM + } + + now := time.Now() // TODO from context + defer func() { + m.timeit("SetFacl", now) + m.of.InvalidateChunk(ino, invalidateAttrOnly) + }() + + return errno(m.txn(func(s *xorm.Session) error { + attr := &Attr{} + n := &node{Inode: ino} + if ok, err := s.ForUpdate().Get(n); err != nil { + return err + } else if !ok { + return syscall.ENOENT + } + m.parseAttr(n, attr) + + if ctx.Uid() != 0 && ctx.Uid() != attr.Uid { + return syscall.EPERM + } + + oriMode := attr.Mode + if rule.IsEmpty() { + // remove acl + setAttrACLId(attr, aclType, aclAPI.None) + } else if rule.IsMinimal() && aclType == aclAPI.TypeAccess { + // remove acl + setAttrACLId(attr, aclType, aclAPI.None) + // set mode + attr.Mode &= 07000 + attr.Mode |= ((rule.Owner & 7) << 6) | ((rule.Group & 7) << 3) | (rule.Other & 7) + } else { + // set acl + rule.InheritPerms(attr.Mode) + aclId, err := m.insertACL(s, rule) + if err != nil { + return err + } + setAttrACLId(attr, aclType, aclId) + + // set mode + if aclType == aclAPI.TypeAccess { + attr.Mode &= 07000 + attr.Mode |= ((rule.Owner & 7) << 6) | ((rule.Mask & 7) << 3) | (rule.Other & 7) + } + } + + // update attr + var dirtyNode node + m.parseNode(attr, &dirtyNode) + dirtyNode.Ctime = now.UnixNano() / 1e3 + dirtyNode.Ctimensec = int16(now.Nanosecond() % 1000) + + updateCols := []string{"ctime", "ctimensec", getACLIdColName(aclType)} + if oriMode != attr.Mode { + updateCols = append(updateCols, "mode") + } + + _, err := s.Cols(updateCols...).Update(&dirtyNode, &node{Inode: ino}) + return err + }, ino)) +} + +func (m *dbMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { + var err error + if err = m.getFaclFromCache(ctx, ino, aclType, rule); err == nil { + return 0 + } + + if !errors.Is(err, errACLNotInCache) { + return errno(err) + } + + defer m.timeit("GetFacl", time.Now()) + + return errno(m.roTxn(func(s *xorm.Session) error { + attr := &Attr{} + n := &node{Inode: ino} + if ok, err := s.ForUpdate().Get(n); err != nil { + return err + } else if !ok { + return syscall.ENOENT + } + m.parseAttr(n, attr) + m.of.Update(ino, attr) + + aclId := getAttrACLId(attr, aclType) + if aclId == aclAPI.None { + return ENOATTR + } + + return m.getACL(s, aclId, rule) + })) +} diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index f24348fa2011..93a5c8c46b37 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -34,6 +34,7 @@ import ( "syscall" "time" + aclAPI "github.com/juicedata/juicefs/pkg/acl" "github.com/pkg/errors" "github.com/juicedata/juicefs/pkg/utils" @@ -79,6 +80,8 @@ type kvMeta struct { snap map[Ino]*DumpedEntry } +var _ Meta = &kvMeta{} + var drivers = make(map[string]func(string) (tkvClient, error)) func newTkvClient(driver, addr string) (tkvClient, error) { @@ -249,6 +252,10 @@ func (m *kvMeta) dirQuotaKey(inode Ino) []byte { return m.fmtKey("QD", inode) } +func (m *kvMeta) aclKey(id uint32) []byte { + return m.fmtKey("R", id) +} + func (m *kvMeta) parseSid(key string) uint64 { buf := []byte(key[2:]) // "SE" or "SH" if len(buf) != 8 { @@ -468,6 +475,25 @@ func (m *kvMeta) doInit(format *Format, force bool) error { tx.incrBy(m.counterKey("nextInode"), 2) tx.incrBy(m.counterKey("nextChunk"), 1) } + + // cache all acls + maxId, err := m.getCounter(ACLCounterName) + if err != nil { + return err + } + + if maxId > 0 { + missKeys := make([][]byte, maxId) + for i := 0; i < int(maxId); i++ { + missKeys[i] = m.aclKey(uint32(i) + 1) + } + acls := tx.gets(missKeys...) + for i, val := range acls { + tmpRule := &aclAPI.Rule{} + tmpRule.Decode(val) + m.aclCache.Put(uint32(i)+1, tmpRule) + } + } return nil }) } @@ -854,13 +880,22 @@ func (m *kvMeta) doLookup(ctx Context, parent Ino, name string, inode *Ino, attr } func (m *kvMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno { - a, err := m.get(m.inodeKey(inode)) - if a != nil { - m.parseAttr(a, attr) - } else if err == nil { - err = syscall.ENOENT - } - return errno(err) + return errno(m.txn(func(tx *kvTxn) error { + val := tx.get(m.inodeKey(inode)) + if val == nil { + return syscall.ENOENT + } + m.parseAttr(val, attr) + + if attr != nil && attr.AccessACLId != aclAPI.None { + rule := &aclAPI.Rule{} + if err := m.getACL(tx, attr.AccessACLId, rule); err != nil { + return err + } + attr.Mode = (rule.GetMode() & 0777) | (attr.Mode & 07000) + } + return nil + })) } func (m *kvMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode uint8, attr *Attr) syscall.Errno { @@ -875,13 +910,33 @@ func (m *kvMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui return syscall.EPERM } now := time.Now() - dirtyAttr, st := m.mergeAttr(ctx, inode, set, &cur, attr, now) + + // get acl + var rule *aclAPI.Rule + if cur.AccessACLId != aclAPI.None { + rule = &aclAPI.Rule{} + if err := m.getACL(tx, cur.AccessACLId, rule); err != nil { + return err + } + } + + dirtyAttr, st := m.mergeAttr(ctx, inode, set, &cur, attr, now, rule) if st != 0 { return st } if dirtyAttr == nil { return nil } + + // set acl + if rule != nil { + aclId, err := m.insertACL(tx, rule) + if err != nil { + return err + } + setAttrACLId(dirtyAttr, aclAPI.TypeAccess, aclId) + } + dirtyAttr.Ctime = now.Unix() dirtyAttr.Ctimensec = uint32(now.Nanosecond()) tx.set(m.inodeKey(inode), m.marshal(dirtyAttr)) @@ -1105,7 +1160,6 @@ func (m *kvMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode attr = &Attr{} } attr.Typ = _type - attr.Mode = mode & ^cumask attr.Uid = ctx.Uid() attr.Gid = ctx.Gid() if _type == TypeDirectory { @@ -1171,6 +1225,35 @@ func (m *kvMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode return syscall.EEXIST } + mode &= 07777 + if pattr.DefaultACLId != aclAPI.None && _type != TypeSymlink { + // inherit default acl + if _type == TypeDirectory { + attr.DefaultACLId = pattr.DefaultACLId + } + + // set access acl by parent's default acl + rule := &aclAPI.Rule{} + if err = m.getACL(tx, pattr.DefaultACLId, rule); err != nil { + return err + } + + if rule.IsMinimal() { + // simple acl as default + attr.Mode = (mode & 0xFE00) | rule.GetMode() + } else { + cRule := rule.ChildAccessACL(mode) + id, err := m.insertACL(tx, cRule) + if err != nil { + return err + } + attr.AccessACLId = id + attr.Mode = (mode & 0xFE00) | cRule.GetMode() + } + } else { + attr.Mode = mode & ^cumask + } + var updateParent bool now := time.Now() if parent != TrashInode { @@ -3587,3 +3670,140 @@ func (m *kvMeta) doTouchAtime(ctx Context, inode Ino, attr *Attr, now time.Time) }, inode) return updated, err } + +func (m *kvMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { + if aclType != aclAPI.TypeAccess && aclType != aclAPI.TypeDefault { + return syscall.EINVAL + } + + if !ino.IsNormal() { + return syscall.EPERM + } + + now := time.Now() // TODO from context + defer func() { + m.timeit("SetFacl", now) + m.of.InvalidateChunk(ino, invalidateAttrOnly) + }() + + return errno(m.txn(func(tx *kvTxn) error { + val := tx.get(m.inodeKey(ino)) + if val == nil { + return syscall.ENOENT + } + attr := &Attr{} + m.parseAttr(val, attr) + + if ctx.Uid() != 0 && ctx.Uid() != attr.Uid { + return syscall.EPERM + } + + if rule.IsEmpty() { + // remove acl + setAttrACLId(attr, aclType, aclAPI.None) + } else if rule.IsMinimal() && aclType == aclAPI.TypeAccess { + // remove acl + setAttrACLId(attr, aclType, aclAPI.None) + // set mode + attr.Mode &= 07000 + attr.Mode |= ((rule.Owner & 7) << 6) | ((rule.Group & 7) << 3) | (rule.Other & 7) + } else { + // set acl + rule.InheritPerms(attr.Mode) + aclId, err := m.insertACL(tx, rule) + if err != nil { + return err + } + setAttrACLId(attr, aclType, aclId) + + // set mode + if aclType == aclAPI.TypeAccess { + attr.Mode &= 07000 + attr.Mode |= ((rule.Owner & 7) << 6) | ((rule.Mask & 7) << 3) | (rule.Other & 7) + } + } + + // update attr + attr.Ctime = now.Unix() + attr.Ctimensec = uint32(now.Nanosecond()) + tx.set(m.inodeKey(ino), m.marshal(attr)) + return nil + }, ino)) +} + +func (m *kvMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { + var err error + if err = m.getFaclFromCache(ctx, ino, aclType, rule); err == nil { + return 0 + } + + if !errors.Is(err, errACLNotInCache) { + return errno(err) + } + + defer m.timeit("GetFacl", time.Now()) + + return errno(m.txn(func(tx *kvTxn) error { + val := tx.get(m.inodeKey(ino)) + if val == nil { + return syscall.ENOENT + } + attr := &Attr{} + m.parseAttr(val, attr) + m.of.Update(ino, attr) + + aclId := getAttrACLId(attr, aclType) + if aclId == aclAPI.None { + return ENOATTR + } + + return m.getACL(tx, aclId, rule) + })) +} + +func (m *kvMeta) insertACL(tx *kvTxn, rule *aclAPI.Rule) (uint32, error) { + var aclId uint32 = aclAPI.None + if aclId = m.aclCache.GetId(rule); aclId == aclAPI.None { + newId, err := m.incrCounter(ACLCounterName, 1) + if err != nil { + return aclAPI.None, err + } + aclId = uint32(newId) + + tx.set(m.aclKey(aclId), rule.Encode()) + m.aclCache.Put(aclId, rule) + + // try load miss + missIds := m.aclCache.GetMissIds(aclId) + if len(missIds) > 0 { + missKeys := make([][]byte, len(missIds)) + for i, id := range missIds { + missKeys[i] = m.aclKey(id) + } + + acls := tx.gets(missKeys...) + for i, data := range acls { + tmpRule := &aclAPI.Rule{} + tmpRule.Decode(data) + m.aclCache.Put(missIds[i], tmpRule) + } + } + } + return aclId, nil +} + +func (m *kvMeta) getACL(tx *kvTxn, id uint32, rule *aclAPI.Rule) error { + if cRule := m.aclCache.Get(id); cRule != nil { + *rule = *cRule + return nil + } + + val := tx.get(m.aclKey(id)) + if val == nil { + return ENOATTR + } + + rule.Decode(val) + m.aclCache.Put(id, rule) + return nil +} diff --git a/pkg/vfs/vfs.go b/pkg/vfs/vfs.go index 3e571b072d5b..1cd7977803bd 100644 --- a/pkg/vfs/vfs.go +++ b/pkg/vfs/vfs.go @@ -24,6 +24,7 @@ import ( "syscall" "time" + "github.com/juicedata/juicefs/pkg/acl" "github.com/juicedata/juicefs/pkg/chunk" "github.com/juicedata/juicefs/pkg/meta" "github.com/juicedata/juicefs/pkg/utils" @@ -901,11 +902,23 @@ func (v *VFS) SetXattr(ctx Context, ino Ino, name string, value []byte, flags ui err = syscall.EINVAL return } - if name == "system.posix_acl_access" || name == "system.posix_acl_default" { - err = syscall.ENOTSUP - return + + aclType := GetACLType(name) + if aclType != acl.TypeNone { + if !v.Conf.Format.EnableACL { + err = syscall.ENOTSUP + return + } + + var rule *acl.Rule + rule, err = decodeACL(value) + if err != 0 { + return + } + err = v.Meta.SetFacl(ctx, ino, aclType, rule) + } else { + err = v.Meta.SetXattr(ctx, ino, name, value, flags) } - err = v.Meta.SetXattr(ctx, ino, name, value, flags) return } @@ -927,13 +940,24 @@ func (v *VFS) GetXattr(ctx Context, ino Ino, name string, size uint32) (value [] err = syscall.EINVAL return } - if name == "system.posix_acl_access" || name == "system.posix_acl_default" { - err = syscall.ENOTSUP - return - } - err = v.Meta.GetXattr(ctx, ino, name, &value) - if size > 0 && len(value) > int(size) { - err = syscall.ERANGE + + aclType := GetACLType(name) + if aclType != acl.TypeNone { + if !v.Conf.Format.EnableACL { + err = syscall.ENOTSUP + return + } + + rule := &acl.Rule{} + if errno := v.Meta.GetFacl(ctx, ino, aclType, rule); errno != 0 { + return nil, errno + } + value, err = encodeACL(rule) + } else { + err = v.Meta.GetXattr(ctx, ino, name, &value) + if size > 0 && len(value) > int(size) { + err = syscall.ERANGE + } } return } @@ -957,9 +981,6 @@ func (v *VFS) RemoveXattr(ctx Context, ino Ino, name string) (err syscall.Errno) err = syscall.EPERM return } - if name == "system.posix_acl_access" || name == "system.posix_acl_default" { - return syscall.ENOTSUP - } if len(name) > xattrMaxName { if runtime.GOOS == "darwin" { err = syscall.EPERM @@ -972,7 +993,18 @@ func (v *VFS) RemoveXattr(ctx Context, ino Ino, name string) (err syscall.Errno) err = syscall.EINVAL return } - err = v.Meta.RemoveXattr(ctx, ino, name) + + aclType := GetACLType(name) + if aclType != acl.TypeNone { + if !v.Conf.Format.EnableACL { + err = syscall.ENOTSUP + return + } + err = v.Meta.SetFacl(ctx, ino, aclType, acl.EmptyRule()) + } else { + err = v.Meta.RemoveXattr(ctx, ino, name) + } + return } @@ -1123,3 +1155,102 @@ func InitMetrics(registerer prometheus.Registerer) { registerer.MustRegister(opsDurationsHistogram) registerer.MustRegister(compactSizeHistogram) } + +// Linux ACL format: +// +// version:8 (2) +// flags:8 (0) +// filler:16 +// N * [ tag:16 perm:16 id:32 ] +// tag: +// 01 - user +// 02 - named user +// 04 - group +// 08 - named group +// 10 - mask +// 20 - other + +func encodeACL(n *acl.Rule) ([]byte, syscall.Errno) { + length := 4 + 24 + uint32(len(n.NamedUsers)+len(n.NamedGroups))*8 + if n.Mask != 0xFFFF { + length += 8 + } + buff := make([]byte, length) + w := utils.NewNativeBuffer(buff) + w.Put8(2) // version + w.Put8(0) // flag + w.Put16(0) // filler + wRule := func(tag, perm uint16, id uint32) { + w.Put16(tag) + w.Put16(perm) + w.Put32(id) + } + wRule(1, n.Owner, 0xFFFFFFFF) + for _, rule := range n.NamedUsers { + wRule(2, rule.Perm, rule.Id) + } + wRule(4, n.Group, 0xFFFFFFFF) + for _, rule := range n.NamedGroups { + wRule(8, rule.Perm, rule.Id) + } + if n.Mask != 0xFFFF { + wRule(0x10, n.Mask, 0xFFFFFFFF) + } + wRule(0x20, n.Other, 0xFFFFFFFF) + return buff, 0 +} + +func decodeACL(buff []byte) (*acl.Rule, syscall.Errno) { + length := len(buff) + if length < 4 || ((length % 8) != 4) || buff[0] != acl.Version { + return nil, syscall.EINVAL + } + + n := acl.EmptyRule() + r := utils.NewNativeBuffer(buff[4:]) + for r.HasMore() { + tag := r.Get16() + perm := r.Get16() + id := r.Get32() + switch tag { + case 1: + if n.Owner != 0xFFFF { + return nil, syscall.EINVAL + } + n.Owner = perm + case 2: + n.NamedUsers = append(n.NamedUsers, acl.Entry{Id: id, Perm: perm}) + case 4: + if n.Group != 0xFFFF { + return nil, syscall.EINVAL + } + n.Group = perm + case 8: + n.NamedGroups = append(n.NamedGroups, acl.Entry{Id: id, Perm: perm}) + case 0x10: + if n.Mask != 0xFFFF { + return nil, syscall.EINVAL + } + n.Mask = perm + case 0x20: + if n.Other != 0xFFFF { + return nil, syscall.EINVAL + } + n.Other = perm + } + } + if n.Mask == 0xFFFF && len(n.NamedUsers)+len(n.NamedGroups) > 0 { + return nil, syscall.EINVAL + } + return n, 0 +} + +func GetACLType(name string) uint8 { + switch name { + case "system.posix_acl_access": + return acl.TypeAccess + case "system.posix_acl_default": + return acl.TypeDefault + } + return acl.TypeNone +} From fd7deb284913bf813e8eb0e12f2d64e43f2cf7f6 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 4 Mar 2024 21:35:08 +0800 Subject: [PATCH 02/22] feat/acl: fix test Signed-off-by: jiefeng --- pkg/meta/base_test.go | 2 +- pkg/meta/sql.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/meta/base_test.go b/pkg/meta/base_test.go index db9951ed9197..97f2c6db072d 100644 --- a/pkg/meta/base_test.go +++ b/pkg/meta/base_test.go @@ -149,9 +149,9 @@ func testMeta(t *testing.T, m Meta) { testCheckAndRepair(t, m) testDirStat(t, m) testClone(t, m) + testACL(t, m) base.conf.ReadOnly = true testReadOnly(t, m) - testACL(t, m) } func testACL(t *testing.T, m Meta) { diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index 772f3de61869..5fb00f36d103 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -507,7 +507,7 @@ func (m *dbMeta) doLoad() (data []byte, err error) { func (m *dbMeta) doNewSession(sinfo []byte) error { // add new table - err := m.syncTable(new(session2), new(delslices), new(dirStats), new(detachedNode), new(dirQuota)) + err := m.syncTable(new(session2), new(delslices), new(dirStats), new(detachedNode), new(dirQuota), new(acl)) if err != nil { return fmt.Errorf("update table session2, delslices, dirstats, detachedNode, dirQuota: %s", err) } @@ -3938,6 +3938,9 @@ func (m *dbMeta) LoadMeta(r io.Reader) error { if err := m.syncTable(new(detachedNode)); err != nil { return fmt.Errorf("create table detachedNode: %s", err) } + if err := m.syncTable(new(acl)); err != nil { + return fmt.Errorf("create table acl: %s", err) + } var batch int switch m.Name() { case "sqlite3": From 6fc7594081c6f4abc1eb29ad1af52878b604eeb6 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 4 Mar 2024 21:41:33 +0800 Subject: [PATCH 03/22] feat/acl: fix lint Signed-off-by: jiefeng --- pkg/meta/redis.go | 2 +- pkg/meta/sql.go | 2 +- pkg/meta/tkv.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index 7174dca4a1b3..8ca1e6467126 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -4663,7 +4663,7 @@ func (m *redisMeta) getACL(ctx Context, tx *redis.Tx, id uint32, rule *aclAPI.Ru } func (m *redisMeta) insertACL(ctx Context, tx *redis.Tx, rule *aclAPI.Rule) (uint32, error) { - var aclId uint32 = aclAPI.None + var aclId uint32 if aclId = m.aclCache.GetId(rule); aclId == aclAPI.None { // TODO failures may result in some id wastage. newId, err := m.incrCounter(ACLCounterName, 1) diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index 5fb00f36d103..81500b40b359 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -4301,7 +4301,7 @@ func (m *dbMeta) doTouchAtime(ctx Context, inode Ino, attr *Attr, now time.Time) } func (m *dbMeta) insertACL(s *xorm.Session, rule *aclAPI.Rule) (uint32, error) { - var aclId uint32 = aclAPI.None + var aclId uint32 if aclId = m.aclCache.GetId(rule); aclId == aclAPI.None { // TODO conflicts from multiple clients are rare and result in only minor duplicates, thus not addressed for now. val := newSQLAcl(rule) diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 93a5c8c46b37..4fb545c48fa9 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -3762,7 +3762,7 @@ func (m *kvMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) } func (m *kvMeta) insertACL(tx *kvTxn, rule *aclAPI.Rule) (uint32, error) { - var aclId uint32 = aclAPI.None + var aclId uint32 if aclId = m.aclCache.GetId(rule); aclId == aclAPI.None { newId, err := m.incrCounter(ACLCounterName, 1) if err != nil { From 20bad48bcf1c43ae9d38a57480b7b092ab1c6a0b Mon Sep 17 00:00:00 2001 From: jiefeng Date: Wed, 6 Mar 2024 16:03:41 +0800 Subject: [PATCH 04/22] feat/acl: fix test Signed-off-by: jiefeng --- pkg/acl/cache.go | 14 ++---- pkg/acl/cache_test.go | 3 +- pkg/meta/base.go | 16 +++++-- pkg/meta/redis.go | 104 ++++++++++++++++++++++++++---------------- pkg/meta/sql.go | 91 ++++++++++++++++++++---------------- pkg/meta/tkv.go | 98 +++++++++++++++++++++++---------------- 6 files changed, 191 insertions(+), 135 deletions(-) diff --git a/pkg/acl/cache.go b/pkg/acl/cache.go index d39fbc82b1f9..5a1640ee3b8b 100644 --- a/pkg/acl/cache.go +++ b/pkg/acl/cache.go @@ -33,7 +33,7 @@ type Cache interface { Get(id uint32) *Rule GetId(r *Rule) uint32 Size() int - GetMissIds(maxId uint32) []uint32 + GetMissIds() []uint32 } func NewCache() Cache { @@ -52,20 +52,16 @@ type cache struct { cksum2Id map[uint32][]uint32 } -// GetMissIds return all miss ids from 1 to max(maxId, c.maxId) -func (c *cache) GetMissIds(maxId uint32) []uint32 { +// GetMissIds return all miss ids from 1 to c.maxId +func (c *cache) GetMissIds() []uint32 { c.lock.RLock() defer c.lock.RUnlock() - if c.maxId == maxId && uint32(len(c.id2Rule)) == maxId { + if uint32(len(c.id2Rule)) == c.maxId { return nil } - if c.maxId > maxId { - maxId = c.maxId - } - - n := maxId + 1 + n := c.maxId + 1 mark := make([]bool, n) for i := uint32(1); i < n; i++ { if _, ok := c.id2Rule[i]; ok { diff --git a/pkg/acl/cache_test.go b/pkg/acl/cache_test.go index 760d5d529185..5dc267f731c3 100644 --- a/pkg/acl/cache_test.go +++ b/pkg/acl/cache_test.go @@ -66,8 +66,7 @@ func TestCache(t *testing.T) { assert.Equal(t, uint32(3), c.GetId(rule2)) c.Put(8, rule2) - assert.Equal(t, []uint32{4, 5, 6, 7, 9, 10}, c.GetMissIds(10)) - assert.Equal(t, []uint32{4, 5, 6, 7}, c.GetMissIds(6)) + assert.Equal(t, []uint32{4, 5, 6, 7}, c.GetMissIds()) assert.NotPanics(t, func() { c.Put(10, nil) diff --git a/pkg/meta/base.go b/pkg/meta/base.go index b92175dfa730..95ff1bd93d60 100644 --- a/pkg/meta/base.go +++ b/pkg/meta/base.go @@ -1163,7 +1163,7 @@ func (m *baseMeta) parseAttr(buf []byte, attr *Attr) { attr.Nlink = rb.Get32() attr.Length = rb.Get64() attr.Rdev = rb.Get32() - if rb.Left() >= 16 { + if rb.Left() >= 8 { attr.Parent = Ino(rb.Get64()) } attr.Full = true @@ -1175,7 +1175,11 @@ func (m *baseMeta) parseAttr(buf []byte, attr *Attr) { } func (m *baseMeta) marshal(attr *Attr) []byte { - w := utils.NewBuffer(36 + 24 + 4 + 8 + 8) + size := uint32(36 + 24 + 4 + 8) + if attr.AccessACLId+attr.DefaultACLId > 0 { + size += 8 + } + w := utils.NewBuffer(size) w.Put8(attr.Flags) w.Put16((uint16(attr.Typ) << 12) | (attr.Mode & 0xfff)) w.Put32(attr.Uid) @@ -1190,8 +1194,10 @@ func (m *baseMeta) marshal(attr *Attr) []byte { w.Put64(attr.Length) w.Put32(attr.Rdev) w.Put64(uint64(attr.Parent)) - w.Put32(attr.AccessACLId) - w.Put32(attr.DefaultACLId) + if attr.AccessACLId+attr.DefaultACLId > 0 { + w.Put32(attr.AccessACLId) + w.Put32(attr.DefaultACLId) + } logger.Tracef("attr: %+v -> %+v", attr, w.Bytes()) return w.Bytes() } @@ -2811,7 +2817,7 @@ func (m *baseMeta) CheckSetAttr(ctx Context, inode Ino, set uint16, attr Attr) s return st } -const ACLCounterName = "acl" +const aclCounter = "acl" var errACLNotInCache = errors.New("acl not in cache") diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index 8ca1e6467126..b23ca43f37c7 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -323,18 +323,18 @@ func (m *redisMeta) doInit(format *Format, force bool) error { } // cache all acls - maxId, err := m.getCounter(ACLCounterName) + maxId, err := m.getCounter(aclCounter) if err != nil { return err } if maxId > 0 { - missKeys := make([]string, maxId) + allKeys := make([]string, maxId) for i := 0; i < int(maxId); i++ { - missKeys[i] = m.aclKey(uint32(i) + 1) + allKeys[i] = m.aclKey(uint32(i) + 1) } - acls, err := m.rdb.MGet(ctx, missKeys...).Result() + acls, err := m.rdb.MGet(ctx, allKeys...).Result() if err != nil { return err } @@ -342,7 +342,7 @@ func (m *redisMeta) doInit(format *Format, force bool) error { var tmpRule *aclAPI.Rule if val != nil { tmpRule = &aclAPI.Rule{} - tmpRule.Decode(val.([]byte)) + tmpRule.Decode(([]byte)(val.(string))) } // may have empty slot m.aclCache.Put(uint32(i)+1, tmpRule) @@ -663,6 +663,10 @@ func (m *redisMeta) sliceRefs() string { return m.prefix + "sliceRef" } +func (m *redisMeta) counterName(name string) string { + return m.prefix + name +} + func (m *redisMeta) packQuota(space, inodes int64) []byte { wb := utils.NewBuffer(16) wb.Put64(uint64(space)) @@ -841,8 +845,8 @@ func (m *redisMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno m.parseAttr(val, attr) if attr != nil && attr.AccessACLId != aclAPI.None { - rule := &aclAPI.Rule{} - if err := m.getACL(ctx, tx, attr.AccessACLId, rule); err != nil { + rule, err := m.getACL(ctx, tx, attr.AccessACLId) + if err != nil { return err } attr.Mode = (rule.GetMode() & 0777) | (attr.Mode & 07000) @@ -1190,8 +1194,8 @@ func (m *redisMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode // get acl var rule *aclAPI.Rule if cur.AccessACLId != aclAPI.None { - rule = &aclAPI.Rule{} - if err := m.getACL(ctx, tx, cur.AccessACLId, rule); err != nil { + rule, err = m.getACL(ctx, tx, cur.AccessACLId) + if err != nil { return err } } @@ -1211,6 +1215,10 @@ func (m *redisMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode return err } setAttrACLId(dirtyAttr, aclAPI.TypeAccess, aclId) + + if err = m.tryLoadMissACLs(ctx, tx); err != nil { + logger.Warnf("SetAttr: load miss acls error: %s", err) + } } dirtyAttr.Ctime = now.Unix() @@ -1359,8 +1367,8 @@ func (m *redisMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, m } // set access acl by parent's default acl - rule := &aclAPI.Rule{} - if err = m.getACL(ctx, tx, pattr.DefaultACLId, rule); err != nil { + rule, err := m.getACL(ctx, tx, pattr.DefaultACLId) + if err != nil { return err } @@ -1373,6 +1381,10 @@ func (m *redisMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, m if err != nil { return err } + if err = m.tryLoadMissACLs(ctx, tx); err != nil { + logger.Warnf("Mknode: load miss acls error: %s", err) + } + attr.AccessACLId = id attr.Mode = (mode & 0xFE00) | cRule.GetMode() } @@ -4587,6 +4599,10 @@ func (m *redisMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Ru } setAttrACLId(attr, aclType, aclId) + if err = m.tryLoadMissACLs(ctx, tx); err != nil { + logger.Warnf("SetFacl: load miss acls error: %s", err) + } + // set mode if aclType == aclAPI.TypeAccess { attr.Mode &= 07000 @@ -4631,14 +4647,18 @@ func (m *redisMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Ru return ENOATTR } - return m.getACL(ctx, tx, aclId, rule) + a, err := m.getACL(ctx, tx, aclId) + if err != nil { + return err + } + *rule = *a + return nil }, m.inodeKey(ino))) } -func (m *redisMeta) getACL(ctx Context, tx *redis.Tx, id uint32, rule *aclAPI.Rule) error { +func (m *redisMeta) getACL(ctx Context, tx *redis.Tx, id uint32) (*aclAPI.Rule, error) { if cRule := m.aclCache.Get(id); cRule != nil { - *rule = *cRule - return nil + return cRule, nil } cmds, err := tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { @@ -4646,27 +4666,28 @@ func (m *redisMeta) getACL(ctx Context, tx *redis.Tx, id uint32, rule *aclAPI.Ru return nil }) if err != nil { - return err + return nil, err } val, err := cmds[0].(*redis.StringCmd).Bytes() if err != nil { - return err + return nil, err } if val == nil { - return ENOATTR + return nil, ENOATTR } + rule := &aclAPI.Rule{} rule.Decode(val) m.aclCache.Put(id, rule) - return nil + return rule, nil } func (m *redisMeta) insertACL(ctx Context, tx *redis.Tx, rule *aclAPI.Rule) (uint32, error) { var aclId uint32 if aclId = m.aclCache.GetId(rule); aclId == aclAPI.None { // TODO failures may result in some id wastage. - newId, err := m.incrCounter(ACLCounterName, 1) + newId, err := m.incrCounter(aclCounter, 1) if err != nil { return aclAPI.None, err } @@ -4676,29 +4697,32 @@ func (m *redisMeta) insertACL(ctx Context, tx *redis.Tx, rule *aclAPI.Rule) (uin return aclAPI.None, err } m.aclCache.Put(aclId, rule) + } + return aclId, nil +} - // try load miss - missIds := m.aclCache.GetMissIds(aclId) - if len(missIds) > 0 { - missKeys := make([]string, len(missIds)) - for i, id := range missIds { - missKeys[i] = m.aclKey(id) - } +func (m *redisMeta) tryLoadMissACLs(ctx Context, tx *redis.Tx) error { + // try load miss + missIds := m.aclCache.GetMissIds() + if len(missIds) > 0 { + missKeys := make([]string, len(missIds)) + for i, id := range missIds { + missKeys[i] = m.aclKey(id) + } - acls, err := tx.MGet(ctx, missKeys...).Result() - if err != nil { - return aclId, nil - } - for i, data := range acls { - var tmpRule *aclAPI.Rule - if data != nil { - tmpRule = &aclAPI.Rule{} - tmpRule.Decode(data.([]byte)) - } - // may have empty slot - m.aclCache.Put(missIds[i], tmpRule) + acls, err := tx.MGet(ctx, missKeys...).Result() + if err != nil { + return err + } + for i, data := range acls { + var rule *aclAPI.Rule + if data != nil { + rule = &aclAPI.Rule{} + rule.Decode(data.([]byte)) } + // may have empty slot + m.aclCache.Put(missIds[i], rule) } } - return aclId, nil + return nil } diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index 81500b40b359..4c4842ccab28 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -45,7 +45,7 @@ import ( "github.com/sirupsen/logrus" ) -const MaxFieldsCountOfTable = 16 // node table +const MaxFieldsCountOfTable = 18 // node table type setting struct { Name string `xorm:"pk"` @@ -118,16 +118,15 @@ func newSQLAcl(r *aclAPI.Rule) *acl { return a } -func (a *acl) ToRule(r *aclAPI.Rule) { - if r == nil { - *r = aclAPI.Rule{} - } +func (a *acl) toRule() *aclAPI.Rule { + r := &aclAPI.Rule{} r.Owner = a.Owner r.Group = a.Group r.Other = a.Other r.Mask = a.Mask r.NamedUsers.Decode(a.NamedUsers) r.NamedGroups.Decode(a.NamedGroups) + return r } type namedNode struct { @@ -472,9 +471,7 @@ func (m *dbMeta) doInit(format *Format, force bool) error { return err } for _, val := range acls { - tmpRule := &aclAPI.Rule{} - val.ToRule(tmpRule) - m.aclCache.Put(val.Id, tmpRule) + m.aclCache.Put(val.Id, val.toRule()) } return nil }) @@ -485,7 +482,7 @@ func (m *dbMeta) Reset() error { &node{}, &edge{}, &symlink{}, &xattr{}, &chunk{}, &sliceRef{}, &delslices{}, &session{}, &session2{}, &sustained{}, &delfile{}, - &flock{}, &plock{}, &dirStats{}, &dirQuota{}, &detachedNode{}) + &flock{}, &plock{}, &dirStats{}, &dirQuota{}, &detachedNode{}, &acl{}) } func (m *dbMeta) doLoad() (data []byte, err error) { @@ -975,8 +972,8 @@ func (m *dbMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno { m.parseAttr(&n, attr) if attr != nil && attr.AccessACLId != aclAPI.None { - rule := &aclAPI.Rule{} - if err = m.getACL(s, attr.AccessACLId, rule); err != nil { + rule, err := m.getACL(s, attr.AccessACLId) + if err != nil { return err } attr.Mode = (rule.GetMode() & 0777) | (attr.Mode & 07000) @@ -1005,8 +1002,8 @@ func (m *dbMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui // get acl var rule *aclAPI.Rule if curAttr.AccessACLId != aclAPI.None { - rule = &aclAPI.Rule{} - if err = m.getACL(s, curAttr.AccessACLId, rule); err != nil { + rule, err = m.getACL(s, curAttr.AccessACLId) + if err != nil { return err } } @@ -1026,6 +1023,10 @@ func (m *dbMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui return err } setAttrACLId(dirtyAttr, aclAPI.TypeAccess, aclId) + + if err = m.tryLoadMissACLs(s); err != nil { + logger.Warnf("SetAttr: load miss acls error: %s", err) + } } var dirtyNode node @@ -1392,8 +1393,8 @@ func (m *dbMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode } // set access acl by parent's default acl - rule := &aclAPI.Rule{} - if err = m.getACL(s, pattr.DefaultACLId, rule); err != nil { + rule, err := m.getACL(s, pattr.DefaultACLId) + if err != nil { return err } @@ -1406,6 +1407,10 @@ func (m *dbMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode if err != nil { return err } + if err = m.tryLoadMissACLs(s); err != nil { + logger.Warnf("Mknode: load miss acls error: %s", err) + } + n.AccessACLId = id n.Mode = (mode & 0xFE00) | cRule.GetMode() } @@ -3938,7 +3943,7 @@ func (m *dbMeta) LoadMeta(r io.Reader) error { if err := m.syncTable(new(detachedNode)); err != nil { return fmt.Errorf("create table detachedNode: %s", err) } - if err := m.syncTable(new(acl)); err != nil { + if err = m.syncTable(new(acl)); err != nil { return fmt.Errorf("create table acl: %s", err) } var batch int @@ -4310,41 +4315,40 @@ func (m *dbMeta) insertACL(s *xorm.Session, rule *aclAPI.Rule) (uint32, error) { } aclId = val.Id m.aclCache.Put(aclId, rule) + } + return aclId, nil +} - // try load miss - missIds := m.aclCache.GetMissIds(val.Id) - if len(missIds) > 0 { - var acls []acl - if err := s.In("id", missIds).Find(&acls); err != nil { - return aclId, err - } +func (m *dbMeta) tryLoadMissACLs(s *xorm.Session) error { + missIds := m.aclCache.GetMissIds() + if len(missIds) > 0 { + var acls []acl + if err := s.In("id", missIds).Find(&acls); err != nil { + return err + } - for _, data := range acls { - tmpRule := &aclAPI.Rule{} - data.ToRule(tmpRule) - m.aclCache.Put(data.Id, tmpRule) - } + for _, data := range acls { + m.aclCache.Put(data.Id, data.toRule()) } } - return aclId, nil + return nil } -func (m *dbMeta) getACL(s *xorm.Session, id uint32, rule *aclAPI.Rule) error { +func (m *dbMeta) getACL(s *xorm.Session, id uint32) (*aclAPI.Rule, error) { if cRule := m.aclCache.Get(id); cRule != nil { - *rule = *cRule - return nil + return cRule, nil } var aclVal = &acl{Id: id} if ok, err := s.Get(aclVal); err != nil { - return err + return nil, err } else if !ok { - return ENOATTR + return nil, ENOATTR } - aclVal.ToRule(rule) - m.aclCache.Put(id, rule) - return nil + r := aclVal.toRule() + m.aclCache.Put(id, r) + return r, nil } func (m *dbMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { @@ -4395,6 +4399,10 @@ func (m *dbMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) } setAttrACLId(attr, aclType, aclId) + if err = m.tryLoadMissACLs(s); err != nil { + logger.Warnf("SetFacl: load miss acls error: %s", err) + } + // set mode if aclType == aclAPI.TypeAccess { attr.Mode &= 07000 @@ -4433,7 +4441,7 @@ func (m *dbMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) return errno(m.roTxn(func(s *xorm.Session) error { attr := &Attr{} n := &node{Inode: ino} - if ok, err := s.ForUpdate().Get(n); err != nil { + if ok, err := s.Get(n); err != nil { return err } else if !ok { return syscall.ENOENT @@ -4446,6 +4454,11 @@ func (m *dbMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) return ENOATTR } - return m.getACL(s, aclId, rule) + a, err := m.getACL(s, aclId) + if err != nil { + return err + } + *rule = *a + return nil })) } diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 4fb545c48fa9..74c2d0e38d0e 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -256,6 +256,12 @@ func (m *kvMeta) aclKey(id uint32) []byte { return m.fmtKey("R", id) } +func (m *kvMeta) parseACLId(key string) uint32 { + // trim "R" + rb := utils.ReadBuffer([]byte(key[1:])) + return rb.Get32() +} + func (m *kvMeta) parseSid(key string) uint64 { buf := []byte(key[2:]) // "SE" or "SH" if len(buf) != 8 { @@ -477,22 +483,14 @@ func (m *kvMeta) doInit(format *Format, force bool) error { } // cache all acls - maxId, err := m.getCounter(ACLCounterName) + acls, err := m.scanValues(m.fmtKey("R"), -1, nil) if err != nil { return err } - - if maxId > 0 { - missKeys := make([][]byte, maxId) - for i := 0; i < int(maxId); i++ { - missKeys[i] = m.aclKey(uint32(i) + 1) - } - acls := tx.gets(missKeys...) - for i, val := range acls { - tmpRule := &aclAPI.Rule{} - tmpRule.Decode(val) - m.aclCache.Put(uint32(i)+1, tmpRule) - } + for key, val := range acls { + tmpRule := &aclAPI.Rule{} + tmpRule.Decode(val) + m.aclCache.Put(m.parseACLId(key), tmpRule) } return nil }) @@ -888,8 +886,8 @@ func (m *kvMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno { m.parseAttr(val, attr) if attr != nil && attr.AccessACLId != aclAPI.None { - rule := &aclAPI.Rule{} - if err := m.getACL(tx, attr.AccessACLId, rule); err != nil { + rule, err := m.getACL(tx, attr.AccessACLId) + if err != nil { return err } attr.Mode = (rule.GetMode() & 0777) | (attr.Mode & 07000) @@ -914,8 +912,9 @@ func (m *kvMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui // get acl var rule *aclAPI.Rule if cur.AccessACLId != aclAPI.None { - rule = &aclAPI.Rule{} - if err := m.getACL(tx, cur.AccessACLId, rule); err != nil { + var err error + rule, err = m.getACL(tx, cur.AccessACLId) + if err != nil { return err } } @@ -935,6 +934,10 @@ func (m *kvMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui return err } setAttrACLId(dirtyAttr, aclAPI.TypeAccess, aclId) + + if err = m.tryLoadMissACLs(tx); err != nil { + logger.Warnf("SetAttr: load miss acls error: %s", err) + } } dirtyAttr.Ctime = now.Unix() @@ -1233,8 +1236,8 @@ func (m *kvMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode } // set access acl by parent's default acl - rule := &aclAPI.Rule{} - if err = m.getACL(tx, pattr.DefaultACLId, rule); err != nil { + rule, err := m.getACL(tx, pattr.DefaultACLId) + if err != nil { return err } @@ -1247,6 +1250,10 @@ func (m *kvMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode if err != nil { return err } + if err = m.tryLoadMissACLs(tx); err != nil { + logger.Warnf("Mknode: load miss acls error: %s", err) + } + attr.AccessACLId = id attr.Mode = (mode & 0xFE00) | cRule.GetMode() } @@ -3716,6 +3723,10 @@ func (m *kvMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) } setAttrACLId(attr, aclType, aclId) + if err = m.tryLoadMissACLs(tx); err != nil { + logger.Warnf("SetFacl: load miss acls error: %s", err) + } + // set mode if aclType == aclAPI.TypeAccess { attr.Mode &= 07000 @@ -3757,14 +3768,19 @@ func (m *kvMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) return ENOATTR } - return m.getACL(tx, aclId, rule) + a, err := m.getACL(tx, aclId) + if err != nil { + return err + } + *rule = *a + return nil })) } func (m *kvMeta) insertACL(tx *kvTxn, rule *aclAPI.Rule) (uint32, error) { var aclId uint32 if aclId = m.aclCache.GetId(rule); aclId == aclAPI.None { - newId, err := m.incrCounter(ACLCounterName, 1) + newId, err := m.incrCounter(aclCounter, 1) if err != nil { return aclAPI.None, err } @@ -3772,38 +3788,40 @@ func (m *kvMeta) insertACL(tx *kvTxn, rule *aclAPI.Rule) (uint32, error) { tx.set(m.aclKey(aclId), rule.Encode()) m.aclCache.Put(aclId, rule) + } + return aclId, nil +} - // try load miss - missIds := m.aclCache.GetMissIds(aclId) - if len(missIds) > 0 { - missKeys := make([][]byte, len(missIds)) - for i, id := range missIds { - missKeys[i] = m.aclKey(id) - } +func (m *kvMeta) tryLoadMissACLs(tx *kvTxn) error { + missIds := m.aclCache.GetMissIds() + if len(missIds) > 0 { + missKeys := make([][]byte, len(missIds)) + for i, id := range missIds { + missKeys[i] = m.aclKey(id) + } - acls := tx.gets(missKeys...) - for i, data := range acls { - tmpRule := &aclAPI.Rule{} - tmpRule.Decode(data) - m.aclCache.Put(missIds[i], tmpRule) - } + acls := tx.gets(missKeys...) + for i, data := range acls { + r := &aclAPI.Rule{} + r.Decode(data) + m.aclCache.Put(missIds[i], r) } } - return aclId, nil + return nil } -func (m *kvMeta) getACL(tx *kvTxn, id uint32, rule *aclAPI.Rule) error { +func (m *kvMeta) getACL(tx *kvTxn, id uint32) (*aclAPI.Rule, error) { if cRule := m.aclCache.Get(id); cRule != nil { - *rule = *cRule - return nil + return cRule, nil } val := tx.get(m.aclKey(id)) if val == nil { - return ENOATTR + return nil, ENOATTR } + rule := &aclAPI.Rule{} rule.Decode(val) m.aclCache.Put(id, rule) - return nil + return rule, nil } From 116579c80b7b5090612200c0eab50f31f6402173 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Wed, 6 Mar 2024 16:06:31 +0800 Subject: [PATCH 05/22] feat/acl: fix lint Signed-off-by: jiefeng --- pkg/meta/redis.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index b23ca43f37c7..f4825a6363f9 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -663,10 +663,6 @@ func (m *redisMeta) sliceRefs() string { return m.prefix + "sliceRef" } -func (m *redisMeta) counterName(name string) string { - return m.prefix + name -} - func (m *redisMeta) packQuota(space, inodes int64) []byte { wb := utils.NewBuffer(16) wb.Put64(uint64(space)) From 706955fa8d0e7f9c3662bffe2c38968986ccb31d Mon Sep 17 00:00:00 2001 From: jiefeng Date: Fri, 8 Mar 2024 14:28:59 +0800 Subject: [PATCH 06/22] feat/acl: fix umask in acl #4458 Signed-off-by: jiefeng --- go.mod | 2 +- go.sum | 4 ++-- pkg/fuse/fuse.go | 3 ++- pkg/fuse/fuse_darwin.go | 4 ++++ pkg/fuse/fuse_linux.go | 4 ++++ 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 8e5eca15fb01..6c5fb093759e 100644 --- a/go.mod +++ b/go.mod @@ -255,7 +255,7 @@ require ( replace github.com/minio/minio v0.0.0-20210206053228-97fe57bba92c => github.com/juicedata/minio v0.0.0-20231213085529-c243663574ba -replace github.com/hanwen/go-fuse/v2 v2.1.1-0.20210611132105-24a1dfe6b4f8 => github.com/juicedata/go-fuse/v2 v2.1.1-0.20230726081302-124dbfa991d7 +replace github.com/hanwen/go-fuse/v2 v2.1.1-0.20210611132105-24a1dfe6b4f8 => github.com/juicedata/go-fuse/v2 v2.1.1-0.20240202080323-002ef792942e replace github.com/dgrijalva/jwt-go v3.2.0+incompatible => github.com/golang-jwt/jwt v3.2.1+incompatible diff --git a/go.sum b/go.sum index c8ca53d478f3..12193292fea6 100644 --- a/go.sum +++ b/go.sum @@ -622,8 +622,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juicedata/cli/v2 v2.19.4-0.20230605075551-9c9c5c0dce83 h1:RyHTka3jCnTaUqfRYjlwcQlr53aasmkvHEbYLXthqr8= github.com/juicedata/cli/v2 v2.19.4-0.20230605075551-9c9c5c0dce83/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= -github.com/juicedata/go-fuse/v2 v2.1.1-0.20230726081302-124dbfa991d7 h1:4evzoVz1/AZfk9tqxWdzVYTMl2dC7VjEJHfaSFDrKS8= -github.com/juicedata/go-fuse/v2 v2.1.1-0.20230726081302-124dbfa991d7/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc= +github.com/juicedata/go-fuse/v2 v2.1.1-0.20240202080323-002ef792942e h1:Oj9V2Losi2fVgVjQ4my0zDbw4F+Ry3VOnfTRCiWwy7Q= +github.com/juicedata/go-fuse/v2 v2.1.1-0.20240202080323-002ef792942e/go.mod h1:B1nGE/6RBFyBRC1RRnf23UpwCdyJ31eukw34oAKukAc= github.com/juicedata/go-nfs-client v0.0.0-20231018052507-dbca444fa7e8 h1:mVVipCbohnzKZPiHGzFncLKEJZpypqjpGr4End2PP48= github.com/juicedata/go-nfs-client v0.0.0-20231018052507-dbca444fa7e8/go.mod h1:xOMqi3lOrcGe9uZLnSzgaq94Vc3oz6VPCNDLJUnXpKs= github.com/juicedata/godaemon v0.0.0-20210629045518-3da5144a127d h1:kpQMvNZJKGY3PTt7OSoahYc4nM0HY67SvK0YyS0GLwA= diff --git a/pkg/fuse/fuse.go b/pkg/fuse/fuse.go index 79d920688172..20758f45218c 100644 --- a/pkg/fuse/fuse.go +++ b/pkg/fuse/fuse.go @@ -224,7 +224,7 @@ func (fs *fileSystem) RemoveXAttr(cancel <-chan struct{}, header *fuse.InHeader, func (fs *fileSystem) Create(cancel <-chan struct{}, in *fuse.CreateIn, name string, out *fuse.CreateOut) (code fuse.Status) { ctx := fs.newContext(cancel, &in.InHeader) defer releaseContext(ctx) - entry, fh, err := fs.v.Create(ctx, Ino(in.NodeId), name, uint16(in.Mode), 0, in.Flags) + entry, fh, err := fs.v.Create(ctx, Ino(in.NodeId), name, uint16(in.Mode), getCreateUmask(in), in.Flags) if err != 0 { return fuse.Status(err) } @@ -447,6 +447,7 @@ func Serve(v *vfs.VFS, options string, xattrs, ioctl bool) error { opt.MaxBackground = 50 opt.EnableLocks = true opt.EnableAcl = conf.Format.EnableACL + opt.DontUmask = conf.Format.EnableACL opt.DisableXAttrs = !xattrs opt.EnableIoctl = ioctl opt.MaxWrite = 1 << 20 diff --git a/pkg/fuse/fuse_darwin.go b/pkg/fuse/fuse_darwin.go index db7fa1fc7e44..d6bc4cae8154 100644 --- a/pkg/fuse/fuse_darwin.go +++ b/pkg/fuse/fuse_darwin.go @@ -24,5 +24,9 @@ func getUmask(in *fuse.MknodIn) uint16 { return 0 } +func getCreateUmask(in *fuse.CreateIn) uint16 { + return 0 +} + func setBlksize(out *fuse.Attr, size uint32) { } diff --git a/pkg/fuse/fuse_linux.go b/pkg/fuse/fuse_linux.go index d6988f9a06e2..171b89403a31 100644 --- a/pkg/fuse/fuse_linux.go +++ b/pkg/fuse/fuse_linux.go @@ -20,6 +20,10 @@ import ( "github.com/hanwen/go-fuse/v2/fuse" ) +func getCreateUmask(in *fuse.CreateIn) uint16 { + return uint16(in.Umask) +} + func getUmask(in *fuse.MknodIn) uint16 { return uint16(in.Umask) } From 2ffb5c942f4e93adab9d5c6b9b3ab952c3237e7f Mon Sep 17 00:00:00 2001 From: jiefeng Date: Fri, 8 Mar 2024 14:33:44 +0800 Subject: [PATCH 07/22] feat/acl: add acl key cache for kv meta Signed-off-by: jiefeng --- pkg/meta/redis.go | 22 ++++++++++++++-------- pkg/meta/tkv.go | 16 +++++++++++----- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index f4825a6363f9..8d4d7a57a7e1 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -84,10 +84,11 @@ import ( type redisMeta struct { *baseMeta - rdb redis.UniversalClient - prefix string - shaLookup string // The SHA returned by Redis for the loaded `scriptLookup` - shaResolve string // The SHA returned by Redis for the loaded `scriptResolve` + rdb redis.UniversalClient + prefix string + shaLookup string // The SHA returned by Redis for the loaded `scriptLookup` + shaResolve string // The SHA returned by Redis for the loaded `scriptResolve` + aclKeyCache map[uint32]string } var _ Meta = &redisMeta{} @@ -243,9 +244,10 @@ func newRedisMeta(driver, addr string, conf *Config) (Meta, error) { } m := &redisMeta{ - baseMeta: newBaseMeta(addr, conf), - rdb: rdb, - prefix: prefix, + baseMeta: newBaseMeta(addr, conf), + rdb: rdb, + prefix: prefix, + aclKeyCache: make(map[uint32]string), } m.en = m m.checkServerConfig() @@ -636,7 +638,11 @@ func (m *redisMeta) totalInodesKey() string { } func (m *redisMeta) aclKey(id uint32) string { - return fmt.Sprintf("%sacl%d", m.prefix, id) + if key, ok := m.aclKeyCache[id]; ok { + return key + } + m.aclKeyCache[id] = fmt.Sprintf("%sacl%d", m.prefix, id) + return m.aclKeyCache[id] } func (m *redisMeta) delfiles() string { diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 74c2d0e38d0e..664dad0d948a 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -76,8 +76,9 @@ func (tx *kvTxn) deleteKeys(prefix []byte) { type kvMeta struct { *baseMeta - client tkvClient - snap map[Ino]*DumpedEntry + client tkvClient + snap map[Ino]*DumpedEntry + aclKeyCache map[uint32][]byte } var _ Meta = &kvMeta{} @@ -100,8 +101,9 @@ func newKVMeta(driver, addr string, conf *Config) (Meta, error) { // TODO: ping server and check latency > Millisecond // logger.Warnf("The latency to database is too high: %s", time.Since(start)) m := &kvMeta{ - baseMeta: newBaseMeta(addr, conf), - client: client, + baseMeta: newBaseMeta(addr, conf), + client: client, + aclKeyCache: make(map[uint32][]byte), } m.en = m return m, nil @@ -253,7 +255,11 @@ func (m *kvMeta) dirQuotaKey(inode Ino) []byte { } func (m *kvMeta) aclKey(id uint32) []byte { - return m.fmtKey("R", id) + if key, ok := m.aclKeyCache[id]; ok { + return key + } + m.aclKeyCache[id] = m.fmtKey("R", id) + return m.aclKeyCache[id] } func (m *kvMeta) parseACLId(key string) uint32 { From 0ed0b0ba7c7b78572a2a35178e7a4c0989412823 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Fri, 8 Mar 2024 14:51:23 +0800 Subject: [PATCH 08/22] feat/acl: refactor Signed-off-by: jiefeng --- pkg/meta/redis.go | 23 ++++++++++++----------- pkg/meta/sql.go | 23 ++++++++++++----------- pkg/meta/tkv.go | 33 ++++++++++++++++++--------------- pkg/vfs/vfs.go | 22 +++++++++++----------- 4 files changed, 53 insertions(+), 48 deletions(-) diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index 8d4d7a57a7e1..c06b97f6fc0a 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -1212,15 +1212,15 @@ func (m *redisMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode // set acl if rule != nil { + if err = m.tryLoadMissACLs(ctx, tx); err != nil { + logger.Warnf("SetAttr: load miss acls error: %s", err) + } + aclId, err := m.insertACL(ctx, tx, rule) if err != nil { return err } setAttrACLId(dirtyAttr, aclAPI.TypeAccess, aclId) - - if err = m.tryLoadMissACLs(ctx, tx); err != nil { - logger.Warnf("SetAttr: load miss acls error: %s", err) - } } dirtyAttr.Ctime = now.Unix() @@ -1378,14 +1378,15 @@ func (m *redisMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, m // simple acl as default attr.Mode = (mode & 0xFE00) | rule.GetMode() } else { + if err = m.tryLoadMissACLs(ctx, tx); err != nil { + logger.Warnf("Mknode: load miss acls error: %s", err) + } + cRule := rule.ChildAccessACL(mode) id, err := m.insertACL(ctx, tx, cRule) if err != nil { return err } - if err = m.tryLoadMissACLs(ctx, tx); err != nil { - logger.Warnf("Mknode: load miss acls error: %s", err) - } attr.AccessACLId = id attr.Mode = (mode & 0xFE00) | cRule.GetMode() @@ -4593,6 +4594,10 @@ func (m *redisMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Ru attr.Mode &= 07000 attr.Mode |= ((rule.Owner & 7) << 6) | ((rule.Group & 7) << 3) | (rule.Other & 7) } else { + if err = m.tryLoadMissACLs(ctx, tx); err != nil { + logger.Warnf("SetFacl: load miss acls error: %s", err) + } + // set acl rule.InheritPerms(attr.Mode) aclId, err := m.insertACL(ctx, tx, rule) @@ -4601,10 +4606,6 @@ func (m *redisMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Ru } setAttrACLId(attr, aclType, aclId) - if err = m.tryLoadMissACLs(ctx, tx); err != nil { - logger.Warnf("SetFacl: load miss acls error: %s", err) - } - // set mode if aclType == aclAPI.TypeAccess { attr.Mode &= 07000 diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index 4c4842ccab28..d7c2993225eb 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -1018,15 +1018,15 @@ func (m *dbMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui // set acl if rule != nil { + if err = m.tryLoadMissACLs(s); err != nil { + logger.Warnf("SetAttr: load miss acls error: %s", err) + } + aclId, err := m.insertACL(s, rule) if err != nil { return err } setAttrACLId(dirtyAttr, aclAPI.TypeAccess, aclId) - - if err = m.tryLoadMissACLs(s); err != nil { - logger.Warnf("SetAttr: load miss acls error: %s", err) - } } var dirtyNode node @@ -1402,14 +1402,15 @@ func (m *dbMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode // simple acl as default n.Mode = (mode & 0xFE00) | rule.GetMode() } else { + if err = m.tryLoadMissACLs(s); err != nil { + logger.Warnf("Mknode: load miss acls error: %s", err) + } + cRule := rule.ChildAccessACL(mode) id, err := m.insertACL(s, cRule) if err != nil { return err } - if err = m.tryLoadMissACLs(s); err != nil { - logger.Warnf("Mknode: load miss acls error: %s", err) - } n.AccessACLId = id n.Mode = (mode & 0xFE00) | cRule.GetMode() @@ -4391,6 +4392,10 @@ func (m *dbMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) attr.Mode &= 07000 attr.Mode |= ((rule.Owner & 7) << 6) | ((rule.Group & 7) << 3) | (rule.Other & 7) } else { + if err := m.tryLoadMissACLs(s); err != nil { + logger.Warnf("SetFacl: load miss acls error: %s", err) + } + // set acl rule.InheritPerms(attr.Mode) aclId, err := m.insertACL(s, rule) @@ -4399,10 +4404,6 @@ func (m *dbMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) } setAttrACLId(attr, aclType, aclId) - if err = m.tryLoadMissACLs(s); err != nil { - logger.Warnf("SetFacl: load miss acls error: %s", err) - } - // set mode if aclType == aclAPI.TypeAccess { attr.Mode &= 07000 diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 664dad0d948a..4a68eadc01e5 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -170,6 +170,7 @@ func (m *kvMeta) fmtKey(args ...interface{}) []byte { name ... sliceId cccccccc session ssssssss + aclId aaaa All keys: setting format @@ -192,6 +193,7 @@ All keys: Uiiiiiiii data length, space and inodes usage in directory Niiiiiiii detached inde QDiiiiiiii directory quota + Raaaa POSIX acl */ func (m *kvMeta) inodeKey(inode Ino) []byte { @@ -884,7 +886,7 @@ func (m *kvMeta) doLookup(ctx Context, parent Ino, name string, inode *Ino, attr } func (m *kvMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno { - return errno(m.txn(func(tx *kvTxn) error { + return errno(m.client.txn(func(tx *kvTxn) error { val := tx.get(m.inodeKey(inode)) if val == nil { return syscall.ENOENT @@ -899,7 +901,7 @@ func (m *kvMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno { attr.Mode = (rule.GetMode() & 0777) | (attr.Mode & 07000) } return nil - })) + }, 0)) } func (m *kvMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode uint8, attr *Attr) syscall.Errno { @@ -935,15 +937,15 @@ func (m *kvMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui // set acl if rule != nil { + if err := m.tryLoadMissACLs(tx); err != nil { + logger.Warnf("SetAttr: load miss acls error: %s", err) + } + aclId, err := m.insertACL(tx, rule) if err != nil { return err } setAttrACLId(dirtyAttr, aclAPI.TypeAccess, aclId) - - if err = m.tryLoadMissACLs(tx); err != nil { - logger.Warnf("SetAttr: load miss acls error: %s", err) - } } dirtyAttr.Ctime = now.Unix() @@ -1251,14 +1253,15 @@ func (m *kvMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode // simple acl as default attr.Mode = (mode & 0xFE00) | rule.GetMode() } else { + if err = m.tryLoadMissACLs(tx); err != nil { + logger.Warnf("Mknode: load miss acls error: %s", err) + } + cRule := rule.ChildAccessACL(mode) id, err := m.insertACL(tx, cRule) if err != nil { return err } - if err = m.tryLoadMissACLs(tx); err != nil { - logger.Warnf("Mknode: load miss acls error: %s", err) - } attr.AccessACLId = id attr.Mode = (mode & 0xFE00) | cRule.GetMode() @@ -3721,6 +3724,10 @@ func (m *kvMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) attr.Mode &= 07000 attr.Mode |= ((rule.Owner & 7) << 6) | ((rule.Group & 7) << 3) | (rule.Other & 7) } else { + if err := m.tryLoadMissACLs(tx); err != nil { + logger.Warnf("SetFacl: load miss acls error: %s", err) + } + // set acl rule.InheritPerms(attr.Mode) aclId, err := m.insertACL(tx, rule) @@ -3729,10 +3736,6 @@ func (m *kvMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) } setAttrACLId(attr, aclType, aclId) - if err = m.tryLoadMissACLs(tx); err != nil { - logger.Warnf("SetFacl: load miss acls error: %s", err) - } - // set mode if aclType == aclAPI.TypeAccess { attr.Mode &= 07000 @@ -3760,7 +3763,7 @@ func (m *kvMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) defer m.timeit("GetFacl", time.Now()) - return errno(m.txn(func(tx *kvTxn) error { + return errno(m.client.txn(func(tx *kvTxn) error { val := tx.get(m.inodeKey(ino)) if val == nil { return syscall.ENOENT @@ -3780,7 +3783,7 @@ func (m *kvMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) } *rule = *a return nil - })) + }, 0)) } func (m *kvMeta) insertACL(tx *kvTxn, rule *aclAPI.Rule) (uint32, error) { diff --git a/pkg/vfs/vfs.go b/pkg/vfs/vfs.go index 1cd7977803bd..dae1ab304bef 100644 --- a/pkg/vfs/vfs.go +++ b/pkg/vfs/vfs.go @@ -949,15 +949,15 @@ func (v *VFS) GetXattr(ctx Context, ino Ino, name string, size uint32) (value [] } rule := &acl.Rule{} - if errno := v.Meta.GetFacl(ctx, ino, aclType, rule); errno != 0 { - return nil, errno + if err = v.Meta.GetFacl(ctx, ino, aclType, rule); err != 0 { + return nil, err } - value, err = encodeACL(rule) + value = encodeACL(rule) } else { err = v.Meta.GetXattr(ctx, ino, name, &value) - if size > 0 && len(value) > int(size) { - err = syscall.ERANGE - } + } + if size > 0 && len(value) > int(size) { + err = syscall.ERANGE } return } @@ -1170,16 +1170,16 @@ func InitMetrics(registerer prometheus.Registerer) { // 10 - mask // 20 - other -func encodeACL(n *acl.Rule) ([]byte, syscall.Errno) { +func encodeACL(n *acl.Rule) []byte { length := 4 + 24 + uint32(len(n.NamedUsers)+len(n.NamedGroups))*8 if n.Mask != 0xFFFF { length += 8 } buff := make([]byte, length) w := utils.NewNativeBuffer(buff) - w.Put8(2) // version - w.Put8(0) // flag - w.Put16(0) // filler + w.Put8(acl.Version) // version + w.Put8(0) // flag + w.Put16(0) // filler wRule := func(tag, perm uint16, id uint32) { w.Put16(tag) w.Put16(perm) @@ -1197,7 +1197,7 @@ func encodeACL(n *acl.Rule) ([]byte, syscall.Errno) { wRule(0x10, n.Mask, 0xFFFFFFFF) } wRule(0x20, n.Other, 0xFFFFFFFF) - return buff, 0 + return buff } func decodeACL(buff []byte) (*acl.Rule, syscall.Errno) { From ceecd3601b289500874a9444c8adf3d77a490784 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Fri, 8 Mar 2024 15:09:38 +0800 Subject: [PATCH 09/22] feat/acl: refactor Signed-off-by: jiefeng --- pkg/meta/redis.go | 22 +++++++++++++--------- pkg/meta/sql.go | 25 ++++++++++++++++--------- pkg/meta/tkv.go | 9 ++++++--- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index c06b97f6fc0a..caeb628a33c1 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -839,7 +839,7 @@ func (m *redisMeta) Resolve(ctx Context, parent Ino, path string, inode *Ino, at } func (m *redisMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno { - return errno(m.txn(ctx, func(tx *redis.Tx) error { + return errno(m.rdb.Watch(ctx, func(tx *redis.Tx) error { val, err := tx.Get(ctx, m.inodeKey(inode)).Bytes() if err != nil { return err @@ -4584,6 +4584,7 @@ func (m *redisMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Ru return syscall.EPERM } + oriACL, oriMode := getAttrACLId(attr, aclType), attr.Mode if rule.IsEmpty() { // remove acl setAttrACLId(attr, aclType, aclAPI.None) @@ -4614,13 +4615,16 @@ func (m *redisMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Ru } // update attr - attr.Ctime = now.Unix() - attr.Ctimensec = uint32(now.Nanosecond()) - _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { - pipe.Set(ctx, m.inodeKey(ino), m.marshal(attr), 0) - return nil - }) - return err + if oriACL != getAttrACLId(attr, aclType) || oriMode != attr.Mode { + attr.Ctime = now.Unix() + attr.Ctimensec = uint32(now.Nanosecond()) + _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { + pipe.Set(ctx, m.inodeKey(ino), m.marshal(attr), 0) + return nil + }) + return err + } + return nil }, m.inodeKey(ino))) } @@ -4636,7 +4640,7 @@ func (m *redisMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Ru defer m.timeit("GetFacl", time.Now()) - return errno(m.txn(ctx, func(tx *redis.Tx) error { + return errno(m.rdb.Watch(ctx, func(tx *redis.Tx) error { val, err := tx.Get(ctx, m.inodeKey(ino)).Bytes() if err != nil { return err diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index d7c2993225eb..721146275b12 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -4381,7 +4381,7 @@ func (m *dbMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) return syscall.EPERM } - oriMode := attr.Mode + oriACL, oriMode := getAttrACLId(attr, aclType), attr.Mode if rule.IsEmpty() { // remove acl setAttrACLId(attr, aclType, aclAPI.None) @@ -4412,18 +4412,25 @@ func (m *dbMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) } // update attr - var dirtyNode node - m.parseNode(attr, &dirtyNode) - dirtyNode.Ctime = now.UnixNano() / 1e3 - dirtyNode.Ctimensec = int16(now.Nanosecond() % 1000) - - updateCols := []string{"ctime", "ctimensec", getACLIdColName(aclType)} + var updateCols []string + if oriACL != getAttrACLId(attr, aclType) { + updateCols = append(updateCols, getACLIdColName(aclType)) + } if oriMode != attr.Mode { updateCols = append(updateCols, "mode") } + if len(updateCols) > 0 { + updateCols = append(updateCols, "ctime", "ctimensec") - _, err := s.Cols(updateCols...).Update(&dirtyNode, &node{Inode: ino}) - return err + var dirtyNode node + m.parseNode(attr, &dirtyNode) + dirtyNode.Ctime = now.UnixNano() / 1e3 + dirtyNode.Ctimensec = int16(now.Nanosecond() % 1000) + _, err := s.Cols(updateCols...).Update(&dirtyNode, &node{Inode: ino}) + return err + } + + return nil }, ino)) } diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 4a68eadc01e5..a5d79caec6dc 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -3714,6 +3714,7 @@ func (m *kvMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) return syscall.EPERM } + oriACL, oriMode := getAttrACLId(attr, aclType), attr.Mode if rule.IsEmpty() { // remove acl setAttrACLId(attr, aclType, aclAPI.None) @@ -3744,9 +3745,11 @@ func (m *kvMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) } // update attr - attr.Ctime = now.Unix() - attr.Ctimensec = uint32(now.Nanosecond()) - tx.set(m.inodeKey(ino), m.marshal(attr)) + if oriACL != getAttrACLId(attr, aclType) || oriMode != attr.Mode { + attr.Ctime = now.Unix() + attr.Ctimensec = uint32(now.Nanosecond()) + tx.set(m.inodeKey(ino), m.marshal(attr)) + } return nil }, ino)) } From d634b9521f42ba6e9c661118925965ab9b318d10 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Fri, 8 Mar 2024 15:26:14 +0800 Subject: [PATCH 10/22] feat/acl: move common code to baseMeta Signed-off-by: jiefeng --- pkg/meta/base.go | 37 ++++++++++++++++++++++++++++++++++++- pkg/meta/redis.go | 31 ++++--------------------------- pkg/meta/sql.go | 31 ++++--------------------------- pkg/meta/tkv.go | 31 ++++--------------------------- 4 files changed, 48 insertions(+), 82 deletions(-) diff --git a/pkg/meta/base.go b/pkg/meta/base.go index 95ff1bd93d60..4cccc5c10f5c 100644 --- a/pkg/meta/base.go +++ b/pkg/meta/base.go @@ -113,6 +113,9 @@ type engine interface { scanPendingFiles(Context, pendingFileScan) error GetSession(sid uint64, detail bool) (*Session, error) + + doSetFacl(ctx Context, ino Ino, aclType uint8, n *aclAPI.Rule) syscall.Errno + doGetFacl(ctx Context, ino Ino, aclType uint8, n *aclAPI.Rule) syscall.Errno } type trashSliceScan func(ss []Slice, ts int64) (clean bool, err error) @@ -1176,7 +1179,7 @@ func (m *baseMeta) parseAttr(buf []byte, attr *Attr) { func (m *baseMeta) marshal(attr *Attr) []byte { size := uint32(36 + 24 + 4 + 8) - if attr.AccessACLId+attr.DefaultACLId > 0 { + if attr.AccessACLId|attr.DefaultACLId != aclAPI.None { size += 8 } w := utils.NewBuffer(size) @@ -2856,3 +2859,35 @@ func getAttrACLId(attr *Attr, aclType uint8) uint32 { } return aclAPI.None } + +func (m *baseMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { + if aclType != aclAPI.TypeAccess && aclType != aclAPI.TypeDefault { + return syscall.EINVAL + } + + if !ino.IsNormal() { + return syscall.EPERM + } + + defer func() { + m.timeit("SetFacl", time.Now()) + m.of.InvalidateChunk(ino, invalidateAttrOnly) + }() + + return m.en.doSetFacl(ctx, ino, aclType, rule) +} + +func (m *baseMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { + var err error + if err = m.getFaclFromCache(ctx, ino, aclType, rule); err == nil { + return 0 + } + + if !errors.Is(err, errACLNotInCache) { + return errno(err) + } + + defer m.timeit("GetFacl", time.Now()) + + return m.en.doGetFacl(ctx, ino, aclType, rule) +} diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index caeb628a33c1..b248059bbd89 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -92,6 +92,7 @@ type redisMeta struct { } var _ Meta = &redisMeta{} +var _ engine = &redisMeta{} func init() { Register("redis", newRedisMeta) @@ -4557,21 +4558,7 @@ func (m *redisMeta) doTouchAtime(ctx Context, inode Ino, attr *Attr, now time.Ti return updated, err } -func (m *redisMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { - if aclType != aclAPI.TypeAccess && aclType != aclAPI.TypeDefault { - return syscall.EINVAL - } - - if !ino.IsNormal() { - return syscall.EPERM - } - - now := time.Now() // TODO from context - defer func() { - m.timeit("SetFacl", now) - m.of.InvalidateChunk(ino, invalidateAttrOnly) - }() - +func (m *redisMeta) doSetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { return errno(m.txn(ctx, func(tx *redis.Tx) error { val, err := tx.Get(ctx, m.inodeKey(ino)).Bytes() if err != nil { @@ -4616,6 +4603,7 @@ func (m *redisMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Ru // update attr if oriACL != getAttrACLId(attr, aclType) || oriMode != attr.Mode { + now := time.Now() attr.Ctime = now.Unix() attr.Ctimensec = uint32(now.Nanosecond()) _, err = tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { @@ -4628,18 +4616,7 @@ func (m *redisMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Ru }, m.inodeKey(ino))) } -func (m *redisMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { - var err error - if err = m.getFaclFromCache(ctx, ino, aclType, rule); err == nil { - return 0 - } - - if !errors.Is(err, errACLNotInCache) { - return errno(err) - } - - defer m.timeit("GetFacl", time.Now()) - +func (m *redisMeta) doGetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { return errno(m.rdb.Watch(ctx, func(tx *redis.Tx) error { val, err := tx.Get(ctx, m.inodeKey(ino)).Bytes() if err != nil { diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index 721146275b12..0314421fd39f 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -238,6 +238,7 @@ type dbMeta struct { } var _ Meta = &dbMeta{} +var _ engine = &dbMeta{} type dbSnap struct { node map[Ino]*node @@ -4352,21 +4353,7 @@ func (m *dbMeta) getACL(s *xorm.Session, id uint32) (*aclAPI.Rule, error) { return r, nil } -func (m *dbMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { - if aclType != aclAPI.TypeAccess && aclType != aclAPI.TypeDefault { - return syscall.EINVAL - } - - if !ino.IsNormal() { - return syscall.EPERM - } - - now := time.Now() // TODO from context - defer func() { - m.timeit("SetFacl", now) - m.of.InvalidateChunk(ino, invalidateAttrOnly) - }() - +func (m *dbMeta) doSetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { return errno(m.txn(func(s *xorm.Session) error { attr := &Attr{} n := &node{Inode: ino} @@ -4424,6 +4411,7 @@ func (m *dbMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) var dirtyNode node m.parseNode(attr, &dirtyNode) + now := time.Now() dirtyNode.Ctime = now.UnixNano() / 1e3 dirtyNode.Ctimensec = int16(now.Nanosecond() % 1000) _, err := s.Cols(updateCols...).Update(&dirtyNode, &node{Inode: ino}) @@ -4434,18 +4422,7 @@ func (m *dbMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) }, ino)) } -func (m *dbMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { - var err error - if err = m.getFaclFromCache(ctx, ino, aclType, rule); err == nil { - return 0 - } - - if !errors.Is(err, errACLNotInCache) { - return errno(err) - } - - defer m.timeit("GetFacl", time.Now()) - +func (m *dbMeta) doGetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { return errno(m.roTxn(func(s *xorm.Session) error { attr := &Attr{} n := &node{Inode: ino} diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index a5d79caec6dc..93668d941d85 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -82,6 +82,7 @@ type kvMeta struct { } var _ Meta = &kvMeta{} +var _ engine = &kvMeta{} var drivers = make(map[string]func(string) (tkvClient, error)) @@ -3687,21 +3688,7 @@ func (m *kvMeta) doTouchAtime(ctx Context, inode Ino, attr *Attr, now time.Time) return updated, err } -func (m *kvMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { - if aclType != aclAPI.TypeAccess && aclType != aclAPI.TypeDefault { - return syscall.EINVAL - } - - if !ino.IsNormal() { - return syscall.EPERM - } - - now := time.Now() // TODO from context - defer func() { - m.timeit("SetFacl", now) - m.of.InvalidateChunk(ino, invalidateAttrOnly) - }() - +func (m *kvMeta) doSetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { return errno(m.txn(func(tx *kvTxn) error { val := tx.get(m.inodeKey(ino)) if val == nil { @@ -3746,6 +3733,7 @@ func (m *kvMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) // update attr if oriACL != getAttrACLId(attr, aclType) || oriMode != attr.Mode { + now := time.Now() attr.Ctime = now.Unix() attr.Ctimensec = uint32(now.Nanosecond()) tx.set(m.inodeKey(ino), m.marshal(attr)) @@ -3754,18 +3742,7 @@ func (m *kvMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) }, ino)) } -func (m *kvMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { - var err error - if err = m.getFaclFromCache(ctx, ino, aclType, rule); err == nil { - return 0 - } - - if !errors.Is(err, errACLNotInCache) { - return errno(err) - } - - defer m.timeit("GetFacl", time.Now()) - +func (m *kvMeta) doGetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { return errno(m.client.txn(func(tx *kvTxn) error { val := tx.get(m.inodeKey(ino)) if val == nil { From c9849a2f0ef2b10f94c16b48f149f68a3d654a60 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Fri, 8 Mar 2024 16:04:06 +0800 Subject: [PATCH 11/22] feat/acl: rename acl Signed-off-by: jiefeng --- pkg/meta/base.go | 20 ++++++++++---------- pkg/meta/redis.go | 16 ++++++++-------- pkg/meta/sql.go | 22 +++++++++++----------- pkg/meta/tkv.go | 16 ++++++++-------- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/pkg/meta/base.go b/pkg/meta/base.go index 4cccc5c10f5c..b126b73faa76 100644 --- a/pkg/meta/base.go +++ b/pkg/meta/base.go @@ -1171,15 +1171,15 @@ func (m *baseMeta) parseAttr(buf []byte, attr *Attr) { } attr.Full = true if rb.Left() >= 8 { - attr.AccessACLId = rb.Get32() - attr.DefaultACLId = rb.Get32() + attr.AccessACL = rb.Get32() + attr.DefaultACL = rb.Get32() } logger.Tracef("attr: %+v -> %+v", buf, attr) } func (m *baseMeta) marshal(attr *Attr) []byte { size := uint32(36 + 24 + 4 + 8) - if attr.AccessACLId|attr.DefaultACLId != aclAPI.None { + if attr.AccessACL|attr.DefaultACL != aclAPI.None { size += 8 } w := utils.NewBuffer(size) @@ -1197,9 +1197,9 @@ func (m *baseMeta) marshal(attr *Attr) []byte { w.Put64(attr.Length) w.Put32(attr.Rdev) w.Put64(uint64(attr.Parent)) - if attr.AccessACLId+attr.DefaultACLId > 0 { - w.Put32(attr.AccessACLId) - w.Put32(attr.DefaultACLId) + if attr.AccessACL+attr.DefaultACL > 0 { + w.Put32(attr.AccessACL) + w.Put32(attr.DefaultACL) } logger.Tracef("attr: %+v -> %+v", attr, w.Bytes()) return w.Bytes() @@ -2844,18 +2844,18 @@ func (m *baseMeta) getFaclFromCache(ctx Context, ino Ino, aclType uint8, rule *a func setAttrACLId(attr *Attr, aclType uint8, id uint32) { switch aclType { case aclAPI.TypeAccess: - attr.AccessACLId = id + attr.AccessACL = id case aclAPI.TypeDefault: - attr.DefaultACLId = id + attr.DefaultACL = id } } func getAttrACLId(attr *Attr, aclType uint8) uint32 { switch aclType { case aclAPI.TypeAccess: - return attr.AccessACLId + return attr.AccessACL case aclAPI.TypeDefault: - return attr.DefaultACLId + return attr.DefaultACL } return aclAPI.None } diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index b248059bbd89..ccfe669a4215 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -847,8 +847,8 @@ func (m *redisMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno } m.parseAttr(val, attr) - if attr != nil && attr.AccessACLId != aclAPI.None { - rule, err := m.getACL(ctx, tx, attr.AccessACLId) + if attr != nil && attr.AccessACL != aclAPI.None { + rule, err := m.getACL(ctx, tx, attr.AccessACL) if err != nil { return err } @@ -1196,8 +1196,8 @@ func (m *redisMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode // get acl var rule *aclAPI.Rule - if cur.AccessACLId != aclAPI.None { - rule, err = m.getACL(ctx, tx, cur.AccessACLId) + if cur.AccessACL != aclAPI.None { + rule, err = m.getACL(ctx, tx, cur.AccessACL) if err != nil { return err } @@ -1363,14 +1363,14 @@ func (m *redisMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, m } mode &= 07777 - if pattr.DefaultACLId != aclAPI.None && _type != TypeSymlink { + if pattr.DefaultACL != aclAPI.None && _type != TypeSymlink { // inherit default acl if _type == TypeDirectory { - attr.DefaultACLId = pattr.DefaultACLId + attr.DefaultACL = pattr.DefaultACL } // set access acl by parent's default acl - rule, err := m.getACL(ctx, tx, pattr.DefaultACLId) + rule, err := m.getACL(ctx, tx, pattr.DefaultACL) if err != nil { return err } @@ -1389,7 +1389,7 @@ func (m *redisMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, m return err } - attr.AccessACLId = id + attr.AccessACL = id attr.Mode = (mode & 0xFE00) | cRule.GetMode() } } else { diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index 0314421fd39f..1928cb365621 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -878,8 +878,8 @@ func (m *dbMeta) parseAttr(n *node, attr *Attr) { attr.Rdev = n.Rdev attr.Parent = n.Parent attr.Full = true - attr.AccessACLId = n.AccessACLId - attr.DefaultACLId = n.DefaultACLId + attr.AccessACL = n.AccessACLId + attr.DefaultACL = n.DefaultACLId } func (m *dbMeta) parseNode(attr *Attr, n *node) { @@ -901,8 +901,8 @@ func (m *dbMeta) parseNode(attr *Attr, n *node) { n.Length = attr.Length n.Rdev = attr.Rdev n.Parent = attr.Parent - n.AccessACLId = attr.AccessACLId - n.DefaultACLId = attr.DefaultACLId + n.AccessACLId = attr.AccessACL + n.DefaultACLId = attr.DefaultACL } func (m *dbMeta) updateStats(space int64, inodes int64) { @@ -972,8 +972,8 @@ func (m *dbMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno { } m.parseAttr(&n, attr) - if attr != nil && attr.AccessACLId != aclAPI.None { - rule, err := m.getACL(s, attr.AccessACLId) + if attr != nil && attr.AccessACL != aclAPI.None { + rule, err := m.getACL(s, attr.AccessACL) if err != nil { return err } @@ -1002,8 +1002,8 @@ func (m *dbMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui // get acl var rule *aclAPI.Rule - if curAttr.AccessACLId != aclAPI.None { - rule, err = m.getACL(s, curAttr.AccessACLId) + if curAttr.AccessACL != aclAPI.None { + rule, err = m.getACL(s, curAttr.AccessACL) if err != nil { return err } @@ -1387,14 +1387,14 @@ func (m *dbMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode } mode &= 07777 - if pattr.DefaultACLId != aclAPI.None && _type != TypeSymlink { + if pattr.DefaultACL != aclAPI.None && _type != TypeSymlink { // inherit default acl if _type == TypeDirectory { - n.DefaultACLId = pattr.DefaultACLId + n.DefaultACLId = pattr.DefaultACL } // set access acl by parent's default acl - rule, err := m.getACL(s, pattr.DefaultACLId) + rule, err := m.getACL(s, pattr.DefaultACL) if err != nil { return err } diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 93668d941d85..203af4609b7f 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -894,8 +894,8 @@ func (m *kvMeta) doGetAttr(ctx Context, inode Ino, attr *Attr) syscall.Errno { } m.parseAttr(val, attr) - if attr != nil && attr.AccessACLId != aclAPI.None { - rule, err := m.getACL(tx, attr.AccessACLId) + if attr != nil && attr.AccessACL != aclAPI.None { + rule, err := m.getACL(tx, attr.AccessACL) if err != nil { return err } @@ -920,9 +920,9 @@ func (m *kvMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui // get acl var rule *aclAPI.Rule - if cur.AccessACLId != aclAPI.None { + if cur.AccessACL != aclAPI.None { var err error - rule, err = m.getACL(tx, cur.AccessACLId) + rule, err = m.getACL(tx, cur.AccessACL) if err != nil { return err } @@ -1238,14 +1238,14 @@ func (m *kvMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode } mode &= 07777 - if pattr.DefaultACLId != aclAPI.None && _type != TypeSymlink { + if pattr.DefaultACL != aclAPI.None && _type != TypeSymlink { // inherit default acl if _type == TypeDirectory { - attr.DefaultACLId = pattr.DefaultACLId + attr.DefaultACL = pattr.DefaultACL } // set access acl by parent's default acl - rule, err := m.getACL(tx, pattr.DefaultACLId) + rule, err := m.getACL(tx, pattr.DefaultACL) if err != nil { return err } @@ -1264,7 +1264,7 @@ func (m *kvMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode return err } - attr.AccessACLId = id + attr.AccessACL = id attr.Mode = (mode & 0xFE00) | cRule.GetMode() } } else { From 12c47f5f4e260ea5db587f9b5d1b4799f6e8af7d Mon Sep 17 00:00:00 2001 From: jiefeng Date: Fri, 8 Mar 2024 16:21:17 +0800 Subject: [PATCH 12/22] feat/acl: rename acl Signed-off-by: jiefeng --- pkg/meta/interface.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/meta/interface.go b/pkg/meta/interface.go index c0485f337c73..eec25ca610c7 100644 --- a/pkg/meta/interface.go +++ b/pkg/meta/interface.go @@ -167,8 +167,8 @@ type Attr struct { Full bool // the attributes are completed or not KeepCache bool // whether to keep the cached page or not - AccessACLId uint32 // access ACL id (identical ACL rules share the same access ACL ID.) - DefaultACLId uint32 // default ACL id (default ACL and the access ACL share the same cache and store) + AccessACL uint32 // access ACL id (identical ACL rules share the same access ACL ID.) + DefaultACL uint32 // default ACL id (default ACL and the access ACL share the same cache and store) } func typeToStatType(_type uint8) uint32 { From e9ee8880b706ac0cff8080e68fb92482f097fa6b Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 11 Mar 2024 10:14:51 +0800 Subject: [PATCH 13/22] fix: add FlagImmutable check Signed-off-by: jiefeng --- pkg/meta/redis.go | 4 ++++ pkg/meta/sql.go | 4 ++++ pkg/meta/tkv.go | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index ccfe669a4215..38140ad044d5 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -4571,6 +4571,10 @@ func (m *redisMeta) doSetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI. return syscall.EPERM } + if attr.Flags&FlagImmutable != 0 { + return syscall.EPERM + } + oriACL, oriMode := getAttrACLId(attr, aclType), attr.Mode if rule.IsEmpty() { // remove acl diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index 1928cb365621..c17d5f7cf03e 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -4368,6 +4368,10 @@ func (m *dbMeta) doSetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rul return syscall.EPERM } + if attr.Flags&FlagImmutable != 0 { + return syscall.EPERM + } + oriACL, oriMode := getAttrACLId(attr, aclType), attr.Mode if rule.IsEmpty() { // remove acl diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 203af4609b7f..ed736a7fd946 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -3701,6 +3701,10 @@ func (m *kvMeta) doSetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rul return syscall.EPERM } + if attr.Flags&FlagImmutable != 0 { + return syscall.EPERM + } + oriACL, oriMode := getAttrACLId(attr, aclType), attr.Mode if rule.IsEmpty() { // remove acl From 792f660bff55a5919e67f4c383e69c20f5280561 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 11 Mar 2024 15:44:46 +0800 Subject: [PATCH 14/22] fix: rule reused #4472 Signed-off-by: jiefeng --- pkg/meta/redis.go | 4 +++- pkg/meta/sql.go | 4 +++- pkg/meta/tkv.go | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index 38140ad044d5..f7030780847e 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -1197,10 +1197,12 @@ func (m *redisMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode // get acl var rule *aclAPI.Rule if cur.AccessACL != aclAPI.None { - rule, err = m.getACL(ctx, tx, cur.AccessACL) + oldRule, err := m.getACL(ctx, tx, cur.AccessACL) if err != nil { return err } + rule = &aclAPI.Rule{} + *rule = *oldRule } dirtyAttr, st := m.mergeAttr(ctx, inode, set, &cur, attr, now, rule) diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index c17d5f7cf03e..c0df44eaeefe 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -1003,10 +1003,12 @@ func (m *dbMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui // get acl var rule *aclAPI.Rule if curAttr.AccessACL != aclAPI.None { - rule, err = m.getACL(s, curAttr.AccessACL) + oldRule, err := m.getACL(s, curAttr.AccessACL) if err != nil { return err } + rule = &aclAPI.Rule{} + *rule = *oldRule } dirtyAttr, st := m.mergeAttr(ctx, inode, set, &curAttr, attr, now, rule) diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index ed736a7fd946..87284bee03d6 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -922,10 +922,12 @@ func (m *kvMeta) doSetAttr(ctx Context, inode Ino, set uint16, sugidclearmode ui var rule *aclAPI.Rule if cur.AccessACL != aclAPI.None { var err error - rule, err = m.getACL(tx, cur.AccessACL) + oldRule, err := m.getACL(tx, cur.AccessACL) if err != nil { return err } + rule = &aclAPI.Rule{} + *rule = *oldRule } dirtyAttr, st := m.mergeAttr(ctx, inode, set, &cur, attr, now, rule) From f5d2108c8e5f9d49d54f818e4e7313a2dd80c8e6 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 11 Mar 2024 15:50:22 +0800 Subject: [PATCH 15/22] fix: time Signed-off-by: jiefeng --- pkg/meta/base.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/meta/base.go b/pkg/meta/base.go index b126b73faa76..ca6d68d99fbd 100644 --- a/pkg/meta/base.go +++ b/pkg/meta/base.go @@ -2869,8 +2869,9 @@ func (m *baseMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rul return syscall.EPERM } + now := time.Now() defer func() { - m.timeit("SetFacl", time.Now()) + m.timeit("SetFacl", now) m.of.InvalidateChunk(ino, invalidateAttrOnly) }() @@ -2887,7 +2888,8 @@ func (m *baseMeta) GetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rul return errno(err) } - defer m.timeit("GetFacl", time.Now()) + now := time.Now() + defer m.timeit("GetFacl", now) return m.en.doGetFacl(ctx, ino, aclType, rule) } From a05ea18f371ad639e0e8d6b6c1609440f66d5903 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 11 Mar 2024 16:06:57 +0800 Subject: [PATCH 16/22] refactor: rename counter Signed-off-by: jiefeng --- pkg/meta/base.go | 2 -- pkg/meta/utils.go | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/meta/base.go b/pkg/meta/base.go index ca6d68d99fbd..3b145c4ab2fb 100644 --- a/pkg/meta/base.go +++ b/pkg/meta/base.go @@ -2820,8 +2820,6 @@ func (m *baseMeta) CheckSetAttr(ctx Context, inode Ino, set uint16, attr Attr) s return st } -const aclCounter = "acl" - var errACLNotInCache = errors.New("acl not in cache") func (m *baseMeta) getFaclFromCache(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) error { diff --git a/pkg/meta/utils.go b/pkg/meta/utils.go index 639e6b11f94f..8df768c202d8 100644 --- a/pkg/meta/utils.go +++ b/pkg/meta/utils.go @@ -34,6 +34,7 @@ import ( ) const ( + aclCounter = "aclMaxId" usedSpace = "usedSpace" totalInodes = "totalInodes" legacySessions = "sessions" From 1ad28e21a62f41601c33f6b985c65758e74d7087 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 11 Mar 2024 16:54:29 +0800 Subject: [PATCH 17/22] fix: init acl cache in NewSession Signed-off-by: jiefeng --- pkg/meta/base.go | 8 ++++++++ pkg/meta/redis.go | 6 +++--- pkg/meta/sql.go | 10 ++++++---- pkg/meta/tkv.go | 25 ++++++++++++++----------- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/pkg/meta/base.go b/pkg/meta/base.go index 3b145c4ab2fb..bd983f7ab37f 100644 --- a/pkg/meta/base.go +++ b/pkg/meta/base.go @@ -116,6 +116,7 @@ type engine interface { doSetFacl(ctx Context, ino Ino, aclType uint8, n *aclAPI.Rule) syscall.Errno doGetFacl(ctx Context, ino Ino, aclType uint8, n *aclAPI.Rule) syscall.Errno + cacheACLs(ctx Context) error } type trashSliceScan func(ss []Slice, ts int64) (clean bool, err error) @@ -483,6 +484,13 @@ func (m *baseMeta) newSessionInfo() []byte { func (m *baseMeta) NewSession(record bool) error { go m.refresh() + + if m.getFormat().EnableACL { + if err := m.en.cacheACLs(Background); err != nil { + return err + } + } + if m.conf.ReadOnly { logger.Infof("Create read-only session OK with version: %s", version.Version()) return nil diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index f7030780847e..5ea7f6a87126 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -321,10 +321,10 @@ func (m *redisMeta) doInit(format *Format, force bool) error { // root inode attr.Mode = 0777 - if err = m.rdb.Set(ctx, m.inodeKey(1), m.marshal(attr), 0).Err(); err != nil { - return err - } + return m.rdb.Set(ctx, m.inodeKey(1), m.marshal(attr), 0).Err() +} +func (m *redisMeta) cacheACLs(ctx Context) error { // cache all acls maxId, err := m.getCounter(aclCounter) if err != nil { diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index c0df44eaeefe..e40d6010dfcb 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -462,13 +462,15 @@ func (m *dbMeta) doInit(format *Format, force bool) error { {"totalInodes", 0}, {"nextCleanupSlices", 0}, } - if err = mustInsert(s, n, &cs); err != nil { - return err - } + return mustInsert(s, n, &cs) + }) +} +func (m *dbMeta) cacheACLs(ctx Context) error { + return m.roTxn(func(s *xorm.Session) error { // cache all acls var acls []acl - if err = s.Find(&acls); err != nil { + if err := s.Find(&acls); err != nil { return err } for _, val := range acls { diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 87284bee03d6..049120767f26 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -490,21 +490,24 @@ func (m *kvMeta) doInit(format *Format, force bool) error { tx.incrBy(m.counterKey("nextInode"), 2) tx.incrBy(m.counterKey("nextChunk"), 1) } - - // cache all acls - acls, err := m.scanValues(m.fmtKey("R"), -1, nil) - if err != nil { - return err - } - for key, val := range acls { - tmpRule := &aclAPI.Rule{} - tmpRule.Decode(val) - m.aclCache.Put(m.parseACLId(key), tmpRule) - } return nil }) } +func (m *kvMeta) cacheACLs(ctx Context) error { + // cache all acls + acls, err := m.scanValues(m.fmtKey("R"), -1, nil) + if err != nil { + return err + } + for key, val := range acls { + tmpRule := &aclAPI.Rule{} + tmpRule.Decode(val) + m.aclCache.Put(m.parseACLId(key), tmpRule) + } + return nil +} + func (m *kvMeta) Reset() error { return m.client.reset(nil) } From e261b3074d15707bb1f5dabc8ae0eb812c78c3bc Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 11 Mar 2024 17:02:46 +0800 Subject: [PATCH 18/22] refactor: remove unused code Signed-off-by: jiefeng --- pkg/acl/cache.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pkg/acl/cache.go b/pkg/acl/cache.go index 5a1640ee3b8b..89cfdf41a9fb 100644 --- a/pkg/acl/cache.go +++ b/pkg/acl/cache.go @@ -62,16 +62,9 @@ func (c *cache) GetMissIds() []uint32 { } n := c.maxId + 1 - mark := make([]bool, n) - for i := uint32(1); i < n; i++ { - if _, ok := c.id2Rule[i]; ok { - mark[i] = true - } - } - var ret []uint32 for i := uint32(1); i < n; i++ { - if !mark[i] { + if _, ok := c.id2Rule[i]; !ok { ret = append(ret, i) } } From ef98e251f6ce762104999d5931d3624e821ca15f Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 11 Mar 2024 17:59:31 +0800 Subject: [PATCH 19/22] fix: acl key cache not thread-safe Signed-off-by: jiefeng --- pkg/meta/redis.go | 11 ++++------- pkg/meta/tkv.go | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index 5ea7f6a87126..99c489e703d3 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -88,7 +88,7 @@ type redisMeta struct { prefix string shaLookup string // The SHA returned by Redis for the loaded `scriptLookup` shaResolve string // The SHA returned by Redis for the loaded `scriptResolve` - aclKeyCache map[uint32]string + aclKeyCache sync.Map } var _ Meta = &redisMeta{} @@ -248,7 +248,7 @@ func newRedisMeta(driver, addr string, conf *Config) (Meta, error) { baseMeta: newBaseMeta(addr, conf), rdb: rdb, prefix: prefix, - aclKeyCache: make(map[uint32]string), + aclKeyCache: sync.Map{}, } m.en = m m.checkServerConfig() @@ -639,11 +639,8 @@ func (m *redisMeta) totalInodesKey() string { } func (m *redisMeta) aclKey(id uint32) string { - if key, ok := m.aclKeyCache[id]; ok { - return key - } - m.aclKeyCache[id] = fmt.Sprintf("%sacl%d", m.prefix, id) - return m.aclKeyCache[id] + key, _ := m.aclKeyCache.LoadOrStore(id, fmt.Sprintf("%sacl%d", m.prefix, id)) + return key.(string) } func (m *redisMeta) delfiles() string { diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 049120767f26..00b440d6a246 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -78,7 +78,7 @@ type kvMeta struct { *baseMeta client tkvClient snap map[Ino]*DumpedEntry - aclKeyCache map[uint32][]byte + aclKeyCache sync.Map } var _ Meta = &kvMeta{} @@ -104,7 +104,7 @@ func newKVMeta(driver, addr string, conf *Config) (Meta, error) { m := &kvMeta{ baseMeta: newBaseMeta(addr, conf), client: client, - aclKeyCache: make(map[uint32][]byte), + aclKeyCache: sync.Map{}, } m.en = m return m, nil @@ -258,11 +258,8 @@ func (m *kvMeta) dirQuotaKey(inode Ino) []byte { } func (m *kvMeta) aclKey(id uint32) []byte { - if key, ok := m.aclKeyCache[id]; ok { - return key - } - m.aclKeyCache[id] = m.fmtKey("R", id) - return m.aclKeyCache[id] + key, _ := m.aclKeyCache.LoadOrStore(id, m.fmtKey("R", id)) + return key.([]byte) } func (m *kvMeta) parseACLId(key string) uint32 { From d188bdabe6c9bbccf3b6b737c8693ea9a02e88cd Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 11 Mar 2024 18:33:25 +0800 Subject: [PATCH 20/22] feat: add ListXAttr support #4483 Signed-off-by: jiefeng --- pkg/meta/base.go | 11 +++++++++++ pkg/meta/redis.go | 8 ++++++++ pkg/meta/sql.go | 11 +++++++++++ pkg/meta/tkv.go | 11 +++++++++++ 4 files changed, 41 insertions(+) diff --git a/pkg/meta/base.go b/pkg/meta/base.go index bd983f7ab37f..4e7ae31495da 100644 --- a/pkg/meta/base.go +++ b/pkg/meta/base.go @@ -2866,6 +2866,17 @@ func getAttrACLId(attr *Attr, aclType uint8) uint32 { return aclAPI.None } +func setXAttrACL(xattrs *[]byte, accessACL, defaultACL uint32) { + if accessACL != aclAPI.None { + *xattrs = append(*xattrs, []byte("system.posix_acl_access")...) + *xattrs = append(*xattrs, 0) + } + if defaultACL != aclAPI.None { + *xattrs = append(*xattrs, []byte("system.posix_acl_default")...) + *xattrs = append(*xattrs, 0) + } +} + func (m *baseMeta) SetFacl(ctx Context, ino Ino, aclType uint8, rule *aclAPI.Rule) syscall.Errno { if aclType != aclAPI.TypeAccess && aclType != aclAPI.TypeDefault { return syscall.EINVAL diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index 99c489e703d3..c75add04110f 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -3549,6 +3549,14 @@ func (m *redisMeta) ListXattr(ctx Context, inode Ino, names *[]byte) syscall.Err *names = append(*names, []byte(name)...) *names = append(*names, 0) } + + val, err := m.rdb.Get(ctx, m.inodeKey(inode)).Bytes() + if err != nil { + return errno(err) + } + attr := &Attr{} + m.parseAttr(val, attr) + setXAttrACL(names, attr.AccessACL, attr.DefaultACL) return 0 } diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index e40d6010dfcb..489e7b29a418 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -3320,6 +3320,17 @@ func (m *dbMeta) ListXattr(ctx Context, inode Ino, names *[]byte) syscall.Errno *names = append(*names, []byte(x.Name)...) *names = append(*names, 0) } + + var n = node{Inode: inode} + ok, err := s.Get(&n) + if err != nil { + return err + } else if !ok { + return syscall.ENOENT + } + attr := &Attr{} + m.parseAttr(&n, attr) + setXAttrACL(names, attr.AccessACL, attr.DefaultACL) return nil })) } diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 00b440d6a246..8877cbd0e8f3 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -2846,6 +2846,17 @@ func (m *kvMeta) ListXattr(ctx Context, inode Ino, names *[]byte) syscall.Errno *names = append(*names, name[prefix:]...) *names = append(*names, 0) } + + val, err := m.get(m.inodeKey(inode)) + if err != nil { + return errno(err) + } + if val == nil { + return syscall.ENOENT + } + attr := &Attr{} + m.parseAttr(val, attr) + setXAttrACL(names, attr.AccessACL, attr.DefaultACL) return 0 } From 8a13686769cdef76d8443be23c740bdd3297e454 Mon Sep 17 00:00:00 2001 From: jiefeng Date: Mon, 11 Mar 2024 19:43:26 +0800 Subject: [PATCH 21/22] refactor: remove key cache Signed-off-by: jiefeng --- pkg/meta/redis.go | 19 ++++++++----------- pkg/meta/tkv.go | 13 +++++-------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index c75add04110f..5dbc16105116 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -84,11 +84,10 @@ import ( type redisMeta struct { *baseMeta - rdb redis.UniversalClient - prefix string - shaLookup string // The SHA returned by Redis for the loaded `scriptLookup` - shaResolve string // The SHA returned by Redis for the loaded `scriptResolve` - aclKeyCache sync.Map + rdb redis.UniversalClient + prefix string + shaLookup string // The SHA returned by Redis for the loaded `scriptLookup` + shaResolve string // The SHA returned by Redis for the loaded `scriptResolve` } var _ Meta = &redisMeta{} @@ -245,10 +244,9 @@ func newRedisMeta(driver, addr string, conf *Config) (Meta, error) { } m := &redisMeta{ - baseMeta: newBaseMeta(addr, conf), - rdb: rdb, - prefix: prefix, - aclKeyCache: sync.Map{}, + baseMeta: newBaseMeta(addr, conf), + rdb: rdb, + prefix: prefix, } m.en = m m.checkServerConfig() @@ -639,8 +637,7 @@ func (m *redisMeta) totalInodesKey() string { } func (m *redisMeta) aclKey(id uint32) string { - key, _ := m.aclKeyCache.LoadOrStore(id, fmt.Sprintf("%sacl%d", m.prefix, id)) - return key.(string) + return fmt.Sprintf("%sacl%d", m.prefix, id) } func (m *redisMeta) delfiles() string { diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 8877cbd0e8f3..f7c2a69329b4 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -76,9 +76,8 @@ func (tx *kvTxn) deleteKeys(prefix []byte) { type kvMeta struct { *baseMeta - client tkvClient - snap map[Ino]*DumpedEntry - aclKeyCache sync.Map + client tkvClient + snap map[Ino]*DumpedEntry } var _ Meta = &kvMeta{} @@ -102,9 +101,8 @@ func newKVMeta(driver, addr string, conf *Config) (Meta, error) { // TODO: ping server and check latency > Millisecond // logger.Warnf("The latency to database is too high: %s", time.Since(start)) m := &kvMeta{ - baseMeta: newBaseMeta(addr, conf), - client: client, - aclKeyCache: sync.Map{}, + baseMeta: newBaseMeta(addr, conf), + client: client, } m.en = m return m, nil @@ -258,8 +256,7 @@ func (m *kvMeta) dirQuotaKey(inode Ino) []byte { } func (m *kvMeta) aclKey(id uint32) []byte { - key, _ := m.aclKeyCache.LoadOrStore(id, m.fmtKey("R", id)) - return key.([]byte) + return m.fmtKey("R", id) } func (m *kvMeta) parseACLId(key string) uint32 { From 3e2de08e9eca23fc072c6ae926684bf81e99a40b Mon Sep 17 00:00:00 2001 From: jiefeng Date: Tue, 12 Mar 2024 11:14:15 +0800 Subject: [PATCH 22/22] refactor: put all acls into redis hashmap Signed-off-by: jiefeng --- pkg/acl/cache.go | 9 +++++++++ pkg/meta/base_test.go | 14 +++++++++++++- pkg/meta/redis.go | 40 +++++++++++++--------------------------- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/pkg/acl/cache.go b/pkg/acl/cache.go index 89cfdf41a9fb..bdd726c4d2dc 100644 --- a/pkg/acl/cache.go +++ b/pkg/acl/cache.go @@ -34,6 +34,7 @@ type Cache interface { GetId(r *Rule) uint32 Size() int GetMissIds() []uint32 + Clear() } func NewCache() Cache { @@ -52,6 +53,14 @@ type cache struct { cksum2Id map[uint32][]uint32 } +func (c *cache) Clear() { + c.lock.Lock() + defer c.lock.Unlock() + c.maxId = None + c.id2Rule = make(map[uint32]*Rule) + c.cksum2Id = make(map[uint32][]uint32) +} + // GetMissIds return all miss ids from 1 to c.maxId func (c *cache) GetMissIds() []uint32 { c.lock.RLock() diff --git a/pkg/meta/base_test.go b/pkg/meta/base_test.go index 97f2c6db072d..72268c620fbe 100644 --- a/pkg/meta/base_test.go +++ b/pkg/meta/base_test.go @@ -155,7 +155,10 @@ func testMeta(t *testing.T, m Meta) { } func testACL(t *testing.T, m Meta) { - if err := m.Init(testFormat(), false); err != nil { + format := testFormat() + format.EnableACL = true + + if err := m.Init(format, false); err != nil { t.Fatalf("test acl failed: %s", err) } @@ -167,6 +170,7 @@ func testACL(t *testing.T, m Meta) { if st := m.Mkdir(ctx, RootInode, testDir, 0644, 0, 0, &testDirIno, attr1); st != 0 { t.Fatalf("create %s: %s", testDir, st) } + defer m.Rmdir(ctx, RootInode, testDir) rule := &aclAPI.Rule{ Owner: 7, @@ -258,6 +262,7 @@ func testACL(t *testing.T, m Meta) { if st := m.Mkdir(ctx, testDirIno, subDir, mode, 0022, 0, &subDirIno, attr2); st != 0 { t.Fatalf("create %s: %s", subDir, st) } + defer m.Rmdir(ctx, testDirIno, subDir) // subdir inherit default acl rule3 = &aclAPI.Rule{} @@ -305,6 +310,7 @@ func testACL(t *testing.T, m Meta) { if st := m.Mkdir(ctx, testDirIno, subDir2, mode, 0022, 0, &subDirIno2, attr2); st != 0 { t.Fatalf("create %s: %s", subDir, st) } + defer m.Rmdir(ctx, testDirIno, subDir2) // subdir inherit default acl rule3 = &aclAPI.Rule{} @@ -323,6 +329,12 @@ func testACL(t *testing.T, m Meta) { t.Fatalf("getattr error: %s", st) } assert.Equal(t, rule.GetMode(), attr2.Mode) + + // test cache all + sz := m.getBase().aclCache.Size() + err := m.getBase().en.cacheACLs(ctx) + assert.Nil(t, err) + assert.Equal(t, sz, m.getBase().aclCache.Size()) } func testMetaClient(t *testing.T, m Meta) { diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index 5dbc16105116..0ca2f016256f 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -324,30 +324,16 @@ func (m *redisMeta) doInit(format *Format, force bool) error { func (m *redisMeta) cacheACLs(ctx Context) error { // cache all acls - maxId, err := m.getCounter(aclCounter) + vals, err := m.rdb.HGetAll(ctx, m.aclKey()).Result() if err != nil { return err } - if maxId > 0 { - allKeys := make([]string, maxId) - for i := 0; i < int(maxId); i++ { - allKeys[i] = m.aclKey(uint32(i) + 1) - } - - acls, err := m.rdb.MGet(ctx, allKeys...).Result() - if err != nil { - return err - } - for i, val := range acls { - var tmpRule *aclAPI.Rule - if val != nil { - tmpRule = &aclAPI.Rule{} - tmpRule.Decode(([]byte)(val.(string))) - } - // may have empty slot - m.aclCache.Put(uint32(i)+1, tmpRule) - } + for k, v := range vals { + id, _ := strconv.ParseUint(k, 10, 32) + tmpRule := &aclAPI.Rule{} + tmpRule.Decode([]byte(v)) + m.aclCache.Put(uint32(id), tmpRule) } return nil } @@ -636,8 +622,8 @@ func (m *redisMeta) totalInodesKey() string { return m.prefix + totalInodes } -func (m *redisMeta) aclKey(id uint32) string { - return fmt.Sprintf("%sacl%d", m.prefix, id) +func (m *redisMeta) aclKey() string { + return m.prefix + "acl" } func (m *redisMeta) delfiles() string { @@ -4654,7 +4640,7 @@ func (m *redisMeta) getACL(ctx Context, tx *redis.Tx, id uint32) (*aclAPI.Rule, } cmds, err := tx.TxPipelined(ctx, func(pipe redis.Pipeliner) error { - pipe.Get(ctx, m.aclKey(id)) + pipe.HGet(ctx, m.aclKey(), strconv.FormatUint(uint64(id), 10)) return nil }) if err != nil { @@ -4685,7 +4671,7 @@ func (m *redisMeta) insertACL(ctx Context, tx *redis.Tx, rule *aclAPI.Rule) (uin } aclId = uint32(newId) - if err = tx.Set(ctx, m.aclKey(aclId), rule.Encode(), 0).Err(); err != nil { + if err = tx.HSet(ctx, m.aclKey(), strconv.FormatUint(uint64(aclId), 10), rule.Encode()).Err(); err != nil { return aclAPI.None, err } m.aclCache.Put(aclId, rule) @@ -4699,14 +4685,14 @@ func (m *redisMeta) tryLoadMissACLs(ctx Context, tx *redis.Tx) error { if len(missIds) > 0 { missKeys := make([]string, len(missIds)) for i, id := range missIds { - missKeys[i] = m.aclKey(id) + missKeys[i] = strconv.FormatUint(uint64(id), 10) } - acls, err := tx.MGet(ctx, missKeys...).Result() + vals, err := tx.HMGet(ctx, m.aclKey(), missKeys...).Result() if err != nil { return err } - for i, data := range acls { + for i, data := range vals { var rule *aclAPI.Rule if data != nil { rule = &aclAPI.Rule{}