Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Support discriminated unions correctly (#2)
Browse files Browse the repository at this point in the history
This makes use of the discriminated union support in the xdr2 package,
and uses that to correct several protocol errors in the previous code.

Additionally this adds support for chunked readdir responses and
chunked file writing.

This client is still woefully incomplete and likely does not handle
various corner cases correctly.

Basic testing performed against Linux kernel NFS server and NetApp
simulator.
  • Loading branch information
hickeng authored Oct 4, 2017
1 parent 492894f commit 7592fd7
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 152 deletions.
24 changes: 16 additions & 8 deletions nfs/example/test/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ func main() {
}
defer v.Close()

// discover any system files such as lost+found or .snapshot
dirs, err := ls(v, ".")
if err != nil {
log.Fatalf("ls: %s", err.Error())
}
baseDirCount := len(dirs)

if _, err = v.Mkdir(dir, 0775); err != nil {
log.Fatalf("mkdir error: %v", err)
}
Expand All @@ -65,14 +72,14 @@ func main() {
log.Fatalf("mkdir error: %v", err)
}

dirs, err := ls(v, ".")
dirs, err = ls(v, ".")
if err != nil {
log.Fatalf("ls: %s", err.Error())
}

// check the length. There should only be 1 entry in the target (aside from . and ..)
if len(dirs) != 3 {
log.Fatalf("expected 3 dirs, got %d", len(dirs))
// check the length. There should only be 1 entry in the target (aside from . and .., et al)
if len(dirs) != 1+baseDirCount {
log.Fatalf("expected %d dirs, got %d", 1+baseDirCount, len(dirs))
}

// 10 MB file
Expand Down Expand Up @@ -131,15 +138,16 @@ func main() {
log.Fatalf("ls: %s", err.Error())
}

if len(outDirs) != 2 {
log.Fatalf("directory shoudl be empty!")
if len(outDirs) != baseDirCount {
log.Fatalf("directory shoudl be empty of our created files!")
}

if err = mount.Unmount(); err != nil {
log.Fatalf("unable to umount target: %v", err)
}

mount.Close()
util.Infof("Completed tests")
}

func testFileRW(v *nfs.Target, name string, filesize uint64) error {
Expand All @@ -162,9 +170,9 @@ func testFileRW(v *nfs.Target, name string, filesize uint64) error {
t := io.TeeReader(f, h)

// Copy filesize
_, err = io.CopyN(wr, t, int64(filesize))
n, err := io.CopyN(wr, t, int64(filesize))
if err != nil {
util.Errorf("error copying: %s", err.Error())
util.Errorf("error copying: n=%d, %s", n, err.Error())
return err
}
expectedSum := h.Sum(nil)
Expand Down
81 changes: 50 additions & 31 deletions nfs/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,15 @@ func (f *File) Read(p []byte) (int, error) {
}

type ReadRes struct {
Follows uint32
Attrs struct {
Attrs Fattr
}
Attr PostOpAttr
Count uint32
EOF uint32
Data struct {
Length uint32
}
}

readSize := uint32(min(uint64(f.fsinfo.RTPref), uint64(len(p))))
readSize := min(f.fsinfo.RTPref, uint32(len(p)))
util.Debugf("read(%x) len=%d offset=%d", f.fh, readSize, f.curr)

r, err := f.call(&ReadArgs{
Expand Down Expand Up @@ -97,36 +94,58 @@ func (f *File) Write(p []byte) (int, error) {
Contents []byte
}

totalToWrite := len(p)
writeSize := uint64(min(uint64(f.fsinfo.WTPref), uint64(totalToWrite)))
type WriteRes struct {
Wcc WccData
Count uint32
How uint32
WriteVerf uint64
}

_, err := f.call(&WriteArgs{
Header: rpc.Header{
Rpcvers: 2,
Prog: Nfs3Prog,
Vers: Nfs3Vers,
Proc: NFSProc3Write,
Cred: f.auth,
Verf: rpc.AuthNull,
},
FH: f.fh,
Offset: f.curr,
Count: uint32(writeSize),
How: 2,
Contents: p[:writeSize],
})
totalToWrite := uint32(len(p))
written := uint32(0)

for written = 0; written < totalToWrite; {
writeSize := min(f.fsinfo.WTPref, totalToWrite-written)

res, err := f.call(&WriteArgs{
Header: rpc.Header{
Rpcvers: 2,
Prog: Nfs3Prog,
Vers: Nfs3Vers,
Proc: NFSProc3Write,
Cred: f.auth,
Verf: rpc.AuthNull,
},
FH: f.fh,
Offset: f.curr,
Count: writeSize,
How: 2,
Contents: p[written : written+writeSize],
})

if err != nil {
util.Errorf("write(%x): %s", f.fh, err.Error())
return int(written), err
}

if err != nil {
util.Debugf("write(%x): %s", f.fh, err.Error())
return int(writeSize), err
}
writeres := &WriteRes{}
if err = xdr.Read(res, writeres); err != nil {
util.Errorf("write(%x) failed to parse result: %s", f.fh, err.Error())
util.Debugf("write(%x) partial result: %+v", f.fh, writeres)
return int(written), err
}

if writeres.Count != writeSize {
util.Debugf("write(%x) did not write full data payload: sent: %d, written: %d", writeSize, writeres.Count)
}

util.Debugf("write(%x) len=%d offset=%d written=%d total=%d",
f.fh, writeSize, f.curr, writeSize, totalToWrite)
f.curr += uint64(writeres.Count)
written += writeres.Count

f.curr = f.curr + writeSize
util.Debugf("write(%x) len=%d new_offset=%d written=%d total=%d", f.fh, totalToWrite, f.curr, writeres.Count, written)
}

return int(writeSize), nil
return int(written), nil
}

// Close commits the file
Expand Down Expand Up @@ -197,7 +216,7 @@ func (v *Target) Open(path string) (io.ReadCloser, error) {
return f, nil
}

func min(x, y uint64) uint64 {
func min(x, y uint32) uint32 {
if x > y {
return y
}
Expand Down
91 changes: 69 additions & 22 deletions nfs/nfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const (
NFSProc3FSInfo = 19
NFSProc3Commit = 21

// The size in bytes of the opaque cookie verifier passed by
// READDIR and READDIRPLUS.
NFS3_COOKIEVERFSIZE = 8

// file types
NF3Reg = 1
NF3Dir = 2
Expand All @@ -51,19 +55,37 @@ type Sattr3 struct {
Mode SetMode
UID SetUID
GID SetUID
Size uint64
Atime NFS3Time
Mtime NFS3Time
Size SetSize
Atime SetTime
Mtime SetTime
}

type SetMode struct {
Set uint32
Mode uint32
SetIt bool `xdr:"union"`
Mode uint32 `xdr:"unioncase=1"`
}

type SetUID struct {
Set uint32
UID uint32
SetIt bool `xdr:"union"`
UID uint32 `xdr:"unioncase=1"`
}

type SetSize struct {
SetIt bool `xdr:"union"`
Size uint64 `xdr:"unioncase=1"`
}

type TimeHow int

const (
DontChange TimeHow = iota
SetToServerTime
SetToClientTime
)

type SetTime struct {
SetIt TimeHow `xdr:"union"`
Time NFS3Time `xdr:"unioncase=2"` //SetToClientTime
}

type NFS3Time struct {
Expand Down Expand Up @@ -109,65 +131,81 @@ func (f *Fattr) Sys() interface{} {
return nil
}

type PostOpFH3 struct {
IsSet bool `xdr:"union"`
FH []byte `xdr:"unioncase=1"`
}

type PostOpAttr struct {
IsSet bool `xdr:"union"`
Attr Fattr `xdr:"unioncase=1"`
}

type EntryPlus struct {
FileId uint64
FileName string
Cookie uint64
Attr struct {
Follows uint32
Attr Fattr
}
FHSet uint32
FH []byte
ValueFollows uint32
Attr PostOpAttr
Handle PostOpFH3
// NextEntry *EntryPlus
}

func (e *EntryPlus) Name() string {
return e.FileName
}

func (e *EntryPlus) Size() int64 {
if e.Attr.Follows == 0 {
if !e.Attr.IsSet {
return 0
}

return e.Attr.Attr.Size()
}

func (e *EntryPlus) Mode() os.FileMode {
if e.Attr.Follows == 0 {
if !e.Attr.IsSet {
return 0
}

return e.Attr.Attr.Mode()
}

func (e *EntryPlus) ModTime() time.Time {
if e.Attr.Follows == 0 {
if !e.Attr.IsSet {
return time.Time{}
}

return e.Attr.Attr.ModTime()
}

func (e *EntryPlus) IsDir() bool {
if e.Attr.Follows == 0 {
if !e.Attr.IsSet {
return false
}

return e.Attr.Attr.IsDir()
}

func (e *EntryPlus) Sys() interface{} {
if e.Attr.Follows == 0 {
if !e.Attr.IsSet {
return 0
}

return e.FileId
}

type WccData struct {
Before struct {
IsSet bool `xdr:"union"`
Size uint64 `xdr:"unioncase=1"`
MTime NFS3Time `xdr:"unioncase=1"`
CTime NFS3Time `xdr:"unioncase=1"`
}
After PostOpAttr
}

type FSInfo struct {
Follows uint32
Attr PostOpAttr
RTMax uint32
RTPref uint32
RTMult uint32
Expand All @@ -184,6 +222,7 @@ type FSInfo struct {
func DialService(addr string, prog rpc.Mapping) (*rpc.Client, error) {
pm, err := rpc.DialPortmapper("tcp", addr)
if err != nil {
util.Errorf("Failed to connect to portmapper: %s", err)
return nil, err
}
defer pm.Close()
Expand All @@ -194,6 +233,9 @@ func DialService(addr string, prog rpc.Mapping) (*rpc.Client, error) {
}

client, err := dialService(addr, port)
if err != nil {
return nil, err
}

return client, nil
}
Expand Down Expand Up @@ -222,7 +264,10 @@ func dialService(addr string, port int) (*rpc.Client, error) {
Port: p,
}

client, err = rpc.DialTCP("tcp", ldr, fmt.Sprintf("%s:%d", addr, port))
raddr := fmt.Sprintf("%s:%d", addr, port)
util.Debugf("Connecting to %s", raddr)

client, err = rpc.DialTCP("tcp", ldr, raddr)
if err == nil {
break
}
Expand All @@ -236,8 +281,10 @@ func dialService(addr string, port int) (*rpc.Client, error) {

util.Debugf("using random port %d -> %d", p, port)
} else {
raddr := fmt.Sprintf("%s:%d", addr, port)
util.Debugf("Connecting to %s from unprivileged port", raddr)

client, err = rpc.DialTCP("tcp", ldr, fmt.Sprintf("%s:%d", addr, port))
client, err = rpc.DialTCP("tcp", ldr, raddr)
if err != nil {
return nil, err
}
Expand Down
Loading

0 comments on commit 7592fd7

Please sign in to comment.