feature (plg_backend_nfs4): poc for nfsv4

This commit is contained in:
MickaelK 2024-01-03 22:44:23 +11:00
parent 86175220cd
commit 2853898f75
12 changed files with 19164 additions and 0 deletions

View file

@ -0,0 +1,164 @@
package plg_backend_nfs4
import (
"context"
"io"
"os"
"strconv"
"strings"
. "github.com/mickael-kerjean/filestash/server/common"
"github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nfs4/repo/nfs4"
)
const DEFAULT_PORT = ":2049"
type Nfs4Share struct {
client *nfs4.NfsClient
ctx context.Context
}
func init() {
Backend.Register("nfs4", Nfs4Share{})
}
func (this Nfs4Share) Init(params map[string]string, app *App) (IBackend, error) {
if params["hostname"] == "" {
return nil, ErrNotFound
}
var (
uid uint32 = 1000
gid uint32 = 1000
)
if params["uid"] != "" {
if _uid, err := strconv.Atoi(params["uid"]); err == nil {
uid = uint32(_uid)
}
}
if params["gid"] != "" {
if _gid, err := strconv.Atoi(params["gid"]); err == nil {
gid = uint32(_gid)
}
}
if params["machine_name"] == "" {
params["machine_name"] = "filestash"
}
if strings.Contains(params["hostname"], ":") == false {
params["hostname"] = params["hostname"] + DEFAULT_PORT
}
client, err := nfs4.NewNfsClient(app.Context, params["hostname"], nfs4.AuthParams{
MachineName: params["machine_name"],
Uid: uid,
Gid: gid,
})
if err != nil {
return nil, err
}
return Nfs4Share{
client,
app.Context,
}, nil
}
func (this Nfs4Share) LoginForm() Form {
return Form{
Elmnts: []FormElement{
FormElement{
Name: "type",
Type: "hidden",
Value: "nfs4",
},
FormElement{
Name: "hostname",
Type: "text",
Placeholder: "Hostname",
},
FormElement{
Name: "advanced",
Type: "enable",
Placeholder: "Advanced",
Target: []string{"nfs_uid", "nfs_gid", "nfs_machinename"},
},
FormElement{
Id: "nfs_uid",
Name: "uid",
Type: "number",
Placeholder: "uid",
},
FormElement{
Id: "nfs_gid",
Name: "gid",
Type: "number",
Placeholder: "gid",
},
FormElement{
Id: "nfs_machinename",
Name: "machine_name",
Type: "text",
Placeholder: "machine name",
},
},
}
}
func (this Nfs4Share) Ls(path string) ([]os.FileInfo, error) {
list, err := this.client.GetFileList(path)
if err != nil {
return nil, err
}
files := make([]os.FileInfo, 0)
for _, info := range list {
files = append(files, File{
FName: info.Name,
FType: func() string {
if info.IsDir {
return "directory"
}
return "file"
}(),
FSize: int64(info.Size),
FTime: int64(info.Mtime.Nanosecond()),
})
}
return files, nil
}
func (this Nfs4Share) Cat(path string) (io.ReadCloser, error) {
_, err := this.client.GetFileInfo(path)
if err != nil {
return nil, err
}
pr, pw := io.Pipe()
go this.client.ReadFileAll(path, pw)
return pr, nil
}
func (this Nfs4Share) Mkdir(path string) error {
return this.client.MakePath(path)
}
func (this Nfs4Share) Rm(path string) error {
if strings.HasSuffix(path, "/") {
return ErrNotImplemented
}
return nfs4.RemoveRecursive(this.client, path)
}
func (this Nfs4Share) Mv(from string, to string) error {
return ErrNotImplemented
}
func (this Nfs4Share) Touch(path string) error {
_, err := this.client.WriteFile(path, false, 0, strings.NewReader(""))
return err
}
func (this Nfs4Share) Save(path string, file io.Reader) error {
_, err := this.client.ReWriteFile(path, file)
return err
}
func (this Nfs4Share) Close() {
this.client.Close()
return
}

View file

@ -0,0 +1 @@
TODO: we can't go get github.com/kha7iq/go-nfs-client => fork over and fix it instead

View file

