377 lines
8.6 KiB
Go
377 lines
8.6 KiB
Go
package nfs
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"hash/fnv"
|
|
"io"
|
|
"math"
|
|
"os"
|
|
"time"
|
|
|
|
"git.kmsign.ru/royalcat/tstor/pkg/go-nfs/file"
|
|
"github.com/willscott/go-nfs-client/nfs/xdr"
|
|
)
|
|
|
|
// FileAttribute holds metadata about a filesystem object
|
|
type FileAttribute struct {
|
|
Type FileType
|
|
FileMode uint32
|
|
Nlink uint32
|
|
UID uint32
|
|
GID uint32
|
|
Filesize uint64
|
|
Used uint64
|
|
SpecData [2]uint32
|
|
FSID uint64
|
|
Fileid uint64
|
|
Atime, Mtime, Ctime FileTime
|
|
}
|
|
|
|
// FileType represents a NFS File Type
|
|
type FileType uint32
|
|
|
|
// Enumeration of NFS FileTypes
|
|
const (
|
|
FileTypeRegular FileType = iota + 1
|
|
FileTypeDirectory
|
|
FileTypeBlock
|
|
FileTypeCharacter
|
|
FileTypeLink
|
|
FileTypeSocket
|
|
FileTypeFIFO
|
|
)
|
|
|
|
func (f FileType) String() string {
|
|
switch f {
|
|
case FileTypeRegular:
|
|
return "Regular"
|
|
case FileTypeDirectory:
|
|
return "Directory"
|
|
case FileTypeBlock:
|
|
return "Block Device"
|
|
case FileTypeCharacter:
|
|
return "Character Device"
|
|
case FileTypeLink:
|
|
return "Symbolic Link"
|
|
case FileTypeSocket:
|
|
return "Socket"
|
|
case FileTypeFIFO:
|
|
return "FIFO"
|
|
default:
|
|
return "Unknown"
|
|
}
|
|
}
|
|
|
|
// Mode provides the OS interpreted mode of the file attributes
|
|
func (f *FileAttribute) Mode() os.FileMode {
|
|
return os.FileMode(f.FileMode)
|
|
}
|
|
|
|
// FileCacheAttribute is the subset of FileAttribute used by
|
|
// wcc_attr
|
|
type FileCacheAttribute struct {
|
|
Filesize uint64
|
|
Mtime, Ctime FileTime
|
|
}
|
|
|
|
// AsCache provides the wcc view of the file attributes
|
|
func (f FileAttribute) AsCache() *FileCacheAttribute {
|
|
wcc := FileCacheAttribute{
|
|
Filesize: f.Filesize,
|
|
Mtime: f.Mtime,
|
|
Ctime: f.Ctime,
|
|
}
|
|
return &wcc
|
|
}
|
|
|
|
// ToFileAttribute creates an NFS fattr3 struct from an OS.FileInfo
|
|
func ToFileAttribute(info os.FileInfo, filePath string) *FileAttribute {
|
|
f := FileAttribute{}
|
|
|
|
m := info.Mode()
|
|
f.FileMode = uint32(m)
|
|
if info.IsDir() {
|
|
f.Type = FileTypeDirectory
|
|
} else if m&os.ModeSymlink != 0 {
|
|
f.Type = FileTypeLink
|
|
} else if m&os.ModeCharDevice != 0 {
|
|
f.Type = FileTypeCharacter
|
|
} else if m&os.ModeDevice != 0 {
|
|
f.Type = FileTypeBlock
|
|
} else if m&os.ModeSocket != 0 {
|
|
f.Type = FileTypeSocket
|
|
} else if m&os.ModeNamedPipe != 0 {
|
|
f.Type = FileTypeFIFO
|
|
} else {
|
|
f.Type = FileTypeRegular
|
|
}
|
|
// The number of hard links to the file.
|
|
f.Nlink = 1
|
|
|
|
if a := file.GetInfo(info); a != nil {
|
|
f.Nlink = a.Nlink
|
|
f.UID = a.UID
|
|
f.GID = a.GID
|
|
f.SpecData = [2]uint32{a.Major, a.Minor}
|
|
f.Fileid = a.Fileid
|
|
} else {
|
|
hasher := fnv.New64()
|
|
_, _ = hasher.Write([]byte(filePath))
|
|
f.Fileid = hasher.Sum64()
|
|
}
|
|
|
|
f.Filesize = uint64(info.Size())
|
|
f.Used = uint64(info.Size())
|
|
f.Atime = ToNFSTime(info.ModTime())
|
|
f.Mtime = f.Atime
|
|
f.Ctime = f.Atime
|
|
return &f
|
|
}
|
|
|
|
// tryStat attempts to create a FileAttribute from a path.
|
|
func tryStat(ctx context.Context, fs Filesystem, path []string) *FileAttribute {
|
|
fullPath := fs.Join(path...)
|
|
attrs, err := fs.Lstat(ctx, fullPath)
|
|
if err != nil || attrs == nil {
|
|
Log.Errorf("err loading attrs for %s: %v", fs.Join(path...), err)
|
|
return nil
|
|
}
|
|
return ToFileAttribute(attrs, fullPath)
|
|
}
|
|
|
|
// WriteWcc writes the `wcc_data` representation of an object.
|
|
func WriteWcc(writer io.Writer, pre *FileCacheAttribute, post *FileAttribute) error {
|
|
if pre == nil {
|
|
if err := xdr.Write(writer, uint32(0)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := xdr.Write(writer, uint32(1)); err != nil {
|
|
return err
|
|
}
|
|
if err := xdr.Write(writer, *pre); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if post == nil {
|
|
if err := xdr.Write(writer, uint32(0)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := xdr.Write(writer, uint32(1)); err != nil {
|
|
return err
|
|
}
|
|
if err := xdr.Write(writer, *post); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WritePostOpAttrs writes the `post_op_attr` representation of a files attributes
|
|
func WritePostOpAttrs(writer io.Writer, post *FileAttribute) error {
|
|
if post == nil {
|
|
if err := xdr.Write(writer, uint32(0)); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := xdr.Write(writer, uint32(1)); err != nil {
|
|
return err
|
|
}
|
|
if err := xdr.Write(writer, *post); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetFileAttributes represents a command to update some metadata
|
|
// about a file.
|
|
type SetFileAttributes struct {
|
|
SetMode *uint32
|
|
SetUID *uint32
|
|
SetGID *uint32
|
|
SetSize *uint64
|
|
SetAtime *time.Time
|
|
SetMtime *time.Time
|
|
}
|
|
|
|
// Apply uses a `Change` implementation to set defined attributes on a
|
|
// provided file.
|
|
func (s *SetFileAttributes) Apply(ctx context.Context, changer Change, fs Filesystem, file string) error {
|
|
curOS, err := fs.Lstat(ctx, file)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return &NFSStatusError{NFSStatusNoEnt, os.ErrNotExist}
|
|
} else if errors.Is(err, os.ErrPermission) {
|
|
return &NFSStatusError{NFSStatusAccess, os.ErrPermission}
|
|
} else if err != nil {
|
|
return nil
|
|
}
|
|
curr := ToFileAttribute(curOS, file)
|
|
|
|
if s.SetMode != nil {
|
|
mode := os.FileMode(*s.SetMode) & os.ModePerm
|
|
if mode != curr.Mode().Perm() {
|
|
if changer == nil {
|
|
return &NFSStatusError{NFSStatusNotSupp, os.ErrPermission}
|
|
}
|
|
if err := changer.Chmod(ctx, file, mode); err != nil {
|
|
if errors.Is(err, os.ErrPermission) {
|
|
return &NFSStatusError{NFSStatusAccess, os.ErrPermission}
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if s.SetUID != nil || s.SetGID != nil {
|
|
euid := curr.UID
|
|
if s.SetUID != nil {
|
|
euid = *s.SetUID
|
|
}
|
|
egid := curr.GID
|
|
if s.SetGID != nil {
|
|
egid = *s.SetGID
|
|
}
|
|
if euid != curr.UID || egid != curr.GID {
|
|
if changer == nil {
|
|
return &NFSStatusError{NFSStatusNotSupp, os.ErrPermission}
|
|
}
|
|
if err := changer.Lchown(ctx, file, int(euid), int(egid)); err != nil {
|
|
if errors.Is(err, os.ErrPermission) {
|
|
return &NFSStatusError{NFSStatusAccess, os.ErrPermission}
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if s.SetSize != nil {
|
|
if curr.Mode()&os.ModeSymlink != 0 {
|
|
return &NFSStatusError{NFSStatusNotSupp, os.ErrInvalid}
|
|
}
|
|
fp, err := fs.OpenFile(ctx, file, os.O_WRONLY|os.O_EXCL, 0)
|
|
if errors.Is(err, os.ErrPermission) {
|
|
return &NFSStatusError{NFSStatusAccess, err}
|
|
} else if err != nil {
|
|
return err
|
|
}
|
|
if *s.SetSize > math.MaxInt64 {
|
|
return &NFSStatusError{NFSStatusInval, os.ErrInvalid}
|
|
}
|
|
if err := fp.Truncate(ctx, int64(*s.SetSize)); err != nil {
|
|
return err
|
|
}
|
|
if err := fp.Close(ctx); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if s.SetAtime != nil || s.SetMtime != nil {
|
|
atime := curr.Atime.Native()
|
|
if s.SetAtime != nil {
|
|
atime = s.SetAtime
|
|
}
|
|
mtime := curr.Mtime.Native()
|
|
if s.SetMtime != nil {
|
|
mtime = s.SetMtime
|
|
}
|
|
if atime != curr.Atime.Native() || mtime != curr.Mtime.Native() {
|
|
if changer == nil {
|
|
return &NFSStatusError{NFSStatusNotSupp, os.ErrPermission}
|
|
}
|
|
if err := changer.Chtimes(ctx, file, *atime, *mtime); err != nil {
|
|
if errors.Is(err, os.ErrPermission) {
|
|
return &NFSStatusError{NFSStatusAccess, err}
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Mode returns a mode if specified or the provided default mode.
|
|
func (s *SetFileAttributes) Mode(def os.FileMode) os.FileMode {
|
|
if s.SetMode != nil {
|
|
return os.FileMode(*s.SetMode) & os.ModePerm
|
|
}
|
|
return def
|
|
}
|
|
|
|
// ReadSetFileAttributes reads an sattr3 xdr stream into a go struct.
|
|
func ReadSetFileAttributes(r io.Reader) (*SetFileAttributes, error) {
|
|
attrs := SetFileAttributes{}
|
|
hasMode, err := xdr.ReadUint32(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hasMode != 0 {
|
|
mode, err := xdr.ReadUint32(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attrs.SetMode = &mode
|
|
}
|
|
hasUID, err := xdr.ReadUint32(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hasUID != 0 {
|
|
uid, err := xdr.ReadUint32(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attrs.SetUID = &uid
|
|
}
|
|
hasGID, err := xdr.ReadUint32(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hasGID != 0 {
|
|
gid, err := xdr.ReadUint32(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
attrs.SetGID = &gid
|
|
}
|
|
hasSize, err := xdr.ReadUint32(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hasSize != 0 {
|
|
var size uint64
|
|
attrs.SetSize = &size
|
|
if err := xdr.Read(r, &size); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
aTime, err := xdr.ReadUint32(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if aTime == 1 {
|
|
now := time.Now()
|
|
attrs.SetAtime = &now
|
|
} else if aTime == 2 {
|
|
t := FileTime{}
|
|
if err := xdr.Read(r, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
attrs.SetAtime = t.Native()
|
|
}
|
|
mTime, err := xdr.ReadUint32(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if mTime == 1 {
|
|
now := time.Now()
|
|
attrs.SetMtime = &now
|
|
} else if mTime == 2 {
|
|
t := FileTime{}
|
|
if err := xdr.Read(r, &t); err != nil {
|
|
return nil, err
|
|
}
|
|
attrs.SetMtime = t.Native()
|
|
}
|
|
return &attrs, nil
|
|
}
|