filestash/vendor/github.com/vmware/go-nfs-client/nfs/rpc/client.go
2022-12-08 09:19:04 +11:00

179 lines
3.3 KiB
Go

// Copyright © 2017 VMware, Inc. All Rights Reserved.
// SPDX-License-Identifier: BSD-2-Clause
//
package rpc
import (
"bufio"
"bytes"
"fmt"
"io"
"math/rand"
"net"
"sync/atomic"
"time"
"github.com/vmware/go-nfs-client/nfs/util"
"github.com/vmware/go-nfs-client/nfs/xdr"
)
const (
MsgAccepted = iota
MsgDenied
)
const (
Success = iota
ProgUnavail
ProgMismatch
ProcUnavail
GarbageArgs
SystemErr
)
const (
RpcMismatch = iota
)
var xid uint32
func init() {
// seed the XID (which is set by the client)
xid = rand.New(rand.NewSource(time.Now().UnixNano())).Uint32()
}
type Client struct {
*tcpTransport
}
func DialTCP(network string, ldr *net.TCPAddr, addr string) (*Client, error) {
a, err := net.ResolveTCPAddr(network, addr)
if err != nil {
return nil, err
}
conn, err := net.DialTCP(a.Network(), ldr, a)
if err != nil {
return nil, err
}
t := &tcpTransport{
r: bufio.NewReader(conn),
wc: conn,
}
return &Client{t}, nil
}
type message struct {
Xid uint32
Msgtype uint32
Body interface{}
}
func (c *Client) Call(call interface{}) (io.ReadSeeker, error) {
retries := 1
msg := &message{
Xid: atomic.AddUint32(&xid, 1),
Body: call,
}
retry:
w := new(bytes.Buffer)
if err := xdr.Write(w, msg); err != nil {
return nil, err
}
if _, err := c.Write(w.Bytes()); err != nil {
return nil, err
}
res, err := c.recv()
if err != nil {
return nil, err
}
xid, err := xdr.ReadUint32(res)
if err != nil {
return nil, err
}
if xid != msg.Xid {
return nil, fmt.Errorf("xid did not match, expected: %x, received: %x", msg.Xid, xid)
}
mtype, err := xdr.ReadUint32(res)
if err != nil {
return nil, err
}
if mtype != 1 {
return nil, fmt.Errorf("message as not a reply: %d", mtype)
}
status, err := xdr.ReadUint32(res)
if err != nil {
return nil, err
}
switch status {
case MsgAccepted:
// padding
_, err = xdr.ReadUint32(res)
if err != nil {
panic(err.Error())
}
opaque_len, err := xdr.ReadUint32(res)
if err != nil {
panic(err.Error())
}
_, err = res.Seek(int64(opaque_len), io.SeekCurrent)
if err != nil {
panic(err.Error())
}
acceptStatus, _ := xdr.ReadUint32(res)
switch acceptStatus {
case Success:
return res, nil
case ProgUnavail:
return nil, fmt.Errorf("rpc: PROG_UNAVAIL - server does not recognize the program number")
case ProgMismatch:
return nil, fmt.Errorf("rpc: PROG_MISMATCH - program version does not exist on the server")
case ProcUnavail:
return nil, fmt.Errorf("rpc: PROC_UNAVAIL - unrecognized procedure number")
case GarbageArgs:
// emulate Linux behaviour for GARBAGE_ARGS
if retries > 0 {
util.Debugf("Retrying on GARBAGE_ARGS per linux semantics")
retries--
goto retry
}
return nil, fmt.Errorf("rpc: GARBAGE_ARGS - rpc arguments cannot be XDR decoded")
case SystemErr:
return nil, fmt.Errorf("rpc: SYSTEM_ERR - unknown error on server")
default:
return nil, fmt.Errorf("rpc: unknown accepted status error: %d", acceptStatus)
}
case MsgDenied:
rejectStatus, _ := xdr.ReadUint32(res)
switch rejectStatus {
case RpcMismatch:
default:
return nil, fmt.Errorf("rejectedStatus was not valid: %d", rejectStatus)
}
default:
return nil, fmt.Errorf("rejectedStatus was not valid: %d", status)
}
panic("unreachable")
}