@ -0,0 +1,30 @@
package internal
type cleanuper struct {
cleanupErr func() error
cleanup func()
}
func NewCleanupErr(cl func() error) *cleanuper {
return &cleanuper{cleanupErr: cl}
}
func NewCleanup(cl func()) *cleanuper {
return &cleanuper{cleanup: cl}
}
func (c *cleanuper) Disarm() {
c.cleanupErr = nil
c.cleanup = nil
}
func (c *cleanuper) Cleanup() {
if c.cleanupErr != nil {
_ = c.cleanupErr()
}
if c.cleanup != nil {
c.cleanup()
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,17 @@
package internal
/*
const OPEN4_SHARE_ACCESS_READ = 0x00000001
const OPEN4_SHARE_ACCESS_WRITE = 0x00000002
const OPEN4_SHARE_ACCESS_BOTH = 0x00000003
const OPEN4_SHARE_DENY_NONE = 0x00000000
const OPEN4_SHARE_DENY_READ = 0x00000001
const OPEN4_SHARE_DENY_WRITE = 0x00000002
const OPEN4_SHARE_DENY_BOTH = 0x00000003
*/

View file

@ -0,0 +1,917 @@
// Code generated by goxdr -B -p nfs4 nfs4/rpc.x; DO NOT EDIT.
package internal
import (
"context"
"fmt"
)
var _ = fmt.Sprintf
var _ context.Context
//
// Data types defined in XDR file
//
type Auth_flavor int32
const (
AUTH_NONE Auth_flavor = 0
AUTH_SYS Auth_flavor = 1
AUTH_SHORT Auth_flavor = 2
AUTH_DH Auth_flavor = 3
)
type Opaque_auth struct {
Flavor Auth_flavor
Body []byte // bound 400
}
type Msg_type int32
const (
CALL Msg_type = 0
REPLY Msg_type = 1
)
/* A reply to a call message can take on two forms: the message was
either accepted or rejected. */
type Reply_stat int32
const (
MSG_ACCEPTED Reply_stat = 0
MSG_DENIED Reply_stat = 1
)
/* Given that a call message was accepted, the following is the status
of an attempt to call a remote procedure. */
type Accept_stat int32
const (
/* RPC executed successfully */
SUCCESS Accept_stat = 0
/* remote hasn't exported program */
PROG_UNAVAIL Accept_stat = 1
/* remote can't support version # */
PROG_MISMATCH Accept_stat = 2
/* program can't support procedure */
PROC_UNAVAIL Accept_stat = 3
/* procedure can't decode params */
GARBAGE_ARGS Accept_stat = 4
/* e.g. memory allocation failure */
SYSTEM_ERR Accept_stat = 5
)
/* Reasons why a call message was rejected: */
type Reject_stat int32
const (
/* RPC version number != 2 */
RPC_MISMATCH Reject_stat = 0
/* remote can't authenticate caller */
AUTH_ERROR Reject_stat = 1
)
/* Why authentication failed: */
type Auth_stat int32
const (
/* success */
AUTH_OK Auth_stat = 0
/* bad credential (seal broken) */
AUTH_BADCRED Auth_stat = 1
/* client must begin new session */
AUTH_REJECTEDCRED Auth_stat = 2
/* bad verifier (seal broken) */
AUTH_BADVERF Auth_stat = 3
/* verifier expired or replayed */
AUTH_REJECTEDVERF Auth_stat = 4
/* rejected for security reasons */
AUTH_TOOWEAK Auth_stat = 5
/* bogus response verifier */
AUTH_INVALIDRESP Auth_stat = 6
/* reason unknown */
AUTH_FAILED Auth_stat = 7
/* kerberos generic error */
AUTH_KERB_GENERIC Auth_stat = 8
/* time of credential expired */
AUTH_TIMEEXPIRE Auth_stat = 9
/* problem with ticket file */
AUTH_TKT_FILE Auth_stat = 10
/* can't decode authenticator */
AUTH_DECODE Auth_stat = 11
/* wrong net address in ticket */
AUTH_NET_ADDR Auth_stat = 12
/* no credentials for user */
RPCSEC_GSS_CREDPROBLEM Auth_stat = 13
/* problem with context */
RPCSEC_GSS_CTXPROBLEM Auth_stat = 14
)
/* Body of an RPC call: */
type Call_body struct {
/* must be equal to two (2) */
Rpcvers uint32
Prog uint32
Vers uint32
Proc uint32
Cred Opaque_auth
Verf Opaque_auth
}
/* Reply to an RPC call that was accepted by the server: */
type Accepted_reply struct {
Verf Opaque_auth
Reply_data XdrAnon_Accepted_reply_Reply_data
}
type XdrAnon_Accepted_reply_Reply_data struct {
// The union discriminant Stat selects among the following arms:
// SUCCESS:
// Results() *[0]byte
// PROG_MISMATCH:
// Mismatch_info() *XdrAnon_Accepted_reply_Reply_data_Mismatch_info
// default:
// void
Stat Accept_stat
U interface{}
}
type XdrAnon_Accepted_reply_Reply_data_Mismatch_info struct {
Low uint32
High uint32
}
/* Reply to an RPC call that was rejected by the server: */
type Rejected_reply struct {
// The union discriminant Stat selects among the following arms:
// RPC_MISMATCH:
// Mismatch_info() *XdrAnon_Rejected_reply_Mismatch_info
// AUTH_ERROR:
// Rj_why() *Auth_stat
Stat Reject_stat
U interface{}
}
type XdrAnon_Rejected_reply_Mismatch_info struct {
Low uint32
High uint32
}
/* Body of a reply to an RPC call: */
type Reply_body struct {
// The union discriminant Stat selects among the following arms:
// MSG_ACCEPTED:
// Areply() *Accepted_reply
// MSG_DENIED:
// Rreply() *Rejected_reply
Stat Reply_stat
U interface{}
}
/* The RPC message: */
type Rpc_msg struct {
Xid uint32
Body XdrAnon_Rpc_msg_Body
}
type XdrAnon_Rpc_msg_Body struct {
// The union discriminant Mtype selects among the following arms:
// CALL:
// Cbody() *Call_body
// REPLY:
// Rbody() *Reply_body
Mtype Msg_type
U interface{}
}
//
// Helper types and generated marshaling functions
//
var _XdrNames_Auth_flavor = map[int32]string{
int32(AUTH_NONE): "AUTH_NONE",
int32(AUTH_SYS): "AUTH_SYS",
int32(AUTH_SHORT): "AUTH_SHORT",
int32(AUTH_DH): "AUTH_DH",
int32(RPCSEC_GSS): "RPCSEC_GSS",
}
var _XdrValues_Auth_flavor = map[string]int32{
"AUTH_NONE": int32(AUTH_NONE),
"AUTH_SYS": int32(AUTH_SYS),
"AUTH_SHORT": int32(AUTH_SHORT),
"AUTH_DH": int32(AUTH_DH),
"RPCSEC_GSS": int32(RPCSEC_GSS),
}
func (Auth_flavor) XdrEnumNames() map[int32]string {
return _XdrNames_Auth_flavor
}
func (v Auth_flavor) String() string {
if s, ok := _XdrNames_Auth_flavor[int32(v)]; ok {
return s
}
return fmt.Sprintf("Auth_flavor#%d", v)
}
func (v *Auth_flavor) Scan(ss fmt.ScanState, _ rune) error {
if tok, err := ss.Token(true, XdrSymChar); err != nil {
return err
} else {
stok := string(tok)
if val, ok := _XdrValues_Auth_flavor[stok]; ok {
*v = Auth_flavor(val)
return nil
} else if stok == "Auth_flavor" {
if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v));
n == 1 && err == nil {
return nil
}
}
return XdrError(fmt.Sprintf("%s is not a valid Auth_flavor.", stok))
}
}
func (v Auth_flavor) GetU32() uint32 { return uint32(v) }
func (v *Auth_flavor) SetU32(n uint32) { *v = Auth_flavor(n) }
func (v *Auth_flavor) XdrPointer() interface{} { return v }
func (Auth_flavor) XdrTypeName() string { return "Auth_flavor" }
func (v Auth_flavor) XdrValue() interface{} { return v }
func (v *Auth_flavor) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
type XdrType_Auth_flavor = *Auth_flavor
func XDR_Auth_flavor(v *Auth_flavor) *Auth_flavor { return v }
type XdrType_Opaque_auth = *Opaque_auth
func (v *Opaque_auth) XdrPointer() interface{} { return v }
func (Opaque_auth) XdrTypeName() string { return "Opaque_auth" }
func (v Opaque_auth) XdrValue() interface{} { return v }
func (v *Opaque_auth) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func (v *Opaque_auth) XdrRecurse(x XDR, name string) {
if name != "" {
name = x.Sprintf("%s.", name)
}
x.Marshal(x.Sprintf("%sflavor", name), XDR_Auth_flavor(&v.Flavor))
x.Marshal(x.Sprintf("%sbody", name), XdrVecOpaque{&v.Body, 400})
}
func XDR_Opaque_auth(v *Opaque_auth) *Opaque_auth { return v }
var _XdrNames_Msg_type = map[int32]string{
int32(CALL): "CALL",
int32(REPLY): "REPLY",
}
var _XdrValues_Msg_type = map[string]int32{
"CALL": int32(CALL),
"REPLY": int32(REPLY),
}
func (Msg_type) XdrEnumNames() map[int32]string {
return _XdrNames_Msg_type
}
func (v Msg_type) String() string {
if s, ok := _XdrNames_Msg_type[int32(v)]; ok {
return s
}
return fmt.Sprintf("Msg_type#%d", v)
}
func (v *Msg_type) Scan(ss fmt.ScanState, _ rune) error {
if tok, err := ss.Token(true, XdrSymChar); err != nil {
return err
} else {
stok := string(tok)
if val, ok := _XdrValues_Msg_type[stok]; ok {
*v = Msg_type(val)
return nil
} else if stok == "Msg_type" {
if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v));
n == 1 && err == nil {
return nil
}
}
return XdrError(fmt.Sprintf("%s is not a valid Msg_type.", stok))
}
}
func (v Msg_type) GetU32() uint32 { return uint32(v) }
func (v *Msg_type) SetU32(n uint32) { *v = Msg_type(n) }
func (v *Msg_type) XdrPointer() interface{} { return v }
func (Msg_type) XdrTypeName() string { return "Msg_type" }
func (v Msg_type) XdrValue() interface{} { return v }
func (v *Msg_type) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
type XdrType_Msg_type = *Msg_type
func XDR_Msg_type(v *Msg_type) *Msg_type { return v }
var _XdrNames_Reply_stat = map[int32]string{
int32(MSG_ACCEPTED): "MSG_ACCEPTED",
int32(MSG_DENIED): "MSG_DENIED",
}
var _XdrValues_Reply_stat = map[string]int32{
"MSG_ACCEPTED": int32(MSG_ACCEPTED),
"MSG_DENIED": int32(MSG_DENIED),
}
func (Reply_stat) XdrEnumNames() map[int32]string {
return _XdrNames_Reply_stat
}
func (v Reply_stat) String() string {
if s, ok := _XdrNames_Reply_stat[int32(v)]; ok {
return s
}
return fmt.Sprintf("Reply_stat#%d", v)
}
func (v *Reply_stat) Scan(ss fmt.ScanState, _ rune) error {
if tok, err := ss.Token(true, XdrSymChar); err != nil {
return err
} else {
stok := string(tok)
if val, ok := _XdrValues_Reply_stat[stok]; ok {
*v = Reply_stat(val)
return nil
} else if stok == "Reply_stat" {
if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v));
n == 1 && err == nil {
return nil
}
}
return XdrError(fmt.Sprintf("%s is not a valid Reply_stat.", stok))
}
}
func (v Reply_stat) GetU32() uint32 { return uint32(v) }
func (v *Reply_stat) SetU32(n uint32) { *v = Reply_stat(n) }
func (v *Reply_stat) XdrPointer() interface{} { return v }
func (Reply_stat) XdrTypeName() string { return "Reply_stat" }
func (v Reply_stat) XdrValue() interface{} { return v }
func (v *Reply_stat) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
type XdrType_Reply_stat = *Reply_stat
func XDR_Reply_stat(v *Reply_stat) *Reply_stat { return v }
var _XdrNames_Accept_stat = map[int32]string{
int32(SUCCESS): "SUCCESS",
int32(PROG_UNAVAIL): "PROG_UNAVAIL",
int32(PROG_MISMATCH): "PROG_MISMATCH",
int32(PROC_UNAVAIL): "PROC_UNAVAIL",
int32(GARBAGE_ARGS): "GARBAGE_ARGS",
int32(SYSTEM_ERR): "SYSTEM_ERR",
}
var _XdrValues_Accept_stat = map[string]int32{
"SUCCESS": int32(SUCCESS),
"PROG_UNAVAIL": int32(PROG_UNAVAIL),
"PROG_MISMATCH": int32(PROG_MISMATCH),
"PROC_UNAVAIL": int32(PROC_UNAVAIL),
"GARBAGE_ARGS": int32(GARBAGE_ARGS),
"SYSTEM_ERR": int32(SYSTEM_ERR),
}
func (Accept_stat) XdrEnumNames() map[int32]string {
return _XdrNames_Accept_stat
}
func (v Accept_stat) String() string {
if s, ok := _XdrNames_Accept_stat[int32(v)]; ok {
return s
}
return fmt.Sprintf("Accept_stat#%d", v)
}
func (v *Accept_stat) Scan(ss fmt.ScanState, _ rune) error {
if tok, err := ss.Token(true, XdrSymChar); err != nil {
return err
} else {
stok := string(tok)
if val, ok := _XdrValues_Accept_stat[stok]; ok {
*v = Accept_stat(val)
return nil
} else if stok == "Accept_stat" {
if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v));
n == 1 && err == nil {
return nil
}
}
return XdrError(fmt.Sprintf("%s is not a valid Accept_stat.", stok))
}
}
func (v Accept_stat) GetU32() uint32 { return uint32(v) }
func (v *Accept_stat) SetU32(n uint32) { *v = Accept_stat(n) }
func (v *Accept_stat) XdrPointer() interface{} { return v }
func (Accept_stat) XdrTypeName() string { return "Accept_stat" }
func (v Accept_stat) XdrValue() interface{} { return v }
func (v *Accept_stat) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
type XdrType_Accept_stat = *Accept_stat
func XDR_Accept_stat(v *Accept_stat) *Accept_stat { return v }
var _XdrNames_Reject_stat = map[int32]string{
int32(RPC_MISMATCH): "RPC_MISMATCH",
int32(AUTH_ERROR): "AUTH_ERROR",
}
var _XdrValues_Reject_stat = map[string]int32{
"RPC_MISMATCH": int32(RPC_MISMATCH),
"AUTH_ERROR": int32(AUTH_ERROR),
}
func (Reject_stat) XdrEnumNames() map[int32]string {
return _XdrNames_Reject_stat
}
func (v Reject_stat) String() string {
if s, ok := _XdrNames_Reject_stat[int32(v)]; ok {
return s
}
return fmt.Sprintf("Reject_stat#%d", v)
}
func (v *Reject_stat) Scan(ss fmt.ScanState, _ rune) error {
if tok, err := ss.Token(true, XdrSymChar); err != nil {
return err
} else {
stok := string(tok)
if val, ok := _XdrValues_Reject_stat[stok]; ok {
*v = Reject_stat(val)
return nil
} else if stok == "Reject_stat" {
if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v));
n == 1 && err == nil {
return nil
}
}
return XdrError(fmt.Sprintf("%s is not a valid Reject_stat.", stok))
}
}
func (v Reject_stat) GetU32() uint32 { return uint32(v) }
func (v *Reject_stat) SetU32(n uint32) { *v = Reject_stat(n) }
func (v *Reject_stat) XdrPointer() interface{} { return v }
func (Reject_stat) XdrTypeName() string { return "Reject_stat" }
func (v Reject_stat) XdrValue() interface{} { return v }
func (v *Reject_stat) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
type XdrType_Reject_stat = *Reject_stat
func XDR_Reject_stat(v *Reject_stat) *Reject_stat { return v }
var _XdrNames_Auth_stat = map[int32]string{
int32(AUTH_OK): "AUTH_OK",
int32(AUTH_BADCRED): "AUTH_BADCRED",
int32(AUTH_REJECTEDCRED): "AUTH_REJECTEDCRED",
int32(AUTH_BADVERF): "AUTH_BADVERF",
int32(AUTH_REJECTEDVERF): "AUTH_REJECTEDVERF",
int32(AUTH_TOOWEAK): "AUTH_TOOWEAK",
int32(AUTH_INVALIDRESP): "AUTH_INVALIDRESP",
int32(AUTH_FAILED): "AUTH_FAILED",
int32(AUTH_KERB_GENERIC): "AUTH_KERB_GENERIC",
int32(AUTH_TIMEEXPIRE): "AUTH_TIMEEXPIRE",
int32(AUTH_TKT_FILE): "AUTH_TKT_FILE",
int32(AUTH_DECODE): "AUTH_DECODE",
int32(AUTH_NET_ADDR): "AUTH_NET_ADDR",
int32(RPCSEC_GSS_CREDPROBLEM): "RPCSEC_GSS_CREDPROBLEM",
int32(RPCSEC_GSS_CTXPROBLEM): "RPCSEC_GSS_CTXPROBLEM",
}
var _XdrValues_Auth_stat = map[string]int32{
"AUTH_OK": int32(AUTH_OK),
"AUTH_BADCRED": int32(AUTH_BADCRED),
"AUTH_REJECTEDCRED": int32(AUTH_REJECTEDCRED),
"AUTH_BADVERF": int32(AUTH_BADVERF),
"AUTH_REJECTEDVERF": int32(AUTH_REJECTEDVERF),
"AUTH_TOOWEAK": int32(AUTH_TOOWEAK),
"AUTH_INVALIDRESP": int32(AUTH_INVALIDRESP),
"AUTH_FAILED": int32(AUTH_FAILED),
"AUTH_KERB_GENERIC": int32(AUTH_KERB_GENERIC),
"AUTH_TIMEEXPIRE": int32(AUTH_TIMEEXPIRE),
"AUTH_TKT_FILE": int32(AUTH_TKT_FILE),
"AUTH_DECODE": int32(AUTH_DECODE),
"AUTH_NET_ADDR": int32(AUTH_NET_ADDR),
"RPCSEC_GSS_CREDPROBLEM": int32(RPCSEC_GSS_CREDPROBLEM),
"RPCSEC_GSS_CTXPROBLEM": int32(RPCSEC_GSS_CTXPROBLEM),
}
func (Auth_stat) XdrEnumNames() map[int32]string {
return _XdrNames_Auth_stat
}
func (v Auth_stat) String() string {
if s, ok := _XdrNames_Auth_stat[int32(v)]; ok {
return s
}
return fmt.Sprintf("Auth_stat#%d", v)
}
func (v *Auth_stat) Scan(ss fmt.ScanState, _ rune) error {
if tok, err := ss.Token(true, XdrSymChar); err != nil {
return err
} else {
stok := string(tok)
if val, ok := _XdrValues_Auth_stat[stok]; ok {
*v = Auth_stat(val)
return nil
} else if stok == "Auth_stat" {
if n, err := fmt.Fscanf(ss, "#%d", (*int32)(v));
n == 1 && err == nil {
return nil
}
}
return XdrError(fmt.Sprintf("%s is not a valid Auth_stat.", stok))
}
}
func (v Auth_stat) GetU32() uint32 { return uint32(v) }
func (v *Auth_stat) SetU32(n uint32) { *v = Auth_stat(n) }
func (v *Auth_stat) XdrPointer() interface{} { return v }
func (Auth_stat) XdrTypeName() string { return "Auth_stat" }
func (v Auth_stat) XdrValue() interface{} { return v }
func (v *Auth_stat) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
type XdrType_Auth_stat = *Auth_stat
func XDR_Auth_stat(v *Auth_stat) *Auth_stat { return v }
type XdrType_Call_body = *Call_body
func (v *Call_body) XdrPointer() interface{} { return v }
func (Call_body) XdrTypeName() string { return "Call_body" }
func (v Call_body) XdrValue() interface{} { return v }
func (v *Call_body) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func (v *Call_body) XdrRecurse(x XDR, name string) {
if name != "" {
name = x.Sprintf("%s.", name)
}
x.Marshal(x.Sprintf("%srpcvers", name), XDR_uint32(&v.Rpcvers))
x.Marshal(x.Sprintf("%sprog", name), XDR_uint32(&v.Prog))
x.Marshal(x.Sprintf("%svers", name), XDR_uint32(&v.Vers))
x.Marshal(x.Sprintf("%sproc", name), XDR_uint32(&v.Proc))
x.Marshal(x.Sprintf("%scred", name), XDR_Opaque_auth(&v.Cred))
x.Marshal(x.Sprintf("%sverf", name), XDR_Opaque_auth(&v.Verf))
}
func XDR_Call_body(v *Call_body) *Call_body { return v }
type XdrType_XdrAnon_Accepted_reply_Reply_data_Mismatch_info = *XdrAnon_Accepted_reply_Reply_data_Mismatch_info
func (v *XdrAnon_Accepted_reply_Reply_data_Mismatch_info) XdrPointer() interface{} { return v }
func (XdrAnon_Accepted_reply_Reply_data_Mismatch_info) XdrTypeName() string { return "XdrAnon_Accepted_reply_Reply_data_Mismatch_info" }
func (v XdrAnon_Accepted_reply_Reply_data_Mismatch_info) XdrValue() interface{} { return v }
func (v *XdrAnon_Accepted_reply_Reply_data_Mismatch_info) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func (v *XdrAnon_Accepted_reply_Reply_data_Mismatch_info) XdrRecurse(x XDR, name string) {
if name != "" {
name = x.Sprintf("%s.", name)
}
x.Marshal(x.Sprintf("%slow", name), XDR_uint32(&v.Low))
x.Marshal(x.Sprintf("%shigh", name), XDR_uint32(&v.High))
}
func XDR_XdrAnon_Accepted_reply_Reply_data_Mismatch_info(v *XdrAnon_Accepted_reply_Reply_data_Mismatch_info) *XdrAnon_Accepted_reply_Reply_data_Mismatch_info { return v }
type _XdrArray_0_opaque [0]byte
func (v *_XdrArray_0_opaque) GetByteSlice() []byte { return v[:] }
func (v *_XdrArray_0_opaque) XdrTypeName() string { return "opaque[]" }
func (v *_XdrArray_0_opaque) XdrValue() interface{} { return v[:] }
func (v *_XdrArray_0_opaque) XdrPointer() interface{} { return (*[0]byte)(v) }
func (v *_XdrArray_0_opaque) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func (v *_XdrArray_0_opaque) String() string { return fmt.Sprintf("%x", v[:]) }
func (v *_XdrArray_0_opaque) Scan(ss fmt.ScanState, c rune) error {
return XdrArrayOpaqueScan(v[:], ss, c)
}
func (_XdrArray_0_opaque) XdrArraySize() uint32 {
const bound uint32 = 0 // Force error if not const or doesn't fit
return bound
}
func (u *XdrAnon_Accepted_reply_Reply_data) Results() *[0]byte {
switch u.Stat {
case SUCCESS:
if v, ok := u.U.(*[0]byte); ok {
return v
} else {
var zero [0]byte
u.U = &zero
return &zero
}
default:
XdrPanic("XdrAnon_Accepted_reply_Reply_data.Results accessed when Stat == %v", u.Stat)
return nil
}
}
func (u *XdrAnon_Accepted_reply_Reply_data) Mismatch_info() *XdrAnon_Accepted_reply_Reply_data_Mismatch_info {
switch u.Stat {
case PROG_MISMATCH:
if v, ok := u.U.(*XdrAnon_Accepted_reply_Reply_data_Mismatch_info); ok {
return v
} else {
var zero XdrAnon_Accepted_reply_Reply_data_Mismatch_info
u.U = &zero
return &zero
}
default:
XdrPanic("XdrAnon_Accepted_reply_Reply_data.Mismatch_info accessed when Stat == %v", u.Stat)
return nil
}
}
func (u XdrAnon_Accepted_reply_Reply_data) XdrValid() bool {
return true
}
func (u *XdrAnon_Accepted_reply_Reply_data) XdrUnionTag() XdrNum32 {
return XDR_Accept_stat(&u.Stat)
}
func (u *XdrAnon_Accepted_reply_Reply_data) XdrUnionTagName() string {
return "Stat"
}
func (u *XdrAnon_Accepted_reply_Reply_data) XdrUnionBody() XdrType {
switch u.Stat {
case SUCCESS:
return (*_XdrArray_0_opaque)(u.Results())
case PROG_MISMATCH:
return XDR_XdrAnon_Accepted_reply_Reply_data_Mismatch_info(u.Mismatch_info())
default:
return nil
}
}
func (u *XdrAnon_Accepted_reply_Reply_data) XdrUnionBodyName() string {
switch u.Stat {
case SUCCESS:
return "Results"
case PROG_MISMATCH:
return "Mismatch_info"
default:
return ""
}
}
type XdrType_XdrAnon_Accepted_reply_Reply_data = *XdrAnon_Accepted_reply_Reply_data
func (v *XdrAnon_Accepted_reply_Reply_data) XdrPointer() interface{} { return v }
func (XdrAnon_Accepted_reply_Reply_data) XdrTypeName() string { return "XdrAnon_Accepted_reply_Reply_data" }
func (v XdrAnon_Accepted_reply_Reply_data) XdrValue() interface{} { return v }
func (v *XdrAnon_Accepted_reply_Reply_data) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func (u *XdrAnon_Accepted_reply_Reply_data) XdrRecurse(x XDR, name string) {
if name != "" {
name = x.Sprintf("%s.", name)
}
XDR_Accept_stat(&u.Stat).XdrMarshal(x, x.Sprintf("%sstat", name))
switch u.Stat {
case SUCCESS:
x.Marshal(x.Sprintf("%sresults", name), (*_XdrArray_0_opaque)(u.Results()))
return
case PROG_MISMATCH:
x.Marshal(x.Sprintf("%smismatch_info", name), XDR_XdrAnon_Accepted_reply_Reply_data_Mismatch_info(u.Mismatch_info()))
return
default:
return
}
}
func XDR_XdrAnon_Accepted_reply_Reply_data(v *XdrAnon_Accepted_reply_Reply_data) *XdrAnon_Accepted_reply_Reply_data { return v}
type XdrType_Accepted_reply = *Accepted_reply
func (v *Accepted_reply) XdrPointer() interface{} { return v }
func (Accepted_reply) XdrTypeName() string { return "Accepted_reply" }
func (v Accepted_reply) XdrValue() interface{} { return v }
func (v *Accepted_reply) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func (v *Accepted_reply) XdrRecurse(x XDR, name string) {
if name != "" {
name = x.Sprintf("%s.", name)
}
x.Marshal(x.Sprintf("%sverf", name), XDR_Opaque_auth(&v.Verf))
x.Marshal(x.Sprintf("%sreply_data", name), XDR_XdrAnon_Accepted_reply_Reply_data(&v.Reply_data))
}
func XDR_Accepted_reply(v *Accepted_reply) *Accepted_reply { return v }
type XdrType_XdrAnon_Rejected_reply_Mismatch_info = *XdrAnon_Rejected_reply_Mismatch_info
func (v *XdrAnon_Rejected_reply_Mismatch_info) XdrPointer() interface{} { return v }
func (XdrAnon_Rejected_reply_Mismatch_info) XdrTypeName() string { return "XdrAnon_Rejected_reply_Mismatch_info" }
func (v XdrAnon_Rejected_reply_Mismatch_info) XdrValue() interface{} { return v }
func (v *XdrAnon_Rejected_reply_Mismatch_info) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func (v *XdrAnon_Rejected_reply_Mismatch_info) XdrRecurse(x XDR, name string) {
if name != "" {
name = x.Sprintf("%s.", name)
}
x.Marshal(x.Sprintf("%slow", name), XDR_uint32(&v.Low))
x.Marshal(x.Sprintf("%shigh", name), XDR_uint32(&v.High))
}
func XDR_XdrAnon_Rejected_reply_Mismatch_info(v *XdrAnon_Rejected_reply_Mismatch_info) *XdrAnon_Rejected_reply_Mismatch_info { return v }
var _XdrTags_Rejected_reply = map[int32]bool{
XdrToI32(RPC_MISMATCH): true,
XdrToI32(AUTH_ERROR): true,
}
func (_ Rejected_reply) XdrValidTags() map[int32]bool {
return _XdrTags_Rejected_reply
}
func (u *Rejected_reply) Mismatch_info() *XdrAnon_Rejected_reply_Mismatch_info {
switch u.Stat {
case RPC_MISMATCH:
if v, ok := u.U.(*XdrAnon_Rejected_reply_Mismatch_info); ok {
return v
} else {
var zero XdrAnon_Rejected_reply_Mismatch_info
u.U = &zero
return &zero
}
default:
XdrPanic("Rejected_reply.Mismatch_info accessed when Stat == %v", u.Stat)
return nil
}
}
func (u *Rejected_reply) Rj_why() *Auth_stat {
switch u.Stat {
case AUTH_ERROR:
if v, ok := u.U.(*Auth_stat); ok {
return v
} else {
var zero Auth_stat
u.U = &zero
return &zero
}
default:
XdrPanic("Rejected_reply.Rj_why accessed when Stat == %v", u.Stat)
return nil
}
}
func (u Rejected_reply) XdrValid() bool {
switch u.Stat {
case RPC_MISMATCH,AUTH_ERROR:
return true
}
return false
}
func (u *Rejected_reply) XdrUnionTag() XdrNum32 {
return XDR_Reject_stat(&u.Stat)
}
func (u *Rejected_reply) XdrUnionTagName() string {
return "Stat"
}
func (u *Rejected_reply) XdrUnionBody() XdrType {
switch u.Stat {
case RPC_MISMATCH:
return XDR_XdrAnon_Rejected_reply_Mismatch_info(u.Mismatch_info())
case AUTH_ERROR:
return XDR_Auth_stat(u.Rj_why())
}
return nil
}
func (u *Rejected_reply) XdrUnionBodyName() string {
switch u.Stat {
case RPC_MISMATCH:
return "Mismatch_info"
case AUTH_ERROR:
return "Rj_why"
}
return ""
}
type XdrType_Rejected_reply = *Rejected_reply
func (v *Rejected_reply) XdrPointer() interface{} { return v }
func (Rejected_reply) XdrTypeName() string { return "Rejected_reply" }
func (v Rejected_reply) XdrValue() interface{} { return v }
func (v *Rejected_reply) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func (u *Rejected_reply) XdrRecurse(x XDR, name string) {
if name != "" {
name = x.Sprintf("%s.", name)
}
XDR_Reject_stat(&u.Stat).XdrMarshal(x, x.Sprintf("%sstat", name))
switch u.Stat {
case RPC_MISMATCH:
x.Marshal(x.Sprintf("%smismatch_info", name), XDR_XdrAnon_Rejected_reply_Mismatch_info(u.Mismatch_info()))
return
case AUTH_ERROR:
x.Marshal(x.Sprintf("%srj_why", name), XDR_Auth_stat(u.Rj_why()))
return
}
XdrPanic("invalid Stat (%v) in Rejected_reply", u.Stat)
}
func XDR_Rejected_reply(v *Rejected_reply) *Rejected_reply { return v}
var _XdrTags_Reply_body = map[int32]bool{
XdrToI32(MSG_ACCEPTED): true,
XdrToI32(MSG_DENIED): true,
}
func (_ Reply_body) XdrValidTags() map[int32]bool {
return _XdrTags_Reply_body
}
func (u *Reply_body) Areply() *Accepted_reply {
switch u.Stat {
case MSG_ACCEPTED:
if v, ok := u.U.(*Accepted_reply); ok {
return v
} else {
var zero Accepted_reply
u.U = &zero
return &zero
}
default:
XdrPanic("Reply_body.Areply accessed when Stat == %v", u.Stat)
return nil
}
}
func (u *Reply_body) Rreply() *Rejected_reply {
switch u.Stat {
case MSG_DENIED:
if v, ok := u.U.(*Rejected_reply); ok {
return v
} else {
var zero Rejected_reply
u.U = &zero
return &zero
}
default:
XdrPanic("Reply_body.Rreply accessed when Stat == %v", u.Stat)
return nil
}
}
func (u Reply_body) XdrValid() bool {
switch u.Stat {
case MSG_ACCEPTED,MSG_DENIED:
return true
}
return false
}
func (u *Reply_body) XdrUnionTag() XdrNum32 {
return XDR_Reply_stat(&u.Stat)
}
func (u *Reply_body) XdrUnionTagName() string {
return "Stat"
}
func (u *Reply_body) XdrUnionBody() XdrType {
switch u.Stat {
case MSG_ACCEPTED:
return XDR_Accepted_reply(u.Areply())
case MSG_DENIED:
return XDR_Rejected_reply(u.Rreply())
}
return nil
}
func (u *Reply_body) XdrUnionBodyName() string {
switch u.Stat {
case MSG_ACCEPTED:
return "Areply"
case MSG_DENIED:
return "Rreply"
}
return ""
}
type XdrType_Reply_body = *Reply_body
func (v *Reply_body) XdrPointer() interface{} { return v }
func (Reply_body) XdrTypeName() string { return "Reply_body" }
func (v Reply_body) XdrValue() interface{} { return v }
func (v *Reply_body) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func (u *Reply_body) XdrRecurse(x XDR, name string) {
if name != "" {
name = x.Sprintf("%s.", name)
}
XDR_Reply_stat(&u.Stat).XdrMarshal(x, x.Sprintf("%sstat", name))
switch u.Stat {
case MSG_ACCEPTED:
x.Marshal(x.Sprintf("%sareply", name), XDR_Accepted_reply(u.Areply()))
return
case MSG_DENIED:
x.Marshal(x.Sprintf("%srreply", name), XDR_Rejected_reply(u.Rreply()))
return
}
XdrPanic("invalid Stat (%v) in Reply_body", u.Stat)
}
func XDR_Reply_body(v *Reply_body) *Reply_body { return v}
var _XdrTags_XdrAnon_Rpc_msg_Body = map[int32]bool{
XdrToI32(CALL): true,
XdrToI32(REPLY): true,
}
func (_ XdrAnon_Rpc_msg_Body) XdrValidTags() map[int32]bool {
return _XdrTags_XdrAnon_Rpc_msg_Body
}
func (u *XdrAnon_Rpc_msg_Body) Cbody() *Call_body {
switch u.Mtype {
case CALL:
if v, ok := u.U.(*Call_body); ok {
return v
} else {
var zero Call_body
u.U = &zero
return &zero
}
default:
XdrPanic("XdrAnon_Rpc_msg_Body.Cbody accessed when Mtype == %v", u.Mtype)
return nil
}
}
func (u *XdrAnon_Rpc_msg_Body) Rbody() *Reply_body {
switch u.Mtype {
case REPLY:
if v, ok := u.U.(*Reply_body); ok {
return v
} else {
var zero Reply_body
u.U = &zero
return &zero
}
default:
XdrPanic("XdrAnon_Rpc_msg_Body.Rbody accessed when Mtype == %v", u.Mtype)
return nil
}
}
func (u XdrAnon_Rpc_msg_Body) XdrValid() bool {
switch u.Mtype {
case CALL,REPLY:
return true
}
return false
}
func (u *XdrAnon_Rpc_msg_Body) XdrUnionTag() XdrNum32 {
return XDR_Msg_type(&u.Mtype)
}
func (u *XdrAnon_Rpc_msg_Body) XdrUnionTagName() string {
return "Mtype"
}
func (u *XdrAnon_Rpc_msg_Body) XdrUnionBody() XdrType {
switch u.Mtype {
case CALL:
return XDR_Call_body(u.Cbody())
case REPLY:
return XDR_Reply_body(u.Rbody())
}
return nil
}
func (u *XdrAnon_Rpc_msg_Body) XdrUnionBodyName() string {
switch u.Mtype {
case CALL:
return "Cbody"
case REPLY:
return "Rbody"
}
return ""
}
type XdrType_XdrAnon_Rpc_msg_Body = *XdrAnon_Rpc_msg_Body
func (v *XdrAnon_Rpc_msg_Body) XdrPointer() interface{} { return v }
func (XdrAnon_Rpc_msg_Body) XdrTypeName() string { return "XdrAnon_Rpc_msg_Body" }
func (v XdrAnon_Rpc_msg_Body) XdrValue() interface{} { return v }
func (v *XdrAnon_Rpc_msg_Body) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func (u *XdrAnon_Rpc_msg_Body) XdrRecurse(x XDR, name string) {
if name != "" {
name = x.Sprintf("%s.", name)
}
XDR_Msg_type(&u.Mtype).XdrMarshal(x, x.Sprintf("%smtype", name))
switch u.Mtype {
case CALL:
x.Marshal(x.Sprintf("%scbody", name), XDR_Call_body(u.Cbody()))
return
case REPLY:
x.Marshal(x.Sprintf("%srbody", name), XDR_Reply_body(u.Rbody()))
return
}
XdrPanic("invalid Mtype (%v) in XdrAnon_Rpc_msg_Body", u.Mtype)
}
func XDR_XdrAnon_Rpc_msg_Body(v *XdrAnon_Rpc_msg_Body) *XdrAnon_Rpc_msg_Body { return v}
type XdrType_Rpc_msg = *Rpc_msg
func (v *Rpc_msg) XdrPointer() interface{} { return v }
func (Rpc_msg) XdrTypeName() string { return "Rpc_msg" }
func (v Rpc_msg) XdrValue() interface{} { return v }
func (v *Rpc_msg) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func (v *Rpc_msg) XdrRecurse(x XDR, name string) {
if name != "" {
name = x.Sprintf("%s.", name)
}
x.Marshal(x.Sprintf("%sxid", name), XDR_uint32(&v.Xid))
x.Marshal(x.Sprintf("%sbody", name), XDR_XdrAnon_Rpc_msg_Body(&v.Body))
}
func XDR_Rpc_msg(v *Rpc_msg) *Rpc_msg { return v }

