mirror of
https://github.com/mickael-kerjean/filestash
synced 2025-12-20 15:24:20 +01:00
559 lines
11 KiB
Go
559 lines
11 KiB
Go
// Copyright © 2017 VMware, Inc. All Rights Reserved.
|
|
// SPDX-License-Identifier: BSD-2-Clause
|
|
//
|
|
package nfs
|
|
|
|
import (
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/vmware/go-nfs-client/nfs/rpc"
|
|
"github.com/vmware/go-nfs-client/nfs/util"
|
|
"github.com/vmware/go-nfs-client/nfs/xdr"
|
|
)
|
|
|
|
type Target struct {
|
|
*rpc.Client
|
|
|
|
auth rpc.Auth
|
|
fh []byte
|
|
dirPath string
|
|
fsinfo *FSInfo
|
|
}
|
|
|
|
func NewTarget(addr string, auth rpc.Auth, fh []byte, dirpath string) (*Target, error) {
|
|
m := rpc.Mapping{
|
|
Prog: Nfs3Prog,
|
|
Vers: Nfs3Vers,
|
|
Prot: rpc.IPProtoTCP,
|
|
Port: 0,
|
|
}
|
|
|
|
client, err := DialService(addr, m)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vol := &Target{
|
|
Client: client,
|
|
auth: auth,
|
|
fh: fh,
|
|
dirPath: dirpath,
|
|
}
|
|
|
|
fsinfo, err := vol.FSInfo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vol.fsinfo = fsinfo
|
|
util.Debugf("%s:%s fsinfo=%#v", addr, dirpath, fsinfo)
|
|
|
|
return vol, nil
|
|
}
|
|
|
|
// wraps the Call function to check status and decode errors
|
|
func (v *Target) call(c interface{}) (io.ReadSeeker, error) {
|
|
res, err := v.Call(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
status, err := xdr.ReadUint32(res)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = NFS3Error(status); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (v *Target) FSInfo() (*FSInfo, error) {
|
|
type FSInfoArgs struct {
|
|
rpc.Header
|
|
FsRoot []byte
|
|
}
|
|
|
|
res, err := v.call(&FSInfoArgs{
|
|
Header: rpc.Header{
|
|
Rpcvers: 2,
|
|
Prog: Nfs3Prog,
|
|
Vers: Nfs3Vers,
|
|
Proc: NFSProc3FSInfo,
|
|
Cred: v.auth,
|
|
Verf: rpc.AuthNull,
|
|
},
|
|
FsRoot: v.fh,
|
|
})
|
|
|
|
if err != nil {
|
|
util.Debugf("fsroot: %s", err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
fsinfo := new(FSInfo)
|
|
if err = xdr.Read(res, fsinfo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return fsinfo, nil
|
|
}
|
|
|
|
// Lookup returns attributes and the file handle to a given dirent
|
|
func (v *Target) Lookup(p string) (os.FileInfo, []byte, error) {
|
|
var (
|
|
err error
|
|
fattr *Fattr
|
|
fh = v.fh
|
|
)
|
|
|
|
// desecend down a path heirarchy to get the last elem's fh
|
|
dirents := strings.Split(path.Clean(p), "/")
|
|
for _, dirent := range dirents {
|
|
// we're assuming the root is always the root of the mount
|
|
if dirent == "." || dirent == "" {
|
|
util.Debugf("root -> 0x%x", fh)
|
|
continue
|
|
}
|
|
|
|
fattr, fh, err = v.lookup(fh, dirent)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
//util.Debugf("%s -> 0x%x", dirent, fh)
|
|
}
|
|
|
|
return fattr, fh, nil
|
|
}
|
|
|
|
// lookup returns the same as above, but by fh and name
|
|
func (v *Target) lookup(fh []byte, name string) (*Fattr, []byte, error) {
|
|
type Lookup3Args struct {
|
|
rpc.Header
|
|
What Diropargs3
|
|
}
|
|
|
|
type LookupOk struct {
|
|
FH []byte
|
|
Attr PostOpAttr
|
|
DirAttr PostOpAttr
|
|
}
|
|
|
|
res, err := v.call(&Lookup3Args{
|
|
Header: rpc.Header{
|
|
Rpcvers: 2,
|
|
Prog: Nfs3Prog,
|
|
Vers: Nfs3Vers,
|
|
Proc: NFSProc3Lookup,
|
|
Cred: v.auth,
|
|
Verf: rpc.AuthNull,
|
|
},
|
|
What: Diropargs3{
|
|
FH: fh,
|
|
Filename: name,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
util.Debugf("lookup(%s): %s", name, err.Error())
|
|
return nil, nil, err
|
|
}
|
|
|
|
lookupres := new(LookupOk)
|
|
if err := xdr.Read(res, lookupres); err != nil {
|
|
util.Errorf("lookup(%s) failed to parse return: %s", name, err)
|
|
util.Debugf("lookup partial decode: %+v", *lookupres)
|
|
return nil, nil, err
|
|
}
|
|
|
|
util.Debugf("lookup(%s): FH 0x%x, attr: %+v", name, lookupres.FH, lookupres.Attr.Attr)
|
|
return &lookupres.Attr.Attr, lookupres.FH, nil
|
|
}
|
|
|
|
func (v *Target) ReadDirPlus(dir string) ([]*EntryPlus, error) {
|
|
_, fh, err := v.Lookup(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return v.readDirPlus(fh)
|
|
}
|
|
|
|
func (v *Target) readDirPlus(fh []byte) ([]*EntryPlus, error) {
|
|
cookie := uint64(0)
|
|
cookieVerf := uint64(0)
|
|
eof := false
|
|
|
|
type ReadDirPlus3Args struct {
|
|
rpc.Header
|
|
FH []byte
|
|
Cookie uint64
|
|
CookieVerf uint64
|
|
DirCount uint32
|
|
MaxCount uint32
|
|
}
|
|
|
|
type DirListPlus3 struct {
|
|
IsSet bool `xdr:"union"`
|
|
Entry EntryPlus `xdr:"unioncase=1"`
|
|
}
|
|
|
|
type DirListOK struct {
|
|
DirAttrs PostOpAttr
|
|
CookieVerf uint64
|
|
}
|
|
|
|
var entries []*EntryPlus
|
|
for !eof {
|
|
res, err := v.call(&ReadDirPlus3Args{
|
|
Header: rpc.Header{
|
|
Rpcvers: 2,
|
|
Prog: Nfs3Prog,
|
|
Vers: Nfs3Vers,
|
|
Proc: NFSProc3ReadDirPlus,
|
|
Cred: v.auth,
|
|
Verf: rpc.AuthNull,
|
|
},
|
|
FH: fh,
|
|
Cookie: cookie,
|
|
CookieVerf: cookieVerf,
|
|
DirCount: 512,
|
|
MaxCount: 4096,
|
|
})
|
|
|
|
if err != nil {
|
|
util.Debugf("readdir(%x): %s", fh, err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
// The dir list entries are so-called "optional-data". We need to check
|
|
// the Follows fields before continuing down the array. Effectively, it's
|
|
// an encoding used to flatten a linked list into an array where the
|
|
// Follows field is set when the next idx has data. See
|
|
// https://tools.ietf.org/html/rfc4506.html#section-4.19 for details.
|
|
dirlistOK := new(DirListOK)
|
|
if err = xdr.Read(res, dirlistOK); err != nil {
|
|
util.Errorf("readdir failed to parse result (%x): %s", fh, err.Error())
|
|
util.Debugf("partial dirlist: %+v", dirlistOK)
|
|
return nil, err
|
|
}
|
|
|
|
for {
|
|
var item DirListPlus3
|
|
if err = xdr.Read(res, &item); err != nil {
|
|
util.Errorf("readdir failed to parse directory entry, aborting")
|
|
util.Debugf("partial dirent: %+v", item)
|
|
return nil, err
|
|
}
|
|
|
|
if !item.IsSet {
|
|
break
|
|
}
|
|
|
|
cookie = item.Entry.Cookie
|
|
entries = append(entries, &item.Entry)
|
|
}
|
|
|
|
if err = xdr.Read(res, &eof); err != nil {
|
|
util.Errorf("readdir failed to determine presence of more data to read, aborting")
|
|
return nil, err
|
|
}
|
|
|
|
util.Debugf("No EOF for dirents so calling back for more")
|
|
cookieVerf = dirlistOK.CookieVerf
|
|
}
|
|
|
|
return entries, nil
|
|
}
|
|
|
|
// Creates a directory of the given name and returns its handle
|
|
func (v *Target) Mkdir(path string, perm os.FileMode) ([]byte, error) {
|
|
dir, newDir := filepath.Split(path)
|
|
_, fh, err := v.Lookup(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
type MkdirArgs struct {
|
|
rpc.Header
|
|
Where Diropargs3
|
|
Attrs Sattr3
|
|
}
|
|
|
|
type MkdirOk struct {
|
|
FH PostOpFH3
|
|
Attr PostOpAttr
|
|
DirWcc WccData
|
|
}
|
|
|
|
args := &MkdirArgs{
|
|
Header: rpc.Header{
|
|
Rpcvers: 2,
|
|
Prog: Nfs3Prog,
|
|
Vers: Nfs3Vers,
|
|
Proc: NFSProc3Mkdir,
|
|
Cred: v.auth,
|
|
Verf: rpc.AuthNull,
|
|
},
|
|
Where: Diropargs3{
|
|
FH: fh,
|
|
Filename: newDir,
|
|
},
|
|
Attrs: Sattr3{
|
|
Mode: SetMode{
|
|
SetIt: true,
|
|
Mode: uint32(perm.Perm()),
|
|
},
|
|
},
|
|
}
|
|
res, err := v.call(args)
|
|
|
|
if err != nil {
|
|
util.Debugf("mkdir(%s): %s", path, err.Error())
|
|
util.Debugf("mkdir args (%+v)", args)
|
|
return nil, err
|
|
}
|
|
|
|
mkdirres := new(MkdirOk)
|
|
if err := xdr.Read(res, mkdirres); err != nil {
|
|
util.Errorf("mkdir(%s) failed to parse return: %s", path, err)
|
|
util.Debugf("mkdir(%s) partial response: %+v", mkdirres)
|
|
return nil, err
|
|
}
|
|
|
|
util.Debugf("mkdir(%s): created successfully (0x%x)", path, fh)
|
|
return mkdirres.FH.FH, nil
|
|
}
|
|
|
|
// Create a file with name the given mode
|
|
func (v *Target) Create(path string, perm os.FileMode) ([]byte, error) {
|
|
dir, newFile := filepath.Split(path)
|
|
_, fh, err := v.Lookup(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
type How struct {
|
|
// 0 : UNCHECKED (default)
|
|
// 1 : GUARDED
|
|
// 2 : EXCLUSIVE
|
|
Mode uint32
|
|
Attr Sattr3
|
|
}
|
|
type Create3Args struct {
|
|
rpc.Header
|
|
Where Diropargs3
|
|
HW How
|
|
}
|
|
|
|
type Create3Res struct {
|
|
FH PostOpFH3
|
|
Attr PostOpAttr
|
|
DirWcc WccData
|
|
}
|
|
|
|
res, err := v.call(&Create3Args{
|
|
Header: rpc.Header{
|
|
Rpcvers: 2,
|
|
Prog: Nfs3Prog,
|
|
Vers: Nfs3Vers,
|
|
Proc: NFSProc3Create,
|
|
Cred: v.auth,
|
|
Verf: rpc.AuthNull,
|
|
},
|
|
Where: Diropargs3{
|
|
FH: fh,
|
|
Filename: newFile,
|
|
},
|
|
HW: How{
|
|
Attr: Sattr3{
|
|
Mode: SetMode{
|
|
SetIt: true,
|
|
Mode: uint32(perm.Perm()),
|
|
},
|
|
},
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
util.Debugf("create(%s): %s", path, err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
status := new(Create3Res)
|
|
if err = xdr.Read(res, status); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
util.Debugf("create(%s): created successfully", path)
|
|
return status.FH.FH, nil
|
|
}
|
|
|
|
// Remove a file
|
|
func (v *Target) Remove(path string) error {
|
|
parentDir, deleteFile := filepath.Split(path)
|
|
_, fh, err := v.Lookup(parentDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return v.remove(fh, deleteFile)
|
|
}
|
|
|
|
// remove the named file from the parent (fh)
|
|
func (v *Target) remove(fh []byte, deleteFile string) error {
|
|
type RemoveArgs struct {
|
|
rpc.Header
|
|
Object Diropargs3
|
|
}
|
|
|
|
_, err := v.call(&RemoveArgs{
|
|
Header: rpc.Header{
|
|
Rpcvers: 2,
|
|
Prog: Nfs3Prog,
|
|
Vers: Nfs3Vers,
|
|
Proc: NFSProc3Remove,
|
|
Cred: v.auth,
|
|
Verf: rpc.AuthNull,
|
|
},
|
|
Object: Diropargs3{
|
|
FH: fh,
|
|
Filename: deleteFile,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
util.Debugf("remove(%s): %s", deleteFile, err.Error())
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RmDir removes a non-empty directory
|
|
func (v *Target) RmDir(path string) error {
|
|
dir, deletedir := filepath.Split(path)
|
|
_, fh, err := v.Lookup(dir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return v.rmDir(fh, deletedir)
|
|
}
|
|
|
|
// delete the named directory from the parent directory (fh)
|
|
func (v *Target) rmDir(fh []byte, name string) error {
|
|
type RmDir3Args struct {
|
|
rpc.Header
|
|
Object Diropargs3
|
|
}
|
|
|
|
_, err := v.call(&RmDir3Args{
|
|
Header: rpc.Header{
|
|
Rpcvers: 2,
|
|
Prog: Nfs3Prog,
|
|
Vers: Nfs3Vers,
|
|
Proc: NFSProc3RmDir,
|
|
Cred: v.auth,
|
|
Verf: rpc.AuthNull,
|
|
},
|
|
Object: Diropargs3{
|
|
FH: fh,
|
|
Filename: name,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
util.Debugf("rmdir(%s): %s", name, err.Error())
|
|
return err
|
|
}
|
|
|
|
util.Debugf("rmdir(%s): deleted successfully", name)
|
|
return nil
|
|
}
|
|
|
|
func (v *Target) RemoveAll(path string) error {
|
|
parentDir, deleteDir := filepath.Split(path)
|
|
_, parentDirfh, err := v.Lookup(parentDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Easy path. This is a directory and it's empty. If not a dir or not an
|
|
// empty dir, this will throw an error.
|
|
err = v.rmDir(parentDirfh, deleteDir)
|
|
if err == nil || os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
|
|
// Collect the not a dir error.
|
|
if IsNotDirError(err) {
|
|
return err
|
|
}
|
|
|
|
_, deleteDirfh, err := v.lookup(parentDirfh, deleteDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = v.removeAll(deleteDirfh); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Delete the directory we started at.
|
|
if err = v.rmDir(parentDirfh, deleteDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// removeAll removes the deleteDir recursively
|
|
func (v *Target) removeAll(deleteDirfh []byte) error {
|
|
|
|
// BFS the dir tree recursively. If dir, recurse, then delete the dir and
|
|
// all files.
|
|
|
|
// This is a directory, get all of its Entries
|
|
entries, err := v.readDirPlus(deleteDirfh)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
// skip "." and ".."
|
|
if entry.FileName == "." || entry.FileName == ".." {
|
|
continue
|
|
}
|
|
|
|
// If directory, recurse, then nuke it. It should be empty when we get
|
|
// back.
|
|
if entry.Attr.Attr.Type == NF3Dir {
|
|
if entry.Handle.IsSet {
|
|
if err = v.removeAll(entry.Handle.FH); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = v.rmDir(deleteDirfh, entry.FileName)
|
|
} else {
|
|
|
|
// nuke all files
|
|
err = v.remove(deleteDirfh, entry.FileName)
|
|
}
|
|
|
|
if err != nil {
|
|
util.Errorf("error deleting %s: %s", entry.FileName, err.Error())
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|