mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-10-27 19:53:41 +08:00
330 lines
6.7 KiB
Go
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, "/")
|
|
}
|