View file

@ -0,0 +1,138 @@
/* RPC message format as defined by RFC 5531 */
enum auth_flavor {
AUTH_NONE = 0,
AUTH_SYS = 1,
AUTH_SHORT = 2,
AUTH_DH = 3,
RPCSEC_GSS = 6
};
struct opaque_auth {
auth_flavor flavor;
opaque body<400>;
};
enum msg_type {
CALL = 0,
REPLY = 1
};
/* A reply to a call message can take on two forms: the message was
either accepted or rejected. */
enum reply_stat {
MSG_ACCEPTED = 0,
MSG_DENIED = 1
};
/* Given that a call message was accepted, the following is the status
of an attempt to call a remote procedure. */
enum accept_stat {
SUCCESS = 0, /* RPC executed successfully */
PROG_UNAVAIL = 1, /* remote hasn't exported program */
PROG_MISMATCH = 2, /* remote can't support version # */
PROC_UNAVAIL = 3, /* program can't support procedure */
GARBAGE_ARGS = 4, /* procedure can't decode params */
SYSTEM_ERR = 5 /* e.g. memory allocation failure */
};
/* Reasons why a call message was rejected: */
enum reject_stat {
RPC_MISMATCH = 0, /* RPC version number != 2 */
AUTH_ERROR = 1 /* remote can't authenticate caller */
};
/* Why authentication failed: */
enum auth_stat {
AUTH_OK = 0, /* success */
/*
* failed at remote end
*/
AUTH_BADCRED = 1, /* bad credential (seal broken) */
AUTH_REJECTEDCRED = 2, /* client must begin new session */
AUTH_BADVERF = 3, /* bad verifier (seal broken) */
AUTH_REJECTEDVERF = 4, /* verifier expired or replayed */
AUTH_TOOWEAK = 5, /* rejected for security reasons */
/*
* failed locally
*/
AUTH_INVALIDRESP = 6, /* bogus response verifier */
AUTH_FAILED = 7, /* reason unknown */
/*
* AUTH_KERB errors; deprecated. See [RFC2695]
*/
AUTH_KERB_GENERIC = 8, /* kerberos generic error */
AUTH_TIMEEXPIRE = 9, /* time of credential expired */
AUTH_TKT_FILE = 10, /* problem with ticket file */
AUTH_DECODE = 11, /* can't decode authenticator */
AUTH_NET_ADDR = 12, /* wrong net address in ticket */
/*
* RPCSEC_GSS GSS related errors
*/
RPCSEC_GSS_CREDPROBLEM = 13, /* no credentials for user */
RPCSEC_GSS_CTXPROBLEM = 14 /* problem with context */
};
/* Body of an RPC call: */
struct call_body {
unsigned int rpcvers; /* must be equal to two (2) */
unsigned int prog;
unsigned int vers;
unsigned int proc;
opaque_auth cred;
opaque_auth verf;
/* procedure-specific parameters start here */
};
/* Reply to an RPC call that was accepted by the server: */
struct accepted_reply {
opaque_auth verf;
union switch (accept_stat stat) {
case SUCCESS:
opaque results[0];
/*
* procedure-specific results start here
*/
case PROG_MISMATCH:
struct {
unsigned int low;
unsigned int high;
} mismatch_info;
default:
/*
* Void. Cases include PROG_UNAVAIL, PROC_UNAVAIL,
* GARBAGE_ARGS, and SYSTEM_ERR.
*/
void;
} reply_data;
};
/* Reply to an RPC call that was rejected by the server: */
union rejected_reply switch (reject_stat stat) {
case RPC_MISMATCH:
struct {
unsigned int low;
unsigned int high;
} mismatch_info;
case AUTH_ERROR:
auth_stat rj_why;
};
/* Body of a reply to an RPC call: */
union reply_body switch (reply_stat stat) {
case MSG_ACCEPTED:
accepted_reply areply;
case MSG_DENIED:
rejected_reply rreply;
};
/* The RPC message: */
struct rpc_msg {
unsigned int xid;
union switch (msg_type mtype) {
case CALL:
call_body cbody;
case REPLY:
reply_body rbody;
} body;
};

