polardbxoperator/pkg/hpfs/local/local.go

554 lines
13 KiB
Go

/*
Copyright 2021 Alibaba Group Holding Limited.
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 local
import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"syscall"
"golang.org/x/sys/unix"
"github.com/dghubble/trie"
"github.com/minio/minio/pkg/disk"
)
type LocalFileService interface {
CreateFile(path string, perm os.FileMode) error
CreateDirectory(path string, perm os.FileMode) error
CreateDirectories(path string, perm os.FileMode) error
CreateSymlink(target, link string) error
ListDirectory(path string) ([]os.FileInfo, error)
IsExists(path string) (bool, error)
IsFile(path string) (bool, error)
IsDirectory(path string) (bool, error)
IsSymbolicLink(path string) (bool, error)
OpenFile(path string, truncate bool) (*os.File, error)
OpenFileReadOnly(path string) (*os.File, error)
WriteFile(path string, content []byte) error
DeleteFile(path string) error
DeleteDirectory(path string, recursively bool) error
TruncateFile(path string, size int64) error
RenameFile(src, dest string) error
Chown(path string, uid, gid int) error
Lchown(path string, uid, gid int) error
Stat(path string) (os.FileInfo, error)
Lstat(path string) (os.FileInfo, error)
GetDiskUsage(path string) (uint64, error)
GetDiskInfo(path string) (disk.Info, error)
GetMajorMinorNumber(path string, physical bool) (uint32, uint32, error)
}
type BlockDevice struct {
Name string
Dev uint32
Major uint32
Minor uint32
SysDevicePath string
ParentDev uint32
}
type localFileService struct {
pathTestFunc func(string) bool
blockDevices map[uint32]BlockDevice
}
func (l *localFileService) loadBlockDevices() error {
l.blockDevices = make(map[uint32]BlockDevice)
// Read /sys/block
const sysBlockPath = "/sys/block"
dir, err := os.ReadDir(sysBlockPath)
if err != nil {
return err
}
// name to device number
phyBlockDev := make(map[string]uint64, 0)
// /sys/devices/... path to device number
phyBlockDevPath := make(map[string]uint64, 0)
for _, f := range dir {
if strings.HasPrefix(f.Name(), "loop") {
continue
}
major, minor, err := l.GetMajorMinorNumber(path.Join("/dev", f.Name()), false)
if err != nil {
return err
}
// Under /sys/devices
sysDevicePath, err := os.Readlink(path.Join(sysBlockPath, f.Name()))
if err != nil {
return err
}
sysDevicePath, err = filepath.Abs(path.Join(sysBlockPath, sysDevicePath))
if err != nil {
return err
}
dev := unix.Mkdev(major, minor)
phyBlockDev[f.Name()] = dev
phyBlockDevPath[sysDevicePath] = dev
l.blockDevices[uint32(dev)] = BlockDevice{
Name: f.Name(),
Dev: uint32(dev),
Major: major,
Minor: minor,
ParentDev: 0,
SysDevicePath: sysDevicePath,
}
}
// Read /sys/class/block
const sysClassBlockPath = "/sys/class/block"
dir, err = os.ReadDir(sysClassBlockPath)
if err != nil {
return err
}
for _, f := range dir {
if strings.HasPrefix(f.Name(), "loop") {
continue
}
if _, ok := phyBlockDev[f.Name()]; ok {
continue
}
major, minor, err := l.GetMajorMinorNumber(path.Join("/dev", f.Name()), false)
if err != nil {
return err
}
sysDevicePath, err := os.Readlink(path.Join(sysClassBlockPath, f.Name()))
if err != nil {
return err
}
sysDevicePath, err = filepath.Abs(path.Join(sysClassBlockPath, sysDevicePath))
if err != nil {
return err
}
parentDevicePath := path.Dir(sysDevicePath)
parentDev, ok := phyBlockDevPath[parentDevicePath]
if !ok {
fmt.Println("WARN: block device " + f.Name() + " doesn't found parent physical block device!")
continue
}
dev := unix.Mkdev(major, minor)
l.blockDevices[uint32(dev)] = BlockDevice{
Name: f.Name(),
Dev: uint32(dev),
Major: major,
Minor: minor,
SysDevicePath: sysDevicePath,
ParentDev: uint32(parentDev),
}
}
// Print all
fmt.Println("Found block devices:")
for dev, blk := range l.blockDevices {
fmt.Printf(" %s %d %d:%d %d\n", blk.Name, dev, blk.Major, blk.Minor, blk.ParentDev)
}
return nil
}
func (l *localFileService) getPhysicalBlockDevice(dev uint32) *BlockDevice {
blk, ok := l.blockDevices[dev]
if !ok {
return nil
}
for blk.ParentDev > 0 {
blk, ok = l.blockDevices[blk.ParentDev]
}
return &blk
}
func (l *localFileService) GetMajorMinorNumber(path string, physical bool) (uint32, uint32, error) {
stat, err := os.Stat(path)
if err != nil {
return 0, 0, err
}
sysStat, ok := stat.Sys().(*syscall.Stat_t)
if !ok {
return 0, 0, errors.New("unable to determine the device major and minor numbers")
}
// ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular
switch stat.Mode().Type() {
case os.ModeDevice, os.ModeCharDevice:
return unix.Major(uint64(sysStat.Rdev)), unix.Minor(uint64(sysStat.Rdev)), nil
case 0, os.ModeDir, os.ModeSymlink:
if !physical {
return unix.Major(uint64(sysStat.Dev)), unix.Minor(uint64(sysStat.Dev)), nil
} else {
blk := l.getPhysicalBlockDevice(uint32(sysStat.Dev))
if blk == nil {
return 0, 0, fmt.Errorf("physical block device not found")
}
return blk.Major, blk.Minor, nil
}
default:
return 0, 0, fmt.Errorf("unsupported file mode: %d", stat.Mode())
}
}
func (l *localFileService) checkPaths(paths ...*string) error {
var err error
for _, p := range paths {
if *p, err = l.Abs(*p); err != nil {
return err
}
if !l.isOperationOnFileOrDirectoryPermitted(*p) {
return os.ErrPermission
}
}
return nil
}
func (l *localFileService) ListDirectory(path string) (fileInfo []os.FileInfo, err error) {
if err := l.checkPaths(&path); err != nil {
return nil, err
}
files, err := os.ReadDir(path)
if err != nil {
return nil, err
}
for _, file := range files {
info, err := file.Info()
if err != nil {
return nil, err
}
fileInfo = append(fileInfo, info)
}
return fileInfo, nil
}
func (l *localFileService) isOperationOnFileOrDirectoryPermitted(path string) bool {
return l.pathTestFunc(path)
}
func (l *localFileService) IsExists(path string) (bool, error) {
if err := l.checkPaths(&path); err != nil {
return false, err
}
if _, err := l.Lstat(path); err == nil {
return true, nil
} else if os.IsNotExist(err) {
return false, nil
} else {
return false, err
}
}
func (l *localFileService) IsFile(path string) (bool, error) {
if err := l.checkPaths(&path); err != nil {
return false, err
}
fi, err := l.Lstat(path)
if err != nil {
return false, err
}
return fi.Mode().IsRegular(), nil
}
func (l *localFileService) IsDirectory(path string) (bool, error) {
if err := l.checkPaths(&path); err != nil {
return false, err
}
fi, err := l.Lstat(path)
if err != nil {
return false, err
}
return fi.Mode().IsDir(), nil
}
func (l *localFileService) IsSymbolicLink(path string) (bool, error) {
if err := l.checkPaths(&path); err != nil {
return false, err
}
fi, err := l.Lstat(path)
if err != nil {
return false, err
}
return fi.Mode()&os.ModeSymlink != 0, nil
}
func (l *localFileService) Abs(path string) (string, error) {
if filepath.IsAbs(path) {
return path, nil
}
return filepath.Abs(path)
}
func (l *localFileService) CreateFile(path string, perm os.FileMode) error {
if err := l.checkPaths(&path); err != nil {
return err
}
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
}
defer f.Close()
return nil
}
func (l *localFileService) CreateDirectory(path string, perm os.FileMode) error {
if err := l.checkPaths(&path); err != nil {
return err
}
return os.Mkdir(path, perm)
}
func (l *localFileService) CreateSymlink(target, link string) error {
if err := l.checkPaths(&target, &link); err != nil {
return err
}
return os.Symlink(target, link)
}
func (l *localFileService) CreateDirectories(path string, perm os.FileMode) error {
if err := l.checkPaths(&path); err != nil {
return err
}
return os.MkdirAll(path, perm)
}
func (l *localFileService) OpenFile(path string, truncate bool) (*os.File, error) {
if err := l.checkPaths(&path); err != nil {
return nil, err
}
flag := os.O_RDWR | os.O_CREATE
if truncate {
flag |= os.O_TRUNC
}
return os.OpenFile(path, flag, 0664)
}
func (l *localFileService) OpenFileReadOnly(path string) (*os.File, error) {
if err := l.checkPaths(&path); err != nil {
return nil, err
}
return os.OpenFile(path, os.O_RDONLY, 0)
}
func (l *localFileService) WriteFile(path string, content []byte) error {
if err := l.checkPaths(&path); err != nil {
return err
}
f, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC, 0)
if err != nil {
return err
}
defer f.Close()
if _, err = f.Write(content); err != nil {
return err
}
return nil
}
func (l *localFileService) DeleteFile(path string) error {
if err := l.checkPaths(&path); err != nil {
return err
}
if !l.isOperationOnFileOrDirectoryPermitted(path) {
return os.ErrPermission
}
return os.Remove(path)
}
func (l *localFileService) RenameFile(src, dest string) error {
if err := l.checkPaths(&src, &dest); err != nil {
return err
}
return os.Rename(src, dest)
}
func (l *localFileService) DeleteDirectory(path string, recursively bool) error {
if err := l.checkPaths(&path); err != nil {
return err
}
if !recursively {
return os.Remove(path)
} else {
return os.RemoveAll(path)
}
}
func (l *localFileService) TruncateFile(path string, size int64) error {
if err := l.checkPaths(&path); err != nil {
return err
}
return os.Truncate(path, size)
}
func (l *localFileService) Chown(path string, uid, gid int) error {
if err := l.checkPaths(&path); err != nil {
return err
}
return os.Chown(path, uid, gid)
}
func (l *localFileService) Lchown(path string, uid, gid int) error {
if err := l.checkPaths(&path); err != nil {
return err
}
return os.Lchown(path, uid, gid)
}
func (l *localFileService) Stat(path string) (os.FileInfo, error) {
if err := l.checkPaths(&path); err != nil {
return nil, err
}
return os.Stat(path)
}
func (l *localFileService) Lstat(path string) (os.FileInfo, error) {
if err := l.checkPaths(&path); err != nil {
return nil, err
}
return os.Lstat(path)
}
func (l *localFileService) dirSize(path string) (uint64, error) {
var size uint64 = 0
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
if err != nil {
return err
}
size += uint64(info.Size())
return nil
})
return size, err
}
func (l *localFileService) GetDiskUsage(path string) (uint64, error) {
if err := l.checkPaths(&path); err != nil {
return 0, err
}
fi, err := l.Stat(path)
if err != nil {
return 0, err
}
if fi.IsDir() {
return l.dirSize(path)
} else {
return uint64(fi.Size()), nil
}
}
func (l *localFileService) GetDiskInfo(path string) (disk.Info, error) {
di, err := disk.GetInfo(path)
if err != nil {
return di, err
}
// Remove trailing 0s in fs type.
di.FSType = strings.TrimRight(di.FSType, "\x00")
return di, nil
}
func newPathTrie(paths []string) (*trie.PathTrie, error) {
t := trie.NewPathTrie()
for _, p := range paths {
// Put absolute path into the trie.
if path.IsAbs(p) {
t.Put(p, 1)
} else {
absPath, err := filepath.Abs(p)
if err != nil {
return nil, err
}
t.Put(absPath, 1)
}
}
return t, nil
}
func newPathTestFunc(pathTrie *trie.PathTrie) func(s string) bool {
var errTrieFastAbort = errors.New("trie: fast abort")
return func(path string) bool {
err := pathTrie.WalkPath(path, func(key string, value interface{}) error {
if value == 1 {
return errTrieFastAbort
}
return nil
})
return err == errTrieFastAbort
}
}
func newLocalFileService(limitedPaths []string) (*localFileService, error) {
// Set the default root path if not specified.
if limitedPaths == nil || len(limitedPaths) == 0 {
limitedPaths = []string{"/"}
}
pathTrie, err := newPathTrie(limitedPaths)
if err != nil {
return nil, err
}
lfs := &localFileService{
pathTestFunc: newPathTestFunc(pathTrie),
}
if runtime.GOOS == "linux" {
fmt.Println("Loading block devices...")
if err := lfs.loadBlockDevices(); err != nil {
return nil, err
}
}
return lfs, err
}
func NewLocalFileService(limitedPaths []string) (LocalFileService, error) {
return newLocalFileService(limitedPaths)
}