-
Notifications
You must be signed in to change notification settings - Fork 987
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat/acl: add acl rule and cache (#4437)
Signed-off-by: jiefeng <[email protected]>
- Loading branch information
1 parent
91b4505
commit 59659db
Showing
3 changed files
with
432 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
/* | ||
* JuiceFS, Copyright 2024 Juicedata, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package acl | ||
|
||
import ( | ||
"fmt" | ||
"hash/crc32" | ||
|
||
"github.com/juicedata/juicefs/pkg/utils" | ||
) | ||
|
||
const Version uint8 = 2 | ||
|
||
type Entry struct { | ||
Id uint32 | ||
Perm uint16 | ||
} | ||
|
||
type Entries []Entry | ||
|
||
func (es *Entries) Len() int { return len(*es) } | ||
func (es *Entries) Less(i, j int) bool { return (*es)[i].Id < (*es)[j].Id } | ||
func (es *Entries) Swap(i, j int) { (*es)[i], (*es)[j] = (*es)[j], (*es)[i] } | ||
|
||
func (es *Entries) IsEqual(other *Entries) bool { | ||
if es.Len() != other.Len() { | ||
return false | ||
} | ||
for i := 0; i < es.Len(); i++ { | ||
if (*es)[i].Id != (*other)[i].Id || (*es)[i].Perm != (*other)[i].Perm { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func (es *Entries) Encode() []byte { | ||
w := utils.NewBuffer(uint32(es.Len() * 6)) | ||
for _, e := range *es { | ||
w.Put32(e.Id) | ||
w.Put16(e.Perm) | ||
} | ||
return w.Bytes() | ||
} | ||
|
||
func (es *Entries) Decode(data []byte) { | ||
r := utils.ReadBuffer(data) | ||
for r.HasMore() { | ||
*es = append(*es, Entry{ | ||
Id: r.Get32(), | ||
Perm: r.Get16(), | ||
}) | ||
} | ||
} | ||
|
||
// Rule acl rule | ||
type Rule struct { | ||
Owner uint16 | ||
Group uint16 | ||
Mask uint16 | ||
Other uint16 | ||
NamedUsers Entries | ||
NamedGroups Entries | ||
} | ||
|
||
func (r *Rule) String() string { | ||
return fmt.Sprintf("owner %o, group %o, mask %o, other %o, named users: %+v, named group %+v", | ||
r.Owner, r.Group, r.Mask, r.Other, r.NamedUsers, r.NamedGroups) | ||
} | ||
|
||
func (r *Rule) Encode() []byte { | ||
w := utils.NewBuffer(uint32(16 + (len(r.NamedUsers)+len(r.NamedGroups))*6)) | ||
w.Put16(r.Owner) | ||
w.Put16(r.Group) | ||
w.Put16(r.Mask) | ||
w.Put16(r.Other) | ||
w.Put32(uint32(len(r.NamedUsers))) | ||
for _, entry := range r.NamedUsers { | ||
w.Put32(entry.Id) | ||
w.Put16(entry.Perm) | ||
} | ||
w.Put32(uint32(len(r.NamedGroups))) | ||
for _, entry := range r.NamedGroups { | ||
w.Put32(entry.Id) | ||
w.Put16(entry.Perm) | ||
} | ||
return w.Bytes() | ||
} | ||
|
||
func (r *Rule) Decode(buf []byte) { | ||
rb := utils.ReadBuffer(buf) | ||
r.Owner = rb.Get16() | ||
r.Group = rb.Get16() | ||
r.Mask = rb.Get16() | ||
r.Other = rb.Get16() | ||
uCnt := rb.Get32() | ||
r.NamedUsers = make([]Entry, uCnt) | ||
for i := 0; i < int(uCnt); i++ { | ||
r.NamedUsers[i].Id = rb.Get32() | ||
r.NamedUsers[i].Perm = rb.Get16() | ||
} | ||
|
||
gCnt := rb.Get32() | ||
r.NamedGroups = make([]Entry, gCnt) | ||
for i := 0; i < int(gCnt); i++ { | ||
r.NamedGroups[i].Id = rb.Get32() | ||
r.NamedGroups[i].Perm = rb.Get16() | ||
} | ||
} | ||
|
||
func EmptyRule() *Rule { | ||
return &Rule{ | ||
Owner: 0xFFFF, | ||
Group: 0xFFFF, | ||
Other: 0xFFFF, | ||
Mask: 0xFFFF, | ||
} | ||
} | ||
|
||
func (r *Rule) IsEmpty() bool { | ||
return len(r.NamedUsers)+len(r.NamedGroups) == 0 && | ||
r.Owner&r.Group&r.Other&r.Mask == 0xFFFF | ||
} | ||
|
||
// IsMinimal just like normal permission | ||
func (r *Rule) IsMinimal() bool { | ||
return len(r.NamedGroups)+len(r.NamedUsers) == 0 && r.Mask == 0xFFFF | ||
} | ||
|
||
func (r *Rule) IsEqual(other *Rule) bool { | ||
if r.Owner != other.Owner || r.Group != other.Group || r.Mask != other.Mask || r.Other != other.Other { | ||
return false | ||
} | ||
|
||
return r.NamedUsers.IsEqual(&other.NamedUsers) && | ||
r.NamedGroups.IsEqual(&other.NamedGroups) | ||
} | ||
|
||
// InheritPerms from normal permission | ||
func (r *Rule) InheritPerms(mode uint16) { | ||
if r.Owner == 0xFFFF { | ||
r.Owner = (mode >> 6) & 7 | ||
} | ||
if r.Group == 0xFFFF { | ||
r.Group = (mode >> 3) & 7 | ||
} | ||
if r.Other == 0xFFFF { | ||
r.Other = mode & 7 | ||
} | ||
} | ||
|
||
func (r *Rule) SetMode(mode uint16) { | ||
r.Owner &= 0xFFF8 | ||
r.Owner |= (mode >> 6) & 7 | ||
|
||
if r.IsMinimal() { | ||
r.Group &= 0xFFF8 | ||
r.Group |= (mode >> 3) & 7 | ||
} else { | ||
r.Mask &= 0xFFF8 | ||
r.Mask |= (mode >> 3) & 7 | ||
} | ||
r.Other &= 0xFFF8 | ||
r.Other |= mode & 7 | ||
} | ||
|
||
func (r *Rule) GetMode() uint16 { | ||
if r.IsMinimal() { | ||
return ((r.Owner & 7) << 6) | ((r.Group & 7) << 3) | (r.Other & 7) | ||
} | ||
return ((r.Owner & 7) << 6) | ((r.Mask & 7) << 3) | (r.Other & 7) | ||
} | ||
|
||
// ChildAccessACL return the child node access acl with this default acl | ||
func (r *Rule) ChildAccessACL(mode uint16) *Rule { | ||
cRule := &Rule{} | ||
cRule.Owner = (mode >> 6) & 7 & r.Owner | ||
cRule.Mask = (mode >> 3) & 7 & r.Mask | ||
cRule.Other = mode & 7 & r.Other | ||
|
||
cRule.Group = r.Group | ||
cRule.NamedUsers = r.NamedUsers | ||
cRule.NamedGroups = r.NamedGroups | ||
return cRule | ||
} | ||
|
||
var crc32c = crc32.MakeTable(crc32.Castagnoli) | ||
|
||
func (r *Rule) Checksum() uint32 { | ||
return crc32.Checksum(r.Encode(), crc32c) | ||
} | ||
|
||
const ( | ||
TypeNone = iota | ||
TypeAccess | ||
TypeDefault | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
/* | ||
* JuiceFS, Copyright 2024 Juicedata, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package acl | ||
|
||
import ( | ||
"sort" | ||
"sync" | ||
) | ||
|
||
const None = 0 | ||
|
||
// Cache all rules | ||
// - cache all rules when meta init. | ||
// - on getfacl failure, read and cache rule from meta. | ||
// - on setfacl success, read and cache all missed rules from meta. (considered as a low-frequency operation) | ||
// - concurrent mounts may result in duplicate rules. | ||
type Cache interface { | ||
Put(id uint32, r *Rule) | ||
Get(id uint32) *Rule | ||
GetId(r *Rule) uint32 | ||
Size() int | ||
GetMissIds(maxId uint32) []uint32 | ||
} | ||
|
||
func NewCache() Cache { | ||
return &cache{ | ||
lock: sync.RWMutex{}, | ||
maxId: None, | ||
id2Rule: make(map[uint32]*Rule), | ||
cksum2Id: make(map[uint32][]uint32), | ||
} | ||
} | ||
|
||
type cache struct { | ||
lock sync.RWMutex | ||
maxId uint32 | ||
id2Rule map[uint32]*Rule | ||
cksum2Id map[uint32][]uint32 | ||
} | ||
|
||
// GetMissIds return all miss ids from 1 to max(maxId, c.maxId) | ||
func (c *cache) GetMissIds(maxId uint32) []uint32 { | ||
c.lock.RLock() | ||
defer c.lock.RUnlock() | ||
|
||
if c.maxId == maxId && uint32(len(c.id2Rule)) == maxId { | ||
return nil | ||
} | ||
|
||
if c.maxId > maxId { | ||
maxId = c.maxId | ||
} | ||
|
||
n := 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] { | ||
ret = append(ret, i) | ||
} | ||
} | ||
return ret | ||
} | ||
|
||
func (c *cache) Size() int { | ||
c.lock.RLock() | ||
defer c.lock.RUnlock() | ||
return len(c.id2Rule) | ||
} | ||
|
||
func (c *cache) Get(id uint32) *Rule { | ||
c.lock.RLock() | ||
defer c.lock.RUnlock() | ||
if r, ok := c.id2Rule[id]; ok { | ||
return r | ||
} | ||
return nil | ||
} | ||
|
||
func (c *cache) Put(id uint32, r *Rule) { | ||
c.lock.Lock() | ||
defer c.lock.Unlock() | ||
|
||
if _, ok := c.id2Rule[id]; ok { | ||
return | ||
} | ||
|
||
if id > c.maxId { | ||
c.maxId = id | ||
} | ||
|
||
c.id2Rule[id] = r | ||
|
||
// empty slot | ||
if r == nil { | ||
return | ||
} | ||
|
||
sort.Sort(&c.id2Rule[id].NamedUsers) | ||
sort.Sort(&c.id2Rule[id].NamedGroups) | ||
|
||
cksum := r.Checksum() | ||
if _, ok := c.cksum2Id[cksum]; ok { | ||
c.cksum2Id[cksum] = append(c.cksum2Id[cksum], id) | ||
} else { | ||
c.cksum2Id[r.Checksum()] = []uint32{id} | ||
} | ||
} | ||
|
||
func (c *cache) GetId(r *Rule) uint32 { | ||
c.lock.RLock() | ||
defer c.lock.RUnlock() | ||
|
||
if r == nil { | ||
return None | ||
} | ||
|
||
if ids, ok := c.cksum2Id[r.Checksum()]; ok { | ||
for _, id := range ids { | ||
if r.IsEqual(c.id2Rule[id]) { | ||
return id | ||
} | ||
} | ||
} | ||
return None | ||
} |
Oops, something went wrong.