Files
2025-09-15 15:16:29 +10:00

330 lines
6.7 KiB
Go

package plg_backend_nfs
import (
"context"
"io"
"os"
"path/filepath"
"strings"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/vmware/go-nfs-client/nfs"
"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 NfsShare struct {
mount *nfs.Mount
v *nfs.Target
auth rpc.Auth
ctx context.Context
uid uint32
gid uint32
gids []uint32
}
func init() {
Backend.Register("nfs", NfsShare{})
util.DefaultLogger.SetDebug(false)
}
func (this NfsShare) Init(params map[string]string, app *App) (IBackend, error) {
if params["hostname"] == "" {
return nil, ErrNotFound
}
if params["machine_name"] == "" {
params["machine_name"] = "Filestash"
}
params["username"] = params["uid"]
uid, gid, gids := ExtractUserInfo(params["uid"], params["gid"], params["gids"])
Log.Debug("plg_backend_nfs::userInfo user=%s uid=%d gid=%d gids=%+v", params["uid"], uid, gid, gids)
mount, err := nfs.DialMount(params["hostname"])
if err != nil {
return nil, err
}
auth := NewAuthUnix(params["machine_name"], uid, gid, gids, params["gids"])
v, err := mount.Mount(
params["target"],
auth,
)
if err != nil {
return nil, err
}
return NfsShare{mount, v, auth, app.Context, uid, gid, toGids(gids)}, nil
}
func toGids(gids []GroupLabel) []uint32 {
g := make([]uint32, len(gids))
for i, _ := range gids {
g[i] = gids[i].Id
}
return g
}
func (this NfsShare) LoginForm() Form {
return Form{
Elmnts: []FormElement{
FormElement{
Name: "type",
Type: "hidden",
Value: "nfs",
},
FormElement{
Name: "hostname",
Type: "text",
Placeholder: "Hostname",
},
FormElement{
Name: "target",
Type: "text",
Placeholder: "Mount Path",
},
FormElement{
Name: "advanced",
Type: "enable",
Placeholder: "Advanced",
Target: []string{"nfs_uid", "nfs_gid", "nfs_gids", "nfs_machinename", "nfs_chroot"},
},
FormElement{
Id: "nfs_uid",
Name: "uid",
Type: "text",
Placeholder: "UID",
},
FormElement{
Id: "nfs_gid",
Name: "gid",
Type: "text",
Placeholder: "GID",
},
FormElement{
Id: "nfs_gids",
Name: "gids",
Type: "text",
Placeholder: "Auxiliary GIDs",
},
FormElement{
Id: "nfs_machinename",
Name: "machine_name",
Type: "text",
Placeholder: "Machine Name",
},
FormElement{
Id: "nfs_chroot",
Name: "path",
Type: "text",
Placeholder: "Chroot",
},
},
}
}
func (this NfsShare) Meta(path string) Metadata {
f, _, err := this.v.Lookup(strings.TrimSuffix(path, "/"))
if err != nil {
return Metadata{}
} else if f == nil {
return Metadata{}
}
fattr, ok := f.(*nfs.Fattr)
if ok == false {
return Metadata{}
}
if fattr == nil { // happen at the root
return Metadata{}
}
var (
r, w bool
perms = fattr.Mode().Perm()
)
if perms&0002 != 0 {
w = true
}
if perms&0004 != 0 {
r = true
}
if fattr.UID == this.uid {
if perms&0400 != 0 {
r = true
}
if perms&0200 != 0 {
w = true
}
}
if (fattr.GID == this.gid) || isIn(fattr.GID, this.gids) {
if perms&0040 != 0 {
r = true
}
if perms&0020 != 0 {
w = true
}
}
return Metadata{
CanSee: NewBool(r),
CanCreateFile: NewBool(w),
CanCreateDirectory: NewBool(w),
CanRename: NewBool(w),
CanMove: NewBool(w),
CanUpload: NewBool(w),
CanDelete: NewBool(w),
}
}
func isIn(id uint32, list []uint32) bool {
for i, _ := range list {
if list[i] == id {
return true
}
}
return false
}
func (this NfsShare) Ls(path string) ([]os.FileInfo, error) {
defer this.Close()
files := make([]os.FileInfo, 0)
dirs, err := this.v.ReadDirPlus(path)
if err != nil {
return files, err
}
for _, dir := range dirs {
if dir.FileName == "." || dir.FileName == ".." {
continue
} else if dir.Attr.Attr.Type != 1 && dir.Attr.Attr.Type != 2 {
// don't show anything else than file and folder
continue
}
if len(this.gids) > 0 { // filter out what users don't have access
hasAccess := false
for _, gid := range this.gids {
if gid == dir.Attr.Attr.GID {
hasAccess = true
}
}
if this.gid == dir.Attr.Attr.GID {
hasAccess = true
}
if hasAccess == false {
continue
}
}
files = append(files, File{
FName: dir.FileName,
FType: func() string {
if dir.Attr.Attr.Type == 1 {
return "file"
}
return "directory"
}(),
FSize: int64(dir.Attr.Attr.Filesize),
FTime: int64(dir.Attr.Attr.Ctime.Seconds),
})
}
return files, nil
}
func (this NfsShare) Cat(path string) (io.ReadCloser, error) {
go func() {
<-this.ctx.Done()
this.Close()
}()
rc, err := this.v.OpenFile(path, 0777)
if err != nil {
return nil, err
}
return rc, nil
}
func (this NfsShare) Mkdir(path string) error {
defer this.Close()
_, err := this.v.Mkdir(this.nfsPath(path), 0775)
return err
}
func (this NfsShare) Rm(path string) error {
defer this.Close()
if strings.HasSuffix(path, "/") {
return this.v.RemoveAll(this.nfsPath(path))
}
return this.v.Remove(path)
}
// this wasn't implemented in the original lib and considering
// PR aren't handled by vmware, we did come with the implementation as
// of RFC1813 in: https://www.rfc-editor.org/rfc/rfc1813#section-3.3.14
func (this NfsShare) Mv(from string, to string) error {
defer this.Close()
f, fName := filepath.Split(this.nfsPath(from))
_, fh, err := this.v.Lookup(f)
if err != nil {
return err
}
t, tName := filepath.Split(this.nfsPath(to))
_, th, err := this.v.Lookup(t)
if err != nil {
return err
}
type RenameArgs struct {
rpc.Header
From nfs.Diropargs3
To nfs.Diropargs3
}
const RENAME3res = 14
res, err := this.v.Call(&RenameArgs{
Header: rpc.Header{
Rpcvers: 2,
Prog: nfs.Nfs3Prog,
Vers: nfs.Nfs3Vers,
Proc: RENAME3res,
Cred: this.auth,
Verf: rpc.AuthNull,
},
From: nfs.Diropargs3{
FH: fh,
Filename: fName,
},
To: nfs.Diropargs3{
FH: th,
Filename: tName,
},
})
if err != nil {
return err
}
status, err := xdr.ReadUint32(res)
if err != nil {
return err
}
return nfs.NFS3Error(status)
}
func (this NfsShare) Touch(path string) error {
return this.Save(path, strings.NewReader(""))
}
func (this NfsShare) Save(path string, file io.Reader) error {
defer this.Close()
w, err := this.v.OpenFile(path, 0644)
if err != nil {
return err
}
_, err = io.Copy(w, file)
w.Close()
return err
}
func (this NfsShare) Close() {
this.v.Close()
this.mount.Close()
}
func (this NfsShare) nfsPath(path string) string {
return strings.TrimSuffix(path, "/")
}