mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-11 09:00:45 +02:00
* mount: drop the unused go-fuse fs package dependency WFS embedded fs.Inode but never used any of its methods, and the only other reference was RENAME_EXCHANGE, a constant sitting next to three literals. Removing both drops fs and five internal packages from the mount build graph. * mount: build the package on windows Windows has no fcntl lock types, no O_ACCMODE and no x/sys/unix, so a handful of constants kept weed/mount pinned to unix even though the code using them is portable in-memory logic. Route them through per-OS shims and give setBlksize a windows no-op. The POSIX lock table now compiles on windows but stays unreachable: WinFsp resolves byte-range locks in its own kernel driver, so nothing will feed it there. go.mod points at a go-fuse branch commit and needs repinning to a release tag once that lands. * ci: cross-compile for windows Nothing caught the unix-only constants creeping into weed/mount until a release build failed. * mount: let readdir feed a sink instead of the kernel buffer doReadDirectory wrote directly into fuse.DirEntryList, which is the kernel's wire format. A front end that is not the kernel would have to pack entries only to parse them straight back out. Route it through DirEntrySink instead. ReadDir and ReadDirPlus pass the reply buffer, so nothing changes for the FUSE server. * mount: pin go-fuse v2.9.4 for the windows build
167 lines
4.4 KiB
Go
167 lines
4.4 KiB
Go
package mount
|
|
|
|
import (
|
|
"os/user"
|
|
"strconv"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/go-fuse/v2/fuse"
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
)
|
|
|
|
type cachedGroupIDs struct {
|
|
groups []string
|
|
expiresAt time.Time
|
|
}
|
|
|
|
var (
|
|
supplementaryGroupCache = make(map[uint32]*cachedGroupIDs)
|
|
supplementaryGroupCacheMu sync.RWMutex
|
|
supplementaryGroupCacheTTL = 5 * time.Minute
|
|
|
|
lookupSupplementaryGroupIDs = func(callerUid uint32) ([]string, error) {
|
|
u, err := user.LookupId(strconv.Itoa(int(callerUid)))
|
|
if err != nil {
|
|
glog.Warningf("hasAccess: user.LookupId for uid %d failed: %v", callerUid, err)
|
|
return nil, err
|
|
}
|
|
groupIDs, err := u.GroupIds()
|
|
if err != nil {
|
|
glog.Warningf("hasAccess: u.GroupIds for uid %d failed: %v", callerUid, err)
|
|
return nil, err
|
|
}
|
|
return groupIDs, nil
|
|
}
|
|
)
|
|
|
|
// cachedLookupSupplementaryGroupIDs returns supplementary group IDs for a UID,
|
|
// caching results for 5 minutes to avoid repeated expensive system calls.
|
|
func cachedLookupSupplementaryGroupIDs(callerUid uint32) ([]string, error) {
|
|
now := time.Now()
|
|
|
|
supplementaryGroupCacheMu.RLock()
|
|
cached, ok := supplementaryGroupCache[callerUid]
|
|
supplementaryGroupCacheMu.RUnlock()
|
|
if ok && now.Before(cached.expiresAt) {
|
|
return cached.groups, nil
|
|
}
|
|
|
|
groupIDs, err := lookupSupplementaryGroupIDs(callerUid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
supplementaryGroupCacheMu.Lock()
|
|
supplementaryGroupCache[callerUid] = &cachedGroupIDs{
|
|
groups: groupIDs,
|
|
expiresAt: now.Add(supplementaryGroupCacheTTL),
|
|
}
|
|
supplementaryGroupCacheMu.Unlock()
|
|
|
|
return groupIDs, nil
|
|
}
|
|
|
|
// clearSupplementaryGroupCache wipes the UID->groups cache for test isolation.
|
|
func clearSupplementaryGroupCache() {
|
|
supplementaryGroupCacheMu.Lock()
|
|
defer supplementaryGroupCacheMu.Unlock()
|
|
for k := range supplementaryGroupCache {
|
|
delete(supplementaryGroupCache, k)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check file access permissions
|
|
*
|
|
* This will be called for the access() system call. If the
|
|
* 'default_permissions' mount option is given, this method is not
|
|
* called.
|
|
*
|
|
* This method is not called under Linux kernel versions 2.4.x
|
|
*/
|
|
func (wfs *WFS) Access(cancel <-chan struct{}, input *fuse.AccessIn) (code fuse.Status) {
|
|
_, _, entry, code := wfs.maybeReadEntry(input.NodeId)
|
|
if code != fuse.OK {
|
|
return code
|
|
}
|
|
if entry == nil || entry.Attributes == nil {
|
|
return fuse.EIO
|
|
}
|
|
// Map entry uid/gid from filer-space to local-space so the permission
|
|
// check compares like with like (caller uid/gid from FUSE are local).
|
|
fileUid, fileGid := entry.Attributes.Uid, entry.Attributes.Gid
|
|
if wfs.option.UidGidMapper != nil {
|
|
fileUid, fileGid = wfs.option.UidGidMapper.FilerToLocal(fileUid, fileGid)
|
|
}
|
|
if hasAccess(input.Uid, input.Gid, fileUid, fileGid, entry.Attributes.FileMode, input.Mask) {
|
|
return fuse.OK
|
|
}
|
|
return fuse.EACCES
|
|
}
|
|
|
|
func hasAccess(callerUid, callerGid, fileUid, fileGid uint32, perm uint32, mask uint32) bool {
|
|
mask &= fuse.R_OK | fuse.W_OK | fuse.X_OK
|
|
if mask == 0 {
|
|
return true
|
|
}
|
|
if callerUid == 0 {
|
|
return mask&fuse.X_OK == 0 || perm&0o111 != 0
|
|
}
|
|
|
|
if callerUid == fileUid {
|
|
return (perm>>6)&mask == mask
|
|
}
|
|
|
|
isMember := callerGid == fileGid
|
|
if !isMember {
|
|
groupIDs, err := cachedLookupSupplementaryGroupIDs(callerUid)
|
|
if err != nil {
|
|
// Cannot determine supplementary group membership.
|
|
// Fall through to "other" permission check since we already
|
|
// know the caller is not the owner (checked above) and not
|
|
// in the primary group.
|
|
return (perm & mask) == mask
|
|
}
|
|
fileGidStr := strconv.Itoa(int(fileGid))
|
|
for _, gidStr := range groupIDs {
|
|
if gidStr == fileGidStr {
|
|
isMember = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if isMember {
|
|
return (perm>>3)&mask == mask
|
|
}
|
|
|
|
return (perm & mask) == mask
|
|
}
|
|
|
|
// checkStickyBit enforces the POSIX sticky-bit rule: when a directory has the
|
|
// sticky bit set, only the file owner, the directory owner, or root may
|
|
// delete or rename entries within it.
|
|
func checkStickyBit(dirMode, dirUid, targetUid, callerUid uint32) fuse.Status {
|
|
if dirMode&0o1000 == 0 {
|
|
return fuse.OK
|
|
}
|
|
if callerUid == 0 || callerUid == dirUid || callerUid == targetUid {
|
|
return fuse.OK
|
|
}
|
|
return fuse.EPERM
|
|
}
|
|
|
|
// openFlagsToAccessMask converts open(2) flags to an access permission mask.
|
|
func openFlagsToAccessMask(flags uint32) uint32 {
|
|
switch flags & uint32(o_ACCMODE) {
|
|
case syscall.O_WRONLY:
|
|
return fuse.W_OK
|
|
case syscall.O_RDWR:
|
|
return fuse.R_OK | fuse.W_OK
|
|
default: // O_RDONLY
|
|
return fuse.R_OK
|
|
}
|
|
}
|