mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-05-22 07:16:41 +00:00
Bumps [github.com/ceph/go-ceph](https://github.com/ceph/go-ceph) from 0.32.1-0.20250307053135-38b9676b1d4e to 0.33.0. - [Release notes](https://github.com/ceph/go-ceph/releases) - [Changelog](https://github.com/ceph/go-ceph/blob/master/docs/release-process.md) - [Commits](https://github.com/ceph/go-ceph/commits/v0.33.0) --- updated-dependencies: - dependency-name: github.com/ceph/go-ceph dependency-version: 0.33.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
409 lines
9.8 KiB
Go
409 lines
9.8 KiB
Go
//go:build ceph_preview
|
|
|
|
package cephfs
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ceph/go-ceph/internal/log"
|
|
)
|
|
|
|
var (
|
|
errIsDir = errors.New("is a directory")
|
|
)
|
|
|
|
// MountWrapper provides a wrapper type that adapts a CephFS Mount into a
|
|
// io.FS compatible type.
|
|
type MountWrapper struct {
|
|
mount *MountInfo
|
|
enableTrace bool
|
|
}
|
|
|
|
type fileWrapper struct {
|
|
parent *MountWrapper
|
|
file *File
|
|
name string
|
|
}
|
|
|
|
type dirWrapper struct {
|
|
parent *MountWrapper
|
|
directory *Directory
|
|
name string
|
|
}
|
|
|
|
type dentryWrapper struct {
|
|
parent *MountWrapper
|
|
de *DirEntryPlus
|
|
}
|
|
|
|
type infoWrapper struct {
|
|
parent *MountWrapper
|
|
sx *CephStatx
|
|
name string
|
|
}
|
|
|
|
// Wrap a CephFS Mount object into a new type that is compatible with Go's io.FS
|
|
// interface. CephFS Mounts are not compatible with io.FS directly because the
|
|
// go-ceph library predates the addition of io.FS to Go as well as the fact that
|
|
// go-ceph attempts to provide APIs that match the cephfs libraries first and
|
|
// foremost.
|
|
func Wrap(mount *MountInfo) *MountWrapper {
|
|
wm := &MountWrapper{mount: mount}
|
|
debugf(wm, "Wrap", "created")
|
|
return wm
|
|
}
|
|
|
|
/* MountWrapper:
|
|
** Implements https://pkg.go.dev/io/fs#FS
|
|
** Wraps cephfs.MountInfo
|
|
*/
|
|
|
|
// SetTracing configures the MountWrapper and objects connected to it for debug
|
|
// tracing. True enables tracing and false disables it. A debug logging
|
|
// function must also be set using go-ceph's common.log.SetDebugf function.
|
|
func (mw *MountWrapper) SetTracing(enable bool) {
|
|
mw.enableTrace = enable
|
|
}
|
|
|
|
// identify the MountWrapper object for logging purposes.
|
|
func (mw *MountWrapper) identify() string {
|
|
return fmt.Sprintf("MountWrapper<%p>", mw)
|
|
}
|
|
|
|
// trace returns true if debug tracing is enabled.
|
|
func (mw *MountWrapper) trace() bool {
|
|
return mw.enableTrace
|
|
}
|
|
|
|
// Open opens the named file. This may be either a regular file or a directory.
|
|
// Directories opened with this function will return object compatible with the
|
|
// io.ReadDirFile interface.
|
|
func (mw *MountWrapper) Open(name string) (fs.File, error) {
|
|
debugf(mw, "Open", "(%v)", name)
|
|
// there are a bunch of patterns that fsTetster/testfs looks for that seems
|
|
// under-documented. They mainly seem to try and enforce "clean" paths.
|
|
// look for them and reject them here because ceph libs won't reject on
|
|
// its own
|
|
if strings.HasPrefix(name, "/") ||
|
|
strings.HasSuffix(name, "/.") ||
|
|
strings.Contains(name, "//") ||
|
|
strings.Contains(name, "/./") ||
|
|
strings.Contains(name, "/../") {
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: errInvalid}
|
|
}
|
|
|
|
d, err := mw.mount.OpenDir(name)
|
|
if err == nil {
|
|
debugf(mw, "Open", "(%v): dir ok", name)
|
|
dw := &dirWrapper{parent: mw, directory: d, name: name}
|
|
return dw, nil
|
|
}
|
|
if !errors.Is(err, errNotDir) {
|
|
debugf(mw, "Open", "(%v): dir error: %v", name, err)
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
|
}
|
|
|
|
f, err := mw.mount.Open(name, os.O_RDONLY, 0)
|
|
if err == nil {
|
|
debugf(mw, "Open", "(%v): file ok", name)
|
|
fw := &fileWrapper{parent: mw, file: f, name: name}
|
|
return fw, nil
|
|
}
|
|
debugf(mw, "Open", "(%v): file error: %v", name, err)
|
|
return nil, &fs.PathError{Op: "open", Path: name, Err: err}
|
|
}
|
|
|
|
/* fileWrapper:
|
|
** Implements https://pkg.go.dev/io/fs#FS
|
|
** Wraps cephfs.File
|
|
*/
|
|
|
|
func (fw *fileWrapper) Stat() (fs.FileInfo, error) {
|
|
debugf(fw, "Stat", "()")
|
|
sx, err := fw.file.Fstatx(StatxBasicStats, AtSymlinkNofollow)
|
|
if err != nil {
|
|
debugf(fw, "Stat", "() -> err:%v", err)
|
|
return nil, &fs.PathError{Op: "stat", Path: fw.name, Err: err}
|
|
}
|
|
debugf(fw, "Stat", "() ok")
|
|
return &infoWrapper{fw.parent, sx, path.Base(fw.name)}, nil
|
|
}
|
|
|
|
func (fw *fileWrapper) Read(b []byte) (int, error) {
|
|
debugf(fw, "Read", "(...)")
|
|
return fw.file.Read(b)
|
|
}
|
|
|
|
func (fw *fileWrapper) Close() error {
|
|
debugf(fw, "Close", "()")
|
|
return fw.file.Close()
|
|
}
|
|
|
|
func (fw *fileWrapper) identify() string {
|
|
return fmt.Sprintf("fileWrapper<%p>[%v]", fw, fw.name)
|
|
}
|
|
|
|
func (fw *fileWrapper) trace() bool {
|
|
return fw.parent.trace()
|
|
}
|
|
|
|
/* dirWrapper:
|
|
** Implements https://pkg.go.dev/io/fs#ReadDirFile
|
|
** Wraps cephfs.Directory
|
|
*/
|
|
|
|
func (dw *dirWrapper) Stat() (fs.FileInfo, error) {
|
|
debugf(dw, "Stat", "()")
|
|
sx, err := dw.parent.mount.Statx(dw.name, StatxBasicStats, AtSymlinkNofollow)
|
|
if err != nil {
|
|
debugf(dw, "Stat", "() -> err:%v", err)
|
|
return nil, &fs.PathError{Op: "stat", Path: dw.name, Err: err}
|
|
}
|
|
debugf(dw, "Stat", "() ok")
|
|
return &infoWrapper{dw.parent, sx, path.Base(dw.name)}, nil
|
|
}
|
|
|
|
func (dw *dirWrapper) Read(_ []byte) (int, error) {
|
|
debugf(dw, "Read", "(...)")
|
|
return 0, &fs.PathError{Op: "read", Path: dw.name, Err: errIsDir}
|
|
}
|
|
|
|
func (dw *dirWrapper) ReadDir(n int) ([]fs.DirEntry, error) {
|
|
debugf(dw, "ReadDir", "(%v)", n)
|
|
if n > 0 {
|
|
return dw.readDirSome(n)
|
|
}
|
|
return dw.readDirAll()
|
|
}
|
|
|
|
const defaultDirReadCount = 256 // how many entries to read per loop
|
|
|
|
func (dw *dirWrapper) readDirAll() ([]fs.DirEntry, error) {
|
|
debugf(dw, "readDirAll", "()")
|
|
var (
|
|
err error
|
|
egroup []fs.DirEntry
|
|
entries = make([]fs.DirEntry, 0)
|
|
size = defaultDirReadCount
|
|
)
|
|
for {
|
|
egroup, err = dw.readDirSome(size)
|
|
entries = append(entries, egroup...)
|
|
if err == io.EOF {
|
|
err = nil
|
|
break
|
|
}
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
debugf(dw, "readDirAll", "() -> len:%v, err:%v", len(entries), err)
|
|
return entries, err
|
|
}
|
|
|
|
func (dw *dirWrapper) readDirSome(n int) ([]fs.DirEntry, error) {
|
|
debugf(dw, "readDirSome", "(%v)", n)
|
|
var (
|
|
idx int
|
|
err error
|
|
entry *DirEntryPlus
|
|
entries = make([]fs.DirEntry, n)
|
|
)
|
|
for {
|
|
entry, err = dw.directory.ReadDirPlus(StatxBasicStats, AtSymlinkNofollow)
|
|
debugf(dw, "readDirSome", "(%v): got entry:%v, err:%v", n, entry, err)
|
|
if err != nil || entry == nil {
|
|
break
|
|
}
|
|
switch entry.Name() {
|
|
case ".", "..":
|
|
continue
|
|
}
|
|
entries[idx] = &dentryWrapper{dw.parent, entry}
|
|
idx++
|
|
if idx >= n {
|
|
break
|
|
}
|
|
}
|
|
if idx == 0 {
|
|
debugf(dw, "readDirSome", "(%v): EOF", n)
|
|
return nil, io.EOF
|
|
}
|
|
debugf(dw, "readDirSome", "(%v): got entry:%v, err:%v", n, entries[:idx], err)
|
|
return entries[:idx], err
|
|
}
|
|
|
|
func (dw *dirWrapper) Close() error {
|
|
debugf(dw, "Close", "()")
|
|
return dw.directory.Close()
|
|
}
|
|
|
|
func (dw *dirWrapper) identify() string {
|
|
return fmt.Sprintf("dirWrapper<%p>[%v]", dw, dw.name)
|
|
}
|
|
|
|
func (dw *dirWrapper) trace() bool {
|
|
return dw.parent.trace()
|
|
}
|
|
|
|
/* dentryWrapper:
|
|
** Implements https://pkg.go.dev/io/fs#DirEntry
|
|
** Wraps cephfs.DirEntryPlus
|
|
*/
|
|
|
|
func (dew *dentryWrapper) Name() string {
|
|
debugf(dew, "Name", "()")
|
|
return dew.de.Name()
|
|
}
|
|
|
|
func (dew *dentryWrapper) IsDir() bool {
|
|
v := dew.de.DType() == DTypeDir
|
|
debugf(dew, "IsDir", "() -> %v", v)
|
|
return v
|
|
}
|
|
|
|
func (dew *dentryWrapper) Type() fs.FileMode {
|
|
m := dew.de.Statx().Mode
|
|
v := cephModeToFileMode(m).Type()
|
|
debugf(dew, "Type", "() -> %v", v)
|
|
return v
|
|
}
|
|
|
|
func (dew *dentryWrapper) Info() (fs.FileInfo, error) {
|
|
debugf(dew, "Info", "()")
|
|
sx := dew.de.Statx()
|
|
name := dew.de.Name()
|
|
return &infoWrapper{dew.parent, sx, name}, nil
|
|
}
|
|
|
|
func (dew *dentryWrapper) identify() string {
|
|
return fmt.Sprintf("dentryWrapper<%p>[%v]", dew, dew.de.Name())
|
|
}
|
|
|
|
func (dew *dentryWrapper) trace() bool {
|
|
return dew.parent.trace()
|
|
}
|
|
|
|
/* infoWrapper:
|
|
** Implements https://pkg.go.dev/io/fs#FileInfo
|
|
** Wraps cephfs.CephStatx
|
|
*/
|
|
|
|
func (iw *infoWrapper) Name() string {
|
|
debugf(iw, "Name", "()")
|
|
return iw.name
|
|
}
|
|
|
|
func (iw *infoWrapper) Size() int64 {
|
|
debugf(iw, "Size", "() -> %v", iw.sx.Size)
|
|
return int64(iw.sx.Size)
|
|
}
|
|
|
|
func (iw *infoWrapper) Sys() any {
|
|
debugf(iw, "Sys", "()")
|
|
return iw.sx
|
|
}
|
|
|
|
func (iw *infoWrapper) Mode() fs.FileMode {
|
|
v := cephModeToFileMode(iw.sx.Mode)
|
|
debugf(iw, "Mode", "() -> %#o -> %#o/%v", iw.sx.Mode, uint32(v), v.Type())
|
|
return v
|
|
}
|
|
|
|
func (iw *infoWrapper) IsDir() bool {
|
|
v := iw.sx.Mode&modeIFMT == modeIFDIR
|
|
debugf(iw, "IsDir", "() -> %v", v)
|
|
return v
|
|
}
|
|
|
|
func (iw *infoWrapper) ModTime() time.Time {
|
|
v := time.Unix(iw.sx.Mtime.Sec, iw.sx.Mtime.Nsec)
|
|
debugf(iw, "ModTime", "() -> %v", v)
|
|
return v
|
|
}
|
|
|
|
func (iw *infoWrapper) identify() string {
|
|
return fmt.Sprintf("infoWrapper<%p>[%v]", iw, iw.name)
|
|
}
|
|
|
|
func (iw *infoWrapper) trace() bool {
|
|
return iw.parent.trace()
|
|
}
|
|
|
|
/* copy and paste values from the linux headers. We always need to use
|
|
** the linux header values, regardless of the platform go-ceph is built
|
|
** for. Rather than jumping through header hoops, copy and paste is
|
|
** more consistent and reliable.
|
|
*/
|
|
const (
|
|
/* file type mask */
|
|
modeIFMT = uint16(0170000)
|
|
/* file types */
|
|
modeIFDIR = uint16(0040000)
|
|
modeIFCHR = uint16(0020000)
|
|
modeIFBLK = uint16(0060000)
|
|
modeIFREG = uint16(0100000)
|
|
modeIFIFO = uint16(0010000)
|
|
modeIFLNK = uint16(0120000)
|
|
modeIFSOCK = uint16(0140000)
|
|
/* protection bits */
|
|
modeISUID = uint16(0004000)
|
|
modeISGID = uint16(0002000)
|
|
modeISVTX = uint16(0001000)
|
|
)
|
|
|
|
// cephModeToFileMode takes a linux compatible cephfs mode value
|
|
// and returns a Go-compatiable os-agnostic FileMode value.
|
|
func cephModeToFileMode(m uint16) fs.FileMode {
|
|
// start with permission bits
|
|
mode := fs.FileMode(m & 0777)
|
|
// file type - inspired by go's src/os/stat_linux.go
|
|
switch m & modeIFMT {
|
|
case modeIFBLK:
|
|
mode |= fs.ModeDevice
|
|
case modeIFCHR:
|
|
mode |= fs.ModeDevice | fs.ModeCharDevice
|
|
case modeIFDIR:
|
|
mode |= fs.ModeDir
|
|
case modeIFIFO:
|
|
mode |= fs.ModeNamedPipe
|
|
case modeIFLNK:
|
|
mode |= fs.ModeSymlink
|
|
case modeIFREG:
|
|
// nothing to do
|
|
case modeIFSOCK:
|
|
mode |= fs.ModeSocket
|
|
}
|
|
// protection bits
|
|
if m&modeISUID != 0 {
|
|
mode |= fs.ModeSetuid
|
|
}
|
|
if m&modeISGID != 0 {
|
|
mode |= fs.ModeSetgid
|
|
}
|
|
if m&modeISVTX != 0 {
|
|
mode |= fs.ModeSticky
|
|
}
|
|
return mode
|
|
}
|
|
|
|
// wrapperObject helps identify an object to be logged.
|
|
type wrapperObject interface {
|
|
identify() string
|
|
trace() bool
|
|
}
|
|
|
|
// debugf formats info about a function and logs it.
|
|
func debugf(o wrapperObject, fname, format string, args ...any) {
|
|
if o.trace() {
|
|
log.Debugf(fmt.Sprintf("%v.%v: %s", o.identify(), fname, format), args...)
|
|
}
|
|
}
|