View file

@ -0,0 +1,28 @@
package internal
//goland:noinspection GoSnakeCaseUsage
type Uint32_t = uint32
//goland:noinspection GoSnakeCaseUsage
type Uint64_t = uint64
//goland:noinspection GoSnakeCaseUsage
type Int64_t = int64
//goland:noinspection GoSnakeCaseUsage
type XDR_Uint32_t = *XdrUint32
//goland:noinspection GoSnakeCaseUsage
type XdrType_Uint32_t = XdrType_uint32
//goland:noinspection GoSnakeCaseUsage
type XDR_Uint64_t = *XdrUint64
//goland:noinspection GoSnakeCaseUsage
type XdrType_Uint64_t = XdrType_uint64
//goland:noinspection GoSnakeCaseUsage
type XdrType_Int64_t = XdrType_int64
//goland:noinspection GoSnakeCaseUsage
type XDR_Int64_t = *XdrInt64
func MinUint64(i1, i2 uint64) uint64 {
if i1 < i2 {
return i1
}
return i2
}

View file

@ -0,0 +1,903 @@
package nfs4
import (
"bytes"
"context"
"crypto/rand"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"math"
"net"
"os"
"strings"
"time"
. "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nfs4/repo/internal"
)
const NfsReadBlockLen = 512 * 1024
var standardNfsAttrs = Bitmap4{
1<<FATTR4_TYPE | 1<<FATTR4_SIZE,
1 << (FATTR4_TIME_MODIFY - 32),
}
type NfsInterface interface {
Ping() error
Close()
GetFileList(path string) ([]FileInfo, error)
GetFileInfo(path string) (FileInfo, error)
ReadFileAll(path string, writer io.Writer) (uint64, error)
ReadFile(path string, offset, count uint64, writer io.Writer) (uint64, error)
ReWriteFile(path string, reader io.Reader) (written uint64, err error)
WriteFile(path string, truncate bool, offset uint64, reader io.Reader) (written uint64, err error)
DeleteFile(path string) error
MakePath(path string) error
}
type NfsClient struct {
conn net.Conn
xid uint32
nfsSeqId uint32
authType Auth_flavor
authData []byte
clientId string
clientIdShort uint64
openConfirmed bool
rootFh Nfs_fh4
}
var _ NfsInterface = &NfsClient{}
type AuthParams struct {
Uid, Gid uint32
MachineName string
}
type FileInfo struct {
Name string
IsDir bool
Size uint64
Mtime time.Time
}
// Create the NFS client with the specified parameters. The `server` string must include port.
// The default NFS port is 2049. E.g.: "127.0.0.1:2049".
// `auth` should contain a fairly unique MachineId to make sure clients can be distinguished.
func NewNfsClient(ctx context.Context, server string, auth AuthParams) (*NfsClient, error) {
d := net.Dialer{}
conn, err := d.DialContext(ctx, "tcp", server)
if err != nil {
return nil, err
}
return NewNfsClientWithConn(conn, auth)
}
func NewNfsClientWithConn(conn net.Conn, auth AuthParams) (*NfsClient, error) {
clientId, err := os.Hostname()
if err != nil {
return nil, err
}
randId := make([]byte, 8)
_, _ = rand.Read(randId)
clientId += "-" + hex.EncodeToString(randId)
cli := &NfsClient{
conn: conn,
authType: AUTH_SYS,
authData: makeAuthData(auth),
nfsSeqId: 0,
clientId: clientId,
}
cl := NewCleanup(cli.Close)
defer cl.Cleanup()
err = cli.Ping()
if err != nil {
return nil, err
}
err = cli.setClientId()
if err != nil {
return nil, err
}
err = cli.retrieveRootFh()
if err != nil {
return nil, err
}
cl.Disarm()
return cli, nil
}
func (c *NfsClient) Close() {
_ = c.conn.Close()
}
func makeAuthData(auth AuthParams) []byte {
machName := auth.MachineName
if len(machName) > 255 {
machName = machName[0:255]
}
ap := Authsys_parms{
Machinename: machName,
Uid: auth.Uid,
Gid: auth.Gid,
Gids: nil,
}
// Fill the machine ID (needs not to be super-unique but nice to have them distinct)
_ = binary.Read(rand.Reader, binary.LittleEndian, &ap.Stamp)
apBuf := bytes.NewBuffer([]byte{})
XdrOut{Out: apBuf}.Marshal("", &ap)
return apBuf.Bytes()
}
func (c *NfsClient) sendMessage(proc XdrProc) (xid uint32, err error) {
xid = c.xid
c.xid++
msg := Rpc_msg{
Xid: xid,
Body: XdrAnon_Rpc_msg_Body{
Mtype: CALL,
U: &Call_body{
Rpcvers: 2,
Prog: proc.Prog(),
Vers: proc.Vers(),
Proc: proc.Proc(),
Cred: Opaque_auth{
Flavor: c.authType,
Body: c.authData,
},
Verf: Opaque_auth{
Flavor: AUTH_NONE,
},
},
},
}
// The marshaller for some reason loves to panic.
defer func() {
if i := recover(); i != nil {
if e, ok := i.(XdrError); ok {
err = e
} else {
panic(i)
}
}
}()
buffer := bytes.NewBuffer([]byte{})
out := XdrOut{Out: buffer}
out.Marshal("", &msg)
if _, ok := proc.GetArg().(XdrType_void); !ok {
out.Marshal("", proc.GetArg())
}
// Yep, the RPC protocol requires this strange OR
err = binary.Write(c.conn, binary.BigEndian, 0x80000000|uint32(buffer.Len()))
if err != nil {
return
}
_, err = c.conn.Write(buffer.Bytes())
if err != nil {
return
}
return
}
func (c *NfsClient) readNfsMessage(result XdrType) (xid uint32, err error) {
lenBuf := make([]byte, 4)
_, err = io.ReadFull(c.conn, lenBuf)
if err != nil {
return
}
// The RPC protocol sets the MSB to 1 for length fields. Don't ask me why.
msgLen := binary.BigEndian.Uint32(lenBuf) & 0x7fffffff
msgBuf := make([]byte, msgLen)
_, err = io.ReadFull(c.conn, msgBuf)
if err != nil {
return
}
// The unmarshaller for some reason loves to panic. Sigh.
defer func() {
if i := recover(); i != nil {
if e, ok := i.(XdrError); ok {
err = e
} else {
panic(i)
}
}
}()
reply := Rpc_msg{}
in := XdrIn{In: bytes.NewReader(msgBuf)}
in.Marshal("", &reply)
if _, ok := result.(XdrType_void); !ok {
in.Marshal("", result)
}
if !c.isRpcSuccess(&reply) {
err = fmt.Errorf("RPC error: %s", c.getRpcError(&reply))
return
}
xid = reply.Xid
return
}
// Returns true iff msg is an accepted REPLY with status SUCCESS.
func (c *NfsClient) isRpcSuccess(msg *Rpc_msg) bool {
return msg != nil &&
msg.Body.Mtype == REPLY &&
msg.Body.Rbody().Stat == MSG_ACCEPTED &&
msg.Body.Rbody().Areply().Reply_data.Stat == SUCCESS
}
// An *Rpc_msg can represent an error. Call IsSuccess to see if there
// was actually an error.
func (c *NfsClient) getRpcError(m *Rpc_msg) string {
if m.Body.Mtype != REPLY {
return "RPC message not a REPLY"
} else if m.Body.Rbody().Stat == MSG_ACCEPTED {
stat := m.Body.Rbody().Areply().Reply_data.Stat
c := stat.String()
if stat == PROG_MISMATCH {
mmi := m.Body.Rbody().Areply().Reply_data.Mismatch_info()
c = fmt.Sprintf("%s (low %d, high %d)", c, mmi.Low, mmi.High)
}
return c
} else if m.Body.Rbody().Stat == MSG_DENIED {
stat := m.Body.Rbody().Rreply().Stat
c := stat.String()
return c
}
return "Invalid reply_stat"
}
func (c *NfsClient) Ping() error {
nullProc := XdrProc_NFSPROC4_NULL{}
xid, err := c.sendMessage(&nullProc)
if err != nil {
return err
}
xidRes, err := c.readNfsMessage(nullProc.GetRes())
if err != nil {
return err
}
if xidRes != xid {
return fmt.Errorf("mismathced xids: %d and %d", xid, xidRes)
}
return nil
}
func (c *NfsClient) runNfsTransaction(ops []Nfs_argop4, pathHint string) ([]Nfs_resop4, error) {
compound := XdrProc_NFSPROC4_COMPOUND{}
args := compound.GetArg().(*COMPOUND4args)
args.Argarray = ops
xid, err := c.sendMessage(&compound)
if err != nil {
return nil, err
}
xid2, err := c.readNfsMessage(compound.GetRes())
if err != nil {
return nil, err
}
if xid != xid2 {
return nil, fmt.Errorf("xids don't match: %d and %d", xid, xid2)
}
res := compound.GetRes().(*COMPOUND4res)
// TODO: translate the error better
if res.Status != NFS4_OK {
return nil, &NfsError{
Path: pathHint,
ErrorCode: NfsErrorCode(res.Status),
ErrorString: fmt.Sprintf("NFS error: %s (%d), path='%s'",
res.Status.String(), int32(res.Status), pathHint),
}
}
return res.Resarray, nil
}
func (c *NfsClient) setClientId() error {
res, err := c.runNfsTransaction([]Nfs_argop4{{
Argop: OP_SETCLIENTID,
U: &SETCLIENTID4args{
Client: Nfs_client_id4{
Verifier: Verifier4{},
Id: []byte(c.clientId),
},
Callback: Cb_client4{},
Callback_ident: 0,
},
}}, "")
if err != nil {
return err
}
resOk := res[0].Opsetclientid().Resok4()
c.clientIdShort = resOk.Clientid
_, err = c.runNfsTransaction([]Nfs_argop4{{
Argop: OP_SETCLIENTID_CONFIRM,
U: &SETCLIENTID_CONFIRM4args{
Clientid: resOk.Clientid,
Setclientid_confirm: resOk.Setclientid_confirm,
},
}}, "")
if err != nil {
return err
}
return nil
}
func (c *NfsClient) retrieveRootFh() error {
res, err := c.runNfsTransaction([]Nfs_argop4{
{
Argop: OP_PUTROOTFH,
},
{
Argop: OP_GETFH,
},
}, "/")
if err != nil {
return err
}
c.rootFh = res[1].Opgetfh().Resok4().Object
return nil
}
func splitPath(path string) []string {
splits := strings.Split(path, "/")
curPos := 0
for _, s := range splits {
if s == "" {
continue
}
splits[curPos] = s
curPos++
}
return splits[0:curPos]
}
func (c *NfsClient) GetFileList(path string) ([]FileInfo, error) {
var args = c.makePathLookupArgs(splitPath(path))
args = append(args,
Nfs_argop4{Argop: OP_GETFH},
Nfs_argop4{Argop: OP_READDIR,
U: &READDIR4args{
Cookie: 0,
Cookieverf: Verifier4{},
Dircount: 1024 * 128,
Maxcount: 1024 * 128,
Attr_request: standardNfsAttrs,
}},
)
res, err := c.runNfsTransaction(args, path)
if err != nil {
return nil, err
}
var fileList []FileInfo
dirFh := res[len(res)-2].Opgetfh().Resok4().Object
curDirList := res[len(res)-1].Opreaddir().Resok4()
for {
ent := curDirList.Reply.Entries
if ent == nil {
break
}
for {
fileList = append(fileList, c.translateFileMeta(string(ent.Name), ent.Attrs))
if ent.Nextentry == nil {
break
}
ent = ent.Nextentry
}
if curDirList.Reply.Eof {
break
}
res, err := c.runNfsTransaction([]Nfs_argop4{
{
Argop: OP_PUTFH,
U: &PUTFH4args{Object: dirFh},
},
{
Argop: OP_READDIR,
U: &READDIR4args{
Cookie: ent.Cookie,
Cookieverf: curDirList.Cookieverf,
Dircount: 1024 * 128,
Maxcount: 1024 * 128,
Attr_request: standardNfsAttrs,
},
},
}, path)
if err != nil {
return nil, err
}
curDirList = res[1].Opreaddir().Resok4()
}
return fileList, nil
}
// Make the commands to navigate the path to its leaf
func (c *NfsClient) makePathLookupArgs(path []string) []Nfs_argop4 {
var args []Nfs_argop4
args = append(args, Nfs_argop4{Argop: OP_PUTROOTFH})
// Add lookups for the path components
for _, p := range path {
args = append(args, Nfs_argop4{
Argop: OP_LOOKUP,
U: &LOOKUP4args{Objname: Component4(p)},
})
}
return args
}
func (c *NfsClient) translateFileMeta(name string, attrs Fattr4) FileInfo {
res := FileInfo{
Name: name,
}
curOff := 0
atm := attrs.Attrmask
if len(atm) > 0 && atm[0]&(1<<FATTR4_TYPE) != 0 {
fileType := binary.BigEndian.Uint32(attrs.Attr_vals[curOff : curOff+4])
curOff += 4
res.IsDir = Nfs_ftype4(fileType) == NF4DIR
}
if len(atm) > 0 && atm[0]&(1<<FATTR4_SIZE) != 0 {
res.Size = binary.BigEndian.Uint64(attrs.Attr_vals[curOff : curOff+8])
curOff += 8
}
if len(atm) > 1 && atm[1]&(1<<(FATTR4_TIME_MODIFY-32)) != 0 {
mtimeSec := binary.BigEndian.Uint64(attrs.Attr_vals[curOff : curOff+8])
curOff += 8
mtimeNsec := binary.BigEndian.Uint32(attrs.Attr_vals[curOff : curOff+4])
curOff += 4
// I hope this works for times before 1970-01-01...
res.Mtime = time.Unix(int64(mtimeSec), int64(mtimeNsec))
}
return res
}
func (c *NfsClient) GetFileInfo(path string) (FileInfo, error) {
args := c.makePathLookupArgs(splitPath(path))
args = append(args,
Nfs_argop4{
Argop: OP_GETATTR,
U: &GETATTR4args{
Attr_request: standardNfsAttrs,
},
})
res, err := c.runNfsTransaction(args, path)
if err != nil {
return FileInfo{}, err
}
splits := splitPath(path)
var name string
if len(splits) == 1 {
name = path
} else {
name = splits[len(splits)-1]
}
resInfo := c.translateFileMeta(name,
res[len(res)-1].Opgetattr().Resok4().Obj_attributes)
return resInfo, nil
}
func (c *NfsClient) ReadFileAll(path string, writer io.Writer) (uint64, error) {
return c.ReadFile(path, 0, math.MaxUint64, writer)
}
func (c *NfsClient) ReadFile(path string, offset, count uint64, writer io.Writer) (uint64, error) {
anonymousStateId := Stateid4{}
args := c.makePathLookupArgs(splitPath(path))
args = append(args,
Nfs_argop4{Argop: OP_GETFH},
Nfs_argop4{
Argop: OP_READ,
U: &READ4args{
Stateid: anonymousStateId,
Offset: offset,
Count: Count4(MinUint64(NfsReadBlockLen, count)),
},
})
res, err := c.runNfsTransaction(args, path)
if err != nil {
return 0, err
}
flDataBlock := res[len(res)-1].Opread().Resok4()
fileFh := res[len(res)-2].Opgetfh().Resok4().Object
var dataRead uint64
for {
_, err := writer.Write(flDataBlock.Data)
if err != nil {
return 0, err
}
ln := len(flDataBlock.Data)
offset += uint64(ln)
count -= uint64(ln)
dataRead += uint64(ln)
if flDataBlock.Eof || count == 0 {
break
}
// Get the next file block
res, err := c.runNfsTransaction([]Nfs_argop4{
{
Argop: OP_PUTFH,
U: &PUTFH4args{Object: fileFh},
},
{
Argop: OP_READ,
U: &READ4args{
Stateid: anonymousStateId,
Offset: offset,
Count: Count4(MinUint64(NfsReadBlockLen, count)),
},
},
}, path)
if err != nil {
return 0, err
}
flDataBlock = res[1].Opread().Resok4()
}
return dataRead, nil
}
func (c *NfsClient) openFileForWrite(path string, truncate bool) (Stateid4, Nfs_fh4, error) {
splits := splitPath(path)
// Put the directory FH as the current one
args := c.makePathLookupArgs(splits[0 : len(splits)-1])
// Make the file claim (i.e. the file name on top of the directory FH)
flClaim := Component4(splits[len(splits)-1])
var fileAttrs Fattr4
if truncate {
// Set the file size and mode (Unix access mask)
fileAttrs.Attr_vals = make([]byte, 12)
// This file size is set to 0
binary.BigEndian.PutUint32(fileAttrs.Attr_vals[8:], MODE4_WUSR|MODE4_RUSR|MODE4_WGRP|MODE4_RGRP)
fileAttrs.Attrmask = Bitmap4{1 << FATTR4_SIZE, 1 << (FATTR4_MODE - 32)}
} else {
// Set the file mode (Unix access mask)
fileAttrs.Attr_vals = make([]byte, 4)
binary.BigEndian.PutUint32(fileAttrs.Attr_vals, MODE4_WUSR|MODE4_RUSR|MODE4_WGRP|MODE4_RGRP)
fileAttrs.Attrmask = Bitmap4{0, 1 << (FATTR4_MODE - 32)}
}
args = append(args,
Nfs_argop4{
Argop: OP_OPEN,
U: &OPEN4args{
Seqid: c.nfsSeqId,
Share_access: OPEN4_SHARE_ACCESS_WRITE,
Share_deny: OPEN4_SHARE_DENY_NONE,
Owner: Open_owner4{
Clientid: c.clientIdShort,
Owner: []byte(c.clientId),
},
Openhow: Openflag4{
Opentype: OPEN4_CREATE,
U: &Createhow4{
Mode: UNCHECKED4,
U: &fileAttrs,
},
},
Claim: Open_claim4{
Claim: CLAIM_NULL,
U: &flClaim,
},
},
},
Nfs_argop4{
Argop: OP_GETFH,
})
res, err := c.runNfsTransaction(args, path)
c.incrementNfsSeq(err)
if err != nil {
return Stateid4{}, Nfs_fh4{}, err
}
openRes := res[len(res)-2].Opopen().Resok4()
openFh := res[len(res)-1].Opgetfh().Resok4().Object
// We need to confirm the opened file receipt the first time we run the operation
if !c.openConfirmed {
res, err = c.runNfsTransaction([]Nfs_argop4{
{
Argop: OP_PUTFH,
U: &PUTFH4args{Object: openFh},
},
{
Argop: OP_OPEN_CONFIRM,
U: &OPEN_CONFIRM4args{
Open_stateid: openRes.Stateid,
Seqid: c.nfsSeqId,
},
},
}, path)
c.incrementNfsSeq(err)
if err != nil {
return Stateid4{}, Nfs_fh4{}, err
}
c.openConfirmed = true
return res[len(res)-1].Opopen_confirm().Resok4().Open_stateid, openFh, nil
}
return openRes.Stateid, openFh, nil
}
func (c *NfsClient) closeFile(stateId Stateid4, fh Nfs_fh4, path string) error {
_, err := c.runNfsTransaction([]Nfs_argop4{
{
Argop: OP_PUTFH,
U: &PUTFH4args{Object: fh},
},
{
Argop: OP_CLOSE,
U: &CLOSE4args{
Open_stateid: stateId,
Seqid: c.nfsSeqId,
},
},
}, path)
c.incrementNfsSeq(err)
if err != nil {
return err
}
return nil
}
func (c *NfsClient) ReWriteFile(path string, reader io.Reader) (written uint64, err error) {
return c.WriteFile(path, true, 0, reader)
}
func (c *NfsClient) WriteFile(path string, truncate bool, offset uint64,
reader io.Reader) (written uint64, err error) {
stateId, fh, err := c.openFileForWrite(path, truncate)
if err != nil {
return
}
defer func() {
err = c.closeFile(stateId, fh, path)
}()
block := make([]byte, NfsReadBlockLen)
for {
var curRead int
curRead, err = reader.Read(block)
if curRead == 0 || err == io.EOF {
break
}
if err != nil {
return
}
// Write the block!
err = c.writeBlock(stateId, fh, written+offset, block[0:curRead], path)
if err != nil {
return
}
written += uint64(curRead)
}
return
}
func (c *NfsClient) writeBlock(id Stateid4, fh Nfs_fh4, offset uint64, data []byte, path string) error {
for len(data) > 0 {
res, err := c.runNfsTransaction([]Nfs_argop4{
{
Argop: OP_PUTFH,
U: &PUTFH4args{Object: fh},
},
{
Argop: OP_WRITE,
U: &WRITE4args{
Stateid: id,
Offset: offset,
Stable: UNSTABLE4,
Data: data,
},
},
}, path)
if err != nil {
return err
}
written := res[1].Opwrite().Resok4().Count
data = data[written:]
}
return nil
}
func (c *NfsClient) DeleteFile(path string) error {
splits := splitPath(path)
// Put the directory FH as the current one
args := c.makePathLookupArgs(splits[0 : len(splits)-1])
// Make the file claim (i.e. the file name on top of the directory FH)
flClaim := Component4(splits[len(splits)-1])
args = append(args,
Nfs_argop4{
Argop: OP_REMOVE,
U: &REMOVE4args{
Target: flClaim,
},
})
_, err := c.runNfsTransaction(args, path)
if err != nil {
return err
}
return nil
}
func (c *NfsClient) MakePath(path string) error {
curPath := ""
var curPathElems []string
for _, curElem := range splitPath(path) {
if curPath != "" {
curPath += "/"
}
curPath += curElem
curPathElems = append(curPathElems, curElem)
fi, err := c.GetFileInfo(curPath)
if err == nil && !fi.IsDir {
return &NfsError{
ErrorCode: ERROR_NOTDIR,
ErrorString: fmt.Sprintf("NFS error: should be a directory (%d), path='%s'",
ERROR_NOTDIR, curPath),
Path: curPath,
}
}
if err != nil {
if IsNfsError(err, ERROR_NOENT) {
args := c.makePathLookupArgs(curPathElems[0 : len(curPathElems)-1])
// Make the file claim (i.e. the file name on top of the directory FH)
flClaim := Component4(curElem)
// Set the file mode (Unix access mask)
var dirAttrs Fattr4
dirAttrs.Attr_vals = make([]byte, 4)
binary.BigEndian.PutUint32(dirAttrs.Attr_vals,
MODE4_WUSR|MODE4_RUSR|MODE4_XUSR|MODE4_WGRP|MODE4_RGRP|MODE4_XGRP)
dirAttrs.Attrmask = Bitmap4{0, 1 << (FATTR4_MODE - 32)}
args = append(args,
Nfs_argop4{
Argop: OP_CREATE,
U: &CREATE4args{
Objtype: Createtype4{
Type: NF4DIR,
},
Objname: flClaim,
Createattrs: dirAttrs,
},
})
_, err := c.runNfsTransaction(args, curPath)
if err != nil {
return err
}
} else {
return err
}
}
}
return nil
}
// Increment NFS sequence ID for all operations, even the ones that return
// errors, except for a pre-defined list in https://tools.ietf.org/html/rfc3530#section-8.1.5
func (c *NfsClient) incrementNfsSeq(err error) {
if err == nil {
c.nfsSeqId++
return
}
nfsErr, ok := err.(*NfsError)
if !ok {
c.nfsSeqId++
return
}
switch Nfsstat4(nfsErr.ErrorCode) {
case
NFS4ERR_STALE_CLIENTID, NFS4ERR_STALE_STATEID,
NFS4ERR_BAD_STATEID, NFS4ERR_BAD_SEQID, NFS4ERR_BADXDR,
NFS4ERR_RESOURCE, NFS4ERR_NOFILEHANDLE:
return
default:
c.nfsSeqId++
}
}
func RemoveRecursive(nfs NfsInterface, path string) error {
list, err := nfs.GetFileList(path)
if IsNfsError(err, ERROR_NOENT) {
return nil
}
if err != nil {
return err
}
for _, fl := range list {
curPath := path + "/" + fl.Name
if fl.IsDir {
err = RemoveRecursive(nfs, curPath)
if err != nil {
return err
}
} else {
err = nfs.DeleteFile(curPath)
if err != nil {
return err
}
}
}
err = nfs.DeleteFile(path)
if err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,153 @@
package nfs4
type NfsError struct {
Path string
ErrorCode NfsErrorCode
ErrorString string
}
func (n *NfsError) Error() string {
return n.ErrorString
}
func IsNfsError(err error, code NfsErrorCode) bool {
ne, ok := err.(*NfsError)
return ok && ne.ErrorCode == code
}
/*
* Error status. See https://www.rfc-editor.org/rfc/rfc7530
*/
type NfsErrorCode int32
const (
/* everything is okay */
OK NfsErrorCode = 0
/* caller not privileged */
ERROR_PERM NfsErrorCode = 1
/* no such file/directory */
ERROR_NOENT NfsErrorCode = 2
/* hard I/O error */
ERROR_IO NfsErrorCode = 5
/* no such device */
ERROR_NXIO NfsErrorCode = 6
/* access denied */
ERROR_ACCESS NfsErrorCode = 13
/* file already exists */
ERROR_EXIST NfsErrorCode = 17
/* different filesystems */
ERROR_XDEV NfsErrorCode = 18
/* should be a directory */
ERROR_NOTDIR NfsErrorCode = 20
/* should not be directory */
ERROR_ISDIR NfsErrorCode = 21
/* invalid argument */
ERROR_INVAL NfsErrorCode = 22
/* file exceeds server max */
ERROR_FBIG NfsErrorCode = 27
/* no space on filesystem */
ERROR_NOSPC NfsErrorCode = 28
/* read-only filesystem */
ERROR_ROFS NfsErrorCode = 30
/* too many hard links */
ERROR_MLINK NfsErrorCode = 31
/* name exceeds server max */
ERROR_NAMETOOLONG NfsErrorCode = 63
/* directory not empty */
ERROR_NOTEMPTY NfsErrorCode = 66
/* hard quota limit reached*/
ERROR_DQUOT NfsErrorCode = 69
/* file no longer exists */
ERROR_STALE NfsErrorCode = 70
/* Illegal filehandle */
ERROR_BADHANDLE NfsErrorCode = 10001
/* READDIR cookie is stale */
ERROR_BAD_COOKIE NfsErrorCode = 10003
/* operation not supported */
ERROR_NOTSUPP NfsErrorCode = 10004
/* response limit exceeded */
ERROR_TOOSMALL NfsErrorCode = 10005
/* undefined server error */
ERROR_SERVERFAULT NfsErrorCode = 10006
/* type invalid for CREATE */
ERROR_BADTYPE NfsErrorCode = 10007
/* file "busy" - retry */
ERROR_DELAY NfsErrorCode = 10008
/* nverify says attrs same */
ERROR_SAME NfsErrorCode = 10009
/* lock unavailable */
ERROR_DENIED NfsErrorCode = 10010
/* lock lease expired */
ERROR_EXPIRED NfsErrorCode = 10011
/* I/O failed due to lock */
ERROR_LOCKED NfsErrorCode = 10012
/* in grace period */
ERROR_GRACE NfsErrorCode = 10013
/* filehandle expired */
ERROR_FHEXPIRED NfsErrorCode = 10014
/* share reserve denied */
ERROR_SHARE_DENIED NfsErrorCode = 10015
/* wrong security flavor */
ERROR_WRONGSEC NfsErrorCode = 10016
/* clientid in use */
ERROR_CLID_INUSE NfsErrorCode = 10017
/* resource exhaustion */
ERROR_RESOURCE NfsErrorCode = 10018
/* filesystem relocated */
ERROR_MOVED NfsErrorCode = 10019
/* current FH is not set */
ERROR_NOFILEHANDLE NfsErrorCode = 10020
/* minor vers not supp */
ERROR_MINOR_VERS_MISMATCH NfsErrorCode = 10021
/* server has rebooted */
ERROR_STALE_CLIENTID NfsErrorCode = 10022
/* server has rebooted */
ERROR_STALE_STATEID NfsErrorCode = 10023
/* state is out of sync */
ERROR_OLD_STATEID NfsErrorCode = 10024
/* incorrect stateid */
ERROR_BAD_STATEID NfsErrorCode = 10025
/* request is out of seq. */
ERROR_BAD_SEQID NfsErrorCode = 10026
/* verify - attrs not same */
ERROR_NOT_SAME NfsErrorCode = 10027
/* lock range not supported*/
ERROR_LOCK_RANGE NfsErrorCode = 10028
/* should be file/directory*/
ERROR_SYMLINK NfsErrorCode = 10029
/* no saved filehandle */
ERROR_RESTOREFH NfsErrorCode = 10030
/* some filesystem moved */
ERROR_LEASE_MOVED NfsErrorCode = 10031
/* recommended attr not sup*/
ERROR_ATTRNOTSUPP NfsErrorCode = 10032
/* reclaim outside of grace*/
ERROR_NO_GRACE NfsErrorCode = 10033
/* reclaim error at server */
ERROR_RECLAIM_BAD NfsErrorCode = 10034
/* conflict on reclaim */
ERROR_RECLAIM_CONFLICT NfsErrorCode = 10035
/* ZDR decode failed */
ERROR_BADZDR NfsErrorCode = 10036
/* file locks held at CLOSE*/
ERROR_LOCKS_HELD NfsErrorCode = 10037
/* conflict in OPEN and I/O*/
ERROR_OPENMODE NfsErrorCode = 10038
/* owner translation bad */
ERROR_BADOWNER NfsErrorCode = 10039
/* utf-8 char not supported*/
ERROR_BADCHAR NfsErrorCode = 10040
/* name not supported */
ERROR_BADNAME NfsErrorCode = 10041
/* lock range not supported*/
ERROR_BAD_RANGE NfsErrorCode = 10042
/* no atomic up/downgrade */
ERROR_LOCK_NOTSUPP NfsErrorCode = 10043
/* undefined operation */
ERROR_OP_ILLEGAL NfsErrorCode = 10044
/* file locking deadlock */
ERROR_DEADLOCK NfsErrorCode = 10045
/* open file blocks op. */
ERROR_FILE_OPEN NfsErrorCode = 10046
/* lockowner state revoked */
ERROR_ADMIN_REVOKED NfsErrorCode = 10047
)

View file

@ -0,0 +1,136 @@
package nfs4
import (
"context"
"io"
"net"
"sync"
"time"
)
// A wrapper for net.Conn that adds ability to cancel operations via a context.Context and
// also supports deadlines.
type SupervisedConnection struct {
delegate net.Conn
ctx context.Context
deadline time.Time
mtx sync.Mutex
closeSignal chan bool
closeErr error
isClosed int32
}
func NewSupervisedConnection(delegate net.Conn,
ctx context.Context) (*SupervisedConnection, error) {
res := &SupervisedConnection{
delegate: delegate,
ctx: ctx,
closeSignal: make(chan bool),
}
go func() {
select {
case <-ctx.Done():
res.signalDone()
case <-res.closeSignal:
}
}()
deadline, ok := ctx.Deadline()
if ok {
res.deadline = deadline
err := delegate.SetDeadline(deadline)
if err != nil {
return nil, err
}
}
return res, nil
}
func (s *SupervisedConnection) signalDone() {
s.mtx.Lock()
defer s.mtx.Unlock()
if s.isClosed != 0 {
return
}
// Cancel pending operations
s.closeErr = s.delegate.Close()
s.isClosed = 1
}
func (s *SupervisedConnection) SetReadDeadline(t time.Time) error {
s.mtx.Lock()
defer s.mtx.Unlock()
// Don't allow deadline extensions if we're getting interrupted
if !s.deadline.IsZero() && s.deadline.Before(t) {
return nil
}
return s.delegate.SetReadDeadline(t)
}
func (s *SupervisedConnection) SetWriteDeadline(t time.Time) error {
s.mtx.Lock()
defer s.mtx.Unlock()
// Don't allow deadline extensions if we're getting interrupted
if !s.deadline.IsZero() && s.deadline.Before(t) {
return nil
}
return s.delegate.SetWriteDeadline(t)
}
func (s *SupervisedConnection) SetDeadline(t time.Time) error {
s.mtx.Lock()
defer s.mtx.Unlock()
// Don't allow deadline extensions if we're getting interrupted
if !s.deadline.IsZero() && s.deadline.Before(t) {
return nil
}
return s.delegate.SetDeadline(t)
}
func (s *SupervisedConnection) Close() error {
s.mtx.Lock()
defer s.mtx.Unlock()
if s.isClosed != 0 {
return s.closeErr
}
close(s.closeSignal)
s.isClosed = 1
s.closeErr = s.delegate.Close()
return s.closeErr
}
func (s *SupervisedConnection) Read(b []byte) (n int, err error) {
// s.isClosed can only go from 0 to 1, so there's no need to do
// synchronization here.
if s.isClosed != 0 {
return 0, io.EOF
}
return s.delegate.Read(b)
}
func (s *SupervisedConnection) Write(b []byte) (n int, err error) {
// s.isClosed can only go from 0 to 1, so there's no need to do
// synchronization here.
if s.isClosed != 0 {
return 0, io.EOF
}
return s.delegate.Write(b)
}
func (s *SupervisedConnection) LocalAddr() net.Addr {
return s.delegate.LocalAddr()
}
func (s *SupervisedConnection) RemoteAddr() net.Addr {
return s.delegate.RemoteAddr()
}