├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── auth └── auth.go ├── backend ├── backend.go └── stat.go ├── examples ├── .gitignore ├── client-container │ ├── .gitignore │ ├── Dockerfile │ ├── build-image.sh │ ├── entrypoint.sh │ ├── run.sh │ └── tests │ │ └── test_open.py ├── client.py ├── proxy │ ├── main.go │ └── run.sh └── server │ ├── .gitignore │ ├── README.md │ ├── create_vnet.sh │ ├── main.go │ └── run.sh ├── fs ├── fs.go ├── open_state.go ├── path.go ├── path_test.go └── storage.go ├── go.mod ├── log ├── format.go ├── func.go ├── handler.go ├── handler_stdout.go ├── levels.go ├── log.go ├── logger.go ├── logger_builtin.go └── message.go ├── memfs ├── buffer.go ├── buffer_test.go ├── file.go ├── file_info.go ├── folder.go ├── memfs.go ├── memfs_test.go └── storage.go ├── nfs ├── README.md ├── backend.go ├── bitmap4.go ├── context.go ├── implv3 │ ├── access.go │ ├── fsinfo.go │ ├── fsstat.go │ ├── getattr.go │ ├── implv3.go │ ├── lookup.go │ ├── pathconf.go │ ├── readdirplus.go │ ├── utils.go │ └── void.go ├── implv4 │ ├── access.go │ ├── access_test.go │ ├── attrs.go │ ├── attrs_test.go │ ├── bitmap4.go │ ├── close.go │ ├── commit.go │ ├── compound.go │ ├── compound_test.go │ ├── create.go │ ├── getattr.go │ ├── getfh.go │ ├── implv4.go │ ├── link.go │ ├── lookup.go │ ├── open.go │ ├── open_downgrade.go │ ├── read.go │ ├── readdir.go │ ├── readlink.go │ ├── remove.go │ ├── rename.go │ ├── setattr.go │ ├── setclientid.go │ ├── setclientid_confirm.go │ ├── setclientid_test.go │ ├── utils.go │ ├── void.go │ └── write.go ├── nfs.go ├── nfs_v3.go ├── nfs_v4.go └── rpc.go ├── server ├── mux_v3.go ├── mux_v4.go ├── server.go └── session.go ├── unixfs ├── file.go ├── file_info.go ├── file_info_darwin.go ├── file_info_linux.go ├── inode.go ├── unixfs.go ├── unixfs_test.go └── verbose_unixfs.go ├── utils └── rand.go └── xdr ├── README.md ├── header.go ├── pad.go ├── reader.go ├── reader_test.go ├── writer.go ├── writer_test.go └── xdr.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a golang project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go 3 | 4 | name: Go 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v4 21 | with: 22 | go-version: '1.18' 23 | 24 | - name: Test 25 | run: go test -v ./... 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | 4 | .env 5 | .netrc 6 | 7 | go.sum 8 | 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 smallfz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Go Reference](https://pkg.go.dev/badge/github.com/smallfz/libnfs-go.svg)](https://pkg.go.dev/github.com/smallfz/libnfs-go) 3 | 4 | 5 | # NFS Server Library 6 | 7 | Experimental [NFSv4](https://datatracker.ietf.org/doc/html/rfc7530) server library in pure go. 8 | 9 | The motive I started this project is mainly I need to immigrate a few projects to k8s and there are no other storage services except an OSS(like aws s3). So I need something to join OSS 10 | and k8s PV together. To me NFS is a interesting choice. 11 | 12 | Currently this repo doesn't include any COS wrapper in it. 13 | 14 | ## Getting start 15 | 16 | To give it a try: 17 | 18 | ```go 19 | package main 20 | 21 | import ( 22 | "flag" 23 | "fmt" 24 | "os" 25 | 26 | "github.com/smallfz/libnfs-go/auth" 27 | "github.com/smallfz/libnfs-go/backend" 28 | "github.com/smallfz/libnfs-go/fs" 29 | "github.com/smallfz/libnfs-go/log" 30 | "github.com/smallfz/libnfs-go/memfs" 31 | "github.com/smallfz/libnfs-go/server" 32 | ) 33 | 34 | func main() { 35 | listen := ":2049" 36 | flag.StringVar(&listen, "l", listen, "Server listen address") 37 | flag.Parse() 38 | 39 | log.UpdateLevel(log.DEBUG) 40 | 41 | mfs := memfs.NewMemFS() 42 | 43 | // We don't need to create a new fs for each connection as memfs is opaque towards SetCreds. 44 | // If the file system would depend on SetCreds, make sure to generate a new fs.FS for each connection. 45 | backend := backend.New(func() fs.FS { return mfs }, auth.Null) 46 | 47 | mfs.MkdirAll("/mount", os.FileMode(0o755)) 48 | mfs.MkdirAll("/test", os.FileMode(0o755)) 49 | mfs.MkdirAll("/test2", os.FileMode(0o755)) 50 | mfs.MkdirAll("/many", os.FileMode(0o755)) 51 | 52 | perm := os.FileMode(0o755) 53 | for i := 0; i < 256; i++ { 54 | mfs.MkdirAll(fmt.Sprintf("/many/sub-%d", i+1), perm) 55 | } 56 | 57 | svr, err := server.NewServerTCP(listen, backend) 58 | if err != nil { 59 | log.Errorf("server.NewServerTCP: %v", err) 60 | return 61 | } 62 | 63 | if err := svr.Serve(); err != nil { 64 | log.Errorf("svr.Serve: %v", err) 65 | } 66 | } 67 | ``` 68 | 69 | After the server started you can mount the in-memory filesystem to local: 70 | 71 | on Mac: 72 | 73 | ```sh 74 | mount -o nfsvers=4,noacl,tcp -t nfs localhost:/ /Users/smallfz/mnt 75 | ``` 76 | 77 | on Linux: 78 | 79 | ```sh 80 | mount -o nfsvers=4,minorversion=0,noacl,tcp -t nfs localhost:/ /mnt 81 | ``` 82 | 83 | 84 | ## Status 85 | 86 | Recent testing results of [nfstest_posix](https://wiki.linux-nfs.org/wiki/index.php/NFStest) with `--nfsversion=4`: 87 | 88 | - access: 58/58 pass 89 | - open: 19/22 pass 90 | - chdir: 3/3 pass 91 | - readdir: 3/3 pass 92 | - opendir: 2/2 pass 93 | - seekdir,rewinddir: 9/9 pass 94 | - telldir: 5/5 pass 95 | - read: 3/3 pass 96 | - write: 7/7 pass 97 | - close: 3/3 pass 98 | - stat: 22/23 pass 99 | - fstat: 22/23 pass 100 | - chmod: 2/16 pass 101 | - creat: 6/6 pass 102 | - unlink: 2/4 pass 103 | - mkdir: 5/7 pass 104 | - rmdir: 3/5 pass 105 | - link: x 106 | - fcntl: x 107 | - ... 108 | 109 | ## Contributing 110 | 111 | Firing an issue if anything. Any advice is appreciated. 112 | 113 | -------------------------------------------------------------------------------- /auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/smallfz/libnfs-go/fs" 7 | "github.com/smallfz/libnfs-go/nfs" 8 | "github.com/smallfz/libnfs-go/xdr" 9 | ) 10 | 11 | func Null(_, _ *nfs.Auth) (*nfs.Auth, fs.Creds, error) { 12 | return &nfs.Auth{Flavor: nfs.AUTH_FLAVOR_NULL, Body: []byte{}}, nil, nil 13 | } 14 | 15 | func Unix(cred, _ *nfs.Auth) (*nfs.Auth, fs.Creds, error) { 16 | if cred.Flavor < nfs.AUTH_FLAVOR_UNIX { 17 | return nil, nil, nfs.ErrTooWeak 18 | } 19 | 20 | var credentials Creds 21 | 22 | if _, err := xdr.NewReader(bytes.NewBuffer(cred.Body)).ReadAs(&credentials); err != nil { 23 | return nil, nil, err 24 | } 25 | 26 | return &nfs.Auth{Flavor: nfs.AUTH_FLAVOR_UNIX, Body: []byte{}}, &credentials, nil 27 | } 28 | 29 | type Creds struct { 30 | ExpirationValue uint32 31 | Hostname string 32 | UID uint32 33 | GID uint32 34 | AdditionalGroups []uint32 35 | } 36 | 37 | func (c *Creds) Host() string { 38 | return c.Hostname 39 | } 40 | 41 | func (c *Creds) Uid() uint32 { 42 | return c.UID 43 | } 44 | 45 | func (c *Creds) Gid() uint32 { 46 | return c.GID 47 | } 48 | 49 | func (c *Creds) Groups() []uint32 { 50 | return c.AdditionalGroups 51 | } 52 | -------------------------------------------------------------------------------- /backend/backend.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "github.com/smallfz/libnfs-go/fs" 5 | "github.com/smallfz/libnfs-go/nfs" 6 | ) 7 | 8 | type backendSession struct { 9 | vfs fs.FS 10 | stat *Stat 11 | authentication nfs.AuthenticationHandler 12 | } 13 | 14 | func (s *backendSession) Close() error { 15 | if s.stat != nil { 16 | s.stat.CleanUp() 17 | } 18 | return nil 19 | } 20 | 21 | func (s *backendSession) Authentication() nfs.AuthenticationHandler { 22 | return s.authentication 23 | } 24 | 25 | func (s *backendSession) GetFS() fs.FS { 26 | return s.vfs 27 | } 28 | 29 | func (s *backendSession) GetStatService() nfs.StatService { 30 | return s.stat 31 | } 32 | 33 | type Backend struct { 34 | vfsLoader func() fs.FS 35 | authentication nfs.AuthenticationHandler 36 | } 37 | 38 | // New creates a new Backend instance. 39 | func New(vfsLoader func() fs.FS, authentication nfs.AuthenticationHandler) *Backend { 40 | return &Backend{ 41 | vfsLoader: vfsLoader, 42 | authentication: authentication, 43 | } 44 | } 45 | 46 | func (b *Backend) CreateSession(state nfs.SessionState) nfs.BackendSession { 47 | return &backendSession{ 48 | vfs: b.vfsLoader(), 49 | stat: new(Stat), 50 | authentication: b.authentication, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /backend/stat.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/smallfz/libnfs-go/fs" 8 | "github.com/smallfz/libnfs-go/log" 9 | "github.com/smallfz/libnfs-go/nfs" 10 | ) 11 | 12 | type openedFile struct { 13 | f fs.File 14 | pathName string 15 | lastAccessTime *time.Time 16 | } 17 | 18 | func (f *openedFile) File() fs.File { 19 | return f.f 20 | } 21 | 22 | func (f *openedFile) Path() string { 23 | return f.pathName 24 | } 25 | 26 | type Stat struct { 27 | lck sync.RWMutex 28 | current nfs.FileHandle4 29 | handleStack []nfs.FileHandle4 30 | 31 | clientId uint64 32 | 33 | openedFiles map[uint32]*openedFile // stateid4.seqid => *openedFile 34 | 35 | seqId uint32 36 | } 37 | 38 | func (t *Stat) SetCurrentHandle(fh nfs.FileHandle4) { 39 | t.current = fh 40 | } 41 | 42 | func (t *Stat) CurrentHandle() nfs.FileHandle4 { 43 | t.lck.Lock() 44 | defer t.lck.Unlock() 45 | 46 | if t.current == nil { 47 | t.current = []byte{} 48 | } 49 | return t.current 50 | } 51 | 52 | func (t *Stat) PopHandle() (nfs.FileHandle4, bool) { 53 | t.lck.Lock() 54 | defer t.lck.Unlock() 55 | 56 | if len(t.handleStack) == 0 { 57 | return nil, false 58 | } 59 | 60 | size := len(t.handleStack) 61 | last := t.handleStack[size-1] 62 | t.handleStack = t.handleStack[:size-1] 63 | return last, true 64 | } 65 | 66 | func (t *Stat) PeekHandle() (nfs.FileHandle4, bool) { 67 | t.lck.Lock() 68 | defer t.lck.Unlock() 69 | 70 | if len(t.handleStack) == 0 { 71 | return nil, false 72 | } 73 | 74 | size := len(t.handleStack) 75 | return t.handleStack[size-1], true 76 | } 77 | 78 | func (t *Stat) PushHandle(item nfs.FileHandle4) { 79 | t.lck.Lock() 80 | defer t.lck.Unlock() 81 | 82 | t.handleStack = append(t.handleStack, item) // append handles the fact that t.handleStack may be nil 83 | } 84 | 85 | func (t *Stat) SetClientId(clientId uint64) { 86 | t.lck.Lock() 87 | defer t.lck.Unlock() 88 | 89 | t.clientId = clientId 90 | } 91 | 92 | func (t *Stat) ClientId() (uint64, bool) { 93 | return t.clientId, t.clientId > 0 94 | } 95 | 96 | func (t *Stat) nextSeqId() uint32 { 97 | if t.seqId <= 0 { 98 | t.seqId = 1000 99 | } 100 | t.seqId++ 101 | return t.seqId 102 | } 103 | 104 | func (t *Stat) AddOpenedFile(pathName string, f fs.File) uint32 { 105 | t.lck.Lock() 106 | defer t.lck.Unlock() 107 | 108 | if t.openedFiles == nil { 109 | t.openedFiles = map[uint32]*openedFile{} 110 | } 111 | seqId := t.nextSeqId() 112 | now := time.Now() 113 | t.openedFiles[seqId] = &openedFile{ 114 | pathName: pathName, 115 | f: f, 116 | lastAccessTime: &now, 117 | } 118 | return seqId 119 | } 120 | 121 | func (t *Stat) GetOpenedFile(seqId uint32) fs.FileOpenState { 122 | t.lck.RLock() 123 | defer t.lck.RUnlock() 124 | 125 | if t.openedFiles != nil { 126 | if of, found := t.openedFiles[seqId]; found { 127 | return of 128 | } 129 | } 130 | return nil 131 | } 132 | 133 | func (t *Stat) FindOpenedFiles(pathName string) []fs.FileOpenState { 134 | t.lck.RLock() 135 | defer t.lck.RUnlock() 136 | 137 | rs := []fs.FileOpenState{} 138 | if t.openedFiles != nil { 139 | for _, of := range t.openedFiles { 140 | if of.pathName == pathName { 141 | rs = append(rs, of) 142 | } 143 | } 144 | } 145 | 146 | return rs 147 | } 148 | 149 | func (t *Stat) RemoveOpenedFile(seqId uint32) fs.FileOpenState { 150 | t.lck.Lock() 151 | defer t.lck.Unlock() 152 | 153 | if t.openedFiles != nil { 154 | if of, found := t.openedFiles[seqId]; found { 155 | delete(t.openedFiles, seqId) 156 | return of 157 | } 158 | } 159 | return nil 160 | } 161 | 162 | func (t *Stat) CloseAndRemoveStallFiles() { 163 | t.lck.Lock() 164 | defer t.lck.Unlock() 165 | 166 | ttl := time.Minute * 5 167 | now := time.Now() 168 | seqs := []uint32{} 169 | for seqId, f := range t.openedFiles { 170 | if f.lastAccessTime.Add(ttl).Before(now) { 171 | seqs = append(seqs, seqId) 172 | if err := f.f.Close(); err != nil { 173 | log.Warnf("f.Close: %v", err) 174 | } 175 | } 176 | } 177 | 178 | if len(seqs) <= 0 { 179 | return 180 | } 181 | 182 | for _, seqId := range seqs { 183 | delete(t.openedFiles, seqId) 184 | } 185 | } 186 | 187 | func (t *Stat) CleanUp() { 188 | t.lck.Lock() 189 | defer t.lck.Unlock() 190 | 191 | log.Debugf("stat: cleanup()") 192 | t.current = []byte{} 193 | 194 | if t.handleStack != nil { 195 | t.handleStack = t.handleStack[0:0] 196 | } 197 | 198 | if t.openedFiles != nil { 199 | for _, of := range t.openedFiles { 200 | of.f.Close() 201 | } 202 | t.openedFiles = nil 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | proxy/proxy -------------------------------------------------------------------------------- /examples/client-container/.gitignore: -------------------------------------------------------------------------------- 1 | NFStest* 2 | *.gz 3 | *.zip 4 | -------------------------------------------------------------------------------- /examples/client-container/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM --platform=linux/amd64 centos:7 3 | 4 | RUN [ -d /mnt ] || mkdir -p /mnt 5 | 6 | RUN yum -y update 7 | RUN yum install -y nfs-common rpcbind 8 | RUN yum install -y python 9 | # RUN yum install -y iproute2 10 | # RUN yum install -y iptables 11 | # RUN yum install -y iputils-ping 12 | RUN yum install -y nfs-utils 13 | 14 | RUN yum install -y python3 15 | RUN yum install -y krb5-devel python3-devel swig python3-gssapi python3-ply 16 | RUN yum install -y unzip 17 | RUN pip3 install ply 18 | 19 | ADD NFStest-1.0.10.tar.gz /opt 20 | RUN cd /opt/NFStest-1.0.10 && python setup.py install 21 | 22 | ADD pynfs-master.zip /opt 23 | RUN cd /opt && unzip pynfs-master.zip 24 | RUN cd /opt/pynfs-master && python3 setup.py install 25 | 26 | RUN [ -d /run/sendsigs.omit.d ] || mkdir -p /run/sendsigs.omit.d 27 | 28 | COPY entrypoint.sh / 29 | ENTRYPOINT ["/entrypoint.sh"] 30 | 31 | # mount -o port=2049,mountport=2049,nfsvers=4,minorversion=0,noacl,tcp -t nfs nfs-server:/ /mnt -v 32 | 33 | # ./nfstest_posix -s nfs-server --port 2049 --nfsversion=4 --minorversion=0 -o "port=2049,mountport=2049,nfsvers=4,minorversion=0,noacl,tcp" -m /mnt -v debug --runtest open 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/client-container/build-image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | docker build -t centos:18-nfs-cl . 5 | -------------------------------------------------------------------------------- /examples/client-container/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | # service rpcbind start 6 | 7 | exec "$@" 8 | 9 | -------------------------------------------------------------------------------- /examples/client-container/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker run --rm -ti --net nfs_net --platform=linux/amd64/v8 --privileged -v `pwd`:/data centos:18-nfs-cl /bin/bash 4 | -------------------------------------------------------------------------------- /examples/client-container/tests/test_open.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import unittest 4 | 5 | 6 | fpath = '/mnt/demo.dat' 7 | content = '0123456789abcdef' 8 | 9 | 10 | class TestCaseFileOpers(unittest.TestCase): 11 | 12 | def setUp(self): 13 | if os.path.isfile(fpath): 14 | os.unlink(fpath) 15 | f = open(fpath, 'wb') 16 | f.write(content) 17 | f.close() 18 | 19 | def tearDown(self): 20 | if os.path.isfile(fpath): 21 | os.unlink(fpath) 22 | 23 | def test_append(self): 24 | fd = os.open(fpath, os.O_APPEND|os.O_RDWR) 25 | os.write(fd, '____') 26 | os.close(fd) 27 | 28 | f = open(fpath, 'rb') 29 | dat = f.read() 30 | f.close() 31 | 32 | self.assertEqual(dat.decode('utf-8'), content + '____') 33 | 34 | def test_trunc(self): 35 | fd = os.open(fpath, os.O_TRUNC|os.O_RDWR) 36 | os.write(fd, '++++') 37 | os.close(fd) 38 | 39 | f = open(fpath, 'rb') 40 | dat = f.read() 41 | f.close() 42 | 43 | self.assertEqual(dat.decode('utf-8'), '++++') 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() 48 | 49 | -------------------------------------------------------------------------------- /examples/client.py: -------------------------------------------------------------------------------- 1 | 2 | import xdrlib 3 | import socket 4 | import struct 5 | 6 | 7 | def main(): 8 | addr = ('10.189.28.93', 29671) 9 | # addr = ('localhost', 29671) 10 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 11 | sock.connect(addr) 12 | try: 13 | pkr = xdrlib.Packer() 14 | xid = 0 15 | pkr.pack_uint(xid) # xid 16 | pkr.pack_uint(0) # rpc-type-call 17 | pkr.pack_uint(2) # rpc-ver 18 | pkr.pack_uint(100003) # nfs-program 19 | pkr.pack_uint(4) # nfs-ver 20 | 21 | # COMPOUND4args 22 | pkr.pack_string('readdir') # tag 23 | pkr.pack_uint(0) # minor-version 24 | pkr.pack_uint(1) # count of ops 25 | 26 | # op: readdir 27 | pkr.pack_uint(26) # OP_READDIR 28 | pkr.pack_uhyper(0) # cookie 29 | pkr.pack_uhyper(0) # cookie-verf 30 | pkr.pack_uint(32 * 1024) 31 | pkr.pack_uint(32 * 1024) 32 | 33 | # op: readdir: attr-request, bitmap4 34 | pkr.pack_uint(1) 35 | pkr.pack_uint(1 | (1<<1) | (1<<4) | (1<<7) | (1<<19)) 36 | 37 | req = pkr.get_buffer() 38 | frag = (1 << 31) | len(req) 39 | frag = struct.pack('>I', frag) 40 | 41 | print(frag) 42 | sock.sendall(frag) 43 | sock.sendall(req) 44 | 45 | frag = struct.unpack('>I', sock.recv(4))[0] 46 | size = ((1 << 31) - 1) & frag 47 | print(bin(frag)) 48 | print('response header size: %d' % size) 49 | dat = sock.recv(size) 50 | 51 | offset = 0 52 | while True: 53 | iv = dat[offset:offset+4] 54 | if not iv: 55 | break 56 | offset += len(iv) 57 | print(' '.join([bin(ord(c)) for c in iv])) 58 | # print('%x' % dat) 59 | # upkr = xdrlib.Unpacker(dat) 60 | # print('NFS4_OK: %d' % upkr.unpack_uint()) 61 | # print(upkr.unpack_uint()) 62 | # print(upkr.unpack_uint()) 63 | # print(upkr.unpack_uint()) 64 | # print(upkr.unpack_uint()) 65 | # print(upkr.unpack_uint64()) 66 | finally: 67 | if sock: 68 | sock.close() 69 | 70 | 71 | 72 | if __name__ == '__main__': 73 | main() 74 | # pkr = xdrlib.Packer() 75 | # pkr.pack_string(b'123') 76 | # print(bytes(pkr.get_buffer())) 77 | 78 | -------------------------------------------------------------------------------- /examples/proxy/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | GOOS=linux GOARCh=amd64 go build -o proxy 5 | docker run --rm -ti --net nfs_net --name proxy --platform linux/amd64 -v `pwd`:/opt/nfs -w /opt/nfs centos:7 ./proxy 6 | rm -f ./proxy 7 | -------------------------------------------------------------------------------- /examples/server/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | server 3 | server_linux 4 | server_darwin 5 | server.exe 6 | 7 | -------------------------------------------------------------------------------- /examples/server/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # NFS Server Example 4 | 5 | To launch a test server with a builtin in-memory filesystem: 6 | 7 | go run . 8 | 9 | 10 | Then mount it: 11 | 12 | on Mac: 13 | 14 | mount -o nfsvers=4,noacl,tcp -t nfs localhost:/ /Users/smallfz/mnt 15 | 16 | on Linux: 17 | 18 | mount -o nfsvers=4,minorversion=0,noacl,tcp -t nfs localhost:/ /mnt 19 | 20 | -------------------------------------------------------------------------------- /examples/server/create_vnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker network create -d bridge --attachable --subnet 10.9.1.0/24 --gateway 10.9.1.1 nfs_net 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/smallfz/libnfs-go/auth" 9 | "github.com/smallfz/libnfs-go/backend" 10 | "github.com/smallfz/libnfs-go/fs" 11 | "github.com/smallfz/libnfs-go/log" 12 | "github.com/smallfz/libnfs-go/memfs" 13 | "github.com/smallfz/libnfs-go/server" 14 | ) 15 | 16 | func main() { 17 | listen := ":2049" 18 | flag.StringVar(&listen, "l", listen, "Server listen address") 19 | flag.Parse() 20 | 21 | log.UpdateLevel(log.DEBUG) 22 | 23 | mfs := memfs.NewMemFS() 24 | 25 | // We don't need to create a new fs for each connection as memfs is opaque towards SetCreds. 26 | // If the file system would depend on SetCreds, make sure to generate a new fs.FS for each connection. 27 | backend := backend.New(func() fs.FS { return mfs }, auth.Null) 28 | 29 | mfs.MkdirAll("/mount", os.FileMode(0o755)) 30 | mfs.MkdirAll("/test", os.FileMode(0o755)) 31 | mfs.MkdirAll("/test2", os.FileMode(0o755)) 32 | mfs.MkdirAll("/many", os.FileMode(0o755)) 33 | 34 | perm := os.FileMode(0o755) 35 | for i := 0; i < 256; i++ { 36 | mfs.MkdirAll(fmt.Sprintf("/many/sub-%d", i+1), perm) 37 | } 38 | 39 | svr, err := server.NewServerTCP(listen, backend) 40 | if err != nil { 41 | log.Errorf("server.NewServerTCP: %v", err) 42 | return 43 | } 44 | 45 | if err := svr.Serve(); err != nil { 46 | log.Errorf("svr.Serve: %v", err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/server/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | GOOS=linux GOARCH=amd64 go build -o server_linux 5 | docker run --rm -ti --net nfs_net --name nfs-server -p 2049:2049 --platform linux/amd64 -v `pwd`:/opt/nfs -w /opt/nfs centos:7 ./server_linux 6 | -------------------------------------------------------------------------------- /fs/fs.go: -------------------------------------------------------------------------------- 1 | // Backend interface. To build a customized NFS server you need to implement these. 2 | package fs 3 | 4 | import ( 5 | "io" 6 | "os" 7 | "time" 8 | ) 9 | 10 | type Creds interface { 11 | Host() string 12 | Uid() uint32 13 | Gid() uint32 14 | Groups() []uint32 15 | } 16 | 17 | type FileInfo interface { 18 | os.FileInfo 19 | ATime() time.Time 20 | CTime() time.Time 21 | NumLinks() int 22 | } 23 | 24 | type File interface { 25 | Name() string 26 | Stat() (FileInfo, error) 27 | io.ReadWriteCloser 28 | io.Seeker 29 | Truncate() error 30 | Sync() error 31 | Readdir(int) ([]FileInfo, error) 32 | } 33 | 34 | type WithId interface { 35 | Id() uint64 36 | } 37 | 38 | // FS is the most essential interface that need to be implemeted in a derived nfs server. 39 | type FS interface { 40 | // SetCreds is called before all other methods to indicate the credentials of the client. 41 | SetCreds(Creds) 42 | 43 | Open(string) (File, error) 44 | OpenFile(string, int, os.FileMode) (File, error) 45 | Stat(string) (FileInfo, error) 46 | Chmod(string, os.FileMode) error 47 | Chown(string, int, int) error 48 | Symlink(string, string) error 49 | Readlink(string) (string, error) 50 | Link(string, string) error 51 | Rename(string, string) error 52 | Remove(string) error 53 | MkdirAll(string, os.FileMode) error 54 | 55 | // GetFileId returns an unique id of the file in implementing. 56 | GetFileId(FileInfo) uint64 57 | 58 | // GetRootHandle returns the handle of the root node. 59 | GetRootHandle() []byte 60 | 61 | // GetHandle returns the handle of the specified file. 62 | GetHandle(FileInfo) ([]byte, error) 63 | 64 | // ResolveHandle translate the giving handle to full path of the corresponding file. 65 | ResolveHandle([]byte) (string, error) 66 | 67 | // Attributes returns the FS' attributes that can be edited. 68 | Attributes() *Attributes 69 | } 70 | 71 | type FSWithId interface { 72 | FS 73 | WithId 74 | } 75 | 76 | type AllowLink interface { 77 | Lstat(string) (FileInfo, error) 78 | Symlink(string, string) error 79 | } 80 | 81 | // https://datatracker.ietf.org/doc/html/rfc7530#section-5.6 82 | type Attributes struct { 83 | LinkSupport bool // id: 5 84 | SymlinkSupport bool // id: 6 85 | ChownRestricted bool // id: 18 86 | MaxName uint32 // id: 29 87 | MaxRead uint64 // id: 30 88 | MaxWrite uint64 // id: 31 89 | NoTrunc bool // id: 34 90 | } 91 | -------------------------------------------------------------------------------- /fs/open_state.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | type FileOpenState interface { 4 | File() File 5 | Path() string 6 | } 7 | -------------------------------------------------------------------------------- /fs/path.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "path" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | SP = "/" 10 | ROOT = "/" 11 | ) 12 | 13 | func Abs(name string) string { 14 | name = path.Clean(strings.Trim(name, "\x00")) 15 | if len(name) <= 0 || name == "." { 16 | name = ROOT 17 | } 18 | if !path.IsAbs(name) { 19 | name = path.Join(ROOT, name) 20 | } 21 | return name 22 | } 23 | 24 | func Join(parts ...string) string { 25 | return path.Join(parts...) 26 | } 27 | 28 | func Dir(name string) string { 29 | return path.Dir(name) 30 | } 31 | 32 | func Base(name string) string { 33 | return path.Base(name) 34 | } 35 | 36 | func BreakAll(name string) []string { 37 | name = Abs(name) 38 | parts := []string{} 39 | for _, part := range strings.Split(name, SP) { 40 | if len(part) > 0 { 41 | parts = append(parts, part) 42 | } 43 | } 44 | return parts 45 | } 46 | -------------------------------------------------------------------------------- /fs/path_test.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAbs(t *testing.T) { 8 | grantedCases := [][]string{ 9 | {"", "/"}, 10 | {".", "/"}, 11 | {"/", "/"}, 12 | {"a/bc/def", "/a/bc/def"}, 13 | {"/a/bc", "/a/bc"}, 14 | {"abc", "/abc"}, 15 | {"./abc", "/abc"}, 16 | {"../abc", "/abc"}, 17 | {"/../abc", "/abc"}, 18 | {"./../abc", "/abc"}, 19 | {"./../abc/def", "/abc/def"}, 20 | } 21 | 22 | for _, row := range grantedCases { 23 | input := row[0] 24 | output := Abs(input) 25 | if output != row[1] { 26 | t.Fatalf("expects `%s` but get `%s`.", row[1], output) 27 | } 28 | } 29 | } 30 | 31 | func TestPathJoin(t *testing.T) { 32 | grantedCases := [][]string{ 33 | {"", "/", "/"}, 34 | {"abc", "/def", "/abc/def"}, 35 | {"/abc", "/def", "/abc/def"}, 36 | {"/abc", "def", "/abc/def"}, 37 | {"/abc", "def", "/ijk", "/abc/def/ijk"}, 38 | } 39 | 40 | for _, row := range grantedCases { 41 | if len(row) < 3 { 42 | continue 43 | } 44 | output := Abs(Join(row[:len(row)-1]...)) 45 | expect := row[len(row)-1] 46 | if output != expect { 47 | t.Fatalf("expects `%s` but get `%s`.", expect, output) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /fs/storage.go: -------------------------------------------------------------------------------- 1 | package fs 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type StorageNode interface { 8 | Id() uint64 9 | Reader() io.Reader 10 | Size() int 11 | } 12 | 13 | type Storage interface { 14 | Create(io.Reader) (uint64, error) 15 | Update(uint64, io.Reader) (bool, error) 16 | Delete(uint64) bool 17 | Get(uint64) StorageNode 18 | Size(uint64) int 19 | } 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/smallfz/libnfs-go 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /log/format.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | ) 7 | 8 | var ptVar = regexp.MustCompile(`(?is)\$(\w+[*]?)`) 9 | 10 | func formatMessage(format string, msg *Message) string { 11 | return ptVar.ReplaceAllStringFunc(format, func(match string) string { 12 | key := match[1:] 13 | switch key { 14 | case "name": 15 | return msg.LoggerName 16 | case "message": 17 | return msg.Message 18 | case "lev": 19 | return GetLevelName(msg.Lev) 20 | case "lev*": 21 | return GetLevelNameColored(msg.Lev) 22 | case "mod": 23 | return msg.Mod 24 | case "filename": 25 | return msg.FileName 26 | case "lineno": 27 | return fmt.Sprintf("%d", msg.LineNo) 28 | } 29 | return match 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /log/func.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "path" 5 | "runtime" 6 | "strings" 7 | ) 8 | 9 | type funcInfo struct { 10 | mod string 11 | fileName string 12 | line int 13 | } 14 | 15 | func getFuncInfo() *funcInfo { 16 | fi := &funcInfo{} 17 | _, fileThis, _, ok := runtime.Caller(0) 18 | if !ok { 19 | return fi 20 | } 21 | folder := path.Dir(fileThis) 22 | pc := make([]uintptr, 10) 23 | n := runtime.Callers(0, pc) 24 | if n == 0 { 25 | return fi 26 | } 27 | pc = pc[:n] 28 | frames := runtime.CallersFrames(pc) 29 | foundThis := false 30 | for { 31 | frame, _ := frames.Next() 32 | frameFolder := path.Dir(frame.File) 33 | if !foundThis && frameFolder == folder { 34 | foundThis = true 35 | continue 36 | } 37 | if foundThis && frameFolder != folder { 38 | funcName := frame.Function 39 | parts := strings.Split(funcName, "/") 40 | lastPart := parts[len(parts)-1] 41 | lastPartArr := strings.Split(lastPart, ".") 42 | modName := lastPartArr[0] 43 | parts = parts[:len(parts)-1] 44 | parts = append(parts, modName) 45 | modPath := strings.Join(parts, "/") 46 | fi.mod = modPath 47 | fi.fileName = path.Base(frame.File) 48 | fi.line = frame.Line 49 | break 50 | } 51 | } 52 | return fi 53 | } 54 | -------------------------------------------------------------------------------- /log/handler.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Handler interface { 4 | Write(*Message) 5 | } 6 | -------------------------------------------------------------------------------- /log/handler_stdout.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | type defaultHandler struct { 9 | format string 10 | logger *log.Logger 11 | } 12 | 13 | func (h *defaultHandler) Write(msg *Message) { 14 | h.logger.Printf(formatMessage(h.format, msg)) 15 | } 16 | 17 | func DefaultHandler() Handler { 18 | return &defaultHandler{ 19 | format: "[$mod] <$filename:$lineno> $lev* $message", 20 | logger: log.New(os.Stdout, "", log.Flags()), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /log/levels.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | const ( 8 | CRITICAL = 2 9 | ERROR = 3 10 | WARNING = 4 11 | INFO = 6 12 | DEBUG = 7 13 | NOTSET = 8 14 | ) 15 | 16 | var idx = map[int]string{ 17 | CRITICAL: "critical", 18 | ERROR: "error", 19 | WARNING: "warning", 20 | INFO: "info", 21 | DEBUG: "debug", 22 | } 23 | 24 | func GetLevelName(lev int) string { 25 | if name, found := idx[lev]; found { 26 | return name 27 | } 28 | return "" 29 | } 30 | 31 | func GetLevelNameColored(lev int) string { 32 | switch lev { 33 | case CRITICAL: 34 | return "\u001b[31mCRI\u001b[0m" 35 | case ERROR: 36 | return "\u001b[31;1mERR\u001b[0m" 37 | case WARNING: 38 | return "\u001b[33;1mWRN\u001b[0m" 39 | case INFO: 40 | return "\u001b[32;1mINF\u001b[0m" 41 | case DEBUG: 42 | return "\u001b[30;1mDBG\u001b[0m" 43 | } 44 | return "" 45 | } 46 | 47 | func GetLevel(name string) int { 48 | for lev, lname := range idx { 49 | if strings.EqualFold(name, lname) { 50 | return lev 51 | } 52 | } 53 | return NOTSET 54 | } 55 | -------------------------------------------------------------------------------- /log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | var defaultLogger Logger = &LoggerBuiltin{ 4 | Lev: NOTSET, 5 | Handlers: []Handler{ 6 | DefaultHandler(), 7 | }, 8 | } 9 | 10 | func Level() int { 11 | return defaultLogger.Level() 12 | } 13 | 14 | func UpdateLevel(level int) { 15 | defaultLogger.SetLevel(level) 16 | } 17 | 18 | func SetLevel(level int) { 19 | defaultLogger.SetLevel(level) 20 | } 21 | 22 | func SetLevelName(levelName string) { 23 | lev := GetLevel(levelName) 24 | defaultLogger.SetLevel(lev) 25 | } 26 | 27 | func Print(v ...interface{}) { 28 | defaultLogger.Print(NOTSET, v...) 29 | } 30 | 31 | func Printf(format string, v ...interface{}) { 32 | defaultLogger.Printf(NOTSET, format, v...) 33 | } 34 | 35 | func Println(v ...interface{}) { 36 | defaultLogger.Println(NOTSET, v...) 37 | } 38 | 39 | func Debug(v ...interface{}) { 40 | defaultLogger.Debug(v...) 41 | } 42 | 43 | func Debugf(format string, v ...interface{}) { 44 | defaultLogger.Debugf(format, v...) 45 | } 46 | 47 | func Error(v ...interface{}) { 48 | defaultLogger.Error(v...) 49 | } 50 | 51 | func Errorf(format string, v ...interface{}) { 52 | defaultLogger.Errorf(format, v...) 53 | } 54 | 55 | func Warning(v ...interface{}) { 56 | defaultLogger.Warning(v...) 57 | } 58 | 59 | func Warningf(format string, v ...interface{}) { 60 | defaultLogger.Warningf(format, v...) 61 | } 62 | 63 | func Warn(v ...interface{}) { 64 | defaultLogger.Warn(v...) 65 | } 66 | 67 | func Warnf(format string, v ...interface{}) { 68 | defaultLogger.Warnf(format, v...) 69 | } 70 | 71 | func Info(v ...interface{}) { 72 | defaultLogger.Info(v...) 73 | } 74 | 75 | func Infof(format string, v ...interface{}) { 76 | defaultLogger.Infof(format, v...) 77 | } 78 | 79 | // ---- 80 | 81 | func SetLoggerDefault(l Logger) { 82 | defaultLogger = l 83 | } 84 | 85 | func GetLoggerDefault() Logger { 86 | return defaultLogger 87 | } 88 | 89 | func GetLogger(name string) Logger { 90 | return NewLogger(name, NOTSET, DefaultHandler()) 91 | } 92 | 93 | func NewLogger(name string, level int, handler Handler) Logger { 94 | handlers := []Handler{} 95 | if handler != nil { 96 | handlers = append(handlers, handler) 97 | } 98 | return &LoggerBuiltin{ 99 | Lev: level, 100 | Name: name, 101 | Handlers: handlers, 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /log/logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Logger interface { 4 | Level() int 5 | SetLevel(level int) 6 | 7 | Print(level int, v ...interface{}) 8 | Printf(level int, format string, v ...interface{}) 9 | Println(level int, v ...interface{}) 10 | 11 | Debug(v ...interface{}) 12 | Debugf(format string, v ...interface{}) 13 | 14 | Error(v ...interface{}) 15 | Errorf(format string, v ...interface{}) 16 | 17 | Warning(v ...interface{}) 18 | Warningf(format string, v ...interface{}) 19 | 20 | Warn(v ...interface{}) 21 | Warnf(format string, v ...interface{}) 22 | 23 | Info(v ...interface{}) 24 | Infof(format string, v ...interface{}) 25 | } 26 | -------------------------------------------------------------------------------- /log/logger_builtin.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // --- 8 | 9 | type LoggerBuiltin struct { 10 | Lev int 11 | Name string 12 | Handlers []Handler 13 | } 14 | 15 | func (l *LoggerBuiltin) makeMessage(lev int, body string) *Message { 16 | fi := getFuncInfo() 17 | return &Message{ 18 | LoggerName: l.Name, 19 | Message: body, 20 | Lev: lev, 21 | Mod: fi.mod, 22 | FileName: fi.fileName, 23 | LineNo: fi.line, 24 | } 25 | } 26 | 27 | func (l *LoggerBuiltin) Level() int { 28 | return l.Lev 29 | } 30 | 31 | func (l *LoggerBuiltin) SetLevel(level int) { 32 | l.Lev = level 33 | } 34 | 35 | func (l *LoggerBuiltin) Print(lev int, v ...interface{}) { 36 | if lev > l.Lev { 37 | return 38 | } 39 | body := fmt.Sprintln(v...) 40 | msg := l.makeMessage(lev, body) 41 | for _, h := range l.Handlers { 42 | h.Write(msg) 43 | } 44 | } 45 | 46 | func (l *LoggerBuiltin) Printf(lev int, format string, v ...interface{}) { 47 | if lev > l.Lev { 48 | return 49 | } 50 | body := fmt.Sprintf(format, v...) 51 | msg := l.makeMessage(lev, body) 52 | for _, h := range l.Handlers { 53 | h.Write(msg) 54 | } 55 | } 56 | 57 | func (l *LoggerBuiltin) Println(lev int, v ...interface{}) { 58 | if lev > l.Lev { 59 | return 60 | } 61 | body := fmt.Sprintln(v...) 62 | msg := l.makeMessage(lev, body) 63 | for _, h := range l.Handlers { 64 | h.Write(msg) 65 | } 66 | } 67 | 68 | func (l *LoggerBuiltin) Debug(v ...interface{}) { 69 | l.Print(DEBUG, v...) 70 | } 71 | 72 | func (l *LoggerBuiltin) Debugf(format string, v ...interface{}) { 73 | l.Printf(DEBUG, format, v...) 74 | } 75 | 76 | func (l *LoggerBuiltin) Error(v ...interface{}) { 77 | l.Print(ERROR, v...) 78 | } 79 | 80 | func (l *LoggerBuiltin) Errorf(format string, v ...interface{}) { 81 | l.Printf(ERROR, format, v...) 82 | } 83 | 84 | func (l *LoggerBuiltin) Warning(v ...interface{}) { 85 | l.Print(WARNING, v...) 86 | } 87 | 88 | func (l *LoggerBuiltin) Warningf(format string, v ...interface{}) { 89 | l.Printf(WARNING, format, v...) 90 | } 91 | 92 | func (l *LoggerBuiltin) Warn(v ...interface{}) { 93 | l.Print(WARNING, v...) 94 | } 95 | 96 | func (l *LoggerBuiltin) Warnf(format string, v ...interface{}) { 97 | l.Printf(WARNING, format, v...) 98 | } 99 | 100 | func (l *LoggerBuiltin) Info(v ...interface{}) { 101 | l.Print(INFO, v...) 102 | } 103 | 104 | func (l *LoggerBuiltin) Infof(format string, v ...interface{}) { 105 | l.Printf(INFO, format, v...) 106 | } 107 | -------------------------------------------------------------------------------- /log/message.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | type Message struct { 4 | LoggerName string 5 | Message string 6 | Lev int 7 | Mod string 8 | FileName string 9 | LineNo int 10 | } 11 | -------------------------------------------------------------------------------- /memfs/buffer.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "os" 7 | "sync" 8 | ) 9 | 10 | type Buffer struct { 11 | data []byte 12 | cur int 13 | closed bool 14 | lck *sync.RWMutex 15 | } 16 | 17 | func NewBuffer(src []byte) *Buffer { 18 | return &Buffer{data: src} 19 | } 20 | 21 | func (b *Buffer) init() { 22 | if b.lck == nil { 23 | b.lck = &sync.RWMutex{} 24 | } 25 | 26 | b.lck.Lock() 27 | defer b.lck.Unlock() 28 | 29 | if b.data == nil { 30 | b.data = []byte{} 31 | b.cur = 0 32 | } 33 | } 34 | 35 | func (b *Buffer) Size() int64 { 36 | b.init() 37 | b.lck.RLock() 38 | defer b.lck.RUnlock() 39 | return int64(b.size()) 40 | } 41 | 42 | func (b *Buffer) size() int { 43 | if b.data != nil { 44 | return len(b.data) 45 | } 46 | return 0 47 | } 48 | 49 | func (b *Buffer) Bytes() []byte { 50 | b.init() 51 | b.lck.RLock() 52 | defer b.lck.RUnlock() 53 | return b.data[:] 54 | } 55 | 56 | func (b *Buffer) Seek(offset int64, whence int) (int64, error) { 57 | b.init() 58 | b.lck.Lock() 59 | defer b.lck.Unlock() 60 | 61 | if b.closed { 62 | return 0, io.EOF 63 | } 64 | 65 | size := b.size() 66 | cur := b.cur 67 | 68 | switch whence { 69 | case io.SeekStart: 70 | if offset == 0 && b.cur == 0 { 71 | return 0, nil 72 | } 73 | cur = int(offset) 74 | break 75 | 76 | case io.SeekCurrent: 77 | cur = b.cur + int(offset) 78 | break 79 | 80 | case io.SeekEnd: 81 | cur = size + int(offset) 82 | break 83 | } 84 | 85 | if cur < 0 { 86 | return int64(b.cur), errors.New("invalid seeking position") 87 | } 88 | 89 | b.cur = cur 90 | return int64(b.cur), nil 91 | } 92 | 93 | func (b *Buffer) Write(dat []byte) (int, error) { 94 | if dat == nil || len(dat) == 0 { 95 | return 0, nil 96 | } 97 | 98 | b.init() 99 | b.lck.Lock() 100 | defer b.lck.Unlock() 101 | 102 | if b.closed { 103 | return 0, io.EOF 104 | } 105 | 106 | // extend 107 | extend := b.cur + len(dat) - b.size() 108 | if extend > 0 { 109 | b.data = append(b.data, make([]byte, extend)...) 110 | } 111 | 112 | // write 113 | count := copy(b.data[b.cur:], dat) 114 | b.cur += count 115 | 116 | return count, nil 117 | } 118 | 119 | func (b *Buffer) Read(dat []byte) (int, error) { 120 | if dat == nil || len(dat) == 0 { 121 | return 0, nil 122 | } 123 | 124 | b.init() 125 | b.lck.Lock() 126 | defer b.lck.Unlock() 127 | 128 | if b.closed { 129 | return 0, io.EOF 130 | } 131 | 132 | size := b.size() 133 | if b.cur >= size { 134 | return 0, io.EOF 135 | } 136 | 137 | end := b.cur + len(dat) 138 | if end > b.size() { 139 | end = b.size() 140 | } 141 | 142 | count := copy(dat, b.data[b.cur:end]) 143 | b.cur = end 144 | 145 | return count, nil 146 | } 147 | 148 | func (b *Buffer) Truncate() { 149 | b.init() 150 | b.lck.Lock() 151 | defer b.lck.Unlock() 152 | 153 | if b.closed { 154 | return 155 | } 156 | 157 | size := b.size() 158 | if size <= 0 { 159 | return 160 | } 161 | 162 | if b.data != nil { 163 | b.data = b.data[0:b.cur] 164 | } 165 | } 166 | 167 | func (b *Buffer) Close() error { 168 | b.init() 169 | b.lck.Lock() 170 | defer b.lck.Unlock() 171 | 172 | if b.closed { 173 | return os.ErrClosed 174 | } 175 | 176 | if len(b.data) > 0 { 177 | b.data = b.data[0:0] 178 | } 179 | b.cur = 0 180 | 181 | b.closed = true 182 | 183 | return nil 184 | } 185 | -------------------------------------------------------------------------------- /memfs/buffer_test.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "math/rand" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestBufferReadingCopyN(t *testing.T) { 13 | src := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 14 | buff := NewBuffer(src) 15 | 16 | input := bytes.NewBuffer([]byte{200, 201, 202}) 17 | 18 | if size, err := io.CopyN(buff, input, 3); err != nil { 19 | t.Fatalf("io.CopyN: %v", err) 20 | } else if size != 3 { 21 | t.Fatalf("unexpected copied size %d.", size) 22 | } 23 | } 24 | 25 | func TestBufferReading(t *testing.T) { 26 | src := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 27 | buff := NewBuffer(src) 28 | 29 | if _, err := buff.Seek(2, io.SeekStart); err != nil { 30 | t.Fatalf("buff.Seek: %v", err) 31 | return 32 | } 33 | 34 | dat := make([]byte, 3) 35 | 36 | if size, err := buff.Read(dat); err != nil { 37 | t.Fatalf("buff.Read: %v", err) 38 | } else if size != len(dat) { 39 | t.Fatalf( 40 | "unexpected size %d returned from Read. expects %d.", 41 | size, len(dat), 42 | ) 43 | } else if !bytes.Equal(dat, src[2:5]) { 44 | t.Fatalf("unexpected reading result: %v", dat) 45 | } 46 | } 47 | 48 | func TestBufferSeekingHole(t *testing.T) { 49 | buff := NewBuffer([]byte{}) 50 | buff.Write([]byte("0123456789")) 51 | 52 | buff.Seek(4, io.SeekStart) 53 | buff.Write([]byte("____")) 54 | 55 | rs := string(buff.Bytes()) 56 | want := "0123____89" 57 | if rs != want { 58 | t.Fatalf("expects '%s' but gets '%s'.", want, rs) 59 | return 60 | } 61 | 62 | buff.Seek(20, io.SeekStart) 63 | buff.Write([]byte("AaAa")) 64 | 65 | fmt.Printf("%v", buff.Bytes()) 66 | 67 | buff.Seek(10, io.SeekStart) 68 | buff.Write([]byte("-+-+-+****")) 69 | 70 | rs = string(buff.Bytes()) 71 | want = "0123____89-+-+-+****AaAa" 72 | if rs != want { 73 | t.Fatalf("expects '%s' but gets '%s'.", want, rs) 74 | return 75 | } 76 | } 77 | 78 | type writingBlock struct { 79 | offset int64 80 | data []byte 81 | } 82 | 83 | func TestBufferSeekingMultipleTimes(t *testing.T) { 84 | blocks := []*writingBlock{ 85 | {offset: 0, data: []byte("0123")}, 86 | {offset: 4, data: []byte("4567")}, 87 | {offset: 8, data: []byte("89ab")}, 88 | {offset: 12, data: []byte("cdef")}, 89 | {offset: 16, data: []byte("____")}, 90 | {offset: 20, data: []byte("AAA")}, 91 | } 92 | 93 | index := make([]int, len(blocks)) 94 | for i := range index { 95 | index[i] = i 96 | } 97 | 98 | rand.Seed(time.Now().UnixNano()) 99 | 100 | for i := 0; i < 20; i++ { 101 | buff := NewBuffer([]byte{}) 102 | 103 | rand.Shuffle(len(index), func(x, y int) { 104 | index[x], index[y] = index[y], index[x] 105 | }) 106 | 107 | fmt.Println(index) 108 | 109 | for _, v := range index { 110 | block := blocks[v] 111 | 112 | buff.Seek(block.offset, io.SeekStart) 113 | buff.Write(block.data) 114 | } 115 | 116 | rs := string(buff.Bytes()) 117 | want := "0123456789abcdef____AAA" 118 | if rs != want { 119 | t.Fatalf("expects '%s' but gets '%s'.", want, rs) 120 | return 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /memfs/file.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/smallfz/libnfs-go/fs" 8 | "github.com/smallfz/libnfs-go/log" 9 | ) 10 | 11 | type closeHandler func(bool, []byte) 12 | 13 | type fileOpenFlags struct { 14 | trunc bool 15 | append bool 16 | } 17 | 18 | func (f *fileOpenFlags) String() string { 19 | return fmt.Sprintf("{trunc=%v, append=%v}", f.trunc, f.append) 20 | } 21 | 22 | type memFile struct { 23 | s *MemFS 24 | n *memFsNode 25 | fi *fileInfo 26 | buff *Buffer 27 | changed bool 28 | onClose closeHandler 29 | } 30 | 31 | func newMemFile(s *MemFS, n *memFsNode, flag *fileOpenFlags, onClose closeHandler) *memFile { 32 | dat := []byte{} 33 | changed := false 34 | 35 | if !flag.trunc { 36 | dn := s.store.Get(n.nodeId) 37 | if dn != nil { 38 | if data, err := io.ReadAll(dn.Reader()); err == nil { 39 | dat = data 40 | } 41 | } 42 | } else { 43 | if n.size > 0 { 44 | changed = true 45 | } 46 | n.size = 0 47 | } 48 | 49 | buff := NewBuffer(dat) 50 | 51 | if flag.append { 52 | buff.Seek(0, io.SeekEnd) 53 | } 54 | 55 | return &memFile{ 56 | s: s, 57 | n: n, 58 | fi: s.getFileInfo(n), 59 | buff: buff, 60 | changed: changed, 61 | onClose: onClose, 62 | } 63 | } 64 | 65 | func (f *memFile) Name() string { 66 | return f.fi.name 67 | } 68 | 69 | func (f *memFile) Stat() (fs.FileInfo, error) { 70 | return f.fi, nil 71 | } 72 | 73 | func (f *memFile) Read(buff []byte) (int, error) { 74 | if f.fi.IsDir() { 75 | return 0, io.EOF 76 | } 77 | log.Printf("memFile.Read(%d)", len(buff)) 78 | i, err := f.buff.Read(buff) 79 | if err != nil { 80 | log.Warnf(" %v", err) 81 | } else { 82 | log.Printf(" -- %d bytes read.", i) 83 | } 84 | return i, err 85 | } 86 | 87 | func (f *memFile) Write(data []byte) (int, error) { 88 | if f.fi.IsDir() { 89 | return 0, io.EOF 90 | } 91 | log.Printf("memFile.Write(%d)", len(data)) 92 | f.changed = true 93 | return f.buff.Write(data) 94 | } 95 | 96 | func (f *memFile) Seek(offset int64, whence int) (int64, error) { 97 | if f.fi.IsDir() { 98 | return 0, io.EOF 99 | } 100 | log.Printf("memFile.Seek(%d, %d)", offset, whence) 101 | return f.buff.Seek(offset, whence) 102 | } 103 | 104 | func (f *memFile) Truncate() error { 105 | log.Printf("memFile.Truncate()") 106 | f.buff.Truncate() 107 | f.n.size = int64(f.buff.size()) 108 | log.Printf(" -- size after: %d", f.buff.size()) 109 | return nil 110 | } 111 | 112 | func (f *memFile) Close() error { 113 | if f.onClose != nil { 114 | f.onClose(f.changed, f.buff.Bytes()) 115 | } 116 | return nil 117 | } 118 | 119 | func (f *memFile) Sync() error { 120 | return nil 121 | } 122 | 123 | func (f *memFile) Readdir(n int) ([]fs.FileInfo, error) { 124 | if f.fi.IsDir() { 125 | fiList := []fs.FileInfo{} 126 | if f.n.children != nil { 127 | for _, child := range f.n.children { 128 | fiList = append(fiList, f.s.getFileInfo(child)) 129 | } 130 | } 131 | return fiList, nil 132 | } 133 | return nil, io.EOF 134 | } 135 | -------------------------------------------------------------------------------- /memfs/file_info.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "os" 5 | "time" 6 | ) 7 | 8 | type fileInfo struct { 9 | id uint64 10 | name string 11 | perm os.FileMode 12 | size int64 13 | modTime time.Time 14 | aTime time.Time 15 | cTime time.Time 16 | isDir bool 17 | numLinks int 18 | } 19 | 20 | func (fi fileInfo) Name() string { 21 | return fi.name 22 | } 23 | 24 | func (fi fileInfo) Size() int64 { 25 | return fi.size 26 | } 27 | 28 | func (fi fileInfo) Mode() os.FileMode { 29 | return fi.perm 30 | } 31 | 32 | func (fi fileInfo) ModTime() time.Time { 33 | return fi.modTime 34 | } 35 | 36 | func (fi fileInfo) ATime() time.Time { 37 | return fi.aTime 38 | } 39 | 40 | func (fi fileInfo) CTime() time.Time { 41 | return fi.cTime 42 | } 43 | 44 | func (fi fileInfo) IsDir() bool { 45 | return fi.isDir 46 | } 47 | 48 | func (fi fileInfo) Sys() interface{} { 49 | return nil 50 | } 51 | 52 | func (fi fileInfo) NumLinks() int { 53 | return fi.numLinks 54 | } 55 | -------------------------------------------------------------------------------- /memfs/folder.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | // import ( 4 | // "io" 5 | // "os" 6 | // ) 7 | // 8 | // type memFolder struct { 9 | // fi *fileInfo 10 | // children []os.FileInfo 11 | // } 12 | // 13 | // func newMemFolder(fi *fileInfo, children []os.FileInfo) *memFolder { 14 | // return &memFolder{ 15 | // fi: fi, 16 | // children: children, 17 | // } 18 | // } 19 | // 20 | // func (f *memFolder) Name() string { 21 | // return f.fi.name 22 | // } 23 | // 24 | // func (f *memFolder) Stat() (os.FileInfo, error) { 25 | // return f.fi, nil 26 | // } 27 | // 28 | // func (f *memFolder) Read(buff []byte) (int, error) { 29 | // return 0, io.EOF 30 | // } 31 | // 32 | // func (f *memFolder) Write(data []byte) (int, error) { 33 | // return 0, io.EOF 34 | // } 35 | // 36 | // func (f *memFolder) Seek(offset int64, whence int) (int64, error) { 37 | // return 0, io.EOF 38 | // } 39 | // 40 | // func (f *memFolder) Close() error { 41 | // return nil 42 | // } 43 | // 44 | // func (f *memFolder) Sync() error { 45 | // return nil 46 | // } 47 | // 48 | // func (f *memFolder) Readdir(n int) ([]os.FileInfo, error) { 49 | // return f.children, nil 50 | // } 51 | // 52 | -------------------------------------------------------------------------------- /memfs/memfs_test.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "testing" 9 | 10 | "github.com/smallfz/libnfs-go/fs" 11 | ) 12 | 13 | var _ fs.FS = new(MemFS) // Check interface 14 | 15 | func TestMemfsFileSeekRead(t *testing.T) { 16 | vfs := NewMemFS() 17 | 18 | name := "hello.txt" 19 | content := "hello world!" 20 | pathName := fs.Join("/", name) 21 | 22 | perm := os.FileMode(0o644) 23 | flag := os.O_CREATE | os.O_RDWR 24 | 25 | if f, err := vfs.OpenFile(pathName, flag, perm); err != nil { 26 | t.Fatalf("OpenFile: %v", err) 27 | return 28 | } else { 29 | io.WriteString(f, content) 30 | f.Close() 31 | } 32 | 33 | if f, err := vfs.Open(pathName); err != nil { 34 | t.Fatalf("%v", err) 35 | return 36 | } else { 37 | offset := int64(len([]byte("hello "))) 38 | if _, err := f.Seek(offset, io.SeekStart); err != nil { 39 | t.Fatalf("%v", err) 40 | return 41 | } 42 | buff := bytes.NewBuffer([]byte{}) 43 | if _, err := io.CopyN(buff, f, 1024); err != nil { 44 | if err != io.EOF { 45 | t.Fatalf("%v", err) 46 | return 47 | } 48 | } 49 | dat := buff.Bytes() 50 | if string(dat) != "world!" { 51 | t.Fatalf("unexpected content: %s", string(dat)) 52 | } 53 | f.Close() 54 | } 55 | } 56 | 57 | func TestMemfsFileOpenTrunc(t *testing.T) { 58 | vfs := NewMemFS() 59 | 60 | name := "hello.txt" 61 | content := "hello world!" 62 | pathName := fs.Join("/", name) 63 | 64 | perm := os.FileMode(0o644) 65 | flag := os.O_CREATE | os.O_RDWR 66 | 67 | if f, err := vfs.OpenFile(pathName, flag, perm); err != nil { 68 | t.Fatalf("OpenFile: %v", err) 69 | return 70 | } else { 71 | io.WriteString(f, content) 72 | f.Close() 73 | } 74 | 75 | if f, err := vfs.Open(pathName); err != nil { 76 | t.Fatalf("%v", err) 77 | return 78 | } else { 79 | dat, err := io.ReadAll(f) 80 | if err != nil { 81 | t.Fatalf("%v", err) 82 | return 83 | } 84 | if string(dat) != content { 85 | t.Fatalf("unexpected content: %s", string(dat)) 86 | } 87 | f.Close() 88 | } 89 | 90 | // open truncate 91 | flag = os.O_RDWR | os.O_TRUNC 92 | if f, err := vfs.OpenFile(pathName, flag, perm); err != nil { 93 | t.Fatalf("%v", err) 94 | return 95 | } else { 96 | io.WriteString(f, "new content.") 97 | f.Close() 98 | } 99 | 100 | if f, err := vfs.Open(pathName); err != nil { 101 | t.Fatalf("%v", err) 102 | return 103 | } else { 104 | dat, err := io.ReadAll(f) 105 | if err != nil { 106 | t.Fatalf("%v", err) 107 | return 108 | } 109 | if string(dat) != "new content." { 110 | t.Fatalf("unexpected content: %s", string(dat)) 111 | } 112 | f.Close() 113 | } 114 | } 115 | 116 | func TestMemfsFileOperations(t *testing.T) { 117 | vfs := NewMemFS() 118 | 119 | folder := "/webapp" 120 | mode := os.FileMode(0o755) 121 | if err := vfs.MkdirAll(folder, mode); err != nil { 122 | t.Fatalf("MkdirAll(%s): %v", folder, err) 123 | } 124 | 125 | name := "hello.txt" 126 | content := "hello world!" 127 | 128 | pathName := fs.Join(folder, name) 129 | 130 | if _, err := vfs.Stat(pathName); err != nil { 131 | if !os.IsNotExist(err) { 132 | t.Fatalf("vfs.Stat: %v", err) 133 | } else { 134 | // should run into here.. 135 | } 136 | } else { 137 | t.Fatalf("file should not be existing.") 138 | } 139 | 140 | // write file with data... 141 | 142 | mod := os.FileMode(0o644) 143 | if f, err := vfs.OpenFile(pathName, os.O_CREATE|os.O_RDWR, mod); err != nil { 144 | t.Fatalf("OpenFile: %v", err) 145 | return 146 | } else { 147 | io.WriteString(f, content) 148 | f.Close() 149 | } 150 | 151 | if fi, err := vfs.Stat(pathName); err != nil { 152 | t.Fatalf("(after created) vfs.Stat: %v", err) 153 | } else if fi.Name() != name { 154 | t.Fatalf("expects file-info with name `%s`. gets `%s`.", 155 | name, fi.Name(), 156 | ) 157 | } else { 158 | // check file content... 159 | if f, err := vfs.Open(pathName); err != nil { 160 | t.Fatalf("vfs.Open: %v", err) 161 | return 162 | } else { 163 | if dat, err := io.ReadAll(f); err != nil { 164 | t.Fatalf("io.ReadAll: %v", err) 165 | } else { 166 | if string(dat) != content { 167 | t.Fatalf("wrong content: %v", string(dat)) 168 | } 169 | fmt.Println(string(dat)) 170 | } 171 | f.Close() 172 | } 173 | } 174 | 175 | // modify it's content 176 | 177 | contentNew := "great filesystem." 178 | flagWrite := os.O_RDWR | os.O_TRUNC 179 | 180 | if f, err := vfs.OpenFile(pathName, flagWrite, mod); err != nil { 181 | t.Fatalf("%v", err) 182 | } else { 183 | if _, err := io.WriteString(f, contentNew); err != nil { 184 | t.Fatalf("%v", err) 185 | } 186 | f.Close() 187 | } 188 | 189 | if f, err := vfs.Open(pathName); err != nil { 190 | t.Fatalf("%v", err) 191 | } else { 192 | if dat, err := io.ReadAll(f); err != nil { 193 | t.Fatalf("%v", err) 194 | } else if string(dat) != contentNew { 195 | t.Fatalf("failed to re-write file data. current content: %s", 196 | string(dat), 197 | ) 198 | } 199 | } 200 | 201 | // check if the file exists in the expecting folder. 202 | 203 | checkExists := func(name string) { 204 | if d, err := vfs.Open("/webapp"); err != nil { 205 | t.Fatalf("%v", err) 206 | } else { 207 | if items, err := d.Readdir(-1); err != nil { 208 | t.Fatalf("%v", err) 209 | } else if len(items) != 1 { 210 | t.Fatalf("expects 1 file in /webapp. but gets %d.", len(items)) 211 | } else { 212 | if items[0].Name() != name { 213 | t.Fatalf("the file is not the expected one: %s", items[0].Name()) 214 | } else { 215 | fmt.Println(items[0].Name()) 216 | } 217 | } 218 | } 219 | } 220 | 221 | checkExists(name) 222 | 223 | // rename 224 | 225 | newName := "new.txt" 226 | newPathName := fs.Join(folder, newName) 227 | if err := vfs.Rename(pathName, newPathName); err != nil { 228 | t.Fatalf("Rename: %v", err) 229 | } 230 | 231 | // check again 232 | 233 | checkExists(newName) 234 | 235 | // delete it 236 | 237 | if err := vfs.Remove(newPathName); err != nil { 238 | t.Fatalf("%v", err) 239 | } 240 | 241 | // check again: should be deleted 242 | 243 | if d, err := vfs.Open("/webapp"); err != nil { 244 | t.Fatalf("%v", err) 245 | } else { 246 | if items, err := d.Readdir(-1); err != nil { 247 | t.Fatalf("%v", err) 248 | } else if len(items) != 0 { 249 | t.Fatalf("expects no files in /webapp. but gets %d.", len(items)) 250 | } else { 251 | fmt.Printf("%s: deleted as expected.\n", newName) 252 | } 253 | } 254 | } 255 | 256 | func TestMemfsMkdir(t *testing.T) { 257 | vfs := NewMemFS() 258 | 259 | // (1) create a folder 260 | 261 | pathName := "/abc/def/123" 262 | mode := os.FileMode(0o755) 263 | if err := vfs.MkdirAll(pathName, mode); err != nil { 264 | t.Fatalf("MkdirAll(%s): %v", pathName, err) 265 | } 266 | 267 | folder := fs.Dir(pathName) 268 | if folder != "/abc/def" { 269 | t.Fatalf("wrong dir returned from fs.Dir(%s): %s", pathName, folder) 270 | } 271 | 272 | f, err := vfs.Open(folder) 273 | if err != nil { 274 | t.Fatalf("%s: not exists.", folder) 275 | } 276 | 277 | // (2) create more dirs 278 | 279 | paths := []string{ 280 | "/hello", 281 | "/data", 282 | "/data/backups", 283 | "/abc/www", 284 | } 285 | for _, pathName := range paths { 286 | mode := os.FileMode(0o755) 287 | if err := vfs.MkdirAll(pathName, mode); err != nil { 288 | t.Fatalf("MkdirAll(%s): %v", pathName, err) 289 | } 290 | } 291 | 292 | items, err := f.Readdir(-1) 293 | if err != nil { 294 | t.Fatalf("f.Readdir(-1): %v", err) 295 | } 296 | 297 | found := false 298 | for _, item := range items { 299 | fmt.Println(item.Name()) 300 | if item.Name() == fs.Base(pathName) { 301 | found = true 302 | break 303 | } 304 | } 305 | 306 | if !found { 307 | t.Fatalf("target not created: %s", pathName) 308 | return 309 | } 310 | 311 | // (3) read dir of /abc 312 | 313 | d, err := vfs.Open("/abc") 314 | if err != nil { 315 | t.Fatalf("%v", err) 316 | } 317 | 318 | items, err = d.Readdir(-1) 319 | if err != nil { 320 | t.Fatalf("%v", err) 321 | } 322 | 323 | if len(items) != 2 { 324 | t.Fatalf("expects 2 children of /abc. (gets %d)", len(items)) 325 | } 326 | 327 | // (4) read dir of / 328 | 329 | d, err = vfs.Open("/") 330 | if err != nil { 331 | t.Fatalf("%v", err) 332 | } 333 | 334 | items, err = d.Readdir(-1) 335 | if err != nil { 336 | t.Fatalf("%v", err) 337 | } 338 | 339 | if len(items) != 3 { 340 | t.Fatalf("expects 3 children of /. (gets %d)", len(items)) 341 | } 342 | 343 | // (5) rename a dir 344 | if err := vfs.Rename("/data", "/data-new"); err != nil { 345 | t.Fatalf("Rename(/data => /data-new): %v", err) 346 | } 347 | 348 | if _, err := vfs.Open("/data"); err != nil { 349 | if !os.IsNotExist(err) { 350 | t.Fatalf("should not exists: /data (renamed to /data-new).") 351 | return 352 | } 353 | } else { 354 | t.Fatalf("should not exists: /data (renamed to /data-new).") 355 | return 356 | } 357 | 358 | if d, err := vfs.Open("/data-new"); err != nil { 359 | t.Fatalf("%v", err) 360 | } else { 361 | if items, err := d.Readdir(-1); err != nil { 362 | t.Fatalf("%v", err) 363 | } else { 364 | if len(items) != 1 { 365 | t.Fatalf("expects 1 children of /data-new. (gets %d)", len(items)) 366 | } 367 | } 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /memfs/storage.go: -------------------------------------------------------------------------------- 1 | package memfs 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "sync" 7 | 8 | "github.com/smallfz/libnfs-go/fs" 9 | ) 10 | 11 | type dataNode struct { 12 | id uint64 13 | data []byte 14 | } 15 | 16 | func (n *dataNode) Id() uint64 { 17 | return n.id 18 | } 19 | 20 | func (n *dataNode) Reader() io.Reader { 21 | return bytes.NewReader(n.data) 22 | } 23 | 24 | func (n *dataNode) Size() int { 25 | return len(n.data) 26 | } 27 | 28 | type Storage struct { 29 | nodes []*dataNode 30 | nodeId uint64 31 | lck *sync.RWMutex 32 | } 33 | 34 | func NewStorage() *Storage { 35 | return &Storage{ 36 | nodes: []*dataNode{}, 37 | lck: &sync.RWMutex{}, 38 | } 39 | } 40 | 41 | func (s *Storage) nextId() uint64 { 42 | s.lck.Lock() 43 | defer s.lck.Unlock() 44 | s.nodeId++ 45 | return s.nodeId 46 | } 47 | 48 | func (s *Storage) Create(src io.Reader) (uint64, error) { 49 | id := s.nextId() 50 | 51 | s.lck.Lock() 52 | defer s.lck.Unlock() 53 | 54 | data, err := io.ReadAll(src) 55 | if err != nil { 56 | if err != io.EOF { 57 | return id, err 58 | } 59 | } 60 | 61 | node := &dataNode{id: id, data: data} 62 | s.nodes = append(s.nodes, node) 63 | 64 | return id, nil 65 | } 66 | 67 | func (s *Storage) Update(id uint64, src io.Reader) (bool, error) { 68 | s.lck.Lock() 69 | defer s.lck.Unlock() 70 | 71 | for _, n := range s.nodes { 72 | if n.id == id { 73 | data, err := io.ReadAll(src) 74 | if err != nil { 75 | if err != io.EOF { 76 | return false, err 77 | } 78 | } 79 | n.data = data 80 | return true, nil 81 | } 82 | } 83 | 84 | return false, nil 85 | } 86 | 87 | func (s *Storage) Delete(id uint64) bool { 88 | s.lck.Lock() 89 | defer s.lck.Unlock() 90 | 91 | found := false 92 | for _, n := range s.nodes { 93 | if n.id == id { 94 | found = true 95 | break 96 | } 97 | } 98 | 99 | if found { 100 | nList := []*dataNode{} 101 | for _, n := range s.nodes { 102 | if n.id != id { 103 | nList = append(nList, n) 104 | } 105 | } 106 | s.nodes = nList 107 | } 108 | 109 | return found 110 | } 111 | 112 | func (s *Storage) Size(id uint64) int { 113 | n := s.Get(id) 114 | if n != nil { 115 | return n.Size() 116 | } 117 | return 0 118 | } 119 | 120 | func (s *Storage) Get(id uint64) fs.StorageNode { 121 | s.lck.RLock() 122 | defer s.lck.RUnlock() 123 | 124 | for _, n := range s.nodes { 125 | if n.id == id { 126 | return n 127 | } 128 | } 129 | 130 | return nil 131 | } 132 | -------------------------------------------------------------------------------- /nfs/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## References 3 | 4 | 5 | ### xdr & rpc 6 | 7 | - xdr: [https://datatracker.ietf.org/doc/html/rfc1014](https://datatracker.ietf.org/doc/html/rfc1014) 8 | - rpc(v2): [https://datatracker.ietf.org/doc/html/rfc5531](https://datatracker.ietf.org/doc/html/rfc5531) 9 | 10 | 11 | ### nfs v3 12 | 13 | - nfs v3: [https://datatracker.ietf.org/doc/html/rfc1813](https://datatracker.ietf.org/doc/html/rfc1813) 14 | 15 | 16 | ### nfs v4 17 | 18 | - [https://datatracker.ietf.org/doc/html/rfc7530](https://datatracker.ietf.org/doc/html/rfc7530) 19 | - [https://datatracker.ietf.org/doc/html/rfc7531](https://datatracker.ietf.org/doc/html/rfc7531) 20 | 21 | 22 | ### testing 23 | 24 | - nfstest: [https://www.unix.com/man-page/centos/1/nfstest_posix/](https://www.unix.com/man-page/centos/1/nfstest_posix/) 25 | 26 | -------------------------------------------------------------------------------- /nfs/backend.go: -------------------------------------------------------------------------------- 1 | package nfs 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/smallfz/libnfs-go/fs" 7 | ) 8 | 9 | // SessionState represents a client network session. 10 | type SessionState interface { 11 | // Conn returns the current client connection. 12 | Conn() net.Conn 13 | } 14 | 15 | type AuthenticationHandler func(*Auth, *Auth) (*Auth, fs.Creds, error) 16 | 17 | type StatService interface { 18 | // Cwd() string 19 | // SetCwd(string) error 20 | 21 | SetCurrentHandle(FileHandle4) 22 | CurrentHandle() FileHandle4 23 | 24 | PushHandle(FileHandle4) 25 | PeekHandle() (FileHandle4, bool) 26 | PopHandle() (FileHandle4, bool) 27 | 28 | SetClientId(uint64) 29 | ClientId() (uint64, bool) 30 | 31 | AddOpenedFile(string, fs.File) uint32 32 | GetOpenedFile(uint32) fs.FileOpenState 33 | FindOpenedFiles(string) []fs.FileOpenState 34 | RemoveOpenedFile(uint32) fs.FileOpenState 35 | 36 | // CloseAndRemoveStallFiles shall close 37 | // and remove outdated opened files. 38 | CloseAndRemoveStallFiles() 39 | 40 | // CleanUp should remove all opened files and reset handle stack. 41 | CleanUp() 42 | } 43 | 44 | // BackendSession has a lifetime exact as the client connection. 45 | type BackendSession interface { 46 | // Authentication should return an Authentication handler. 47 | Authentication() AuthenticationHandler 48 | 49 | // GetFS should return a FS implementation. 50 | // The backend should cache 51 | GetFS() fs.FS 52 | 53 | // GetStatService returns a StateService in implementation. 54 | // In development you can return a memfs.Stat instance. 55 | GetStatService() StatService 56 | 57 | // Close invoked by server when connection closed by any side. 58 | // Implementation should do some cleaning work at this time. 59 | Close() error 60 | } 61 | 62 | // Backend interface. This is where it starts when building a custom nfs server. 63 | type Backend interface { 64 | // CreateSession returns a session instance. 65 | // In development you can return a memfs.Backend instance. 66 | CreateSession(SessionState) BackendSession 67 | } 68 | -------------------------------------------------------------------------------- /nfs/bitmap4.go: -------------------------------------------------------------------------------- 1 | package nfs 2 | 3 | func Bitmap4Encode(x map[int]bool) []uint32 { 4 | max := 0 5 | for v := range x { 6 | if v > max { 7 | max = v 8 | } 9 | } 10 | 11 | size := int(max / 32) 12 | if max%32 > 0 { 13 | size += 1 14 | } 15 | 16 | rs := make([]uint32, size) 17 | 18 | for v, on := range x { 19 | if !on { 20 | continue 21 | } 22 | i := v / 32 23 | j := v % 32 24 | s := uint32(1) << j 25 | rs[i] |= s 26 | } 27 | 28 | return rs 29 | } 30 | 31 | func Bitmap4Decode(nums []uint32) map[int]bool { 32 | x := map[int]bool{} 33 | for i, v := range nums { 34 | for j := 31; j >= 0; j-- { 35 | s := uint32(1) << j 36 | n := 32*i + j 37 | x[n] = s&v == s 38 | } 39 | } 40 | return x 41 | } 42 | -------------------------------------------------------------------------------- /nfs/context.go: -------------------------------------------------------------------------------- 1 | package nfs 2 | 3 | import ( 4 | "github.com/smallfz/libnfs-go/fs" 5 | "github.com/smallfz/libnfs-go/xdr" 6 | ) 7 | 8 | type RPCContext interface { 9 | Reader() *xdr.Reader 10 | Writer() *xdr.Writer 11 | Authenticate(*Auth, *Auth) (*Auth, error) // Handle authentication and calls fs.FS.SetCreds(). Returns *Auth to reply to the client. 12 | GetFS() fs.FS 13 | Stat() StatService 14 | } 15 | -------------------------------------------------------------------------------- /nfs/implv3/access.go: -------------------------------------------------------------------------------- 1 | package implv3 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/smallfz/libnfs-go/log" 8 | "github.com/smallfz/libnfs-go/nfs" 9 | ) 10 | 11 | func Access(h *nfs.RPCMsgCall, ctx nfs.RPCContext) (int, error) { 12 | r, w := ctx.Reader(), ctx.Writer() 13 | 14 | log.Info("handling access.") 15 | sizeConsumed := 0 16 | 17 | fh3 := []byte{} 18 | access := uint32(0) 19 | if size, err := r.ReadAs(&fh3); err != nil { 20 | return 0, err 21 | } else { 22 | sizeConsumed += size 23 | } 24 | if size, err := r.ReadAs(&access); err != nil { 25 | return sizeConsumed, err 26 | } else { 27 | sizeConsumed += size 28 | } 29 | 30 | log.Info(fmt.Sprintf( 31 | "access: root = %s, access = %x", string(fh3), access, 32 | )) 33 | 34 | resp, err := ctx.Authenticate(h.Cred, h.Verf) 35 | if authErr, ok := err.(*nfs.AuthError); ok { 36 | rh := &nfs.RPCMsgReply{ 37 | Xid: h.Xid, 38 | MsgType: nfs.RPC_REPLY, 39 | ReplyStat: nfs.MSG_DENIED, 40 | } 41 | 42 | if _, err := w.WriteAny(rh); err != nil { 43 | return sizeConsumed, err 44 | } 45 | 46 | if _, err := w.WriteUint32(nfs.REJECT_AUTH_ERROR); err != nil { 47 | return sizeConsumed, err 48 | } 49 | 50 | if _, err := w.WriteUint32(authErr.Code); err != nil { 51 | return sizeConsumed, err 52 | } 53 | 54 | return sizeConsumed, nil 55 | } else if err != nil { 56 | return sizeConsumed, err 57 | } 58 | 59 | rh := &nfs.RPCMsgReply{ 60 | Xid: h.Xid, 61 | MsgType: nfs.RPC_REPLY, 62 | ReplyStat: nfs.MSG_ACCEPTED, 63 | } 64 | if _, err := w.WriteAny(rh); err != nil { 65 | return sizeConsumed, err 66 | } 67 | 68 | if _, err := w.WriteAny(resp); err != nil { 69 | return sizeConsumed, err 70 | } 71 | 72 | if _, err := w.WriteUint32(nfs.ACCEPT_SUCCESS); err != nil { 73 | return sizeConsumed, err 74 | } 75 | 76 | // ---- proc result --- 77 | 78 | if _, err := w.WriteUint32(nfs.NFS3_OK); err != nil { 79 | return sizeConsumed, err 80 | } 81 | 82 | now := time.Now() 83 | rs := nfs.ACCESS3resok{ 84 | ObjAttrs: &nfs.PostOpAttr{ 85 | AttributesFollow: true, 86 | Attributes: &nfs.FileAttrs{ 87 | Type: nfs.FTYPE_NF3DIR, 88 | Mode: uint32(0o777), 89 | Size: 0, 90 | Used: 0, 91 | ATime: nfs.MakeNfsTime(now), 92 | MTime: nfs.MakeNfsTime(now), 93 | CTime: nfs.MakeNfsTime(now), 94 | }, 95 | }, 96 | Access: nfs.ACCESS3_READ | nfs.ACCESS3_LOOKUP | nfs.ACCESS3_MODIFY | nfs.ACCESS3_EXTEND | nfs.ACCESS3_DELETE | nfs.ACCESS3_EXECUTE, 97 | } 98 | 99 | if _, err := w.WriteAny(&rs); err != nil { 100 | return sizeConsumed, err 101 | } 102 | 103 | return sizeConsumed, nil 104 | } 105 | -------------------------------------------------------------------------------- /nfs/implv3/fsinfo.go: -------------------------------------------------------------------------------- 1 | package implv3 2 | 3 | import ( 4 | // "fmt" 5 | "time" 6 | 7 | "github.com/smallfz/libnfs-go/log" 8 | "github.com/smallfz/libnfs-go/nfs" 9 | // "github.com/davecgh/go-xdr/xdr2" 10 | ) 11 | 12 | func FsInfo(h *nfs.RPCMsgCall, ctx nfs.RPCContext) (int, error) { 13 | r, w := ctx.Reader(), ctx.Writer() 14 | 15 | log.Info("handling fsinfo.") 16 | sizeConsumed := 0 17 | 18 | fh3 := []byte{} 19 | if size, err := r.ReadAs(&fh3); err != nil { 20 | return 0, err 21 | } else { 22 | sizeConsumed += size 23 | } 24 | 25 | log.Infof("fsinfo: root = %s", string(fh3)) 26 | 27 | resp, err := ctx.Authenticate(h.Cred, h.Verf) 28 | if authErr, ok := err.(*nfs.AuthError); ok { 29 | rh := &nfs.RPCMsgReply{ 30 | Xid: h.Xid, 31 | MsgType: nfs.RPC_REPLY, 32 | ReplyStat: nfs.MSG_DENIED, 33 | } 34 | 35 | if _, err := w.WriteAny(rh); err != nil { 36 | return sizeConsumed, err 37 | } 38 | 39 | if _, err := w.WriteUint32(nfs.REJECT_AUTH_ERROR); err != nil { 40 | return sizeConsumed, err 41 | } 42 | 43 | if _, err := w.WriteUint32(authErr.Code); err != nil { 44 | return sizeConsumed, err 45 | } 46 | 47 | return sizeConsumed, nil 48 | } else if err != nil { 49 | return sizeConsumed, err 50 | } 51 | 52 | rh := &nfs.RPCMsgReply{ 53 | Xid: h.Xid, 54 | MsgType: nfs.RPC_REPLY, 55 | ReplyStat: nfs.MSG_ACCEPTED, 56 | } 57 | if _, err := w.WriteAny(rh); err != nil { 58 | return sizeConsumed, err 59 | } 60 | 61 | if _, err := w.WriteAny(resp); err != nil { 62 | return sizeConsumed, err 63 | } 64 | 65 | if _, err := w.WriteUint32(nfs.ACCEPT_SUCCESS); err != nil { 66 | return sizeConsumed, err 67 | } 68 | 69 | // ---- proc result --- 70 | 71 | if _, err := w.WriteUint32(nfs.NFS3_OK); err != nil { 72 | return sizeConsumed, err 73 | } 74 | 75 | now := time.Now() 76 | rs := &nfs.FSINFO3resok{ 77 | ObjAttrs: &nfs.PostOpAttr{ 78 | AttributesFollow: true, 79 | Attributes: &nfs.FileAttrs{ 80 | Type: nfs.FTYPE_NF3DIR, 81 | Mode: uint32(0o755), 82 | Size: 1 << 63, 83 | Used: 0, 84 | ATime: nfs.MakeNfsTime(now), 85 | MTime: nfs.MakeNfsTime(now), 86 | CTime: nfs.MakeNfsTime(now), 87 | }, 88 | }, 89 | Rtmax: 1024 * 1024 * 4, 90 | Rtpref: 1024 * 1024 * 4, 91 | Rtmult: 1, 92 | Wtmax: 1024 * 1024 * 64, 93 | Wtpref: 1024 * 1024 * 64, 94 | Wtmult: 1, 95 | Dtpref: 0, 96 | MaxFileSize: 1024 * 1024 * 1024 * 4, 97 | TimeDelta: nfs.NFSTime{Seconds: 1, NanoSeconds: 0}, 98 | Properties: nfs.FSF3_CANSETTIME, 99 | } 100 | 101 | if _, err := w.WriteAny(rs); err != nil { 102 | return sizeConsumed, err 103 | } 104 | 105 | return sizeConsumed, nil 106 | } 107 | -------------------------------------------------------------------------------- /nfs/implv3/fsstat.go: -------------------------------------------------------------------------------- 1 | package implv3 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/smallfz/libnfs-go/log" 8 | "github.com/smallfz/libnfs-go/nfs" 9 | ) 10 | 11 | func FsStat(h *nfs.RPCMsgCall, ctx nfs.RPCContext) (int, error) { 12 | r, w := ctx.Reader(), ctx.Writer() 13 | 14 | log.Info("handling fsstat.") 15 | sizeConsumed := 0 16 | 17 | fh3 := []byte{} // nfs.Fh3{} 18 | if size, err := r.ReadAs(&fh3); err != nil { 19 | return 0, err 20 | } else { 21 | sizeConsumed += size 22 | } 23 | 24 | log.Info(fmt.Sprintf("fsstat: root = %v", fh3)) 25 | 26 | resp, err := ctx.Authenticate(h.Cred, h.Verf) 27 | if authErr, ok := err.(*nfs.AuthError); ok { 28 | rh := &nfs.RPCMsgReply{ 29 | Xid: h.Xid, 30 | MsgType: nfs.RPC_REPLY, 31 | ReplyStat: nfs.MSG_DENIED, 32 | } 33 | 34 | if _, err := w.WriteAny(rh); err != nil { 35 | return sizeConsumed, err 36 | } 37 | 38 | if _, err := w.WriteUint32(nfs.REJECT_AUTH_ERROR); err != nil { 39 | return sizeConsumed, err 40 | } 41 | 42 | if _, err := w.WriteUint32(authErr.Code); err != nil { 43 | return sizeConsumed, err 44 | } 45 | 46 | return sizeConsumed, nil 47 | } else if err != nil { 48 | return sizeConsumed, err 49 | } 50 | 51 | rh := &nfs.RPCMsgReply{ 52 | Xid: h.Xid, 53 | MsgType: nfs.RPC_REPLY, 54 | ReplyStat: nfs.MSG_ACCEPTED, 55 | } 56 | if _, err := w.WriteAny(rh); err != nil { 57 | return sizeConsumed, err 58 | } 59 | 60 | if _, err := w.WriteAny(resp); err != nil { 61 | return sizeConsumed, err 62 | } 63 | 64 | if _, err := w.WriteUint32(nfs.ACCEPT_SUCCESS); err != nil { 65 | return sizeConsumed, err 66 | } 67 | 68 | // ---- proc result --- 69 | 70 | if _, err := w.WriteUint32(nfs.NFS3_OK); err != nil { 71 | return sizeConsumed, err 72 | } 73 | 74 | now := time.Now() 75 | capacity := uint64(1024 * 1024 * 1024 * 1024 * 2) 76 | 77 | rs := &nfs.FSSTAT3resok{ 78 | ObjAttrs: &nfs.PostOpAttr{ 79 | AttributesFollow: true, 80 | Attributes: &nfs.FileAttrs{ 81 | Type: nfs.FTYPE_NF3DIR, 82 | Mode: uint32(0o755), 83 | Size: capacity, 84 | Used: 0, 85 | ATime: nfs.MakeNfsTime(now), 86 | MTime: nfs.MakeNfsTime(now), 87 | CTime: nfs.MakeNfsTime(now), 88 | }, 89 | }, 90 | Tbytes: capacity, 91 | Fbytes: capacity, 92 | Abytes: capacity, 93 | Ffiles: 1024, 94 | Afiles: 1024, 95 | Invarsec: uint32(1), 96 | } 97 | 98 | if _, err := w.WriteAny(rs); err != nil { 99 | return sizeConsumed, err 100 | } 101 | 102 | return sizeConsumed, nil 103 | } 104 | -------------------------------------------------------------------------------- /nfs/implv3/getattr.go: -------------------------------------------------------------------------------- 1 | package implv3 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/smallfz/libnfs-go/log" 8 | "github.com/smallfz/libnfs-go/nfs" 9 | // "github.com/davecgh/go-xdr/xdr2" 10 | ) 11 | 12 | // GetAttr: 13 | // 14 | // SYNOPSIS 15 | // 16 | // GETATTR3res NFSPROC3_GETATTR(GETATTR3args) = 1; 17 | // 18 | // struct GETATTR3args { 19 | // nfs_fh3 object; 20 | // }; 21 | // 22 | // struct GETATTR3resok { 23 | // fattr3 obj_attributes; 24 | // }; 25 | // 26 | // union GETATTR3res switch (nfsstat3 status) { 27 | // case NFS3_OK: 28 | // GETATTR3resok resok; 29 | // default: 30 | // void; 31 | // }; 32 | func GetAttr(h *nfs.RPCMsgCall, ctx nfs.RPCContext) (int, error) { 33 | r, w := ctx.Reader(), ctx.Writer() 34 | 35 | log.Info("handling getattr.") 36 | sizeConsumed := 0 37 | 38 | fh3 := []byte{} 39 | if size, err := r.ReadAs(&fh3); err != nil { 40 | return 0, err 41 | } else { 42 | sizeConsumed += size 43 | } 44 | 45 | log.Infof("getattr: fh3 = %s", string(fh3)) 46 | 47 | resp, err := ctx.Authenticate(h.Cred, h.Verf) 48 | if authErr, ok := err.(*nfs.AuthError); ok { 49 | rh := &nfs.RPCMsgReply{ 50 | Xid: h.Xid, 51 | MsgType: nfs.RPC_REPLY, 52 | ReplyStat: nfs.MSG_DENIED, 53 | } 54 | 55 | if _, err := w.WriteAny(rh); err != nil { 56 | return sizeConsumed, err 57 | } 58 | 59 | if _, err := w.WriteUint32(nfs.REJECT_AUTH_ERROR); err != nil { 60 | return sizeConsumed, err 61 | } 62 | 63 | if _, err := w.WriteUint32(authErr.Code); err != nil { 64 | return sizeConsumed, err 65 | } 66 | 67 | return sizeConsumed, nil 68 | } else if err != nil { 69 | return sizeConsumed, err 70 | } 71 | 72 | rh := &nfs.RPCMsgReply{ 73 | Xid: h.Xid, 74 | MsgType: nfs.RPC_REPLY, 75 | ReplyStat: nfs.MSG_ACCEPTED, 76 | } 77 | if _, err := w.WriteAny(rh); err != nil { 78 | return sizeConsumed, err 79 | } 80 | 81 | if _, err := w.WriteAny(resp); err != nil { 82 | return sizeConsumed, err 83 | } 84 | 85 | if _, err := w.WriteUint32(nfs.ACCEPT_SUCCESS); err != nil { 86 | return sizeConsumed, err 87 | } 88 | 89 | // --- proc result --- 90 | 91 | filename := string(fh3) 92 | fs := ctx.GetFS() 93 | rsCode := nfs.NFS3_OK 94 | 95 | if fs != nil { 96 | if fi, err := fs.Stat(filename); err == nil { 97 | 98 | if _, err := w.WriteUint32(nfs.NFS3_OK); err != nil { 99 | return sizeConsumed, err 100 | } 101 | 102 | now := time.Now() 103 | 104 | ftype := nfs.FTYPE_NF3DIR 105 | if !fi.IsDir() { 106 | ftype = nfs.FTYPE_NF3REG 107 | } 108 | 109 | attr := nfs.FileAttrs{ 110 | Type: ftype, 111 | Mode: uint32(0o755), 112 | Size: uint64(fi.Size()), 113 | Used: uint64(fi.Size()), 114 | Rdev: nfs.SpecData{}, 115 | Fsid: 0, 116 | FileId: 0, 117 | ATime: nfs.MakeNfsTime(now), 118 | MTime: nfs.MakeNfsTime(fi.ModTime()), 119 | CTime: nfs.MakeNfsTime(fi.ModTime()), 120 | } 121 | if _, err := w.WriteAny(&attr); err != nil { 122 | return sizeConsumed, err 123 | } 124 | return sizeConsumed, nil 125 | 126 | } else { 127 | log.Warn(fmt.Sprintf("fs.Stat(%s): %v", filename, err)) 128 | rsCode = nfs.NFS3ERR_BADHANDLE 129 | } 130 | } else { 131 | log.Warnf("no filesystem specified.") 132 | rsCode = nfs.NFS3ERR_IO 133 | } 134 | 135 | if _, err := w.WriteUint32(rsCode); err != nil { 136 | return sizeConsumed, err 137 | } 138 | 139 | return sizeConsumed, nil 140 | } 141 | -------------------------------------------------------------------------------- /nfs/implv3/implv3.go: -------------------------------------------------------------------------------- 1 | // RFC-1813 2 | package implv3 3 | -------------------------------------------------------------------------------- /nfs/implv3/lookup.go: -------------------------------------------------------------------------------- 1 | package implv3 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "time" 7 | 8 | fstools "github.com/smallfz/libnfs-go/fs" 9 | "github.com/smallfz/libnfs-go/log" 10 | "github.com/smallfz/libnfs-go/nfs" 11 | ) 12 | 13 | func Lookup(h *nfs.RPCMsgCall, ctx nfs.RPCContext) (int, error) { 14 | r, w := ctx.Reader(), ctx.Writer() 15 | 16 | log.Info("handling lookup.") 17 | sizeConsumed := 0 18 | 19 | args := nfs.DirOpArgs3{} 20 | if size, err := r.ReadAs(&args); err != nil { 21 | return 0, err 22 | } else { 23 | sizeConsumed += size 24 | } 25 | 26 | log.Info(fmt.Sprintf( 27 | "lookup: dir = %v, filename = %v", args.Dir, args.Filename, 28 | )) 29 | 30 | resp, err := ctx.Authenticate(h.Cred, h.Verf) 31 | if authErr, ok := err.(*nfs.AuthError); ok { 32 | rh := &nfs.RPCMsgReply{ 33 | Xid: h.Xid, 34 | MsgType: nfs.RPC_REPLY, 35 | ReplyStat: nfs.MSG_DENIED, 36 | } 37 | 38 | if _, err := w.WriteAny(rh); err != nil { 39 | return sizeConsumed, err 40 | } 41 | 42 | if _, err := w.WriteUint32(nfs.REJECT_AUTH_ERROR); err != nil { 43 | return sizeConsumed, err 44 | } 45 | 46 | if _, err := w.WriteUint32(authErr.Code); err != nil { 47 | return sizeConsumed, err 48 | } 49 | 50 | return sizeConsumed, nil 51 | } else if err != nil { 52 | return sizeConsumed, err 53 | } 54 | 55 | rh := &nfs.RPCMsgReply{ 56 | Xid: h.Xid, 57 | MsgType: nfs.RPC_REPLY, 58 | ReplyStat: nfs.MSG_ACCEPTED, 59 | } 60 | if _, err := w.WriteAny(rh); err != nil { 61 | return sizeConsumed, err 62 | } 63 | 64 | if _, err := w.WriteAny(resp); err != nil { 65 | return sizeConsumed, err 66 | } 67 | 68 | if _, err := w.WriteUint32(nfs.ACCEPT_SUCCESS); err != nil { 69 | return sizeConsumed, err 70 | } 71 | 72 | // --- proc result --- 73 | 74 | zeros := string([]byte{0}) 75 | folder := string(bytes.TrimRight(args.Dir, zeros)) 76 | filename := args.Filename 77 | filename = fstools.Abs(fstools.Join(folder, filename)) 78 | 79 | fs := ctx.GetFS() 80 | rsCode := nfs.NFS3_OK 81 | 82 | if fs != nil { 83 | 84 | di, err := fs.Stat(fstools.Abs(folder)) 85 | if err != nil { 86 | if _, err := w.WriteUint32(nfs.NFS3ERR_ACCES); err != nil { 87 | return sizeConsumed, err 88 | } 89 | attributesFollow := false 90 | if _, err := w.WriteAny(attributesFollow); err != nil { 91 | return sizeConsumed, err 92 | } 93 | return sizeConsumed, nil 94 | } 95 | 96 | if fi, err := fs.Stat(filename); err == nil { 97 | 98 | if _, err := w.WriteUint32(nfs.NFS3_OK); err != nil { 99 | return sizeConsumed, err 100 | } 101 | 102 | now := time.Now() 103 | 104 | ftype := nfs.FTYPE_NF3DIR 105 | if !fi.IsDir() { 106 | ftype = nfs.FTYPE_NF3REG 107 | } 108 | 109 | attr := &nfs.LOOKUP3resok{ 110 | Object: []byte(fi.Name()), 111 | ObjAttrs: &nfs.PostOpAttr{ 112 | AttributesFollow: true, 113 | Attributes: &nfs.FileAttrs{ 114 | Type: ftype, 115 | Mode: uint32(fi.Mode()), 116 | ATime: nfs.MakeNfsTime(now), 117 | MTime: nfs.MakeNfsTime(fi.ModTime()), 118 | CTime: nfs.MakeNfsTime(fi.ModTime()), 119 | }, 120 | }, 121 | DirAttrs: &nfs.PostOpAttr{ 122 | AttributesFollow: true, 123 | Attributes: &nfs.FileAttrs{ 124 | Type: nfs.FTYPE_NF3DIR, 125 | Mode: uint32(di.Mode()), 126 | ATime: nfs.MakeNfsTime(now), 127 | MTime: nfs.MakeNfsTime(di.ModTime()), 128 | CTime: nfs.MakeNfsTime(di.ModTime()), 129 | }, 130 | }, 131 | } 132 | if _, err := w.WriteAny(attr); err != nil { 133 | return sizeConsumed, err 134 | } 135 | return sizeConsumed, nil 136 | 137 | } else { 138 | log.Warn(fmt.Sprintf("fs.Stat(%s): %v", filename, err)) 139 | rsCode = nfs.NFS3ERR_ACCES 140 | } 141 | } else { 142 | log.Warnf("no filesystem specified.") 143 | rsCode = nfs.NFS3ERR_IO 144 | } 145 | 146 | if _, err := w.WriteUint32(rsCode); err != nil { 147 | return sizeConsumed, err 148 | } 149 | 150 | // post_op_attr.attributes_follow 151 | attributesFollow := false 152 | if _, err := w.WriteAny(attributesFollow); err != nil { 153 | return sizeConsumed, err 154 | } 155 | 156 | return sizeConsumed, nil 157 | } 158 | -------------------------------------------------------------------------------- /nfs/implv3/pathconf.go: -------------------------------------------------------------------------------- 1 | package implv3 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/smallfz/libnfs-go/log" 7 | "github.com/smallfz/libnfs-go/nfs" 8 | ) 9 | 10 | func PathConf(h *nfs.RPCMsgCall, ctx nfs.RPCContext) (int, error) { 11 | r, w := ctx.Reader(), ctx.Writer() 12 | 13 | log.Info("handling pathconf.") 14 | sizeConsumed := 0 15 | 16 | fh3 := []byte{} 17 | if size, err := r.ReadAs(&fh3); err != nil { 18 | return 0, err 19 | } else { 20 | sizeConsumed += size 21 | } 22 | 23 | log.Infof("pathconf: path = %s", string(fh3)) 24 | 25 | resp, err := ctx.Authenticate(h.Cred, h.Verf) 26 | if authErr, ok := err.(*nfs.AuthError); ok { 27 | rh := &nfs.RPCMsgReply{ 28 | Xid: h.Xid, 29 | MsgType: nfs.RPC_REPLY, 30 | ReplyStat: nfs.MSG_DENIED, 31 | } 32 | 33 | if _, err := w.WriteAny(rh); err != nil { 34 | return sizeConsumed, err 35 | } 36 | 37 | if _, err := w.WriteUint32(nfs.REJECT_AUTH_ERROR); err != nil { 38 | return sizeConsumed, err 39 | } 40 | 41 | if _, err := w.WriteUint32(authErr.Code); err != nil { 42 | return sizeConsumed, err 43 | } 44 | 45 | return sizeConsumed, nil 46 | } else if err != nil { 47 | return sizeConsumed, err 48 | } 49 | 50 | rh := &nfs.RPCMsgReply{ 51 | Xid: h.Xid, 52 | MsgType: nfs.RPC_REPLY, 53 | ReplyStat: nfs.MSG_ACCEPTED, 54 | } 55 | if _, err := w.WriteAny(rh); err != nil { 56 | return sizeConsumed, err 57 | } 58 | 59 | if _, err := w.WriteAny(resp); err != nil { 60 | return sizeConsumed, err 61 | } 62 | 63 | if _, err := w.WriteUint32(nfs.ACCEPT_SUCCESS); err != nil { 64 | return sizeConsumed, err 65 | } 66 | 67 | // --- proc result --- 68 | 69 | if _, err := w.WriteUint32(nfs.NFS3_OK); err != nil { 70 | return sizeConsumed, err 71 | } 72 | 73 | now := time.Now() 74 | rs := &nfs.PATHCONF3resok{ 75 | ObjAttrs: &nfs.PostOpAttr{ 76 | AttributesFollow: true, 77 | Attributes: &nfs.FileAttrs{ 78 | Type: nfs.FTYPE_NF3DIR, 79 | Mode: uint32(0o755), 80 | Size: 1 << 63, 81 | Used: 0, 82 | ATime: nfs.MakeNfsTime(now), 83 | MTime: nfs.MakeNfsTime(now), 84 | CTime: nfs.MakeNfsTime(now), 85 | }, 86 | }, 87 | LinkMax: 1024, 88 | NameMax: 64, 89 | NoTrunc: true, 90 | ChownRestricted: false, 91 | CaseInsensitive: false, 92 | CasePreserving: true, 93 | } 94 | if _, err := w.WriteAny(rs); err != nil { 95 | return sizeConsumed, err 96 | } 97 | 98 | return sizeConsumed, nil 99 | } 100 | -------------------------------------------------------------------------------- /nfs/implv3/readdirplus.go: -------------------------------------------------------------------------------- 1 | package implv3 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/smallfz/libnfs-go/fs" 9 | "github.com/smallfz/libnfs-go/log" 10 | "github.com/smallfz/libnfs-go/nfs" 11 | ) 12 | 13 | func ReaddirPlus(h *nfs.RPCMsgCall, ctx nfs.RPCContext) (int, error) { 14 | r, w := ctx.Reader(), ctx.Writer() 15 | 16 | log.Info("> handling readdirplus: enter") 17 | defer func() { 18 | log.Info("< handling readdirplus: return") 19 | }() 20 | 21 | sizeConsumed := 0 22 | 23 | args := &nfs.READDIRPLUS3args{} 24 | if size, err := r.ReadAs(args); err != nil { 25 | return 0, err 26 | } else { 27 | sizeConsumed += size 28 | } 29 | 30 | log.Info(fmt.Sprintf( 31 | "readdirplus: args = %v", args, 32 | )) 33 | log.Debugf(" args.dir : %v", args.Dir) 34 | 35 | resp, err := ctx.Authenticate(h.Cred, h.Verf) 36 | if authErr, ok := err.(*nfs.AuthError); ok { 37 | rh := &nfs.RPCMsgReply{ 38 | Xid: h.Xid, 39 | MsgType: nfs.RPC_REPLY, 40 | ReplyStat: nfs.MSG_DENIED, 41 | } 42 | 43 | if _, err := w.WriteAny(rh); err != nil { 44 | return sizeConsumed, err 45 | } 46 | 47 | if _, err := w.WriteUint32(nfs.REJECT_AUTH_ERROR); err != nil { 48 | return sizeConsumed, err 49 | } 50 | 51 | if _, err := w.WriteUint32(authErr.Code); err != nil { 52 | return sizeConsumed, err 53 | } 54 | 55 | return sizeConsumed, nil 56 | } else if err != nil { 57 | return sizeConsumed, err 58 | } 59 | 60 | rh := &nfs.RPCMsgReply{ 61 | Xid: h.Xid, 62 | MsgType: nfs.RPC_REPLY, 63 | ReplyStat: nfs.MSG_ACCEPTED, 64 | } 65 | if _, err := w.WriteAny(rh); err != nil { 66 | return sizeConsumed, err 67 | } 68 | 69 | if _, err := w.WriteAny(resp); err != nil { 70 | return sizeConsumed, err 71 | } 72 | 73 | if _, err := w.WriteUint32(nfs.ACCEPT_SUCCESS); err != nil { 74 | return sizeConsumed, err 75 | } 76 | 77 | // --- proc result --- 78 | 79 | fail := func(code uint32) error { 80 | if _, err := w.WriteUint32(nfs.NFS3ERR_IO); err != nil { 81 | return err 82 | } 83 | attributesFollow := false 84 | if _, err := w.WriteAny(attributesFollow); err != nil { 85 | return err 86 | } 87 | return nil 88 | } 89 | 90 | zeros := string([]byte{0}) 91 | folder := string(bytes.TrimRight(args.Dir, zeros)) 92 | folder = fs.Abs(folder) 93 | 94 | log.Debugf(" - dir: %s", folder) 95 | 96 | vfs := ctx.GetFS() 97 | 98 | if vfs != nil { 99 | if di, err := vfs.Stat(folder); err != nil { 100 | return sizeConsumed, fail(nfs.NFS3ERR_IO) 101 | } else { 102 | dir, err := vfs.Open(folder) 103 | if err != nil { 104 | return sizeConsumed, fail(nfs.NFS3ERR_IO) 105 | } 106 | 107 | children, err := dir.Readdir(-1) 108 | if err != nil { 109 | return sizeConsumed, fail(nfs.NFS3ERR_IO) 110 | } 111 | 112 | now := time.Now() 113 | 114 | entries := []*nfs.EntryPlus3{} 115 | 116 | for i, fi := range children { 117 | item := fileinfoToEntryPlus3(folder, fi) 118 | 119 | // pathName := fs.Join(folder, fi.Name()) 120 | // h, err := vfs.GetHandle(pathName) 121 | // if err != nil { 122 | // log.Warnf("vfs.GetHandle: %v", err) 123 | // } else { 124 | // item.NameHandle.Handle = h 125 | // } 126 | 127 | item.Cookie = uint64(i + 1) 128 | entries = append(entries, item) 129 | } 130 | 131 | log.Infof(" %d entries found.", len(entries)) 132 | for _, item := range entries { 133 | log.Infof( 134 | " > %s(fileid=%d)", 135 | item.Name, 136 | item.FileId, 137 | ) 138 | } 139 | 140 | dirAttrs := &nfs.PostOpAttr{ 141 | AttributesFollow: true, 142 | Attributes: &nfs.FileAttrs{ 143 | Type: nfs.FTYPE_NF3DIR, 144 | Mode: uint32(di.Mode()), 145 | ATime: nfs.MakeNfsTime(now), 146 | MTime: nfs.MakeNfsTime(di.ModTime()), 147 | CTime: nfs.MakeNfsTime(di.ModTime()), 148 | }, 149 | } 150 | 151 | if _, err := w.WriteUint32(nfs.NFS3_OK); err != nil { 152 | return sizeConsumed, err 153 | } 154 | 155 | // READDIRPLUS3resok.dir_attributes 156 | if _, err := w.WriteAny(dirAttrs); err != nil { 157 | return sizeConsumed, err 158 | } 159 | 160 | // READDIRPLUS3resok.cookieverf 161 | cookieverf := make([]byte, 8) 162 | if _, err := w.WriteAny(cookieverf); err != nil { 163 | return sizeConsumed, err 164 | } 165 | 166 | // dirlistplus3.entries 167 | if len(entries) > 0 { 168 | if _, err := w.WriteAny(true); err != nil { 169 | return sizeConsumed, err 170 | } 171 | for i, entry := range entries { 172 | if _, err := w.WriteAny(entry); err != nil { 173 | return sizeConsumed, err 174 | } else { 175 | // has next 176 | hasNext := i < len(entries)-1 177 | if _, err := w.WriteAny(hasNext); err != nil { 178 | return sizeConsumed, err 179 | } 180 | } 181 | } 182 | } else { 183 | if _, err := w.WriteAny(false); err != nil { 184 | return sizeConsumed, err 185 | } 186 | } 187 | 188 | // dirlistplus3.eof 189 | eof := true 190 | if _, err := w.WriteAny(eof); err != nil { 191 | return sizeConsumed, err 192 | } 193 | 194 | return sizeConsumed, nil 195 | } 196 | } 197 | 198 | log.Warnf("no filesystem specified.") 199 | return sizeConsumed, fail(nfs.NFS3ERR_IO) 200 | } 201 | -------------------------------------------------------------------------------- /nfs/implv3/utils.go: -------------------------------------------------------------------------------- 1 | package implv3 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/binary" 6 | "os" 7 | "path" 8 | "time" 9 | 10 | "github.com/smallfz/libnfs-go/nfs" 11 | ) 12 | 13 | func getFileId(name string) uint64 { 14 | h := md5.New() 15 | h.Write([]byte(name)) 16 | dat := h.Sum(nil) 17 | return binary.BigEndian.Uint64(dat[0:8]) 18 | } 19 | 20 | func fileinfoToEntryPlus3(dir string, fi os.FileInfo) *nfs.EntryPlus3 { 21 | now := time.Now() 22 | 23 | ftype := nfs.FTYPE_NF3REG 24 | if fi.IsDir() { 25 | ftype = nfs.FTYPE_NF3DIR 26 | } 27 | 28 | name := path.Base(fi.Name()) 29 | pathName := fi.Name() 30 | if len(dir) > 0 { 31 | pathName = path.Join(dir, name) 32 | } 33 | 34 | fileId := getFileId(pathName) 35 | 36 | return &nfs.EntryPlus3{ 37 | FileId: fileId, 38 | Name: name, 39 | Cookie: uint64(0), 40 | NameAttrs: &nfs.PostOpAttr{ 41 | AttributesFollow: true, 42 | Attributes: &nfs.FileAttrs{ 43 | Type: ftype, 44 | Mode: uint32(fi.Mode()), 45 | NLink: 4, 46 | Uid: 0, 47 | Gid: 0, 48 | Size: uint64(fi.Size()), 49 | Used: uint64(fi.Size()), 50 | Rdev: nfs.SpecData{D1: 0, D2: 0}, 51 | Fsid: 0, 52 | FileId: fileId, 53 | ATime: nfs.MakeNfsTime(now), 54 | MTime: nfs.MakeNfsTime(fi.ModTime()), 55 | CTime: nfs.MakeNfsTime(fi.ModTime()), 56 | }, 57 | }, 58 | NameHandle: &nfs.PostOpFh3yes{ 59 | HandleFollow: true, 60 | Handle: []byte{}, 61 | }, 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /nfs/implv3/void.go: -------------------------------------------------------------------------------- 1 | package implv3 2 | 3 | import ( 4 | "github.com/smallfz/libnfs-go/log" 5 | "github.com/smallfz/libnfs-go/nfs" 6 | ) 7 | 8 | func Void(h *nfs.RPCMsgCall, ctx nfs.RPCContext) (int, error) { 9 | w := ctx.Writer() 10 | 11 | log.Info("handling void.") 12 | 13 | rh := &nfs.RPCMsgReply{ 14 | Xid: h.Xid, 15 | MsgType: nfs.RPC_REPLY, 16 | ReplyStat: nfs.MSG_ACCEPTED, 17 | } 18 | if _, err := w.WriteAny(rh); err != nil { 19 | return 0, err 20 | } 21 | 22 | auth := &nfs.Auth{ 23 | Flavor: nfs.AUTH_FLAVOR_NULL, 24 | Body: []byte{}, 25 | } 26 | if _, err := w.WriteAny(auth); err != nil { 27 | return 0, err 28 | } 29 | 30 | acceptStat := nfs.ACCEPT_SUCCESS 31 | if _, err := w.WriteUint32(acceptStat); err != nil { 32 | return 0, err 33 | } 34 | 35 | // void => [0]byte 36 | if _, err := w.WriteAny([0]byte{}); err != nil { 37 | return 0, err 38 | } 39 | 40 | return 0, nil 41 | } 42 | -------------------------------------------------------------------------------- /nfs/implv4/access.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/smallfz/libnfs-go/log" 7 | "github.com/smallfz/libnfs-go/nfs" 8 | ) 9 | 10 | func computeAccessOnFile(mode os.FileMode, access uint32) (uint32, uint32) { 11 | support := nfs.ACCESS4_READ 12 | support |= nfs.ACCESS4_LOOKUP 13 | support |= nfs.ACCESS4_MODIFY 14 | support |= nfs.ACCESS4_EXTEND 15 | support |= nfs.ACCESS4_DELETE 16 | support |= nfs.ACCESS4_EXECUTE 17 | 18 | perm := (uint32(mode) >> 6) & uint32(0b0111) 19 | 20 | r := perm & (uint32(1) << 2) 21 | w := perm & (uint32(1) << 1) 22 | xe := perm & uint32(1) 23 | 24 | accForFh := uint32(0) 25 | 26 | if r > 0 { 27 | accForFh = accForFh | nfs.ACCESS4_READ 28 | accForFh = accForFh | nfs.ACCESS4_LOOKUP 29 | } 30 | if w > 0 { 31 | accForFh = accForFh | nfs.ACCESS4_MODIFY 32 | accForFh = accForFh | nfs.ACCESS4_EXTEND 33 | accForFh = accForFh | nfs.ACCESS4_DELETE 34 | } 35 | if xe > 0 { 36 | accForFh = accForFh | nfs.ACCESS4_LOOKUP 37 | accForFh = accForFh | nfs.ACCESS4_EXECUTE 38 | } 39 | 40 | accForFh = accForFh & access 41 | 42 | return support, accForFh 43 | } 44 | 45 | func access(x nfs.RPCContext, args *nfs.ACCESS4args) (*nfs.ACCESS4res, error) { 46 | stat := x.Stat() 47 | 48 | pathName, err := x.GetFS().ResolveHandle(stat.CurrentHandle()) 49 | if err != nil { 50 | log.Warnf(" access: ResolveHandle: %v", err) 51 | return &nfs.ACCESS4res{ 52 | Status: nfs.NFS4ERR_NOENT, 53 | }, nil 54 | } 55 | 56 | fi, err := x.GetFS().Stat(pathName) 57 | if err != nil { 58 | log.Warnf(" access: %s: %v", pathName, err) 59 | return &nfs.ACCESS4res{ 60 | Status: nfs.NFS4ERR_NOENT, 61 | }, nil 62 | } 63 | 64 | // log.Debugf(" access(%v): %s: found: %v", args.Access, pathName, fi) 65 | 66 | support, accForFh := computeAccessOnFile(fi.Mode(), args.Access) 67 | 68 | // log.Printf(" support = %v, access = %v", support, accForFh) 69 | 70 | rs := &nfs.ACCESS4res{ 71 | Status: nfs.NFS4_OK, 72 | Ok: &nfs.ACCESS4resok{ 73 | Supported: support, 74 | Access: accForFh, 75 | }, 76 | } 77 | return rs, nil 78 | } 79 | -------------------------------------------------------------------------------- /nfs/implv4/access_test.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/smallfz/libnfs-go/nfs" 8 | ) 9 | 10 | func TestAccess_file_0555_w(t *testing.T) { 11 | mode := os.FileMode(0o555) 12 | access := nfs.ACCESS4_MODIFY 13 | 14 | support, accForFh := computeAccessOnFile(mode, access) 15 | 16 | if (support & nfs.ACCESS4_MODIFY) == 0 { 17 | t.Fatalf("expects supporting writable. gets otherwise.") 18 | } 19 | 20 | if (accForFh & nfs.ACCESS4_MODIFY) > 0 { 21 | t.Fatalf("expects not writable to the file. gets otherwise.") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /nfs/implv4/attrs_test.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/smallfz/libnfs-go/log" 8 | ) 9 | 10 | func TestFAttr4Decoding(t *testing.T) { 11 | a0 := newFAttr4( 12 | []uint32{1575194, 11575866}, 13 | "AAAAAQAAAABiCghRAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgvZEdjdmJtOXliV0ZzTDNOeWN3PT0vc3JzL2RhdGEvc3IvNmQwOTNjNzgzZTg2MTFlYmI2MWVjYWU4YTgyOTA4MTUvU2NyZWVuc2hvdF8yMDIwMTIxNV8xMTMwMDJfY29tLmNoaW5hdW5pY29tLmdlYXNzaXN0YW50XzE2MDgwMDMyNjQuanBnAAAAAAAAAaYAAAGkAAAAAQAAAAEwAAAAAAAAATAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAGIKCFEAAAAAAAAAAGIKCFEAAAAAAAAAAGIKCFEAAAAAAAAAAAAAAaY=", 14 | ) 15 | 16 | a1 := newFAttr4( 17 | []uint32{1575194, 11575866}, 18 | "AAAAAQAAAABiCimLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHMvc3JzL2RhdGEvc3IvNmQwOTNjNzgzZTg2MTFlYmI2MWVjYWU4YTgyOTA4MTUvU2NyZWVuc2hvdF8yMDIwMTIxNV8xMTMwMDJfY29tLmNoaW5hdW5pY29tLmdlYXNzaXN0YW50XzE2MDgwMDMyNjQuanBnAAAAAAAAAAT0AAAB7QAAAAEAAAABMAAAAAAAAAEwAAAAAAAAAAAAAAAAAAAAAAAQAAAAAABiCimLAAAAAAAAAABiCimLAAAAAAAAAABiCimLAAAAAAAAAAAAAAT0", 19 | ) 20 | 21 | if aa0, err := decodeFAttrs4(a0); err != nil { 22 | t.Fatalf("decodeFAttrs4(a0): %v", err) 23 | } else { 24 | if dat, err := json.MarshalIndent(aa0, "", " "); err != nil { 25 | t.Fatalf("json.MarshalIndent: %v", err) 26 | } else { 27 | log.Println("aa0:") 28 | log.Println(string(dat)) 29 | } 30 | } 31 | 32 | if aa1, err := decodeFAttrs4(a1); err != nil { 33 | t.Fatalf("decodeFAttrs4(a1): %v", err) 34 | } else { 35 | if dat, err := json.MarshalIndent(aa1, "", " "); err != nil { 36 | t.Fatalf("json.MarshalIndent: %v", err) 37 | } else { 38 | log.Println("aa1:") 39 | log.Println(string(dat)) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /nfs/implv4/bitmap4.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | func bitmap4Encode(x map[int]bool) []uint32 { 4 | max := 0 5 | for v := range x { 6 | if v > max { 7 | max = v 8 | } 9 | } 10 | 11 | size := int(max / 32) 12 | if max%32 > 0 { 13 | size += 1 14 | } 15 | 16 | rs := make([]uint32, size) 17 | 18 | for v, on := range x { 19 | if !on { 20 | continue 21 | } 22 | i := v / 32 23 | j := v % 32 24 | s := uint32(1) << j 25 | rs[i] |= s 26 | } 27 | 28 | return rs 29 | } 30 | 31 | func bitmap4Decode(nums []uint32) map[int]bool { 32 | x := map[int]bool{} 33 | if nums != nil { 34 | for i, v := range nums { 35 | for j := 31; j >= 0; j-- { 36 | s := uint32(1) << j 37 | n := 32*i + j 38 | x[n] = s&v == s 39 | } 40 | } 41 | } 42 | return x 43 | } 44 | -------------------------------------------------------------------------------- /nfs/implv4/close.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "github.com/smallfz/libnfs-go/log" 5 | "github.com/smallfz/libnfs-go/nfs" 6 | ) 7 | 8 | func closeFile(x nfs.RPCContext, args *nfs.CLOSE4args) (*nfs.CLOSE4res, error) { 9 | seqId := uint32(0) 10 | if args != nil && args.OpenStateId != nil { 11 | seqId = args.OpenStateId.SeqId 12 | } 13 | 14 | log.Infof("CLOSE4, seq=%d", seqId) 15 | 16 | f := x.Stat().RemoveOpenedFile(seqId) 17 | if f == nil { 18 | log.Warnf("close: opened file in stat not exists.") 19 | return &nfs.CLOSE4res{Status: nfs.NFS4ERR_INVAL}, nil 20 | } else { 21 | log.Debugf(" - %s closed.", f.File().Name()) 22 | f.File().Close() 23 | } 24 | 25 | res := &nfs.CLOSE4res{ 26 | Status: nfs.NFS4_OK, 27 | Ok: args.OpenStateId, 28 | } 29 | return res, nil 30 | } 31 | -------------------------------------------------------------------------------- /nfs/implv4/commit.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "github.com/smallfz/libnfs-go/log" 5 | "github.com/smallfz/libnfs-go/nfs" 6 | ) 7 | 8 | func commit(x nfs.RPCContext, args *nfs.COMMIT4args) (*nfs.COMMIT4res, error) { 9 | vfs := x.GetFS() 10 | fh := x.Stat().CurrentHandle() 11 | pathName, err := vfs.ResolveHandle(fh) 12 | if err != nil { 13 | log.Warnf("commit: ResolveHandle: %v", err) 14 | return &nfs.COMMIT4res{Status: nfs.NFS4ERR_NOENT}, nil 15 | } 16 | 17 | log.Debugf(" commit(%s, offset=%d, count=%d)", 18 | pathName, 19 | args.Offset, 20 | args.Count, 21 | ) 22 | 23 | // verifier := args.Offset 24 | 25 | files := x.Stat().FindOpenedFiles(pathName) 26 | if files != nil && len(files) > 0 { 27 | for _, of := range files { 28 | f := of.File() 29 | if err := f.Sync(); err != nil { 30 | log.Warnf("commit(%s): of.f.Sync: %v", pathName, err) 31 | } else { 32 | log.Infof("commit(%s): ok.", pathName) 33 | } 34 | } 35 | } 36 | 37 | rs := &nfs.COMMIT4res{ 38 | Status: nfs.NFS4_OK, 39 | Ok: &nfs.COMMIT4resok{ 40 | Verifier: 0, 41 | }, 42 | } 43 | return rs, nil 44 | } 45 | -------------------------------------------------------------------------------- /nfs/implv4/compound_test.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/smallfz/libnfs-go/nfs" 10 | "github.com/smallfz/libnfs-go/xdr" 11 | ) 12 | 13 | /* 14 | 15 | C: setclientid 16 | S: jbKrXwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAIwAAAAADsSBgc7fnOp8ZlWGa5oWS 17 | 18 | C: setclientid_confirm 19 | S: jrKrXwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAJAAAAAA= 20 | 21 | C: putfh(fh="AQABAAAAAAA=") + access(Access=31) + getattr(args=[24, 3145728]) 22 | S: K3qbaAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAFgAAAAAAAAADAAAAAAAAAB8AAAAfAAAACQAAAAAAAAACAAAAGAAwAAAAAAAoYCC1bDi7FDcAAAAAAAAQAAAAAABgILVsOLsUNwAAAABgILVsOLsUNw== 23 | 24 | C: putfh(fh="AQABAAAAAAA=") + readdir(args={ 25 | "Cookie": 0, 26 | "CookieVerf": 0, 27 | "DirCount": 8170, 28 | "MaxCount": 32680, 29 | "AttrRequest": [ 30 | 1575194, 31 | 11575866 32 | ] 33 | }) 34 | 35 | S: LHqbaAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAFgAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAW+ZZ6tVAgIjAAAAA29yZwAAAAACABgJGgCwojoAAACYAAAAAmAgtWw7LK/JAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABABAAEBAAAAAAQANAAYTH4XAAAAAAA0AAQAAAHAAAAAAwAAAAEwAAAAAAAAATAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAGFyMN4CqqZ6AAAAAGAgtWw7LK/JAAAAAGAgtWw7LK/JAAAAAAA0AAQAAAABf/////////8AAAAHb3JnLXN2YwAAAAACABgJGgCwojoAAACYAAAAAmAgs54P1yYeAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABABAAEBAAAAAAIANADNxd4fAAAAAAA0AAIAAAHAAAAAAwAAAAEwAAAAAAAAATAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAGFyMN4CqqZ6AAAAAGAgs54P1yYeAAAAAGAgs54P1yYeAAAAAAA0AAIAAAAAAAAAAQ== 36 | 37 | */ 38 | 39 | func TestParsingCOMPOUND4_res_putfh_readdir(t *testing.T) { 40 | raw, _ := base64.StdEncoding.DecodeString("uNo+UAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAFgAAAAAAAAAaAAAAAAAAAAAAAAAAAAAAAW+ZZ6tVAgIjAAAAA29yZwAAAAACABgJGgCwojoAAACYAAAAAmAgtWw7LK/JAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABABAAEBAAAAAAQANAAYTH4XAAAAAAA0AAQAAAHAAAAAAwAAAAEwAAAAAAAAATAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAGGXaXYdnd+oAAAAAGAgtWw7LK/JAAAAAGAgtWw7LK/JAAAAAAA0AAQAAAABf/////////8AAAAHb3JnLXN2YwAAAAACABgJGgCwojoAAACYAAAAAmAgs54P1yYeAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABABAAEBAAAAAAIANADNxd4fAAAAAAA0AAIAAAHAAAAAAwAAAAEwAAAAAAAAATAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAGGXbKkygFszAAAAAGAgs54P1yYeAAAAAGAgs54P1yYeAAAAAAA0AAIAAAAAAAAAAQ==") 41 | 42 | reader := xdr.NewReader(bytes.NewBuffer(raw)) 43 | 44 | _, err := reader.ReadUint32() /* xid */ 45 | if err != nil { 46 | t.Fatalf("%v", err) 47 | return 48 | } 49 | 50 | msgType, err := reader.ReadUint32() 51 | if err != nil { 52 | t.Fatalf("%v", err) 53 | return 54 | } 55 | 56 | if msgType != xdr.RPC_REPLY { 57 | t.Fatalf("expects RPC_REPLY but get %d", msgType) 58 | return 59 | } 60 | 61 | replyStat, err := reader.ReadUint32() 62 | if err != nil { 63 | t.Fatalf("%v", err) 64 | return 65 | } 66 | 67 | if replyStat != nfs.ACCEPT_SUCCESS { 68 | t.Fatalf(" reply-stat: %d", replyStat) 69 | return 70 | } 71 | 72 | auth := &nfs.Auth{} 73 | if _, err := reader.ReadAs(auth); err != nil { 74 | t.Fatalf("%v", err) 75 | return 76 | } 77 | 78 | acceptStatus, err := reader.ReadUint32() 79 | if err != nil { 80 | t.Fatalf("%v", err) 81 | return 82 | } 83 | 84 | if acceptStatus != nfs.ACCEPT_SUCCESS { 85 | t.Fatalf(" accept-status: %d", acceptStatus) 86 | return 87 | } 88 | 89 | status, err := reader.ReadUint32() 90 | if err != nil { 91 | t.Fatalf(" reader.ReadUint32() => status: %v", err) 92 | return 93 | } 94 | 95 | if status != nfs.NFS4_OK { 96 | t.Fatalf(" status: %d", status) 97 | } 98 | 99 | // decode compound result... 100 | 101 | tag := "" 102 | if _, err := reader.ReadAs(&tag); err != nil { 103 | t.Fatalf("%v", err) 104 | return 105 | } 106 | 107 | opsCnt, err := reader.ReadUint32() 108 | if err != nil { 109 | t.Fatalf("%v", err) 110 | return 111 | } 112 | 113 | fmt.Printf("op results count: %d\n", opsCnt) 114 | 115 | for i := 0; i < int(opsCnt); i++ { 116 | opnum4, err := reader.ReadUint32() 117 | if err != nil { 118 | t.Fatalf("%v", err) 119 | return 120 | } 121 | 122 | opStatus, err := reader.ReadUint32() 123 | if err != nil { 124 | t.Fatalf("%v", err) 125 | } 126 | 127 | fmt.Printf( 128 | "op = %s, response status = %d.\n", 129 | nfs.Proc4Name(opnum4), 130 | opStatus, 131 | ) 132 | 133 | switch opnum4 { 134 | case nfs.OP4_PUTFH: 135 | break 136 | 137 | case nfs.OP4_READDIR: 138 | cookieVerf := uint64(0) 139 | if _, err := reader.ReadAs(&cookieVerf); err != nil { 140 | t.Fatalf("%v", err) 141 | return 142 | } 143 | fmt.Printf(" - cookie_verf = %v\n", cookieVerf) 144 | 145 | hasEntries := false 146 | if _, err := reader.ReadAs(&hasEntries); err != nil { 147 | t.Fatalf("%v", err) 148 | return 149 | } 150 | fmt.Printf(" - has_entries = %v\n", hasEntries) 151 | 152 | entries := []*nfs.Entry4{} 153 | for { 154 | entry := &nfs.Entry4{} 155 | if _, err := reader.ReadAs(entry); err != nil { 156 | t.Fatalf("%v", err) 157 | return 158 | } 159 | entries = append(entries, entry) 160 | if !entry.HasNext { 161 | break 162 | } 163 | } 164 | 165 | for _, entry := range entries { 166 | fmt.Printf(" - entry: %s\n", entry.Name) 167 | fmt.Println(toJson(entry)) 168 | if _, err := decodeFAttrs4(entry.Attrs); err != nil { 169 | t.Fatalf("%v", err) 170 | } 171 | } 172 | 173 | eof := false 174 | if _, err := reader.ReadAs(&eof); err != nil { 175 | t.Fatalf("%v", err) 176 | return 177 | } 178 | 179 | fmt.Printf(" - eof = %v\n", eof) 180 | 181 | break 182 | 183 | default: 184 | t.Fatalf("unexpected op: %s", nfs.Proc4Name(opnum4)) 185 | return 186 | } 187 | } 188 | } 189 | 190 | func TestParsingCOMPOUND4_res_getAttr(t *testing.T) { 191 | raw, _ := base64.StdEncoding.DecodeString("KYbWCgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAGAAAAAAAAAAKAAAAAAAAAAgBAAEAAAAAAAAAAAkAAAAAAAAAAgAQARoAsKI6AAAAgAAAAAJgILVsOLsUNwAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANAABAAABwAAAAAQAAAABMAAAAAAAAAEwAAAAAAAAAAAAAAAAAAAAAAAQAAAAAABhlImSN76PiAAAAABgILVsOLsUNwAAAABgILVsOLsUNwAAAAAYMqia") 192 | 193 | reader := xdr.NewReader(bytes.NewBuffer(raw)) 194 | 195 | _, err := reader.ReadUint32() /* xid */ 196 | if err != nil { 197 | t.Fatalf("%v", err) 198 | return 199 | } 200 | 201 | msgType, err := reader.ReadUint32() 202 | if err != nil { 203 | t.Fatalf("%v", err) 204 | return 205 | } 206 | 207 | if msgType != xdr.RPC_REPLY { 208 | t.Fatalf("expects RPC_REPLY but get %d", msgType) 209 | return 210 | } 211 | 212 | replyStat, err := reader.ReadUint32() 213 | if err != nil { 214 | t.Fatalf("%v", err) 215 | return 216 | } 217 | 218 | if replyStat != nfs.ACCEPT_SUCCESS { 219 | t.Fatalf(" reply-stat: %d", replyStat) 220 | return 221 | } 222 | 223 | auth := &nfs.Auth{} 224 | if _, err := reader.ReadAs(auth); err != nil { 225 | t.Fatalf("%v", err) 226 | return 227 | } 228 | 229 | acceptStatus, err := reader.ReadUint32() 230 | if err != nil { 231 | t.Fatalf("%v", err) 232 | return 233 | } 234 | 235 | if acceptStatus != nfs.ACCEPT_SUCCESS { 236 | t.Fatalf(" accept-status: %d", acceptStatus) 237 | return 238 | } 239 | 240 | status, err := reader.ReadUint32() 241 | if err != nil { 242 | t.Fatalf(" reader.ReadUint32() => status: %v", err) 243 | return 244 | } 245 | 246 | if status != nfs.NFS4_OK { 247 | t.Fatalf(" status: %d", status) 248 | } 249 | 250 | // decode compound result... 251 | 252 | tag := "" 253 | if _, err := reader.ReadAs(&tag); err != nil { 254 | t.Fatalf("%v", err) 255 | return 256 | } 257 | 258 | opsCnt, err := reader.ReadUint32() 259 | if err != nil { 260 | t.Fatalf("%v", err) 261 | return 262 | } 263 | 264 | fmt.Printf("op results count: %d\n", opsCnt) 265 | 266 | for i := 0; i < int(opsCnt); i++ { 267 | opnum4, err := reader.ReadUint32() 268 | if err != nil { 269 | t.Fatalf("%v", err) 270 | return 271 | } 272 | 273 | opStatus, err := reader.ReadUint32() 274 | if err != nil { 275 | t.Fatalf("%v", err) 276 | } 277 | 278 | fmt.Printf( 279 | "op = %s, response status = %d.\n", 280 | nfs.Proc4Name(opnum4), 281 | opStatus, 282 | ) 283 | 284 | switch opnum4 { 285 | case nfs.OP4_PUTROOTFH: 286 | break 287 | 288 | case nfs.OP4_GETFH: 289 | res := &nfs.GETFH4resok{} 290 | if _, err := reader.ReadAs(res); err != nil { 291 | t.Fatalf("%v", err) 292 | return 293 | } 294 | break 295 | 296 | case nfs.OP4_GETATTR: 297 | res := &nfs.GETATTR4resok{} 298 | if _, err := reader.ReadAs(res); err != nil { 299 | t.Fatalf("%v", err) 300 | return 301 | } 302 | 303 | fmt.Println(toJson(res)) 304 | 305 | if _, err := decodeFAttrs4(res.Attr); err != nil { 306 | t.Fatalf("decodeAttrs: %v", err) 307 | } 308 | 309 | break 310 | 311 | default: 312 | t.Fatalf("unexpected op: %s", nfs.Proc4Name(opnum4)) 313 | return 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /nfs/implv4/create.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/smallfz/libnfs-go/fs" 7 | "github.com/smallfz/libnfs-go/log" 8 | "github.com/smallfz/libnfs-go/nfs" 9 | "github.com/smallfz/libnfs-go/xdr" 10 | ) 11 | 12 | func readOpCreateArgs(r *xdr.Reader) (*nfs.CREATE4args, int, error) { 13 | sizeConsumed := 0 14 | typ, err := r.ReadUint32() 15 | if err != nil { 16 | return nil, sizeConsumed, err 17 | } 18 | sizeConsumed += 4 19 | 20 | args := &nfs.CREATE4args{ 21 | ObjType: typ, 22 | } 23 | 24 | switch typ { 25 | case nfs.NF4LNK: 26 | var linkData string 27 | size, err := r.ReadAs(&linkData) 28 | if err != nil { 29 | return nil, sizeConsumed, err 30 | } 31 | sizeConsumed += size 32 | args.LinkData = linkData 33 | 34 | case nfs.NF4BLK, nfs.NF4CHR: 35 | devData := &nfs.Specdata4{} 36 | size, err := r.ReadAs(devData) 37 | if err != nil { 38 | return nil, sizeConsumed, err 39 | } 40 | sizeConsumed += size 41 | args.DevData = devData 42 | } 43 | 44 | var objName string 45 | size, err := r.ReadAs(&objName) 46 | if err != nil { 47 | return nil, sizeConsumed, err 48 | } 49 | sizeConsumed += size 50 | args.ObjName = objName 51 | 52 | attrs := &nfs.FAttr4{} 53 | size, err = r.ReadAs(attrs) 54 | if err != nil { 55 | return nil, sizeConsumed, err 56 | } 57 | sizeConsumed += size 58 | args.CreateAttrs = attrs 59 | 60 | return args, sizeConsumed, nil 61 | } 62 | 63 | func create(x nfs.RPCContext, args *nfs.CREATE4args) (*nfs.CREATE4res, error) { 64 | switch args.ObjType { 65 | case nfs.NF4DIR, nfs.NF4REG, nfs.NF4LNK: 66 | // Supported types 67 | case nfs.NF4BLK, nfs.NF4CHR, nfs.NF4FIFO, nfs.NF4SOCK: 68 | return &nfs.CREATE4res{Status: nfs.NFS4ERR_PERM}, nil 69 | default: 70 | return &nfs.CREATE4res{Status: nfs.NFS4ERR_BADTYPE}, nil 71 | } 72 | 73 | resFailPerm := &nfs.CREATE4res{Status: nfs.NFS4ERR_PERM} 74 | resFail500 := &nfs.CREATE4res{Status: nfs.NFS4ERR_SERVERFAULT} 75 | 76 | // cwd := x.Stat().Cwd() 77 | vfs := x.GetFS() 78 | 79 | fh := x.Stat().CurrentHandle() 80 | cwd, err := vfs.ResolveHandle(fh) 81 | if err != nil { 82 | return resFailPerm, nil 83 | } 84 | 85 | fi, err := vfs.Stat(cwd) 86 | if err != nil { 87 | log.Debugf(" create: vfs.Stat(%s): %v", cwd, err) 88 | return resFail500, nil 89 | } 90 | if !fi.IsDir() { 91 | return resFailPerm, nil 92 | } 93 | 94 | pathName := fs.Join(cwd, args.ObjName) 95 | log.Debugf(" create: %s", pathName) 96 | if _, err := vfs.Stat(pathName); err == nil { 97 | return &nfs.CREATE4res{Status: nfs.NFS4ERR_EXIST}, nil 98 | } 99 | 100 | cinfo := &nfs.ChangeInfo4{} 101 | attrSet := []uint32{} 102 | 103 | decAttrs, err := decodeFAttrs4(args.CreateAttrs) 104 | if err != nil { 105 | log.Warnf("create: decodeFAttrs: %v", err) 106 | return resFailPerm, nil 107 | } 108 | 109 | switch args.ObjType { 110 | case nfs.NF4DIR: 111 | 112 | // create a directory 113 | 114 | mod := os.FileMode(0o755) 115 | if decAttrs.Mode != nil { 116 | mod = os.FileMode(*decAttrs.Mode) 117 | } 118 | mod = mod | os.ModeDir 119 | 120 | if err := vfs.MkdirAll(pathName, mod); err != nil { 121 | log.Warnf("create: vfs.MkdirAll(%s): %v", pathName, err) 122 | return resFailPerm, nil 123 | } 124 | 125 | fi, err := vfs.Stat(pathName) 126 | if err != nil { 127 | log.Warnf("create: vfs.Stat(%s): %v", pathName, err) 128 | return resFailPerm, nil 129 | } 130 | 131 | attr := fileInfoToAttrs(vfs, pathName, fi, nil) 132 | attrSet = attr.Mask 133 | 134 | // set current fh to the newly created one. 135 | fh, err := vfs.GetHandle(fi) 136 | if err != nil { 137 | return resFailPerm, nil 138 | } 139 | x.Stat().SetCurrentHandle(fh) 140 | 141 | case nfs.NF4REG: 142 | 143 | // create a regular file 144 | 145 | mod := os.FileMode(0o644) 146 | if decAttrs.Mode != nil { 147 | mod = os.FileMode(*decAttrs.Mode) 148 | } 149 | 150 | flag := os.O_CREATE | os.O_RDWR | os.O_TRUNC 151 | 152 | f, err := vfs.OpenFile(pathName, flag, mod) 153 | if err != nil { 154 | log.Warnf("create: vfs.OpenFile: %v", err) 155 | return resFailPerm, nil 156 | } 157 | defer f.Close() 158 | 159 | fi, err := f.Stat() 160 | if err != nil { 161 | log.Warnf("create: f.Stat(): %v", err) 162 | return resFailPerm, nil 163 | } 164 | 165 | attr := fileInfoToAttrs(vfs, pathName, fi, nil) 166 | attrSet = attr.Mask 167 | 168 | // set current fh to the newly created one. 169 | fh, err := vfs.GetHandle(fi) 170 | if err != nil { 171 | return resFailPerm, nil 172 | } 173 | x.Stat().SetCurrentHandle(fh) 174 | 175 | case nfs.NF4LNK: 176 | 177 | // create symlink 178 | 179 | err = vfs.Symlink(args.LinkData, pathName) 180 | if err != nil { 181 | return &nfs.CREATE4res{Status: nfs.NFS4err(err)}, nil 182 | } 183 | 184 | fi, err := vfs.Stat(pathName) 185 | if err != nil { 186 | log.Warnf("create: vfs.Stat(%s): %v", pathName, err) 187 | return resFailPerm, nil 188 | } 189 | 190 | attr := fileInfoToAttrs(vfs, pathName, fi, nil) 191 | attrSet = attr.Mask 192 | 193 | // set current fh to the newly created one. 194 | fh, err := vfs.GetHandle(fi) 195 | if err != nil { 196 | return resFailPerm, nil 197 | } 198 | x.Stat().SetCurrentHandle(fh) 199 | } 200 | 201 | res := &nfs.CREATE4res{ 202 | Status: nfs.NFS4_OK, 203 | Ok: &nfs.CREATE4resok{ 204 | CInfo: cinfo, 205 | AttrSet: attrSet, 206 | }, 207 | } 208 | return res, nil 209 | } 210 | -------------------------------------------------------------------------------- /nfs/implv4/getattr.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "github.com/smallfz/libnfs-go/log" 5 | "github.com/smallfz/libnfs-go/nfs" 6 | ) 7 | 8 | func getAttr(x nfs.RPCContext, args *nfs.GETATTR4args) (*nfs.GETATTR4res, error) { 9 | // cwd := x.Stat().Cwd() 10 | // pathName := cwd 11 | 12 | vfs := x.GetFS() 13 | fh := x.Stat().CurrentHandle() 14 | pathName, err := vfs.ResolveHandle(fh) 15 | if err != nil { 16 | log.Warnf("getattr: ResolveHandle: %v", err) 17 | return &nfs.GETATTR4res{Status: nfs.NFS4ERR_NOENT}, nil 18 | } 19 | 20 | // log.Debugf(" getattr(%s => %s)", fh, pathName) 21 | log.Debugf(" getattr(%s)", pathName) 22 | 23 | idxReq := bitmap4Decode(args.AttrRequest) 24 | 25 | // log.Debugf("getattr: attrs: %v", idxReq) 26 | // for id, on := range idxReq { 27 | // if on { 28 | // name, found := GetAttrNameById(id) 29 | // if found { 30 | // log.Debugf(" - request attr: [%d] %s.", id, name) 31 | // } 32 | // } 33 | // } 34 | 35 | fi, err := vfs.Stat(pathName) 36 | if err != nil { 37 | log.Debugf("getattr: vfs.Stat(%s): %v", pathName, err) 38 | return &nfs.GETATTR4res{Status: nfs.NFS4ERR_NOENT}, nil 39 | } 40 | 41 | _, err = vfs.GetHandle(fi) 42 | if err != nil { 43 | log.Warnf("getattr: vfs.GetHandle: %v", err) 44 | return &nfs.GETATTR4res{Status: nfs.NFS4ERR_NOENT}, nil 45 | } 46 | 47 | attrs := fileInfoToAttrs(vfs, pathName, fi, idxReq) 48 | 49 | rs := &nfs.GETATTR4res{ 50 | Status: nfs.NFS4_OK, 51 | Ok: &nfs.GETATTR4resok{ 52 | Attr: attrs, 53 | }, 54 | } 55 | return rs, nil 56 | } 57 | -------------------------------------------------------------------------------- /nfs/implv4/getfh.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | -------------------------------------------------------------------------------- /nfs/implv4/implv4.go: -------------------------------------------------------------------------------- 1 | // RFC-7530, 7531 2 | package implv4 3 | -------------------------------------------------------------------------------- /nfs/implv4/link.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "io/fs" 5 | "os" 6 | "path" 7 | "strconv" 8 | 9 | "github.com/smallfz/libnfs-go/log" 10 | "github.com/smallfz/libnfs-go/nfs" 11 | ) 12 | 13 | func link(x nfs.RPCContext, args *nfs.LINK4args) (*nfs.LINK4res, error) { 14 | log.Debugf("link obj: %s", strconv.Quote(args.NewName)) 15 | 16 | stat := x.Stat() 17 | vfs := x.GetFS() 18 | 19 | // 20 | // Check source stored by previous SAVE_FH call. 21 | // 22 | savefh, ok := stat.PeekHandle() 23 | if !ok { 24 | log.Warn("PopHandle: SAVED_FH not found") 25 | return &nfs.LINK4res{Status: nfs.NFS4ERR_INVAL}, nil 26 | } 27 | 28 | oldpath, err := vfs.ResolveHandle(savefh) 29 | if err != nil { 30 | log.Warnf("ResolveHandle: %v", err) 31 | return &nfs.LINK4res{Status: nfs.NFS4err(err)}, nil 32 | } 33 | 34 | _, err = vfs.Stat(oldpath) 35 | if err != nil { 36 | log.Warnf(" link: vfs.Stat(%s): %v", oldpath, err) 37 | return &nfs.LINK4res{Status: nfs.NFS4err(err)}, nil 38 | } 39 | 40 | // 41 | // Check destination. 42 | // 43 | fh := stat.CurrentHandle() 44 | folder, err := vfs.ResolveHandle(fh) 45 | if err != nil { 46 | log.Warnf("ResolveHandle: %v", err) 47 | return &nfs.LINK4res{Status: nfs.NFS4err(err)}, nil 48 | } 49 | 50 | newpath := path.Join(folder, args.NewName) 51 | _, err = vfs.Stat(newpath) 52 | if err == nil || os.IsExist(err) { 53 | if err == nil { 54 | err = fs.ErrExist 55 | } 56 | log.Warnf(" link: exists: vfs.Stat(%s): %v", newpath, err) 57 | return &nfs.LINK4res{Status: nfs.NFS4err(err)}, nil 58 | } 59 | 60 | // 61 | // Perform Link. 62 | // 63 | if err := vfs.Link(oldpath, newpath); err != nil { 64 | log.Warnf("link: vfs.Link(%s, %s): %v", oldpath, newpath, err) 65 | return &nfs.LINK4res{Status: nfs.NFS4err(err)}, nil 66 | } 67 | 68 | return &nfs.LINK4res{ 69 | Status: nfs.NFS4_OK, 70 | Ok: &nfs.LINK4resok{ 71 | CInfo: &nfs.ChangeInfo4{ 72 | Atomic: true, 73 | Before: 0, 74 | After: 0, 75 | }, 76 | }, 77 | }, nil 78 | } 79 | -------------------------------------------------------------------------------- /nfs/implv4/lookup.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "path" 5 | 6 | "github.com/smallfz/libnfs-go/log" 7 | "github.com/smallfz/libnfs-go/nfs" 8 | ) 9 | 10 | func lookup(x nfs.RPCContext, args *nfs.LOOKUP4args) (*nfs.LOOKUP4res, error) { 11 | // log.Debugf("lookup obj: '%s'", args.ObjName) 12 | 13 | if len(args.ObjName) <= 0 { 14 | return &nfs.LOOKUP4res{ 15 | Status: nfs.NFS4ERR_INVAL, 16 | }, nil 17 | } 18 | 19 | stat := x.Stat() 20 | vfs := x.GetFS() 21 | 22 | fh4 := stat.CurrentHandle() 23 | folder, err := vfs.ResolveHandle(fh4) 24 | if err != nil { 25 | log.Warnf("ResolveHandle: %v", err) 26 | return &nfs.LOOKUP4res{Status: nfs.NFS4ERR_PERM}, nil 27 | } 28 | 29 | pathName := path.Join(folder, args.ObjName) 30 | 31 | fi, err := x.GetFS().Stat(pathName) 32 | if err != nil { 33 | log.Warnf(" lookup: %s: %v", pathName, err) 34 | return &nfs.LOOKUP4res{ 35 | Status: nfs.NFS4ERR_NOENT, 36 | }, nil 37 | } 38 | 39 | // stat.SetCwd(pathName) 40 | fh, err := vfs.GetHandle(fi) 41 | if err != nil { 42 | return &nfs.LOOKUP4res{ 43 | Status: nfs.NFS4ERR_NOENT, 44 | }, nil 45 | } 46 | stat.SetCurrentHandle(fh) 47 | 48 | res := &nfs.LOOKUP4res{ 49 | Status: nfs.NFS4_OK, 50 | } 51 | return res, nil 52 | } 53 | -------------------------------------------------------------------------------- /nfs/implv4/open.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/smallfz/libnfs-go/fs" 9 | "github.com/smallfz/libnfs-go/log" 10 | "github.com/smallfz/libnfs-go/nfs" 11 | "github.com/smallfz/libnfs-go/xdr" 12 | ) 13 | 14 | func readOpOpenArgs(r *xdr.Reader) (*nfs.OPEN4args, int, error) { 15 | sizeConsumed := 0 16 | 17 | args := &nfs.OPEN4args{} 18 | 19 | seqId, err := r.ReadUint32() 20 | if err != nil { 21 | return nil, sizeConsumed, err 22 | } 23 | sizeConsumed += 4 24 | args.SeqId = seqId 25 | 26 | v, err := r.ReadUint32() 27 | if err != nil { 28 | return nil, sizeConsumed, err 29 | } 30 | sizeConsumed += 4 31 | args.ShareAccess = v 32 | 33 | v, err = r.ReadUint32() 34 | if err != nil { 35 | return nil, sizeConsumed, err 36 | } 37 | sizeConsumed += 4 38 | args.ShareDeny = v 39 | 40 | /* open_owner4 */ 41 | 42 | owner := &nfs.OpenOwner4{} 43 | size, err := r.ReadAs(owner) 44 | if err != nil { 45 | return nil, sizeConsumed, err 46 | } 47 | sizeConsumed += size 48 | args.Owner = owner 49 | 50 | v, err = r.ReadUint32() 51 | if err != nil { 52 | return nil, sizeConsumed, err 53 | } 54 | sizeConsumed += 4 55 | args.OpenHow = v 56 | 57 | /* openflag4 */ 58 | 59 | switch args.OpenHow { 60 | case nfs.OPEN4_CREATE: 61 | how := &nfs.CreateHow4{} 62 | v, err = r.ReadUint32() 63 | if err != nil { 64 | return nil, sizeConsumed, err 65 | } 66 | sizeConsumed += 4 67 | how.CreateMode = v 68 | 69 | switch how.CreateMode { 70 | case nfs.UNCHECKED4, nfs.GUARDED4: 71 | attr := &nfs.FAttr4{} 72 | size, err = r.ReadAs(attr) 73 | if err != nil { 74 | return nil, sizeConsumed, err 75 | } 76 | sizeConsumed += size 77 | how.CreateAttrs = attr 78 | 79 | case nfs.EXCLUSIVE4: 80 | verf := uint64(0) 81 | size, err = r.ReadAs(&verf) 82 | if err != nil { 83 | return nil, sizeConsumed, err 84 | } 85 | sizeConsumed += size 86 | how.CreateVerf = verf 87 | 88 | default: 89 | return nil, sizeConsumed, fmt.Errorf( 90 | "unexpected createmode: %v", 91 | how.CreateMode, 92 | ) 93 | } 94 | 95 | args.CreateHow = how 96 | } 97 | 98 | // open_claim4 99 | 100 | claim := &nfs.OpenClaim4{} 101 | v, err = r.ReadUint32() 102 | if err != nil { 103 | return nil, sizeConsumed, err 104 | } 105 | sizeConsumed += 4 106 | claim.Claim = v 107 | 108 | switch claim.Claim { 109 | case nfs.CLAIM_NULL: 110 | var file string 111 | size, err := r.ReadAs(&file) 112 | if err != nil { 113 | return nil, sizeConsumed, err 114 | } 115 | sizeConsumed += size 116 | claim.File = file 117 | 118 | case nfs.CLAIM_PREVIOUS: 119 | delegateTyp, err := r.ReadUint32() 120 | if err != nil { 121 | return nil, sizeConsumed, err 122 | } 123 | claim.DelegateType = delegateTyp 124 | 125 | case nfs.CLAIM_DELEGATE_CUR: 126 | curInfo := &nfs.OpenClaimDelegateCur4{} 127 | size, err = r.ReadAs(curInfo) 128 | if err != nil { 129 | return nil, sizeConsumed, err 130 | } 131 | sizeConsumed += size 132 | claim.DelegateCurInfo = curInfo 133 | 134 | case nfs.CLAIM_DELEGATE_PREV: 135 | var prev string 136 | size, err = r.ReadAs(&prev) 137 | if err != nil { 138 | return nil, sizeConsumed, err 139 | } 140 | sizeConsumed += size 141 | claim.FileDelegatePrev = prev 142 | 143 | default: 144 | return nil, sizeConsumed, fmt.Errorf("invalid claim: %v", claim.Claim) 145 | } 146 | 147 | args.Claim = claim 148 | return args, sizeConsumed, nil 149 | } 150 | 151 | func open(x nfs.RPCContext, args *nfs.OPEN4args) (*nfs.ResGenericRaw, error) { 152 | // log.Infof(toJson(args)) 153 | 154 | resFail500 := &nfs.ResGenericRaw{Status: nfs.NFS4ERR_SERVERFAULT} 155 | resFailPerm := &nfs.ResGenericRaw{Status: nfs.NFS4ERR_PERM} 156 | resFailDup := &nfs.ResGenericRaw{Status: nfs.NFS4ERR_EXIST} 157 | resFail404 := &nfs.ResGenericRaw{Status: nfs.NFS4ERR_NOENT} 158 | 159 | createIfNotExists := false 160 | raiseWhenExists := true 161 | trunc := false 162 | 163 | decAttrs := (*Attr)(nil) 164 | 165 | if args.OpenHow == nfs.OPEN4_CREATE { 166 | createIfNotExists = true 167 | switch args.CreateHow.CreateMode { 168 | case nfs.UNCHECKED4: 169 | // no error if target exists. truncate existing target. 170 | raiseWhenExists = false 171 | trunc = true 172 | attr, err := decodeFAttrs4(args.CreateHow.CreateAttrs) 173 | if err != nil { 174 | return resFail500, nil 175 | } 176 | decAttrs = attr 177 | 178 | case nfs.GUARDED4: 179 | // raise NFS4ERR_EXIST if target exists. 180 | raiseWhenExists = true 181 | case nfs.EXCLUSIVE4: 182 | // Nothing to do here. 183 | default: 184 | return &nfs.ResGenericRaw{Status: nfs.NFS4ERR_NOTSUPP}, nil 185 | } 186 | } else { 187 | raiseWhenExists = false 188 | } 189 | 190 | stat := x.Stat() 191 | vfs := x.GetFS() 192 | 193 | cwd, err := vfs.ResolveHandle(stat.CurrentHandle()) 194 | if err != nil { 195 | return &nfs.ResGenericRaw{Status: nfs.NFS4ERR_PERM}, nil 196 | } 197 | 198 | if di, err := vfs.Stat(cwd); err != nil { 199 | return resFail500, nil 200 | } else if !di.IsDir() { 201 | return resFailPerm, nil 202 | } 203 | 204 | pathName := fs.Join(cwd, args.Claim.File) 205 | createNew := false 206 | 207 | fi, err := vfs.Stat(pathName) 208 | if err != nil { 209 | if os.IsNotExist(err) { 210 | if !createIfNotExists { 211 | return resFail404, nil 212 | } else { 213 | // todo: create new file. 214 | createNew = true 215 | } 216 | } else { 217 | return resFail500, nil 218 | } 219 | } else { 220 | if raiseWhenExists { 221 | return resFailDup, nil 222 | } 223 | if fi.IsDir() { 224 | return resFailPerm, nil 225 | } 226 | // ok, already exists. nothing to do. 227 | } 228 | 229 | var finalFi fs.FileInfo = fi 230 | 231 | attrSet := []uint32{} 232 | 233 | seqId := uint32(0) // RFC7531: stateid4.seqid 234 | 235 | if createNew { 236 | flag := os.O_CREATE | os.O_RDWR | os.O_TRUNC 237 | 238 | mode := os.FileMode(0o644) 239 | if decAttrs != nil && decAttrs.Mode != nil { 240 | mode = os.FileMode(*decAttrs.Mode) 241 | } 242 | 243 | if f, err := vfs.OpenFile(pathName, flag, mode); err != nil { 244 | log.Warnf("vfs.OpenFile(%s): %v", pathName, err) 245 | return resFailPerm, nil 246 | } else { 247 | seqId = x.Stat().AddOpenedFile(pathName, f) 248 | 249 | fi, err := f.Stat() 250 | if err != nil { 251 | return resFailPerm, nil 252 | } 253 | 254 | finalFi = fi 255 | 256 | if args.CreateHow != nil && args.CreateHow.CreateAttrs != nil { 257 | idxReq := bitmap4Decode(args.CreateHow.CreateAttrs.Mask) 258 | a4 := fileInfoToAttrs(vfs, pathName, fi, idxReq) 259 | attrSet = a4.Mask 260 | } 261 | } 262 | 263 | } else { 264 | 265 | flag := os.O_RDWR 266 | if trunc { 267 | flag = flag | os.O_TRUNC 268 | } 269 | 270 | if f, err := vfs.OpenFile(pathName, flag, fi.Mode()); err != nil { 271 | log.Warnf("vfs.OpenFile(%s): %v", pathName, err) 272 | return resFailPerm, nil 273 | } else { 274 | seqId = x.Stat().AddOpenedFile(pathName, f) 275 | } 276 | 277 | } 278 | 279 | if fh, err := vfs.GetHandle(finalFi); err != nil { 280 | log.Warnf("vfs.GetHandle: %v", err) 281 | return resFailPerm, nil 282 | } else { 283 | stat.SetCurrentHandle(fh) 284 | } 285 | 286 | res := &nfs.OPEN4res{ 287 | Status: nfs.NFS4_OK, 288 | Ok: &nfs.OPEN4resok{ 289 | StateId: &nfs.StateId4{ 290 | SeqId: seqId, 291 | Other: [3]uint32{0, 0, 0}, 292 | }, 293 | CInfo: &nfs.ChangeInfo4{}, 294 | Rflags: uint32(0), // OPEN4_RESULT_* 295 | AttrSet: attrSet, 296 | Delegation: &nfs.OpenDelegation4{ 297 | Type: nfs.OPEN_DELEGATE_NONE, 298 | }, 299 | }, 300 | } 301 | 302 | buff := bytes.NewBuffer([]byte{}) 303 | w := xdr.NewWriter(buff) 304 | 305 | w.WriteAny(res.Ok) 306 | 307 | return &nfs.ResGenericRaw{ 308 | Status: res.Status, 309 | Reader: bytes.NewReader(buff.Bytes()), 310 | }, nil 311 | } 312 | -------------------------------------------------------------------------------- /nfs/implv4/open_downgrade.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "github.com/smallfz/libnfs-go/log" 5 | "github.com/smallfz/libnfs-go/nfs" 6 | "github.com/smallfz/libnfs-go/xdr" 7 | ) 8 | 9 | func readOpOpenDgArgs(r *xdr.Reader) (*nfs.OPENDG4args, int, error) { 10 | sizeConsumed := 0 11 | args := &nfs.OPENDG4args{} 12 | 13 | if size, err := r.ReadAs(args); err != nil { 14 | return nil, sizeConsumed, err 15 | } else { 16 | sizeConsumed += size 17 | } 18 | 19 | return args, sizeConsumed, nil 20 | } 21 | 22 | func openDg(x nfs.RPCContext, args *nfs.OPENDG4args) (*nfs.ResGenericRaw, error) { 23 | // log.Infof(toJson(args)) 24 | 25 | // resFail500 := &nfs.ResGenericRaw{Status: nfs.NFS4ERR_SERVERFAULT} 26 | resFailPerm := &nfs.ResGenericRaw{Status: nfs.NFS4ERR_PERM} 27 | // resFailDup := &nfs.ResGenericRaw{Status: nfs.NFS4ERR_EXIST} 28 | // resFail404 := &nfs.ResGenericRaw{Status: nfs.NFS4ERR_NOENT} 29 | 30 | state := x.Stat().GetOpenedFile(args.SeqId) 31 | if state == nil { 32 | log.Warnf("try to open_downgrade on a not-openned file.") 33 | return resFailPerm, nil 34 | } 35 | 36 | return &nfs.ResGenericRaw{ 37 | Status: nfs.NFS4_OK, 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /nfs/implv4/read.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "github.com/smallfz/libnfs-go/log" 8 | "github.com/smallfz/libnfs-go/nfs" 9 | ) 10 | 11 | func read(x nfs.RPCContext, args *nfs.READ4args) (*nfs.READ4res, error) { 12 | // stat := x.Stat() 13 | // vfs := x.GetFS() 14 | 15 | // pathName := stat.Cwd() 16 | 17 | // log.Debugf("read data from file: '%s'", pathName) 18 | 19 | seqId := uint32(0) 20 | if args != nil && args.StateId != nil { 21 | seqId = args.StateId.SeqId 22 | } 23 | 24 | of := x.Stat().GetOpenedFile(seqId) 25 | if of == nil { 26 | return &nfs.READ4res{Status: nfs.NFS4ERR_INVAL}, nil 27 | } 28 | 29 | f := of.File() 30 | 31 | if args.Offset >= 0 { 32 | if _, err := f.Seek(int64(args.Offset), io.SeekStart); err != nil { 33 | log.Warnf("f.Seek(%d): %v", args.Offset, err) 34 | return &nfs.READ4res{Status: nfs.NFS4ERR_PERM}, nil 35 | } 36 | } 37 | 38 | // log.Printf(" read(offset = %d, count = %d):", args.Offset, args.Count) 39 | 40 | cnt := int64(args.Count) 41 | eof := false 42 | 43 | buff := bytes.NewBuffer([]byte{}) 44 | if _, err := io.CopyN(buff, f, cnt); err != nil { 45 | if err != io.EOF { 46 | log.Warnf("io.CopyN(): %v", err) 47 | return &nfs.READ4res{Status: nfs.NFS4ERR_PERM}, nil 48 | } else { 49 | eof = true 50 | } 51 | } 52 | 53 | // log.Printf(" %d bytes read. eof = %v.", len(buff.Bytes()), eof) 54 | 55 | res := &nfs.READ4res{ 56 | Status: nfs.NFS4_OK, 57 | Ok: &nfs.READ4resok{ 58 | Eof: eof, 59 | Data: buff.Bytes(), 60 | }, 61 | } 62 | return res, nil 63 | } 64 | -------------------------------------------------------------------------------- /nfs/implv4/readdir.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/smallfz/libnfs-go/fs" 7 | "github.com/smallfz/libnfs-go/log" 8 | "github.com/smallfz/libnfs-go/nfs" 9 | "github.com/smallfz/libnfs-go/xdr" 10 | ) 11 | 12 | func encodeReaddirResult(res *nfs.READDIR4res) *nfs.ResGenericRaw { 13 | // marshal the result 14 | buff := bytes.NewBuffer([]byte{}) 15 | w := xdr.NewWriter(buff) 16 | 17 | if res.Status == nfs.NFS4_OK { 18 | w.WriteAny(res.Ok.CookieVerf) 19 | w.WriteAny(res.Ok.Reply.HasEntries) 20 | for _, entry := range res.Ok.Reply.Entries { 21 | w.WriteAny(entry) 22 | } 23 | w.WriteAny(res.Ok.Reply.Eof) 24 | } 25 | 26 | dat := buff.Bytes() 27 | log.Debugf(" readdir: result data size: %d bytes.", len(dat)) 28 | 29 | // return a wrapped result. 30 | return &nfs.ResGenericRaw{ 31 | Status: res.Status, 32 | Reader: bytes.NewReader(dat), 33 | } 34 | } 35 | 36 | func readDir(x nfs.RPCContext, args *nfs.READDIR4args) (*nfs.ResGenericRaw, error) { 37 | stat := x.Stat() 38 | vfs := x.GetFS() 39 | 40 | cwd, err := vfs.ResolveHandle(stat.CurrentHandle()) 41 | if err != nil { 42 | log.Warnf("vfs.ResolveHandle: %v", err) 43 | return &nfs.ResGenericRaw{Status: nfs.NFS4ERR_NOENT}, nil 44 | } 45 | 46 | pathName := cwd 47 | 48 | // log.Debugf("readdir: '%s'", pathName) 49 | 50 | idxReq := (map[int]bool)(nil) 51 | if args.AttrRequest != nil { 52 | idxReq = bitmap4Decode(args.AttrRequest) 53 | } 54 | 55 | dir, err := vfs.Open(pathName) 56 | if err != nil { 57 | log.Warnf("vfs.Open(%s): %v", pathName, err) 58 | return &nfs.ResGenericRaw{Status: nfs.NFS4ERR_NOENT}, nil 59 | } 60 | 61 | children, err := dir.Readdir(-1) 62 | if err != nil { 63 | log.Warnf("dir.Readdir: %v", err) 64 | return &nfs.ResGenericRaw{Status: nfs.NFS4ERR_NOENT}, nil 65 | } 66 | 67 | log.Debugf(" readdir: actual entries count = %d", len(children)) 68 | 69 | log.Debugf( 70 | " readdir: dircount=%d, maxcount=%d. cookie=%d, cookieverf=%d.", 71 | args.DirCount, 72 | args.MaxCount, 73 | args.Cookie, 74 | args.CookieVerf, 75 | ) 76 | 77 | // force to incease limitations giving by client. 78 | // if args.DirCount < 1024 * 32 { 79 | // args.DirCount = 1024 * 32 80 | // } 81 | // if args.MaxCount < 1024 * 128 { 82 | // args.MaxCount = 1024 * 128 83 | // } 84 | 85 | dirList := &nfs.DirList4{HasEntries: false, Eof: true} 86 | 87 | resCookieVerf := uint64(1000) 88 | 89 | cookieReq := int(args.Cookie) 90 | if cookieReq == 0 { 91 | cookieReq += 1000 92 | } else { 93 | cookieReq += 1 94 | } 95 | 96 | log.Debugf(" readdir: cookie-req = %d", cookieReq) 97 | 98 | attrSize := getAttrsMaxBytesSize(idxReq) 99 | 100 | resDirCount := uint32(0) 101 | resMaxCount := uint32(512) 102 | 103 | eof := false 104 | 105 | entryCookies := []int{} 106 | 107 | if len(children) > 0 { 108 | dirList.HasEntries = true 109 | dirList.Entries = []*nfs.Entry4{} 110 | 111 | for i, child := range children { 112 | cookie := 1000 + i 113 | 114 | if cookie < cookieReq { 115 | continue 116 | } 117 | 118 | entryCookies = append(entryCookies, cookie) 119 | resCookieVerf = uint64(cookie + 1) 120 | 121 | pathName := fs.Join(cwd, child.Name()) 122 | // _, err := vfs.GetHandle(pathName) 123 | // if err != nil { 124 | // log.Warnf("vfs.GetHandle(%s): %v", pathName, err) 125 | // continue 126 | // } 127 | entry := &nfs.Entry4{ 128 | Cookie: uint64(cookie), // should be set. (blood and tears!) 129 | Name: child.Name(), 130 | Attrs: fileInfoToAttrs(vfs, pathName, child, idxReq), 131 | HasNext: true, 132 | } 133 | dirList.Entries = append(dirList.Entries, entry) 134 | // log.Debugf(" - entry: %s", child.Name()) 135 | 136 | if i == len(children)-1 { 137 | eof = true 138 | } 139 | 140 | nameSize := uint32(xdr.Pad(len(child.Name())) + 4) 141 | resDirCount += nameSize + 8 142 | resMaxCount += uint32(nameSize + 8 + attrSize + 4) 143 | 144 | if resDirCount >= args.DirCount || resMaxCount > args.MaxCount { 145 | break 146 | } 147 | } 148 | 149 | if len(dirList.Entries) > 0 { 150 | dirList.Entries[len(dirList.Entries)-1].HasNext = false 151 | } 152 | } else { 153 | eof = true 154 | } 155 | 156 | if len(entryCookies) > 0 { 157 | log.Debugf(" readdir, response: range[%d, %d], count=%d, eof=%v.", 158 | entryCookies[0], 159 | entryCookies[len(entryCookies)-1], 160 | len(dirList.Entries), 161 | eof, 162 | ) 163 | } else { 164 | log.Debugf(" readdir, response: range[], count=%d, eof=%v.", 165 | len(dirList.Entries), 166 | eof, 167 | ) 168 | } 169 | 170 | dirList.Eof = eof 171 | 172 | log.Debugf(" readdir, response: cookieverf=%d", resCookieVerf) 173 | 174 | res := &nfs.READDIR4res{ 175 | Status: nfs.NFS4_OK, 176 | Ok: &nfs.READDIR4resok{ 177 | CookieVerf: resCookieVerf, 178 | Reply: dirList, 179 | }, 180 | } 181 | 182 | // if dat, err := json.MarshalIndent(res, "", " "); err != nil { 183 | // log.Errorf("json.MarshalIndent: %v", err) 184 | // } else { 185 | // log.Println(string(dat)) 186 | // } 187 | 188 | return encodeReaddirResult(res), nil 189 | } 190 | -------------------------------------------------------------------------------- /nfs/implv4/readlink.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "github.com/smallfz/libnfs-go/log" 5 | "github.com/smallfz/libnfs-go/nfs" 6 | ) 7 | 8 | func readlink(x nfs.RPCContext) (*nfs.READLINK4res, error) { 9 | stat := x.Stat() 10 | vfs := x.GetFS() 11 | 12 | name, err := vfs.ResolveHandle(stat.CurrentHandle()) 13 | if err != nil { 14 | log.Warnf("vfs.ResolveHandle: %v", err) 15 | return &nfs.READLINK4res{Status: nfs.NFS4err(err)}, nil 16 | } 17 | 18 | _, err = vfs.Stat(name) 19 | if err != nil { 20 | log.Warnf(" remove: vfs.Stat(%s): %v", name, err) 21 | } 22 | 23 | link, err := vfs.Readlink(name) 24 | if err != nil { 25 | log.Warnf("remove: vfs.Readlink(%s): %v", name, err) 26 | return &nfs.READLINK4res{Status: nfs.NFS4err(err)}, nil 27 | } 28 | 29 | return &nfs.READLINK4res{ 30 | Status: nfs.NFS4_OK, 31 | Ok: &nfs.READLINK4resok{ 32 | Link: link, 33 | }, 34 | }, nil 35 | } 36 | -------------------------------------------------------------------------------- /nfs/implv4/remove.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "path" 5 | 6 | "github.com/smallfz/libnfs-go/log" 7 | "github.com/smallfz/libnfs-go/nfs" 8 | ) 9 | 10 | func remove(x nfs.RPCContext, args *nfs.REMOVE4args) (*nfs.REMOVE4res, error) { 11 | log.Debugf("remove obj: '%s'", args.Target) 12 | 13 | stat := x.Stat() 14 | vfs := x.GetFS() 15 | 16 | fh := stat.CurrentHandle() 17 | folder, err := vfs.ResolveHandle(fh) 18 | if err != nil { 19 | log.Warnf("ResolveHandle: %v", err) 20 | return &nfs.REMOVE4res{Status: nfs.NFS4ERR_PERM}, nil 21 | } 22 | 23 | pathName := path.Join(folder, args.Target) 24 | 25 | fi, err := vfs.Stat(pathName) 26 | if err != nil { 27 | log.Warnf(" remove: vfs.Stat(%s): %v", pathName, err) 28 | return &nfs.REMOVE4res{Status: nfs.NFS4ERR_PERM}, nil 29 | } 30 | 31 | if fi.IsDir() && fi.NumLinks() > 2 { 32 | // Should not be able to remove an non-empty directory (more than 2 numlinks). 33 | return &nfs.REMOVE4res{Status: nfs.NFS3ERR_NOTEMPTY}, nil 34 | } 35 | 36 | if err := vfs.Remove(pathName); err != nil { 37 | log.Warnf("remove: vfs.Remove(%s): %v", pathName, err) 38 | return &nfs.REMOVE4res{Status: nfs.NFS4ERR_PERM}, nil 39 | } 40 | 41 | res := &nfs.REMOVE4res{ 42 | Status: nfs.NFS4_OK, 43 | Ok: &nfs.REMOVE4resok{ 44 | CInfo: &nfs.ChangeInfo4{ 45 | Atomic: true, 46 | Before: 0, 47 | After: 0, 48 | }, 49 | }, 50 | } 51 | return res, nil 52 | } 53 | -------------------------------------------------------------------------------- /nfs/implv4/rename.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "io/fs" 5 | "os" 6 | "path" 7 | "strconv" 8 | 9 | "github.com/smallfz/libnfs-go/log" 10 | "github.com/smallfz/libnfs-go/nfs" 11 | ) 12 | 13 | func rename(x nfs.RPCContext, args *nfs.RENAME4args) (*nfs.RENAME4res, error) { 14 | log.Infof("remame obj: %s -> %s", strconv.Quote(args.OldName), strconv.Quote(args.NewName)) 15 | 16 | stat := x.Stat() 17 | vfs := x.GetFS() 18 | 19 | fh := stat.CurrentHandle() 20 | 21 | // 22 | // Check source. 23 | // 24 | folder, err := vfs.ResolveHandle(fh) 25 | if err != nil { 26 | log.Warnf("ResolveHandle: %v", err) 27 | return &nfs.RENAME4res{Status: nfs.NFS4err(err)}, nil 28 | } 29 | 30 | oldpath := path.Join(folder, args.OldName) 31 | _, err = vfs.Stat(oldpath) 32 | if err != nil { 33 | log.Warnf(" rename: vfs.Stat(%s): %v", oldpath, err) 34 | return &nfs.RENAME4res{Status: nfs.NFS4err(err)}, nil 35 | } 36 | 37 | // 38 | // Check destination. 39 | // 40 | newpath := path.Join(folder, args.NewName) 41 | fi, err := vfs.Stat(newpath) 42 | if err == nil && fi.Mode().Type() != os.ModeSymlink { 43 | // According to NFStest (nfstest_posix), 44 | // nfsv4 can remane a file to an existing symlink so we should not return an error in this case. 45 | err = fs.ErrExist 46 | } 47 | if err != nil && !os.IsNotExist(err) { 48 | log.Warnf(" rename: vfs.Stat(%s): %v", newpath, err) 49 | return &nfs.RENAME4res{Status: nfs.NFS4err(err)}, nil 50 | } 51 | 52 | // 53 | // Perform Rename. 54 | // 55 | if err := vfs.Rename(oldpath, newpath); err != nil { 56 | log.Warnf("rename: vfs.Rename(%s, %s): %v", oldpath, newpath, err) 57 | return &nfs.RENAME4res{Status: nfs.NFS4err(err)}, nil 58 | } 59 | 60 | res := &nfs.RENAME4res{ 61 | Status: nfs.NFS4_OK, 62 | Ok: &nfs.RENAME4resok{ 63 | SourceCInfo: &nfs.ChangeInfo4{ 64 | Atomic: true, 65 | Before: 0, 66 | After: 0, 67 | }, 68 | TargetCInfo: &nfs.ChangeInfo4{ 69 | Atomic: true, 70 | Before: 0, 71 | After: 0, 72 | }, 73 | }, 74 | } 75 | return res, nil 76 | } 77 | -------------------------------------------------------------------------------- /nfs/implv4/setattr.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/smallfz/libnfs-go/fs" 8 | "github.com/smallfz/libnfs-go/log" 9 | "github.com/smallfz/libnfs-go/nfs" 10 | ) 11 | 12 | func setAttr(x nfs.RPCContext, args *nfs.SETATTR4args) (*nfs.SETATTR4res, error) { 13 | resFailNotSupp := &nfs.SETATTR4res{Status: nfs.NFS4ERR_ATTRNOTSUPP} 14 | resFailPerm := &nfs.SETATTR4res{Status: nfs.NFS4ERR_PERM} 15 | 16 | a4 := args.Attrs 17 | idxReq := bitmap4Decode(a4.Mask) 18 | 19 | // uncheck not-writable attributes 20 | 21 | off := map[int]bool{} 22 | for id, on := range idxReq { 23 | if on && !isAttrWritable(id) { 24 | off[id] = false 25 | } 26 | } 27 | for id := range off { 28 | if on, found := idxReq[id]; found && on { 29 | idxReq[id] = false 30 | } 31 | } 32 | 33 | // cwd := x.Stat().Cwd() 34 | vfs := x.GetFS() 35 | fh := x.Stat().CurrentHandle() 36 | pathName, err := vfs.ResolveHandle(fh) 37 | if err != nil { 38 | log.Warnf("ResolveHandle: %v", err) 39 | return resFailPerm, nil 40 | } 41 | 42 | seqId := uint32(0) 43 | if args.StateId != nil { 44 | seqId = args.StateId.SeqId 45 | } 46 | 47 | f := (fs.File)(nil) 48 | // pathName := cwd 49 | 50 | of := x.Stat().GetOpenedFile(seqId) 51 | 52 | if of != nil { 53 | f = of.File() 54 | pathName = of.Path() 55 | } else { 56 | if _f, err := vfs.Open(pathName); err != nil { 57 | log.Warnf("vfs.Open(%s): %v", pathName, err) 58 | return resFailPerm, nil 59 | } else { 60 | defer _f.Close() 61 | f = _f 62 | } 63 | } 64 | 65 | decAttrs, err := decodeFAttrs4(args.Attrs) 66 | if err != nil { 67 | return resFailNotSupp, nil 68 | } 69 | // log.Println(toJson(decAttrs)) 70 | 71 | // TODO: actually set the attributes.... 72 | if decAttrs.Mode != nil { 73 | perm := os.FileMode(*decAttrs.Mode) 74 | if err := vfs.Chmod(pathName, perm); err != nil { 75 | log.Warnf("vfs.Chmod(%s, %o): %v", pathName, perm, err) 76 | return resFailPerm, nil 77 | } 78 | } 79 | if decAttrs.Size != nil { 80 | size := int64(*decAttrs.Size) 81 | 82 | if _, err := f.Seek(size, io.SeekStart); err != nil { 83 | log.Warnf("f.Seek(%d, %d): %v", size, io.SeekStart, err) 84 | } else { 85 | if err := f.Truncate(); err != nil { 86 | log.Warnf("f.Truncate: %v", err) 87 | return resFailPerm, nil 88 | } 89 | } 90 | } 91 | if decAttrs.Owner != "" || decAttrs.OwnerGroup != "" { 92 | if vfs.Attributes().ChownRestricted { 93 | log.Warn("vfs.Chown: Operation not permitted due to chown_restricted attr") 94 | return resFailPerm, nil 95 | } 96 | 97 | uid, gid, err := chownAttrs(decAttrs.Owner, decAttrs.OwnerGroup) 98 | if err != nil { 99 | log.Warnf("vfs.Chown(%s, %s, %s): %v", pathName, decAttrs.Owner, decAttrs.OwnerGroup, err) 100 | return resFailPerm, nil 101 | } 102 | 103 | if err = vfs.Chown(pathName, uid, gid); err != nil { 104 | log.Warnf("vfs.Chown(%s, %d, %d): %v", pathName, uid, gid, err) 105 | return resFailPerm, err 106 | } 107 | } 108 | 109 | fi, err := f.Stat() 110 | if err != nil { 111 | log.Warnf("f.Stat: %v", err) 112 | return resFailPerm, nil 113 | } 114 | 115 | attrs := fileInfoToAttrs(vfs, pathName, fi, idxReq) 116 | attrSet := attrs.Mask 117 | 118 | return &nfs.SETATTR4res{ 119 | Status: nfs.NFS4_OK, 120 | AttrSet: attrSet, 121 | }, nil 122 | } 123 | -------------------------------------------------------------------------------- /nfs/implv4/setclientid.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "github.com/smallfz/libnfs-go/nfs" 5 | ) 6 | 7 | func setClientId(args *nfs.SETCLIENTID4args) (*nfs.SETCLIENTID4res, error) { 8 | rs := &nfs.SETCLIENTID4res{ 9 | Status: nfs.NFS4_OK, 10 | Ok: &nfs.SETCLIENTID4resok{ 11 | ClientId: uint64(1), 12 | SetClientIdConfirm: args.Client.Verifier, 13 | }, 14 | } 15 | return rs, nil 16 | } 17 | -------------------------------------------------------------------------------- /nfs/implv4/setclientid_confirm.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "github.com/smallfz/libnfs-go/nfs" 5 | ) 6 | 7 | func setClientIdConfirm(args *nfs.SETCLIENTID_CONFIRM4args) (*nfs.SETCLIENTID_CONFIRM4res, error) { 8 | rs := &nfs.SETCLIENTID_CONFIRM4res{ 9 | Status: nfs.NFS4_OK, 10 | } 11 | return rs, nil 12 | } 13 | -------------------------------------------------------------------------------- /nfs/implv4/setclientid_test.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/smallfz/libnfs-go/nfs" 10 | "github.com/smallfz/libnfs-go/xdr" 11 | ) 12 | 13 | func TestParsingSETCLIENTID4_res(t *testing.T) { 14 | raw, _ := base64.StdEncoding.DecodeString("J4bWCgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAIwAAAAADsSBgqrPnOuG/lGHO4oWS") 15 | 16 | reader := xdr.NewReader(bytes.NewBuffer(raw)) 17 | 18 | _, err := reader.ReadUint32() /* xid */ 19 | if err != nil { 20 | t.Fatalf("%v", err) 21 | return 22 | } 23 | 24 | msgType, err := reader.ReadUint32() 25 | if err != nil { 26 | t.Fatalf("%v", err) 27 | return 28 | } 29 | 30 | if msgType != xdr.RPC_REPLY { 31 | t.Fatalf("expects RPC_REPLY but get %d", msgType) 32 | return 33 | } 34 | 35 | replyStat, err := reader.ReadUint32() 36 | if err != nil { 37 | t.Fatalf("%v", err) 38 | return 39 | } 40 | 41 | if replyStat != nfs.ACCEPT_SUCCESS { 42 | t.Fatalf(" reply-stat: %d", replyStat) 43 | return 44 | } 45 | 46 | auth := &nfs.Auth{} 47 | if _, err := reader.ReadAs(auth); err != nil { 48 | t.Fatalf("%v", err) 49 | return 50 | } 51 | 52 | acceptStatus, err := reader.ReadUint32() 53 | if err != nil { 54 | t.Fatalf("%v", err) 55 | return 56 | } 57 | 58 | if acceptStatus != nfs.ACCEPT_SUCCESS { 59 | t.Fatalf(" accept-status: %d", acceptStatus) 60 | return 61 | } 62 | 63 | status, err := reader.ReadUint32() 64 | if err != nil { 65 | t.Fatalf(" reader.ReadUint32() => status: %v", err) 66 | return 67 | } 68 | 69 | if status != nfs.NFS4_OK { 70 | t.Fatalf(" status: %d", status) 71 | } 72 | 73 | res := &nfs.SETCLIENTID4resok{} 74 | if _, err := reader.ReadAs(res); err != nil { 75 | t.Fatalf("%v", err) 76 | } 77 | 78 | fmt.Println(toJson(res)) 79 | } 80 | -------------------------------------------------------------------------------- /nfs/implv4/utils.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | func toJson(v interface{}) string { 8 | d, err := json.MarshalIndent(v, "", " ") 9 | if err != nil { 10 | return "" 11 | } 12 | return string(d) 13 | } 14 | -------------------------------------------------------------------------------- /nfs/implv4/void.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "github.com/smallfz/libnfs-go/nfs" 5 | ) 6 | 7 | func Void(h *nfs.RPCMsgCall, ctx nfs.RPCContext) (int, error) { 8 | w := ctx.Writer() 9 | 10 | rh := &nfs.RPCMsgReply{ 11 | Xid: h.Xid, 12 | MsgType: nfs.RPC_REPLY, 13 | ReplyStat: nfs.MSG_ACCEPTED, 14 | } 15 | if _, err := w.WriteAny(rh); err != nil { 16 | return 0, err 17 | } 18 | 19 | auth := &nfs.Auth{ 20 | Flavor: nfs.AUTH_FLAVOR_NULL, 21 | Body: []byte{}, 22 | } 23 | if _, err := w.WriteAny(auth); err != nil { 24 | return 0, err 25 | } 26 | 27 | acceptStat := nfs.ACCEPT_SUCCESS 28 | if _, err := w.WriteUint32(acceptStat); err != nil { 29 | return 0, err 30 | } 31 | 32 | // void => [0]byte 33 | if _, err := w.WriteAny([0]byte{}); err != nil { 34 | return 0, err 35 | } 36 | 37 | return 0, nil 38 | } 39 | -------------------------------------------------------------------------------- /nfs/implv4/write.go: -------------------------------------------------------------------------------- 1 | package implv4 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "github.com/smallfz/libnfs-go/log" 8 | "github.com/smallfz/libnfs-go/nfs" 9 | ) 10 | 11 | func write(x nfs.RPCContext, args *nfs.WRITE4args) (*nfs.WRITE4res, error) { 12 | // stat := x.Stat() 13 | // vfs := x.GetFS() 14 | 15 | // pathName := stat.Cwd() 16 | 17 | // log.Debugf("write data to file: '%s'", pathName) 18 | // log.Printf(toJson(args)) 19 | 20 | seqId := uint32(0) 21 | if args != nil && args.StateId != nil { 22 | seqId = args.StateId.SeqId 23 | } 24 | 25 | of := x.Stat().GetOpenedFile(seqId) 26 | if of == nil { 27 | return &nfs.WRITE4res{Status: nfs.NFS4ERR_INVAL}, nil 28 | } 29 | 30 | f := of.File() 31 | 32 | if args.Offset >= 0 { 33 | // log.Printf(" seek %d", args.Offset) 34 | if _, err := f.Seek(int64(args.Offset), io.SeekStart); err != nil { 35 | log.Warnf("f.Seek(%d): %v", args.Offset, err) 36 | return &nfs.WRITE4res{Status: nfs.NFS4ERR_PERM}, nil 37 | } 38 | } 39 | 40 | sizeWrote := uint32(0) 41 | if args.Data != nil && len(args.Data) > 0 { 42 | buff := bytes.NewReader(args.Data) 43 | size, err := io.CopyN(f, buff, int64(len(args.Data))) 44 | if err != nil { 45 | log.Warnf("io.CopyN(): %v", err) 46 | return &nfs.WRITE4res{Status: nfs.NFS4ERR_PERM}, nil 47 | } 48 | sizeWrote = uint32(size) 49 | // log.Printf(" %d bytes wrote.", sizeWrote) 50 | } else { 51 | // log.Printf(" no data to be written.") 52 | } 53 | 54 | // resultCommitted := nfs.UNSTABLE4 55 | resultCommitted := nfs.UNSTABLE4 56 | fsync := false 57 | if sizeWrote >= 0 { 58 | switch args.Stable { 59 | case nfs.DATA_SYNC4: 60 | fsync = true 61 | case nfs.FILE_SYNC4: 62 | fsync = true 63 | } 64 | 65 | if fsync { 66 | if err := f.Sync(); err != nil { 67 | log.Warnf("f.Sync(%s): %v", f.Name(), err) 68 | } else { 69 | resultCommitted = args.Stable 70 | } 71 | } 72 | } 73 | 74 | res := &nfs.WRITE4res{ 75 | Status: nfs.NFS4_OK, 76 | Ok: &nfs.WRITE4resok{ 77 | Count: sizeWrote, 78 | Committed: resultCommitted, 79 | WriteVerf: 0, 80 | }, 81 | } 82 | return res, nil 83 | } 84 | -------------------------------------------------------------------------------- /nfs/nfs.go: -------------------------------------------------------------------------------- 1 | // NFS protocol related interfaces, requests and responses. 2 | // 3 | // The most important interface is Backend. 4 | // To build a nfs server a backend implementation is essentially needed. 5 | package nfs 6 | -------------------------------------------------------------------------------- /nfs/nfs_v3.go: -------------------------------------------------------------------------------- 1 | package nfs 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | /* nfsstat3 */ 9 | const ( 10 | NFS3_OK = uint32(0) 11 | NFS3ERR_PERM = uint32(1) 12 | NFS3ERR_NOENT = uint32(2) 13 | NFS3ERR_IO = uint32(5) 14 | NFS3ERR_NXIO = uint32(6) 15 | NFS3ERR_ACCES = uint32(13) 16 | NFS3ERR_EXIST = uint32(17) 17 | NFS3ERR_XDEV = uint32(18) 18 | NFS3ERR_NODEV = uint32(19) 19 | NFS3ERR_NOTDIR = uint32(20) 20 | NFS3ERR_ISDIR = uint32(21) 21 | NFS3ERR_INVAL = uint32(22) 22 | NFS3ERR_FBIG = uint32(27) 23 | NFS3ERR_NOSPC = uint32(28) 24 | NFS3ERR_ROFS = uint32(30) 25 | NFS3ERR_MLINK = uint32(31) 26 | NFS3ERR_NAMETOOLONG = uint32(63) 27 | NFS3ERR_NOTEMPTY = uint32(66) 28 | NFS3ERR_DQUOT = uint32(69) 29 | NFS3ERR_STALE = uint32(70) 30 | NFS3ERR_REMOTE = uint32(71) 31 | NFS3ERR_BADHANDLE = uint32(10001) 32 | NFS3ERR_NOT_SYNC = uint32(10002) 33 | NFS3ERR_BAD_COOKIE = uint32(10003) 34 | NFS3ERR_NOTSUPP = uint32(10004) 35 | NFS3ERR_TOOSMALL = uint32(10005) 36 | NFS3ERR_SERVERFAULT = uint32(10006) 37 | NFS3ERR_BADTYPE = uint32(10007) 38 | NFS3ERR_JUKEBOX = uint32(10008) 39 | ) 40 | 41 | const ( 42 | ProcVoid = uint32(0) 43 | ProcGetAttr = uint32(1) 44 | ProcSetAttr = uint32(2) 45 | ProcLookup = uint32(3) 46 | ProcAccess = uint32(4) 47 | ProcReadLink = uint32(5) 48 | ProcRead = uint32(6) 49 | ProcWrite = uint32(7) 50 | ProcCreate = uint32(8) 51 | ProcMkdir = uint32(9) 52 | ProcSymlink = uint32(10) 53 | ProcMknod = uint32(11) 54 | ProcRemove = uint32(12) 55 | ProcRmdir = uint32(13) 56 | ProcRename = uint32(14) 57 | ProcLink = uint32(15) 58 | ProcReaddir = uint32(16) 59 | ProcReaddirPlus = uint32(17) 60 | ProcFsStat = uint32(18) 61 | ProcFsInfo = uint32(19) 62 | ProcPathConf = uint32(20) 63 | ProcCommit = uint32(21) 64 | ) 65 | 66 | func Proc3Name(proc uint32) string { 67 | switch proc { 68 | case ProcVoid: 69 | return "void" 70 | case ProcGetAttr: 71 | return "getattr" 72 | case ProcSetAttr: 73 | return "setattr" 74 | case ProcLookup: 75 | return "lookup" 76 | case ProcAccess: 77 | return "access" 78 | case ProcReadLink: 79 | return "readlink" 80 | case ProcRead: 81 | return "read" 82 | case ProcWrite: 83 | return "write" 84 | case ProcCreate: 85 | return "create" 86 | case ProcMkdir: 87 | return "mkdir" 88 | case ProcSymlink: 89 | return "symlink" 90 | case ProcMknod: 91 | return "mknod" 92 | case ProcRemove: 93 | return "remove" 94 | case ProcRmdir: 95 | return "rmdir" 96 | case ProcRename: 97 | return "rename" 98 | case ProcLink: 99 | return "link" 100 | case ProcReaddir: 101 | return "readdir" 102 | case ProcReaddirPlus: 103 | return "readdirplus" 104 | case ProcFsStat: 105 | return "fsstat" 106 | case ProcFsInfo: 107 | return "fsinfo" 108 | case ProcPathConf: 109 | return "pathconf" 110 | case ProcCommit: 111 | return "commit" 112 | } 113 | return fmt.Sprintf("%d", proc) 114 | } 115 | 116 | /* rpc1813: ftype3 */ 117 | const ( 118 | FTYPE_NF3REG = uint32(iota + 1) 119 | FTYPE_NF3DIR 120 | FTYPE_NF3BLK 121 | FTYPE_NF3CHR 122 | FTYPE_NF3LNK 123 | FTYPE_NF3SOCK 124 | FTYPE_NF3FIFO 125 | ) 126 | 127 | /* specdata3 */ 128 | type SpecData struct { 129 | D1 uint32 130 | D2 uint32 131 | } 132 | 133 | type NFSTime struct { 134 | Seconds uint32 135 | NanoSeconds uint32 136 | } 137 | 138 | func MakeNfsTime(t time.Time) NFSTime { 139 | return NFSTime{ 140 | Seconds: uint32(t.Unix()), 141 | } 142 | } 143 | 144 | type FileAttrs struct { 145 | Type uint32 /* ftype3 */ 146 | Mode uint32 147 | NLink uint32 148 | Uid uint32 149 | Gid uint32 150 | Size uint64 151 | Used uint64 152 | Rdev SpecData 153 | Fsid uint64 154 | FileId uint64 155 | ATime NFSTime 156 | MTime NFSTime 157 | CTime NFSTime 158 | } 159 | 160 | type PostOpAttr struct { 161 | AttributesFollow bool 162 | Attributes *FileAttrs 163 | } 164 | 165 | type Fh3 struct { 166 | Opaque []byte 167 | } 168 | 169 | const ( 170 | FSF3_LINK = uint32(0x0001) 171 | FSF3_SYMLINK = uint32(0x0002) 172 | FSF3_HOMOGENEOUS = uint32(0x0008) 173 | FSF3_CANSETTIME = uint32(0x0010) 174 | ) 175 | 176 | type FSINFO3resok struct { 177 | ObjAttrs *PostOpAttr 178 | Rtmax uint32 179 | Rtpref uint32 180 | Rtmult uint32 181 | Wtmax uint32 182 | Wtpref uint32 183 | Wtmult uint32 184 | Dtpref uint32 185 | MaxFileSize uint64 186 | TimeDelta NFSTime 187 | Properties uint32 188 | } 189 | 190 | type PATHCONF3resok struct { 191 | ObjAttrs *PostOpAttr 192 | LinkMax uint32 193 | NameMax uint32 194 | NoTrunc bool 195 | ChownRestricted bool 196 | CaseInsensitive bool 197 | CasePreserving bool 198 | } 199 | 200 | type FSSTAT3resok struct { 201 | ObjAttrs *PostOpAttr 202 | Tbytes uint64 203 | Fbytes uint64 204 | Abytes uint64 205 | Tfiles uint64 206 | Ffiles uint64 207 | Afiles uint64 208 | Invarsec uint32 209 | } 210 | 211 | const ( 212 | ACCESS3_READ = 0x0001 213 | ACCESS3_LOOKUP = 0x0002 214 | ACCESS3_MODIFY = 0x0004 215 | ACCESS3_EXTEND = 0x0008 216 | ACCESS3_DELETE = 0x0010 217 | ACCESS3_EXECUTE = 0x0020 218 | ) 219 | 220 | type ACCESS3resok struct { 221 | ObjAttrs *PostOpAttr 222 | Access uint32 223 | } 224 | 225 | ////////////////////// lookup ////////////////////// 226 | 227 | type DirOpArgs3 struct { 228 | Dir []byte 229 | Filename string 230 | } 231 | 232 | type LOOKUP3resok struct { 233 | Object []byte 234 | ObjAttrs *PostOpAttr 235 | DirAttrs *PostOpAttr 236 | } 237 | 238 | ////////////////////// readdirplus ////////////////////// 239 | 240 | type READDIRPLUS3args struct { 241 | Dir []byte // type: nfs_fh3 242 | Cookie uint64 // type: cookie3 243 | CookieVerf uint64 // type: cookieverf3 244 | DirCount uint32 // type: count3 245 | MaxCount uint32 // type: count3 246 | } 247 | 248 | type PostOpFh3yes struct { 249 | HandleFollow bool 250 | Handle []byte 251 | } 252 | 253 | type PostOpFh3no struct { 254 | HandleFollow bool 255 | } 256 | 257 | type EntryPlus3 struct { 258 | FileId uint64 259 | Name string 260 | Cookie uint64 261 | NameAttrs *PostOpAttr 262 | NameHandle *PostOpFh3yes 263 | // HasNext bool 264 | } 265 | 266 | type DirListPlus3 struct { 267 | Entries []*EntryPlus3 268 | EOF bool 269 | } 270 | 271 | type READDIRPLUS3resok struct { 272 | DirAttrs *PostOpAttr 273 | CookieVerf uint64 274 | Reply *DirListPlus3 275 | } 276 | -------------------------------------------------------------------------------- /nfs/rpc.go: -------------------------------------------------------------------------------- 1 | package nfs 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // https://datatracker.ietf.org/doc/html/rfc1057 8 | 9 | const ( 10 | RPC_CALL = uint32(iota) 11 | RPC_REPLY 12 | ) 13 | 14 | const ( 15 | MSG_ACCEPTED = uint32(iota) 16 | MSG_DENIED 17 | ) 18 | 19 | const ( 20 | ACCEPT_SUCCESS = uint32(iota) /* RPC executed successfully */ 21 | ACCEPT_PROG_UNAVAIL /* remote hasn't exported program */ 22 | ACCEPT_PROG_MISMATCH /* remote can't support version # */ 23 | ACCEPT_PROC_UNAVAIL /* program can't support procedure */ 24 | ACCEPT_GRABAGE_ARGS /* procedure can't decode params */ 25 | ) 26 | 27 | const ( 28 | REJECT_RPC_MISMATCH = uint32(iota) /* RPC version number != 2 */ 29 | REJECT_AUTH_ERROR /* remote can't authenticate caller */ 30 | ) 31 | 32 | const ( 33 | AUTH_FLAVOR_NULL = iota 34 | AUTH_FLAVOR_UNIX 35 | AUTH_FLAVOR_SHORT 36 | AUTH_FLAVOR_DES 37 | ) 38 | 39 | const ( 40 | AUTH_BADCRED = uint32(iota + 1) /* bad credentials (seal broken) */ 41 | AUTH_REJECTEDCRED /* client must begin new session */ 42 | AUTH_BADVERF /* bad verifier (seal broken) */ 43 | AUTH_REJECTEDVERF /* verifier expired or replayed */ 44 | AUTH_TOOWEAK /* rejected for security reasons */ 45 | ) 46 | 47 | type Auth struct { 48 | Flavor uint32 49 | Body []byte 50 | } 51 | 52 | type AuthError struct { 53 | Code uint32 54 | } 55 | 56 | func (err *AuthError) Error() string { 57 | return fmt.Sprintf("auth error: %d", err.Code) 58 | } 59 | 60 | var ( 61 | ErrBadCredentials = &AuthError{Code: AUTH_BADCRED} 62 | ErrTooWeak = &AuthError{Code: AUTH_TOOWEAK} 63 | ) 64 | 65 | func NewEmptyAuth() *Auth { 66 | return &Auth{Flavor: 0, Body: []byte{}} 67 | } 68 | 69 | type RPCMsgCall struct { 70 | Xid uint32 71 | MsgType uint32 /* RPC_CALL */ 72 | RPCVer uint32 /* rfc1057, const: 2 */ 73 | 74 | Prog uint32 /* nfs: 100003 */ 75 | Vers uint32 /* 3 */ 76 | Proc uint32 /* see proc.go */ 77 | 78 | Cred *Auth 79 | Verf *Auth 80 | } 81 | 82 | func (h *RPCMsgCall) String() string { 83 | procName := fmt.Sprintf("%d", h.Proc) 84 | if h.Prog == 100003 { 85 | switch h.Vers { 86 | case 3: 87 | procName = Proc3Name(h.Proc) 88 | case 4: 89 | procName = Proc4Name(h.Proc) 90 | } 91 | } 92 | return fmt.Sprintf( 93 | "", 94 | h.Prog, h.Vers, procName, 95 | ) 96 | } 97 | 98 | type RPCMsgReply struct { 99 | Xid uint32 /* exact as the corresponding call. */ 100 | MsgType uint32 /* RPC_REPLY */ 101 | ReplyStat uint32 /* MSG_ACCEPT | MSG_DENIED */ 102 | } 103 | 104 | type RejectReply struct { 105 | RejectStat uint32 /* RPC_MISMATCH | AUTH_ERROR */ 106 | Lo uint32 107 | Hi uint32 108 | } 109 | -------------------------------------------------------------------------------- /server/mux_v3.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/smallfz/libnfs-go/fs" 7 | "github.com/smallfz/libnfs-go/nfs" 8 | handlers "github.com/smallfz/libnfs-go/nfs/implv3" 9 | "github.com/smallfz/libnfs-go/xdr" 10 | ) 11 | 12 | type Mux struct { 13 | reader *xdr.Reader 14 | writer *xdr.Writer 15 | auth nfs.AuthenticationHandler 16 | fs fs.FS 17 | stat nfs.StatService 18 | } 19 | 20 | var _ nfs.RPCContext = (*Mux)(nil) 21 | 22 | func (x *Mux) Reader() *xdr.Reader { 23 | return x.reader 24 | } 25 | 26 | func (x *Mux) Writer() *xdr.Writer { 27 | return x.writer 28 | } 29 | 30 | func (x *Mux) Authenticate(cred, verf *nfs.Auth) (*nfs.Auth, error) { 31 | resp, creds, err := x.auth(cred, verf) 32 | 33 | if err == nil { 34 | x.fs.SetCreds(creds) 35 | } 36 | 37 | return resp, err 38 | } 39 | 40 | func (x *Mux) Stat() nfs.StatService { 41 | return x.stat 42 | } 43 | 44 | func (x *Mux) GetFS() fs.FS { 45 | return x.fs 46 | } 47 | 48 | func (x *Mux) HandleProc(h *nfs.RPCMsgCall) (int, error) { 49 | switch h.Proc { 50 | case nfs.ProcVoid: 51 | return handlers.Void(h, x) 52 | case nfs.ProcGetAttr: 53 | return handlers.GetAttr(h, x) 54 | case nfs.ProcFsInfo: 55 | return handlers.FsInfo(h, x) 56 | case nfs.ProcPathConf: 57 | return handlers.PathConf(h, x) 58 | case nfs.ProcFsStat: 59 | return handlers.FsStat(h, x) 60 | case nfs.ProcAccess: 61 | return handlers.Access(h, x) 62 | case nfs.ProcLookup: 63 | return handlers.Lookup(h, x) 64 | case nfs.ProcReaddirPlus: 65 | return handlers.ReaddirPlus(h, x) 66 | } 67 | return 0, errors.New("not implemented") 68 | } 69 | -------------------------------------------------------------------------------- /server/mux_v4.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/smallfz/libnfs-go/fs" 7 | "github.com/smallfz/libnfs-go/nfs" 8 | v4 "github.com/smallfz/libnfs-go/nfs/implv4" 9 | "github.com/smallfz/libnfs-go/xdr" 10 | ) 11 | 12 | type Muxv4 struct { 13 | reader *xdr.Reader 14 | writer *xdr.Writer 15 | auth nfs.AuthenticationHandler 16 | fs fs.FS 17 | stat nfs.StatService 18 | } 19 | 20 | var _ nfs.RPCContext = (*Muxv4)(nil) 21 | 22 | func (x *Muxv4) Reader() *xdr.Reader { 23 | return x.reader 24 | } 25 | 26 | func (x *Muxv4) Writer() *xdr.Writer { 27 | return x.writer 28 | } 29 | 30 | func (x *Muxv4) Authenticate(cred, verf *nfs.Auth) (*nfs.Auth, error) { 31 | resp, creds, err := x.auth(cred, verf) 32 | 33 | if err == nil { 34 | x.fs.SetCreds(creds) 35 | } 36 | 37 | return resp, err 38 | } 39 | 40 | func (x *Muxv4) Stat() nfs.StatService { 41 | return x.stat 42 | } 43 | 44 | func (x *Muxv4) GetFS() fs.FS { 45 | return x.fs 46 | } 47 | 48 | func (x *Muxv4) HandleProc(h *nfs.RPCMsgCall) (int, error) { 49 | // Clear authentication 50 | 51 | switch h.Proc { 52 | case nfs.PROC4_VOID: 53 | return v4.Void(h, x) 54 | case nfs.PROC4_COMPOUND: 55 | return v4.Compound(h, x) 56 | } 57 | return 0, fmt.Errorf("not implemented: %s", nfs.Proc4Name(h.Proc)) 58 | } 59 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | // NFS server implements nfs v4.0. 2 | // 3 | // Package server provides a server implementation of nfs v4. 4 | // 5 | // import ( 6 | // "fmt" 7 | // "github.com/smallfz/libnfs-go/log" 8 | // "github.com/smallfz/libnfs-go/memfs" 9 | // "github.com/smallfz/libnfs-go/server" 10 | // "os" 11 | // ) 12 | // 13 | // func main() { 14 | // mfs := memfs.NewMemFS() 15 | // backend := memfs.NewBackend(mfs) 16 | // 17 | // mfs.MkdirAll("/mount", os.FileMode(0755)) 18 | // mfs.MkdirAll("/test", os.FileMode(0755)) 19 | // mfs.MkdirAll("/test2", os.FileMode(0755)) 20 | // mfs.MkdirAll("/many", os.FileMode(0755)) 21 | // 22 | // perm := os.FileMode(0755) 23 | // for i := 0; i < 256; i++ { 24 | // mfs.MkdirAll(fmt.Sprintf("/many/sub-%d", i+1), perm) 25 | // } 26 | // 27 | // svr, err := server.NewServerTCP(2049, backend) 28 | // if err != nil { 29 | // log.Errorf("server.NewServerTCP: %v", err) 30 | // return 31 | // } 32 | // 33 | // if err := svr.Serve(); err != nil { 34 | // log.Errorf("svr.Serve: %v", err) 35 | // } 36 | // } 37 | package server 38 | 39 | import ( 40 | "context" 41 | "fmt" 42 | "net" 43 | 44 | "github.com/smallfz/libnfs-go/log" 45 | "github.com/smallfz/libnfs-go/nfs" 46 | ) 47 | 48 | type Server struct { 49 | listener net.Listener 50 | backend nfs.Backend 51 | } 52 | 53 | func NewServerTCP(address string, backend nfs.Backend) (*Server, error) { 54 | ln, err := net.Listen("tcp", address) 55 | if err != nil { 56 | return nil, fmt.Errorf("net.Listen: %w", err) 57 | } 58 | return NewServer(ln, backend) 59 | } 60 | 61 | // NewServer returns a new server with the given listener (e.g. net.Listen, tls.Listen, etc.) 62 | func NewServer(l net.Listener, backend nfs.Backend) (*Server, error) { 63 | return &Server{listener: l, backend: backend}, nil 64 | } 65 | 66 | func (s *Server) Serve() error { 67 | ctx, cancel := context.WithCancel(context.Background()) 68 | defer cancel() 69 | 70 | defer s.listener.Close() 71 | 72 | log.Infof("Serving at %s ...", s.listener.Addr()) 73 | 74 | for { 75 | if conn, err := s.listener.Accept(); err != nil { 76 | return fmt.Errorf("listener.Accept: %w", err) 77 | } else { 78 | go func() { 79 | defer conn.Close() 80 | if err := handleSession(ctx, s.backend, conn); err != nil { 81 | log.Errorf("handleSession: %v", err) 82 | } 83 | }() 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /server/session.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net" 10 | 11 | "github.com/smallfz/libnfs-go/log" 12 | "github.com/smallfz/libnfs-go/nfs" 13 | "github.com/smallfz/libnfs-go/xdr" 14 | ) 15 | 16 | type SessionMux interface { 17 | HandleProc(*nfs.RPCMsgCall) (int, error) 18 | } 19 | 20 | type rpcHeader struct { 21 | Xid uint32 22 | Type uint32 23 | } 24 | 25 | type Session struct { 26 | conn net.Conn 27 | backend nfs.Backend 28 | } 29 | 30 | func (sess *Session) sendResponse(dat []byte) error { 31 | frag := uint32(len(dat)) | uint32(1<<31) 32 | writer := xdr.NewWriter(sess.conn) 33 | if _, err := writer.WriteUint32(frag); err != nil { 34 | return err 35 | } 36 | if _, err := writer.Write(dat); err != nil { 37 | return err 38 | } 39 | return nil 40 | } 41 | 42 | func (sess *Session) Conn() net.Conn { 43 | return sess.conn 44 | } 45 | 46 | func (sess *Session) Start(ctx context.Context) error { 47 | conn := sess.conn 48 | defer func() { 49 | conn.Close() 50 | log.Debugf("Disconnected from %v.", conn.RemoteAddr()) 51 | }() 52 | 53 | backendSession := sess.backend.CreateSession(sess) 54 | defer backendSession.Close() 55 | 56 | auth := backendSession.Authentication() 57 | vfs := backendSession.GetFS() 58 | stat := backendSession.GetStatService() 59 | 60 | reader := xdr.NewReader(conn) 61 | 62 | for { 63 | frag, err := reader.ReadUint32() 64 | if err != nil { 65 | if err == io.EOF { 66 | return nil 67 | } 68 | } 69 | 70 | if frag&(1<<31) == 0 { 71 | return errors.New("(!)ignored: fragmented request") 72 | } 73 | 74 | headerSize := (frag << 1) >> 1 75 | restSize := int(headerSize) 76 | 77 | header := &nfs.RPCMsgCall{} 78 | if size, err := reader.ReadAs(header); err != nil { 79 | return fmt.Errorf("ReadAs(%T): %v", header, err) 80 | } else { 81 | restSize -= size 82 | } 83 | 84 | if header.MsgType != nfs.RPC_CALL { 85 | return errors.New("expecting a rpc call message") 86 | } 87 | 88 | // log.Infof("header: %v", header) 89 | 90 | mux := (SessionMux)(nil) 91 | 92 | buff := bytes.NewBuffer([]byte{}) 93 | writer := xdr.NewWriter(buff) 94 | 95 | switch header.Vers { 96 | case 4: 97 | mux = &Muxv4{ 98 | reader: reader, 99 | writer: writer, 100 | auth: auth, 101 | fs: vfs, 102 | stat: stat, 103 | } 104 | 105 | case 3: 106 | mux = &Mux{ 107 | reader: reader, 108 | writer: writer, 109 | auth: auth, 110 | fs: vfs, 111 | stat: stat, 112 | } 113 | 114 | default: 115 | seq := []interface{}{ 116 | &nfs.RPCMsgReply{ 117 | Xid: header.Xid, 118 | MsgType: nfs.RPC_REPLY, 119 | ReplyStat: nfs.MSG_ACCEPTED, 120 | }, 121 | nfs.NewEmptyAuth(), 122 | nfs.ACCEPT_PROG_MISMATCH, 123 | uint32(3), // low: v3 124 | uint32(4), // high: v4 125 | } 126 | for _, v := range seq { 127 | if _, err := writer.WriteAny(v); err != nil { 128 | return err 129 | } 130 | } 131 | } 132 | 133 | if mux != nil { 134 | if size, err := mux.HandleProc(header); err != nil { 135 | return fmt.Errorf("mux.HandlerProc(%d): %v", header.Proc, err) 136 | } else { 137 | restSize -= size 138 | } 139 | } else { 140 | return errors.New("invalid rpc message: no suitable mux") 141 | } 142 | 143 | if err := sess.sendResponse(buff.Bytes()); err != nil { 144 | return fmt.Errorf("sendResponse: %v", err) 145 | } 146 | 147 | if restSize > 0 { 148 | log.Warnf("%d bytes unread.", restSize) 149 | if _, err := reader.ReadBytes(restSize); err != nil { 150 | if err == io.EOF { 151 | return nil 152 | } 153 | } 154 | } 155 | } 156 | } 157 | 158 | func handleSession(ctx context.Context, backend nfs.Backend, conn net.Conn) error { 159 | sess := &Session{ 160 | conn: conn, 161 | backend: backend, 162 | } 163 | return sess.Start(ctx) 164 | } 165 | -------------------------------------------------------------------------------- /unixfs/file.go: -------------------------------------------------------------------------------- 1 | package unixfs 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/smallfz/libnfs-go/fs" 7 | ) 8 | 9 | type file struct { 10 | os.File 11 | } 12 | 13 | func (f *file) Readdir(n int) ([]fs.FileInfo, error) { 14 | infos, err := f.File.Readdir(n) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | var fis []fs.FileInfo 20 | for _, info := range infos { 21 | fis = append(fis, statFromInfo(info)) 22 | } 23 | 24 | return fis, nil 25 | } 26 | 27 | func (f *file) Stat() (fs.FileInfo, error) { 28 | info, err := f.File.Stat() 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return statFromInfo(info), nil 34 | } 35 | 36 | func (f *file) Truncate() error { 37 | return f.File.Truncate(0) 38 | } 39 | -------------------------------------------------------------------------------- /unixfs/file_info.go: -------------------------------------------------------------------------------- 1 | package unixfs 2 | 3 | import ( 4 | "io/fs" 5 | "os" 6 | ) 7 | 8 | type FileInfo struct { 9 | fs.FileInfo 10 | } 11 | 12 | func Stat(name string) (*FileInfo, error) { 13 | fi, err := os.Lstat(name) // Do not follow links 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | return statFromInfo(fi), nil 19 | } 20 | 21 | func statFromInfo(fi fs.FileInfo) *FileInfo { 22 | return &FileInfo{ 23 | FileInfo: fi, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /unixfs/file_info_darwin.go: -------------------------------------------------------------------------------- 1 | package unixfs 2 | 3 | import ( 4 | "syscall" 5 | "time" 6 | ) 7 | 8 | func (fi FileInfo) ATime() time.Time { 9 | return time.Unix(fi.FileInfo.Sys().(*syscall.Stat_t).Atimespec.Unix()) 10 | } 11 | 12 | func (fi FileInfo) CTime() time.Time { 13 | return time.Unix(fi.FileInfo.Sys().(*syscall.Stat_t).Ctimespec.Unix()) 14 | } 15 | 16 | func (fi FileInfo) NumLinks() int { 17 | return int(fi.FileInfo.Sys().(*syscall.Stat_t).Nlink) 18 | } 19 | 20 | func (fi FileInfo) Inode() uint64 { 21 | return fi.FileInfo.Sys().(*syscall.Stat_t).Ino 22 | } 23 | -------------------------------------------------------------------------------- /unixfs/file_info_linux.go: -------------------------------------------------------------------------------- 1 | package unixfs 2 | 3 | import ( 4 | "syscall" 5 | "time" 6 | ) 7 | 8 | func (fi FileInfo) ATime() time.Time { 9 | return time.Unix(fi.FileInfo.Sys().(*syscall.Stat_t).Atim.Unix()) 10 | } 11 | 12 | func (fi FileInfo) CTime() time.Time { 13 | return time.Unix(fi.FileInfo.Sys().(*syscall.Stat_t).Ctim.Unix()) 14 | } 15 | 16 | func (fi FileInfo) NumLinks() int { 17 | return int(fi.FileInfo.Sys().(*syscall.Stat_t).Nlink) 18 | } 19 | 20 | func (fi FileInfo) Inode() uint64 { 21 | return fi.FileInfo.Sys().(*syscall.Stat_t).Ino 22 | } 23 | -------------------------------------------------------------------------------- /unixfs/inode.go: -------------------------------------------------------------------------------- 1 | package unixfs 2 | 3 | import ( 4 | "io/fs" 5 | "path/filepath" 6 | "sync" 7 | 8 | "github.com/smallfz/libnfs-go/log" 9 | "github.com/smallfz/libnfs-go/memfs" 10 | ) 11 | 12 | const InvalidID = memfs.InvalidId 13 | 14 | // Inodes contain the mapping of paths and their inode IDs because 15 | // it's more safe to rely on them instead of other kind of IDs. 16 | type Inodes struct { 17 | mu sync.Mutex 18 | inodes map[uint64]string 19 | paths map[string]uint64 20 | } 21 | 22 | func NewInodes() *Inodes { 23 | return &Inodes{ 24 | inodes: make(map[uint64]string), 25 | paths: make(map[string]uint64), 26 | } 27 | } 28 | 29 | // Scan reads all the inode's IDs under the given workdir. 30 | // It can take a while depending on the number of inodes to read and disk perfomance. 31 | func (i *Inodes) Scan(workdir string) error { 32 | // On macOS we can avoid scanning all the workdir by using these 2 commands: 33 | // 34 | // [~]>> stat $HOME 35 | // 16777220 30109 drwxr-xr-x 75 mdouchement staff 0 2400 "Sep 14 10:07:41 2023" "Sep 14 10:07:51 2023" "Sep 14 10:07:51 2023" "Aug 10 15:49:15 2022" 4096 0 0 /Users/mdouchement 36 | // [~]>> GetFileInfo /.vol/16777220/30109 37 | // directory: "/Users/mdouchement" 38 | // attributes: avbstclinmedz 39 | // created: 08/10/2022 15:49:15 40 | // modified: 09/14/2023 10:08:02 41 | // 42 | // For the moment, no equivalent feature has been found on GNU/Linux. 43 | 44 | i.mu.Lock() 45 | defer i.mu.Unlock() 46 | 47 | log.Info("Scanning inodes...") 48 | 49 | return filepath.WalkDir(workdir, func(path string, d fs.DirEntry, err error) error { 50 | if err != nil { 51 | return err 52 | } 53 | 54 | info, err := d.Info() 55 | if err != nil { 56 | return err 57 | } 58 | 59 | inode := statFromInfo(info).Inode() 60 | i.inodes[inode] = path 61 | i.paths[path] = inode 62 | log.Debug(" ", inode, " ", path) 63 | return nil 64 | }) 65 | } 66 | 67 | func (i *Inodes) UpdateAll(name string) error { 68 | var notfirst bool // Refresh full path because it may change due to UnixFS actions. 69 | 70 | for { 71 | if notfirst && i.ExistPath(name) { 72 | // The workdir (root folder) exists so we should never go outside the workdir. 73 | break 74 | } 75 | 76 | fi, err := Stat(name) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | i.Add(fi.Inode(), name) 82 | 83 | name = filepath.Dir(name) 84 | notfirst = true 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func (i *Inodes) GetPath(inode uint64) string { 91 | i.mu.Lock() 92 | defer i.mu.Unlock() 93 | 94 | return i.inodes[inode] 95 | } 96 | 97 | func (i *Inodes) GetID(path string) uint64 { 98 | i.mu.Lock() 99 | defer i.mu.Unlock() 100 | 101 | return i.paths[path] 102 | } 103 | 104 | func (i *Inodes) ExistPath(path string) bool { 105 | i.mu.Lock() 106 | defer i.mu.Unlock() 107 | 108 | _, ok := i.paths[path] 109 | return ok 110 | } 111 | 112 | func (i *Inodes) Add(inode uint64, path string) { 113 | i.mu.Lock() 114 | defer i.mu.Unlock() 115 | 116 | i.inodes[inode] = path 117 | i.paths[path] = inode 118 | } 119 | 120 | func (i *Inodes) RemoveID(inode uint64) { 121 | i.mu.Lock() 122 | defer i.mu.Unlock() 123 | 124 | path := i.inodes[inode] 125 | delete(i.inodes, inode) 126 | delete(i.paths, path) 127 | } 128 | 129 | func (i *Inodes) RemovePath(path string) { 130 | i.mu.Lock() 131 | defer i.mu.Unlock() 132 | 133 | inode := i.paths[path] 134 | delete(i.inodes, inode) 135 | delete(i.paths, path) 136 | } 137 | -------------------------------------------------------------------------------- /unixfs/unixfs.go: -------------------------------------------------------------------------------- 1 | package unixfs 2 | 3 | import ( 4 | "encoding/binary" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/smallfz/libnfs-go/fs" 10 | ) 11 | 12 | type UnixFS struct { 13 | workdir string 14 | inodes *Inodes 15 | attributes fs.Attributes 16 | } 17 | 18 | func New(workdir string) (*UnixFS, error) { 19 | workdir, err := filepath.Abs(workdir) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | inodes := NewInodes() 25 | err = inodes.Scan(workdir) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return &UnixFS{ 31 | workdir: workdir, 32 | inodes: inodes, 33 | attributes: fs.Attributes{ 34 | LinkSupport: true, 35 | SymlinkSupport: true, 36 | ChownRestricted: true, // Supported but chown is disabled by default for security reason 37 | MaxName: 255, // Common value 38 | MaxRead: 1048576, // common value 39 | MaxWrite: 1048576, // common value 40 | NoTrunc: false, // Safe value 41 | }, 42 | }, nil 43 | } 44 | 45 | func (s *UnixFS) SetCreds(creds fs.Creds) {} 46 | 47 | func (s *UnixFS) Attributes() *fs.Attributes { 48 | return &s.attributes 49 | } 50 | 51 | func (s *UnixFS) Stat(name string) (fs.FileInfo, error) { 52 | name, err := s.ResolveUnix(name) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | return Stat(name) 58 | } 59 | 60 | func (s *UnixFS) Chmod(name string, mode os.FileMode) error { 61 | name, err := s.ResolveUnix(name) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | return os.Chmod(name, mode) 67 | } 68 | 69 | func (s *UnixFS) Chown(name string, uid, gid int) error { 70 | name, err := s.ResolveUnix(name) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | return os.Lchown(name, uid, gid) 76 | } 77 | 78 | func (s *UnixFS) MkdirAll(name string, mode os.FileMode) error { 79 | if name == fs.ROOT { 80 | return nil 81 | } 82 | 83 | name, err := s.ResolveUnix(name) 84 | if err != nil { 85 | return err 86 | } 87 | 88 | // 89 | 90 | if err = os.MkdirAll(name, mode); err != nil { 91 | return err 92 | } 93 | 94 | // 95 | 96 | return s.inodes.UpdateAll(name) 97 | } 98 | 99 | func (s *UnixFS) Remove(name string) error { 100 | if name == fs.ROOT { 101 | return os.ErrPermission 102 | } 103 | 104 | name, err := s.ResolveUnix(name) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | // 110 | 111 | if err = os.Remove(name); err != nil { 112 | return err 113 | } 114 | 115 | // 116 | 117 | s.inodes.RemovePath(name) 118 | return nil 119 | } 120 | 121 | func (s *UnixFS) Rename(oldpath, newpath string) error { 122 | if oldpath == fs.ROOT || newpath == fs.ROOT || oldpath == newpath { 123 | return os.ErrPermission 124 | } 125 | 126 | oldpath, err := s.ResolveUnix(oldpath) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | newpath, err = s.ResolveUnix(newpath) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | // 137 | 138 | if err = os.Rename(oldpath, newpath); err != nil { 139 | return err 140 | } 141 | 142 | // 143 | 144 | s.inodes.RemovePath(oldpath) 145 | return s.inodes.UpdateAll(newpath) 146 | } 147 | 148 | func (s *UnixFS) Link(oldpath, newpath string) error { 149 | if oldpath == fs.ROOT || newpath == fs.ROOT || oldpath == newpath { 150 | return os.ErrPermission 151 | } 152 | 153 | oldpath, err := s.ResolveUnix(oldpath) 154 | if err != nil { 155 | return err 156 | } 157 | 158 | newpath, err = s.ResolveUnix(newpath) 159 | if err != nil { 160 | return err 161 | } 162 | 163 | // 164 | 165 | if err = os.Link(oldpath, newpath); err != nil { 166 | return err 167 | } 168 | 169 | // 170 | 171 | return s.inodes.UpdateAll(newpath) 172 | } 173 | 174 | func (s *UnixFS) Symlink(oldpath, newpath string) error { 175 | if oldpath == fs.ROOT || newpath == fs.ROOT || oldpath == newpath { 176 | return os.ErrPermission 177 | } 178 | 179 | newpath, err := s.ResolveUnix(newpath) 180 | if err != nil { 181 | return err 182 | } 183 | 184 | // 185 | 186 | // We do not check `oldpath', we let the OS put the data in the link file. 187 | if err = os.Symlink(oldpath, newpath); err != nil { 188 | return err 189 | } 190 | 191 | return s.inodes.UpdateAll(newpath) 192 | } 193 | 194 | func (s *UnixFS) Readlink(name string) (string, error) { 195 | name, err := s.ResolveUnix(name) 196 | if err != nil { 197 | return "", err 198 | } 199 | 200 | return os.Readlink(name) 201 | } 202 | 203 | func (s *UnixFS) Open(name string) (fs.File, error) { 204 | return s.OpenFile(name, os.O_RDONLY, os.FileMode(0o644)) 205 | } 206 | 207 | func (s *UnixFS) OpenFile(name string, flag int, mode os.FileMode) (fs.File, error) { 208 | name, err := s.ResolveUnix(name) 209 | if err != nil { 210 | return nil, err 211 | } 212 | 213 | f, err := os.OpenFile(name, flag, mode) 214 | if err != nil { 215 | return nil, err 216 | } 217 | 218 | file := &file{ 219 | File: *f, 220 | } 221 | 222 | { 223 | i, err := f.Stat() 224 | if err != nil { 225 | return nil, err 226 | } 227 | s.inodes.Add(statFromInfo(i).Inode(), name) 228 | } 229 | 230 | return file, nil 231 | } 232 | 233 | func (s *UnixFS) GetFileId(fi fs.FileInfo) uint64 { 234 | switch i := fi.(type) { 235 | case *FileInfo: 236 | return i.Inode() 237 | case fs.WithId: 238 | return i.Id() 239 | default: 240 | return InvalidID 241 | } 242 | } 243 | 244 | func (s *UnixFS) GetHandle(fi fs.FileInfo) ([]byte, error) { 245 | id := s.GetFileId(fi) 246 | if id == InvalidID { 247 | return nil, os.ErrNotExist 248 | } 249 | 250 | buf := make([]byte, 8) 251 | binary.BigEndian.PutUint64(buf, id) 252 | return buf, nil 253 | } 254 | 255 | func (s *UnixFS) GetRootHandle() []byte { 256 | buf := make([]byte, 8) 257 | binary.BigEndian.PutUint64(buf, s.inodes.GetID(s.workdir)) 258 | return buf 259 | } 260 | 261 | // ResolveHandle resolves a file-handle(eg. nfs_fh4) to a full path name. 262 | func (s *UnixFS) ResolveHandle(fh []byte) (string, error) { 263 | var id uint64 264 | if len(fh) <= 8 { 265 | id = binary.BigEndian.Uint64(fh) 266 | } else { 267 | id = binary.BigEndian.Uint64(fh[:8]) 268 | } 269 | 270 | if id == InvalidID { 271 | return "", os.ErrNotExist 272 | } 273 | 274 | path := s.inodes.GetPath(id) 275 | if path == "" { 276 | return "", os.ErrNotExist 277 | } 278 | 279 | return s.ResolveNFS(path), nil 280 | } 281 | 282 | // 283 | // Helpers 284 | // 285 | 286 | func (s *UnixFS) ResolveUnix(name string) (string, error) { 287 | name = fs.Abs(name) 288 | name = filepath.FromSlash(name) 289 | return filepath.Join(s.workdir, name), nil 290 | } 291 | 292 | func (s *UnixFS) ResolveNFS(name string) string { 293 | name = strings.TrimPrefix(name, s.workdir) 294 | name = filepath.ToSlash(name) 295 | if name == "" { 296 | name = "/" 297 | } 298 | 299 | return name 300 | } 301 | -------------------------------------------------------------------------------- /unixfs/unixfs_test.go: -------------------------------------------------------------------------------- 1 | package unixfs_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "testing" 9 | 10 | "github.com/smallfz/libnfs-go/fs" 11 | "github.com/smallfz/libnfs-go/unixfs" 12 | ) 13 | 14 | var _ fs.FS = new(unixfs.UnixFS) // Check interface 15 | 16 | func TestMemfsFileSeekRead(t *testing.T) { 17 | workdir, err := os.MkdirTemp("", "unixfs") 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | fmt.Println("workdir:", workdir) 22 | 23 | vfs, err := unixfs.New(workdir) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | // 29 | 30 | name := "hello.txt" 31 | content := "hello world!" 32 | pathName := fs.Join("/", name) 33 | 34 | perm := os.FileMode(0o644) 35 | flag := os.O_CREATE | os.O_RDWR 36 | 37 | if f, err := vfs.OpenFile(pathName, flag, perm); err != nil { 38 | t.Fatalf("OpenFile: %v", err) 39 | return 40 | } else { 41 | io.WriteString(f, content) 42 | f.Close() 43 | } 44 | 45 | if f, err := vfs.Open(pathName); err != nil { 46 | t.Fatalf("%v", err) 47 | return 48 | } else { 49 | offset := int64(len([]byte("hello "))) 50 | if _, err := f.Seek(offset, io.SeekStart); err != nil { 51 | t.Fatalf("%v", err) 52 | return 53 | } 54 | buff := bytes.NewBuffer([]byte{}) 55 | if _, err := io.CopyN(buff, f, 1024); err != nil { 56 | if err != io.EOF { 57 | t.Fatalf("%v", err) 58 | return 59 | } 60 | } 61 | dat := buff.Bytes() 62 | if string(dat) != "world!" { 63 | t.Fatalf("unexpected content: %s", string(dat)) 64 | } 65 | f.Close() 66 | } 67 | } 68 | 69 | func TestMemfsFileOpenTrunc(t *testing.T) { 70 | workdir, err := os.MkdirTemp("", "unixfs") 71 | if err != nil { 72 | t.Fatal(err) 73 | } 74 | fmt.Println("workdir:", workdir) 75 | 76 | vfs, err := unixfs.New(workdir) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | 81 | // 82 | 83 | name := "hello.txt" 84 | content := "hello world!" 85 | pathName := fs.Join("/", name) 86 | 87 | perm := os.FileMode(0o644) 88 | flag := os.O_CREATE | os.O_RDWR 89 | 90 | if f, err := vfs.OpenFile(pathName, flag, perm); err != nil { 91 | t.Fatalf("OpenFile: %v", err) 92 | return 93 | } else { 94 | io.WriteString(f, content) 95 | f.Close() 96 | } 97 | 98 | if f, err := vfs.Open(pathName); err != nil { 99 | t.Fatalf("%v", err) 100 | return 101 | } else { 102 | dat, err := io.ReadAll(f) 103 | if err != nil { 104 | t.Fatalf("%v", err) 105 | return 106 | } 107 | if string(dat) != content { 108 | t.Fatalf("unexpected content: %s", string(dat)) 109 | } 110 | f.Close() 111 | } 112 | 113 | // open truncate 114 | flag = os.O_RDWR | os.O_TRUNC 115 | if f, err := vfs.OpenFile(pathName, flag, perm); err != nil { 116 | t.Fatalf("%v", err) 117 | return 118 | } else { 119 | io.WriteString(f, "new content.") 120 | f.Close() 121 | } 122 | 123 | if f, err := vfs.Open(pathName); err != nil { 124 | t.Fatalf("%v", err) 125 | return 126 | } else { 127 | dat, err := io.ReadAll(f) 128 | if err != nil { 129 | t.Fatalf("%v", err) 130 | return 131 | } 132 | if string(dat) != "new content." { 133 | t.Fatalf("unexpected content: %s", string(dat)) 134 | } 135 | f.Close() 136 | } 137 | } 138 | 139 | func TestMemfsFileOperations(t *testing.T) { 140 | workdir, err := os.MkdirTemp("", "unixfs") 141 | if err != nil { 142 | t.Fatal(err) 143 | } 144 | fmt.Println("workdir:", workdir) 145 | 146 | vfs, err := unixfs.New(workdir) 147 | if err != nil { 148 | t.Fatal(err) 149 | } 150 | 151 | // 152 | 153 | folder := "/webapp" 154 | mode := os.FileMode(0o755) 155 | if err := vfs.MkdirAll(folder, mode); err != nil { 156 | t.Fatalf("MkdirAll(%s): %v", folder, err) 157 | } 158 | 159 | name := "hello.txt" 160 | content := "hello world!" 161 | 162 | pathName := fs.Join(folder, name) 163 | 164 | if _, err := vfs.Stat(pathName); err != nil { 165 | if !os.IsNotExist(err) { 166 | t.Fatalf("vfs.Stat: %v", err) 167 | } else { 168 | // should run into here.. 169 | } 170 | } else { 171 | t.Fatalf("file should not be existing.") 172 | } 173 | 174 | // write file with data... 175 | 176 | mod := os.FileMode(0o644) 177 | if f, err := vfs.OpenFile(pathName, os.O_CREATE|os.O_RDWR, mod); err != nil { 178 | t.Fatalf("OpenFile: %v", err) 179 | return 180 | } else { 181 | io.WriteString(f, content) 182 | f.Close() 183 | } 184 | 185 | if fi, err := vfs.Stat(pathName); err != nil { 186 | t.Fatalf("(after created) vfs.Stat: %v", err) 187 | } else if fi.Name() != name { 188 | t.Fatalf("expects file-info with name `%s`. gets `%s`.", 189 | name, fi.Name(), 190 | ) 191 | } else { 192 | // check file content... 193 | if f, err := vfs.Open(pathName); err != nil { 194 | t.Fatalf("vfs.Open: %v", err) 195 | return 196 | } else { 197 | if dat, err := io.ReadAll(f); err != nil { 198 | t.Fatalf("io.ReadAll: %v", err) 199 | } else { 200 | if string(dat) != content { 201 | t.Fatalf("wrong content: %v", string(dat)) 202 | } 203 | fmt.Println(string(dat)) 204 | } 205 | f.Close() 206 | } 207 | } 208 | 209 | // modify it's content 210 | 211 | contentNew := "great filesystem." 212 | flagWrite := os.O_RDWR | os.O_TRUNC 213 | 214 | if f, err := vfs.OpenFile(pathName, flagWrite, mod); err != nil { 215 | t.Fatalf("%v", err) 216 | } else { 217 | if _, err := io.WriteString(f, contentNew); err != nil { 218 | t.Fatalf("%v", err) 219 | } 220 | f.Close() 221 | } 222 | 223 | if f, err := vfs.Open(pathName); err != nil { 224 | t.Fatalf("%v", err) 225 | } else { 226 | if dat, err := io.ReadAll(f); err != nil { 227 | t.Fatalf("%v", err) 228 | } else if string(dat) != contentNew { 229 | t.Fatalf("failed to re-write file data. current content: %s", 230 | string(dat), 231 | ) 232 | } 233 | } 234 | 235 | // check if the file exists in the expecting folder. 236 | 237 | checkExists := func(name string) { 238 | if d, err := vfs.Open("/webapp"); err != nil { 239 | t.Fatalf("%v", err) 240 | } else { 241 | if items, err := d.Readdir(-1); err != nil { 242 | t.Fatalf("%v", err) 243 | } else if len(items) != 1 { 244 | t.Fatalf("expects 1 file in /webapp. but gets %d.", len(items)) 245 | } else { 246 | if items[0].Name() != name { 247 | t.Fatalf("the file is not the expected one: %s", items[0].Name()) 248 | } else { 249 | fmt.Println(items[0].Name()) 250 | } 251 | } 252 | } 253 | } 254 | 255 | checkExists(name) 256 | 257 | // rename 258 | 259 | newName := "new.txt" 260 | newPathName := fs.Join(folder, newName) 261 | if err := vfs.Rename(pathName, newPathName); err != nil { 262 | t.Fatalf("Rename: %v", err) 263 | } 264 | 265 | // check again 266 | 267 | checkExists(newName) 268 | 269 | // delete it 270 | 271 | if err := vfs.Remove(newPathName); err != nil { 272 | t.Fatalf("%v", err) 273 | } 274 | 275 | // check again: should be deleted 276 | 277 | if d, err := vfs.Open("/webapp"); err != nil { 278 | t.Fatalf("%v", err) 279 | } else { 280 | if items, err := d.Readdir(-1); err != nil { 281 | t.Fatalf("%v", err) 282 | } else if len(items) != 0 { 283 | t.Fatalf("expects no files in /webapp. but gets %d.", len(items)) 284 | } else { 285 | fmt.Printf("%s: deleted as expected.\n", newName) 286 | } 287 | } 288 | } 289 | 290 | func TestMemfsMkdir(t *testing.T) { 291 | workdir, err := os.MkdirTemp("", "unixfs") 292 | if err != nil { 293 | t.Fatal(err) 294 | } 295 | fmt.Println("workdir:", workdir) 296 | 297 | vfs, err := unixfs.New(workdir) 298 | if err != nil { 299 | t.Fatal(err) 300 | } 301 | 302 | // (1) create a folder 303 | 304 | pathName := "/abc/def/123" 305 | mode := os.FileMode(0o755) 306 | if err := vfs.MkdirAll(pathName, mode); err != nil { 307 | t.Fatalf("MkdirAll(%s): %v", pathName, err) 308 | } 309 | 310 | folder := fs.Dir(pathName) 311 | if folder != "/abc/def" { 312 | t.Fatalf("wrong dir returned from fs.Dir(%s): %s", pathName, folder) 313 | } 314 | 315 | f, err := vfs.Open(folder) 316 | if err != nil { 317 | t.Fatalf("%s: not exists.", folder) 318 | } 319 | 320 | // (2) create more dirs 321 | 322 | paths := []string{ 323 | "/hello", 324 | "/data", 325 | "/data/backups", 326 | "/abc/www", 327 | } 328 | for _, pathName := range paths { 329 | mode := os.FileMode(0o755) 330 | if err := vfs.MkdirAll(pathName, mode); err != nil { 331 | t.Fatalf("MkdirAll(%s): %v", pathName, err) 332 | } 333 | } 334 | 335 | items, err := f.Readdir(-1) 336 | if err != nil { 337 | t.Fatalf("f.Readdir(-1): %v", err) 338 | } 339 | 340 | found := false 341 | for _, item := range items { 342 | fmt.Println(item.Name()) 343 | if item.Name() == fs.Base(pathName) { 344 | found = true 345 | break 346 | } 347 | } 348 | 349 | if !found { 350 | t.Fatalf("target not created: %s", pathName) 351 | return 352 | } 353 | 354 | // (3) read dir of /abc 355 | 356 | d, err := vfs.Open("/abc") 357 | if err != nil { 358 | t.Fatalf("%v", err) 359 | } 360 | 361 | items, err = d.Readdir(-1) 362 | if err != nil { 363 | t.Fatalf("%v", err) 364 | } 365 | 366 | if len(items) != 2 { 367 | t.Fatalf("expects 2 children of /abc. (gets %d)", len(items)) 368 | } 369 | 370 | // (4) read dir of / 371 | 372 | d, err = vfs.Open("/") 373 | if err != nil { 374 | t.Fatalf("%v", err) 375 | } 376 | 377 | items, err = d.Readdir(-1) 378 | if err != nil { 379 | t.Fatalf("%v", err) 380 | } 381 | 382 | if len(items) != 3 { 383 | t.Fatalf("expects 3 children of /. (gets %d)", len(items)) 384 | } 385 | 386 | // (5) rename a dir 387 | if err := vfs.Rename("/data", "/data-new"); err != nil { 388 | t.Fatalf("Rename(/data => /data-new): %v", err) 389 | } 390 | 391 | if _, err := vfs.Open("/data"); err != nil { 392 | if !os.IsNotExist(err) { 393 | t.Fatalf("should not exists: /data (renamed to /data-new).") 394 | return 395 | } 396 | } else { 397 | t.Fatalf("should not exists: /data (renamed to /data-new).") 398 | return 399 | } 400 | 401 | if d, err := vfs.Open("/data-new"); err != nil { 402 | t.Fatalf("%v", err) 403 | } else { 404 | if items, err := d.Readdir(-1); err != nil { 405 | t.Fatalf("%v", err) 406 | } else { 407 | if len(items) != 1 { 408 | t.Fatalf("expects 1 children of /data-new. (gets %d)", len(items)) 409 | } 410 | } 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /unixfs/verbose_unixfs.go: -------------------------------------------------------------------------------- 1 | package unixfs 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/smallfz/libnfs-go/fs" 7 | "github.com/smallfz/libnfs-go/log" 8 | ) 9 | 10 | type VerboseUnixFS struct { 11 | UnixFS 12 | } 13 | 14 | func NewVerbose(workdir string) (*VerboseUnixFS, error) { 15 | fs, err := New(workdir) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | return &VerboseUnixFS{UnixFS: *fs}, nil 21 | } 22 | 23 | func (s *VerboseUnixFS) Chmod(name string, mode os.FileMode) error { 24 | log.Infof("unixfs.Chmod(%s, %o)", name, mode) 25 | 26 | err := s.UnixFS.Chmod(name, mode) 27 | if err != nil { 28 | log.Warn(err) 29 | return err 30 | } 31 | 32 | return nil 33 | } 34 | 35 | func (s *VerboseUnixFS) Chown(name string, uid, gid int) error { 36 | log.Infof("unixfs.Chown(%s, %d, %d)", name, uid, gid) 37 | 38 | err := s.UnixFS.Chown(name, uid, gid) 39 | if err != nil { 40 | log.Warn(err) 41 | return err 42 | } 43 | 44 | return nil 45 | } 46 | 47 | func (s *VerboseUnixFS) MkdirAll(name string, mode os.FileMode) error { 48 | log.Infof("unixfs.MkdirAll(%s, %o)", name, mode) 49 | 50 | err := s.UnixFS.MkdirAll(name, mode) 51 | if err != nil { 52 | log.Warn(err) 53 | return err 54 | } 55 | 56 | return nil 57 | } 58 | 59 | func (s *VerboseUnixFS) Remove(name string) error { 60 | log.Infof("unixfs.Remove(%s)", name) 61 | 62 | err := s.UnixFS.Remove(name) 63 | if err != nil { 64 | log.Warn(err) 65 | return err 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func (s *VerboseUnixFS) Rename(oldpath, newpath string) error { 72 | log.Infof("unixfs.Rename(%s, %s)", oldpath, newpath) 73 | 74 | err := s.UnixFS.Rename(oldpath, newpath) 75 | if err != nil { 76 | log.Warn(err) 77 | return err 78 | } 79 | 80 | return nil 81 | } 82 | 83 | func (s *VerboseUnixFS) Link(oldpath, newpath string) error { 84 | log.Infof("unixfs.Link(%s, %s)", oldpath, newpath) 85 | 86 | err := s.UnixFS.Link(oldpath, newpath) 87 | if err != nil { 88 | log.Warn(err) 89 | return err 90 | } 91 | 92 | return nil 93 | } 94 | 95 | func (s *VerboseUnixFS) Stat(name string) (fs.FileInfo, error) { 96 | log.Infof("unixfs.Stat(%s)", name) 97 | 98 | fi, err := s.UnixFS.Stat(name) 99 | if err != nil { 100 | log.Warn(err) 101 | return nil, err 102 | } 103 | 104 | return fi, nil 105 | } 106 | 107 | func (s *VerboseUnixFS) Open(name string) (fs.File, error) { 108 | log.Infof("unixfs.Open(%s)", name) 109 | 110 | return s.OpenFile(name, os.O_RDONLY, os.FileMode(0o644)) 111 | } 112 | 113 | func (s *VerboseUnixFS) OpenFile(name string, flag int, mode os.FileMode) (fs.File, error) { 114 | log.Infof("unixfs.OpenFile(%s, %d, %o)", name, flag, mode) 115 | 116 | f, err := s.UnixFS.OpenFile(name, flag, mode) 117 | if err != nil { 118 | log.Warn(err) 119 | return nil, err 120 | } 121 | 122 | return f, nil 123 | } 124 | 125 | func (s *VerboseUnixFS) GetFileId(fi fs.FileInfo) uint64 { 126 | log.Infof("unixfs.GetFileId: %#v", fi) 127 | 128 | inode := s.UnixFS.GetFileId(fi) 129 | log.Info(" id: ", inode) 130 | 131 | return inode 132 | } 133 | 134 | func (s *VerboseUnixFS) GetHandle(fi fs.FileInfo) ([]byte, error) { 135 | log.Infof("GetHandle: %s", fi.Name()) 136 | 137 | b, err := s.UnixFS.GetHandle(fi) 138 | if err != nil { 139 | log.Warn(err) 140 | return nil, err 141 | } 142 | 143 | return b, nil 144 | } 145 | 146 | func (s *VerboseUnixFS) GetRootHandle() []byte { 147 | b := s.UnixFS.GetRootHandle() 148 | log.Infof("unixfs.GetRootHandle: %x", b) 149 | 150 | return b 151 | } 152 | 153 | // ResolveHandle resolves a file-handle(eg. nfs_fh4) to a full path name. 154 | func (s *VerboseUnixFS) ResolveHandle(fh []byte) (string, error) { 155 | log.Infof("unixfs.ResolveHandle: %x", fh) 156 | 157 | v, err := s.UnixFS.ResolveHandle(fh) 158 | if err != nil { 159 | log.Warn(err) 160 | return "", err 161 | } 162 | 163 | return v, nil 164 | } 165 | -------------------------------------------------------------------------------- /utils/rand.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | ) 7 | 8 | func RandUint32() uint32 { 9 | b := make([]byte, 4) 10 | if _, err := rand.Read(b); err != nil { 11 | panic(err) 12 | } 13 | return binary.BigEndian.Uint32(b) 14 | } 15 | -------------------------------------------------------------------------------- /xdr/README.md: -------------------------------------------------------------------------------- 1 | # xdr & rpc 2 | 3 | - xdr: [https://datatracker.ietf.org/doc/html/rfc1014](https://datatracker.ietf.org/doc/html/rfc1014) 4 | - rpc(v2): [https://datatracker.ietf.org/doc/html/rfc5531](https://datatracker.ietf.org/doc/html/rfc5531) -------------------------------------------------------------------------------- /xdr/header.go: -------------------------------------------------------------------------------- 1 | package xdr 2 | 3 | const ( 4 | RPC_CALL = uint32(iota) 5 | RPC_REPLY 6 | ) 7 | 8 | type Header struct { 9 | Xid uint32 10 | MsgType uint32 /* RPC_CALL|RPC_REPLY */ 11 | } 12 | -------------------------------------------------------------------------------- /xdr/pad.go: -------------------------------------------------------------------------------- 1 | package xdr 2 | 3 | func Pad(size int) int { 4 | if size%4 != 0 { 5 | return 4 - size%4 6 | } 7 | return 0 8 | } 9 | -------------------------------------------------------------------------------- /xdr/reader.go: -------------------------------------------------------------------------------- 1 | package xdr 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | 10 | // "libnfs-go/log" 11 | "math" 12 | "reflect" 13 | ) 14 | 15 | // int32, uint32, int, bool => xdr.b4 16 | // int64, uint64 => xdr.b8 17 | // float32 => xdr.b4, IEEE 18 | // float64 => xdr.b8, IEEE 19 | 20 | // 21 | // opaque identifier[n] 22 | // [bytes * n...] + PAD 23 | // 24 | 25 | // opaque identifier<> 26 | // or opaque identifier 27 | // or opaque identifier<1024> 28 | // 29 | // [uint32:length][bytes...] + PAD 30 | 31 | // string object<> 32 | // or string object 33 | // 34 | // [uint32:length][bytes...] + PAD 35 | 36 | // 37 | // fixed-length Array: 38 | // type-name identifier[n] 39 | // 40 | // [element * n] 41 | 42 | // var-length Array: 43 | // type-name identifier<> 44 | // type-name identifier 45 | // 46 | // [uint32:count][element * n] 47 | 48 | // structure, struct 49 | // struct{ 50 | // field a; 51 | // field b; 52 | // } 53 | // 54 | // [a][b] 55 | // 56 | 57 | // optional data 58 | // type-name *identifier 59 | // 60 | // like var-length array with max size 1. 61 | 62 | type Reader struct { 63 | base *io.LimitedReader 64 | } 65 | 66 | func NewReader(base io.Reader) *Reader { 67 | return &Reader{base: &io.LimitedReader{R: base, N: 0}} 68 | } 69 | 70 | func (r *Reader) Debugf(t string, args ...interface{}) { 71 | // log.Debugf(t, args...) 72 | } 73 | 74 | func (r *Reader) ReadBytes(size int) ([]byte, error) { 75 | r.base.N = int64(size) 76 | dat, err := ioutil.ReadAll(r.base) 77 | if err != nil { 78 | return nil, err 79 | } else if dat == nil || len(dat) < size { 80 | return dat, io.EOF 81 | } 82 | return dat, nil 83 | } 84 | 85 | func (r *Reader) Read(buff []byte) (int, error) { 86 | r.base.N = int64(len(buff)) 87 | return r.base.Read(buff) 88 | } 89 | 90 | func (r *Reader) ReadUint32() (uint32, error) { 91 | buff, err := r.ReadBytes(4) 92 | if err != nil { 93 | return 0, err 94 | } 95 | return binary.BigEndian.Uint32(buff), nil 96 | } 97 | 98 | func (r *Reader) ReadValue(v reflect.Value) (int, error) { 99 | if !v.IsValid() { 100 | return 0, errors.New("invalid target") 101 | } 102 | 103 | kind := v.Kind() 104 | if kind != reflect.Ptr { 105 | return 0, errors.New("ReadAs: expects a ptr target") 106 | } 107 | 108 | kind = v.Elem().Kind() 109 | 110 | switch kind { 111 | case reflect.Ptr: 112 | ev := v.Elem() 113 | if ev.IsNil() { 114 | ev.Set(reflect.New(ev.Type().Elem())) 115 | } 116 | if size, err := r.ReadValue(ev); err != nil { 117 | return 0, err 118 | } else { 119 | return size, nil 120 | } 121 | 122 | case reflect.Bool: 123 | // todo: convert to xdr.b4 124 | if iv, err := r.ReadUint32(); err != nil { 125 | return 0, err 126 | } else { 127 | if iv > 0 { 128 | v.Elem().SetBool(true) 129 | } else { 130 | v.Elem().SetBool(false) 131 | } 132 | return 4, nil 133 | } 134 | 135 | case reflect.Float32: 136 | if iv, err := r.ReadUint32(); err != nil { 137 | return 0, err 138 | } else { 139 | fv := math.Float32frombits(iv) 140 | v.Elem().Set(reflect.ValueOf(fv)) 141 | return 4, nil 142 | } 143 | 144 | case reflect.Float64: 145 | if buff, err := r.ReadBytes(8); err != nil { 146 | return 0, err 147 | } else { 148 | iv := binary.BigEndian.Uint64(buff) 149 | fv := math.Float64frombits(iv) 150 | v.Elem().SetFloat(fv) 151 | return len(buff), nil 152 | } 153 | 154 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: 155 | fallthrough 156 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: 157 | size := 4 158 | buff, err := r.ReadBytes(size) 159 | if err != nil { 160 | return size, err 161 | } 162 | iv := uint64(binary.BigEndian.Uint32(buff)) 163 | vFrom := reflect.ValueOf(iv) 164 | vTo := v.Elem() 165 | if vFrom.Type().AssignableTo(vTo.Type()) { 166 | v.Elem().SetUint(iv) 167 | return size, nil 168 | } else if vFrom.Type().ConvertibleTo(vTo.Type()) { 169 | v1 := vFrom.Convert(vTo.Type()) 170 | v.Elem().Set(v1) 171 | return size, nil 172 | } 173 | return size, fmt.Errorf("unable to assign %T to %s", iv, kind) 174 | 175 | case reflect.Int64, reflect.Uint64: 176 | // todo: convert to xdr.b8 177 | size := 8 178 | buff, err := r.ReadBytes(size) 179 | if err != nil { 180 | return size, err 181 | } 182 | iv := binary.BigEndian.Uint64(buff) 183 | vFrom := reflect.ValueOf(iv) 184 | vTo := v.Elem() 185 | if vFrom.Type().AssignableTo(vTo.Type()) { 186 | v.Elem().SetUint(iv) 187 | return size, nil 188 | } else if vFrom.Type().ConvertibleTo(vTo.Type()) { 189 | v1 := vFrom.Convert(vTo.Type()) 190 | v.Elem().Set(v1) 191 | return size, nil 192 | } 193 | return size, fmt.Errorf("unable to assign %T to %s", iv, kind) 194 | 195 | case reflect.Array: 196 | // fixed length array 197 | sizeConsumed := 0 198 | vtyp := v.Elem().Type() 199 | arrLen := vtyp.Len() 200 | varr := reflect.New(vtyp) 201 | 202 | // Special case: [n]byte 203 | if vtyp.Elem().Kind() == reflect.Uint8 { 204 | padLen := Pad(arrLen) 205 | dat, err := r.ReadBytes(arrLen + padLen) 206 | if err != nil { 207 | return sizeConsumed, err 208 | } 209 | sizeConsumed += len(dat) 210 | dat = dat[:arrLen] 211 | for i, bv := range dat { 212 | v.Elem().Index(i).Set(reflect.ValueOf(bv)) 213 | } 214 | return sizeConsumed, nil 215 | } 216 | 217 | for i := 0; i < arrLen; i++ { 218 | // todo: read an element 219 | item := reflect.New(vtyp.Elem()) 220 | if size, err := r.ReadValue(item); err != nil { 221 | return sizeConsumed, err 222 | } else { 223 | sizeConsumed += size 224 | } 225 | v1 := varr.Elem().Index(i) 226 | v1.Set(item.Elem()) 227 | } 228 | v.Elem().Set(varr.Elem()) 229 | return sizeConsumed, nil 230 | 231 | case reflect.Slice: 232 | sizeConsumed := 0 233 | arrLen32, err := r.ReadUint32() 234 | if err != nil { 235 | return 0, err 236 | } else { 237 | sizeConsumed += 4 238 | } 239 | 240 | arrLen := int(arrLen32) 241 | vtyp := v.Elem().Type() 242 | 243 | // Special case: []byte 244 | if vtyp.Elem().Kind() == reflect.Uint8 { 245 | padLen := Pad(arrLen) 246 | r.Debugf( 247 | "[]byte: len=%d, len-with-pad=%d", 248 | arrLen, arrLen+padLen, 249 | ) 250 | dat, err := r.ReadBytes(arrLen + padLen) 251 | if err != nil { 252 | return sizeConsumed, err 253 | } 254 | sizeConsumed += len(dat) 255 | dat = dat[:arrLen] 256 | v.Elem().SetBytes(dat) 257 | return sizeConsumed, nil 258 | } 259 | 260 | varr := reflect.MakeSlice(vtyp, arrLen, arrLen) 261 | for i := 0; i < arrLen; i++ { 262 | // read an element 263 | item := reflect.New(vtyp.Elem()) 264 | if size, err := r.ReadValue(item); err != nil { 265 | return sizeConsumed, err 266 | } else { 267 | sizeConsumed += size 268 | } 269 | v1 := varr.Index(i) 270 | v1.Set(item.Elem()) 271 | } 272 | v.Elem().Set(varr) 273 | 274 | return sizeConsumed, nil 275 | 276 | case reflect.String: 277 | sizeConsumed := 0 278 | sLen32, err := r.ReadUint32() 279 | if err != nil { 280 | return 0, err 281 | } else { 282 | sizeConsumed += 4 283 | } 284 | 285 | sLen := int(sLen32) 286 | padLen := Pad(sLen) 287 | 288 | dat, err := r.ReadBytes(sLen + padLen) 289 | if err != nil { 290 | return sizeConsumed, err 291 | } 292 | sizeConsumed += len(dat) 293 | dat = dat[:sLen] 294 | r.Debugf("size: %d", sLen32) 295 | r.Debugf("string: %v", dat) 296 | v.Elem().SetString(string(dat)) 297 | return sizeConsumed, nil 298 | 299 | case reflect.Struct: 300 | sizeConsumed := 0 301 | vtyp := v.Elem().Type() 302 | fieldCount := vtyp.NumField() 303 | for i := 0; i < fieldCount; i++ { 304 | field := vtyp.Field(i) 305 | fv := v.Elem().Field(i) 306 | r.Debugf(" - field: %s", field.Name) 307 | pToFv := fv.Addr() 308 | 309 | switch fv.Type().Kind() { 310 | case reflect.Slice: 311 | if fv.IsNil() { 312 | pToFv = reflect.New(fv.Type()) 313 | } 314 | } 315 | 316 | if size, err := r.ReadValue(pToFv); err != nil { 317 | return sizeConsumed, fmt.Errorf( 318 | "ReadValue(field:%s): %v", field.Name, err, 319 | ) 320 | } else { 321 | fv.Set(pToFv.Elem()) 322 | sizeConsumed += size 323 | } 324 | } 325 | return sizeConsumed, nil 326 | 327 | default: 328 | return 0, fmt.Errorf("type not supported: %s", kind) 329 | } 330 | } 331 | 332 | func (r *Reader) ReadAs(target interface{}) (int, error) { 333 | v := reflect.ValueOf(target) 334 | return r.ReadValue(v) 335 | } 336 | -------------------------------------------------------------------------------- /xdr/reader_test.go: -------------------------------------------------------------------------------- 1 | package xdr 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "math" 8 | "testing" 9 | ) 10 | 11 | func TestReaderRead(t *testing.T) { 12 | srcb := []byte{1, 2, 3, 4, 5, 6} 13 | src := bytes.NewBuffer(srcb) 14 | 15 | reader := NewReader(src) 16 | dst := make([]byte, 4) 17 | if size, err := reader.Read(dst); err != nil { 18 | t.Fatalf("Read: %v", err) 19 | } else if size != len(dst) { 20 | t.Fatalf("expects 4 bytes. get %d.", size) 21 | } 22 | if !bytes.Equal(dst, srcb[:4]) { 23 | t.Fatalf("expects %v but get %v.", srcb[:4], dst) 24 | } 25 | } 26 | 27 | func TestReaderReadBytes(t *testing.T) { 28 | srcb := []byte{1, 2, 3, 4, 5, 6} 29 | src := bytes.NewBuffer(srcb) 30 | 31 | reader := NewReader(src) 32 | size := 4 33 | if dst, err := reader.ReadBytes(size); err != nil { 34 | t.Fatalf("ReadBytes: %v", err) 35 | } else if size != len(dst) { 36 | t.Fatalf("expects 4 bytes. get %d.", size) 37 | } else { 38 | if !bytes.Equal(dst, srcb[:4]) { 39 | t.Fatalf("expects %v but get %v.", srcb[:4], dst) 40 | } 41 | } 42 | } 43 | 44 | func TestReaderReadAs_bool(t *testing.T) { 45 | srcb := []byte{0, 0, 0, 1} 46 | src := bytes.NewBuffer(srcb) 47 | 48 | reader := NewReader(src) 49 | 50 | target := false 51 | if size, err := reader.ReadAs(&target); err != nil { 52 | t.Fatalf("ReadAs: %v", err) 53 | } else if size != 4 { 54 | t.Fatalf("expects 4 bytes. get %d.", size) 55 | } else { 56 | if !target { 57 | t.Fatalf("expects TRUE but get %v.", target) 58 | } 59 | } 60 | 61 | copy(srcb, []byte{0, 0, 0, 0, 0, 0}) 62 | src = bytes.NewBuffer(srcb) 63 | reader = NewReader(src) 64 | 65 | if size, err := reader.ReadAs(&target); err != nil { 66 | t.Fatalf("ReadAs: %v", err) 67 | } else if size != 4 { 68 | t.Fatalf("expects 4 bytes. get %d.", size) 69 | } else { 70 | if target { 71 | t.Fatalf("expects FALSE but get %v.", target) 72 | } 73 | } 74 | } 75 | 76 | func TestReaderReadAs_uint16(t *testing.T) { 77 | srcb := []byte{0, 0, 1, 0} 78 | src := bytes.NewBuffer(srcb) 79 | srcVal := binary.BigEndian.Uint32(srcb) 80 | 81 | reader := NewReader(src) 82 | 83 | target := uint16(0) 84 | if size, err := reader.ReadAs(&target); err != nil { 85 | t.Fatalf("ReadAs: %v", err) 86 | } else if size != 4 { 87 | t.Fatalf("expects 4 bytes. get %d.", size) 88 | } else { 89 | if uint32(target) != srcVal { 90 | t.Fatalf("expects %d but get %d.", srcVal, target) 91 | } 92 | } 93 | } 94 | 95 | func TestReaderReadAs_uint64(t *testing.T) { 96 | srcb := make([]byte, 32) 97 | srcVal := uint64(3721352) 98 | binary.BigEndian.PutUint64(srcb, srcVal) 99 | 100 | src := bytes.NewBuffer(srcb) 101 | 102 | reader := NewReader(src) 103 | 104 | target := uint64(0) 105 | if size, err := reader.ReadAs(&target); err != nil { 106 | t.Fatalf("ReadAs: %v", err) 107 | } else if size != 8 { 108 | t.Fatalf("expects 8 bytes. get %d.", size) 109 | } else { 110 | if target != srcVal { 111 | t.Fatalf("expects %d but get %d.", srcVal, target) 112 | } 113 | } 114 | } 115 | 116 | func TestReaderReadAs_float32(t *testing.T) { 117 | srcb := []byte{0, 0, 0, 0} 118 | srcVal := float32(12.345) 119 | binary.BigEndian.PutUint32(srcb, math.Float32bits(srcVal)) 120 | src := bytes.NewBuffer(srcb) 121 | 122 | reader := NewReader(src) 123 | 124 | target := float32(0) 125 | if size, err := reader.ReadAs(&target); err != nil { 126 | t.Fatalf("ReadAs: %v", err) 127 | } else if size != 4 { 128 | t.Fatalf("expects 4 bytes. get %d.", size) 129 | } else { 130 | if target != srcVal { 131 | t.Fatalf("expects %f but get %f.", srcVal, target) 132 | } 133 | } 134 | } 135 | 136 | func TestReaderReadAs_float64(t *testing.T) { 137 | srcb := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 138 | srcVal := float64(12.345) 139 | binary.BigEndian.PutUint64(srcb, math.Float64bits(srcVal)) 140 | src := bytes.NewBuffer(srcb) 141 | 142 | reader := NewReader(src) 143 | 144 | target := float64(0) 145 | if size, err := reader.ReadAs(&target); err != nil { 146 | t.Fatalf("ReadAs: %v", err) 147 | } else if size != 8 { 148 | t.Fatalf("expects 8 bytes. get %d.", size) 149 | } else { 150 | if target != srcVal { 151 | t.Fatalf("expects %f but get %f.", srcVal, target) 152 | } 153 | } 154 | } 155 | 156 | func TestReaderReadAs_varSizeArr(t *testing.T) { 157 | srcVal := []uint64{123, 456, 8, 9, 0, 999} 158 | src := bytes.NewBuffer([]byte{}) 159 | 160 | // 1st word is array size 161 | buff := make([]byte, 4) 162 | binary.BigEndian.PutUint32(buff, uint32(len(srcVal))) 163 | src.Write(buff) 164 | 165 | for _, v := range srcVal { 166 | buff := make([]byte, 8) 167 | binary.BigEndian.PutUint64(buff, v) 168 | src.Write(buff) 169 | } 170 | 171 | reader := NewReader(bytes.NewReader(src.Bytes())) 172 | 173 | target := []uint64{} 174 | if size, err := reader.ReadAs(&target); err != nil { 175 | t.Fatalf("ReadAs: %v", err) 176 | } else if size != 4+8*len(srcVal) { 177 | t.Fatalf("expects %d bytes. get %d.", 4+8*len(srcVal), size) 178 | } else { 179 | if len(target) != len(srcVal) { 180 | t.Fatalf("expects %d elements but get %d.", len(srcVal), len(target)) 181 | } else { 182 | for i := 0; i < len(srcVal); i++ { 183 | if srcVal[i] != target[i] { 184 | t.Fatalf( 185 | "element[%d]: expects %d but get %d.", 186 | i, srcVal[i], target[i], 187 | ) 188 | } 189 | } 190 | } 191 | } 192 | } 193 | 194 | func TestReaderReadAs_fixedSizeArr(t *testing.T) { 195 | srcVal := [6]uint64{123, 456, 8, 9, 0, 999} 196 | src := bytes.NewBuffer([]byte{}) 197 | 198 | for _, v := range srcVal { 199 | buff := make([]byte, 8) 200 | binary.BigEndian.PutUint64(buff, v) 201 | src.Write(buff) 202 | } 203 | 204 | reader := NewReader(bytes.NewReader(src.Bytes())) 205 | 206 | target := [6]uint64{} 207 | if size, err := reader.ReadAs(&target); err != nil { 208 | t.Fatalf("ReadAs: %v", err) 209 | } else if size != 8*len(srcVal) { 210 | t.Fatalf("expects %d bytes. get %d.", 8*len(srcVal), size) 211 | } else { 212 | if len(target) != len(srcVal) { 213 | t.Fatalf("expects %d elements but get %d.", len(srcVal), len(target)) 214 | } else { 215 | for i := 0; i < len(srcVal); i++ { 216 | if srcVal[i] != target[i] { 217 | t.Fatalf( 218 | "element[%d]: expects %d but get %d.", 219 | i, srcVal[i], target[i], 220 | ) 221 | } 222 | } 223 | } 224 | } 225 | } 226 | 227 | func TestReaderReadAs_varSizeBytes(t *testing.T) { 228 | src := bytes.NewBuffer([]byte{}) 229 | srcb := []byte("hello world") 230 | 231 | buff := make([]byte, 4) 232 | binary.BigEndian.PutUint32(buff, uint32(len(srcb))) 233 | src.Write(buff) 234 | 235 | src.Write(srcb) 236 | size := len(srcb) 237 | if size%4 != 0 { 238 | padLen := Pad(size) 239 | pad := make([]byte, padLen) 240 | src.Write(pad) 241 | } 242 | 243 | fmt.Println(src.Bytes()) 244 | 245 | reader := NewReader(bytes.NewReader(src.Bytes())) 246 | 247 | target := ([]byte)(nil) // {} 248 | if size, err := reader.ReadAs(&target); err != nil { 249 | t.Fatalf("ReadAs: %v", err) 250 | } else if size != len(src.Bytes()) { 251 | t.Fatalf("expects %d bytes. get %d.", len(src.Bytes()), size) 252 | } else { 253 | if len(target) < len(srcb) { 254 | t.Fatalf("expects %d elements but get %d.", len(srcb), len(target)) 255 | } else { 256 | target0 := target[0:len(srcb)] 257 | if !bytes.Equal(target0, srcb) { 258 | t.Fatalf("expects %v but get %v.", srcb, target0) 259 | } 260 | fmt.Println(target0) 261 | } 262 | } 263 | } 264 | 265 | func TestReaderReadAs_fizedSizeBytes(t *testing.T) { 266 | src := bytes.NewBuffer([]byte{}) 267 | srcb := []byte("hello world") 268 | 269 | src.Write(srcb) 270 | size := len(srcb) 271 | if size%4 != 0 { 272 | padLen := Pad(size) 273 | pad := make([]byte, padLen) 274 | src.Write(pad) 275 | } 276 | 277 | fmt.Println(src.Bytes()) 278 | 279 | reader := NewReader(bytes.NewReader(src.Bytes())) 280 | 281 | target := [11]byte{} 282 | if size, err := reader.ReadAs(&target); err != nil { 283 | t.Fatalf("ReadAs: %v", err) 284 | } else if size != len(src.Bytes()) { 285 | t.Fatalf("expects %d bytes. get %d.", len(src.Bytes()), size) 286 | } else { 287 | if len(target) < len(srcb) { 288 | t.Fatalf("expects %d elements but get %d.", len(srcb), len(target)) 289 | } else { 290 | target0 := target[0:len(srcb)] 291 | if !bytes.Equal(target0, srcb) { 292 | t.Fatalf("expects %v but get %v.", srcb, target0) 293 | } 294 | fmt.Println(target0) 295 | } 296 | } 297 | } 298 | 299 | type structTestB struct { 300 | Value int 301 | } 302 | 303 | type structTestA struct { 304 | Iv uint64 305 | Iv32 uint32 306 | Values []*structTestB 307 | SingleB *structTestB 308 | PlainB structTestB 309 | Text string 310 | } 311 | 312 | func TestReaderReadAs_struct(t *testing.T) { 313 | a := &structTestA{ 314 | Iv: 123, 315 | Iv32: 456, 316 | Values: []*structTestB{ 317 | {Value: 789}, 318 | {Value: 10010}, 319 | }, 320 | SingleB: &structTestB{Value: 666}, 321 | PlainB: structTestB{Value: 999}, 322 | Text: "hello", 323 | } 324 | 325 | src := bytes.NewBuffer([]byte{}) 326 | 327 | buff := make([]byte, 8) 328 | binary.BigEndian.PutUint64(buff, a.Iv) 329 | src.Write(buff) 330 | 331 | buff = make([]byte, 4) 332 | binary.BigEndian.PutUint32(buff, a.Iv32) 333 | src.Write(buff) 334 | 335 | buff = make([]byte, 4) 336 | binary.BigEndian.PutUint32(buff, uint32(len(a.Values))) 337 | src.Write(buff) 338 | 339 | for _, v := range a.Values { 340 | buff := make([]byte, 4) 341 | binary.BigEndian.PutUint32(buff, uint32(v.Value)) 342 | src.Write(buff) 343 | } 344 | 345 | buff = make([]byte, 4) 346 | binary.BigEndian.PutUint32(buff, uint32(a.SingleB.Value)) 347 | src.Write(buff) 348 | 349 | // .PlainB 350 | buff = make([]byte, 4) 351 | binary.BigEndian.PutUint32(buff, uint32(a.PlainB.Value)) 352 | src.Write(buff) 353 | 354 | // .Text 355 | buff = make([]byte, 4) 356 | txtb := []byte(a.Text) 357 | binary.BigEndian.PutUint32(buff, uint32(len(txtb))) 358 | src.Write(buff) 359 | src.Write(txtb) 360 | padLen := Pad(len(txtb)) 361 | pad := make([]byte, padLen) 362 | src.Write(pad) 363 | 364 | // --- writing ends. --- 365 | 366 | srcb := src.Bytes() 367 | srcSize := len(srcb) 368 | 369 | reader := NewReader(bytes.NewReader(srcb)) 370 | 371 | target := &structTestA{} 372 | if size, err := reader.ReadAs(target); err != nil { 373 | t.Fatalf("ReadAs: %v", err) 374 | } else if size != srcSize { 375 | t.Fatalf("expects %d bytes. get %d.", srcSize, size) 376 | } else { 377 | if target.Iv != a.Iv { 378 | t.Fatalf("target.Iv: expects %d but get %d.", a.Iv, target.Iv) 379 | } 380 | if target.Iv32 != a.Iv32 { 381 | t.Fatalf("target.Iv32: expects %d but get %d.", a.Iv32, target.Iv32) 382 | } 383 | if target.Values == nil || len(target.Values) != len(a.Values) { 384 | t.Fatalf("target.Values: expects %d elements. get %d.", 385 | len(a.Values), len(target.Values), 386 | ) 387 | } 388 | if target.SingleB == nil || target.SingleB.Value != a.SingleB.Value { 389 | t.Fatalf( 390 | "target.SingleB.Value: expects %v but get %v.", 391 | a.SingleB, target.SingleB, 392 | ) 393 | } 394 | if target.PlainB.Value != a.PlainB.Value { 395 | t.Fatalf( 396 | "target.PlainB.Value: expects %v but get %v.", 397 | a.PlainB, target.PlainB, 398 | ) 399 | } 400 | if target.Text != a.Text { 401 | t.Fatalf( 402 | "target.Text: expects %s but get %s.", 403 | a.Text, target.Text, 404 | ) 405 | } 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /xdr/writer.go: -------------------------------------------------------------------------------- 1 | package xdr 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "math" 8 | "reflect" 9 | 10 | "github.com/smallfz/libnfs-go/log" 11 | ) 12 | 13 | type Writer struct { 14 | base io.Writer 15 | } 16 | 17 | func NewWriter(w io.Writer) *Writer { 18 | return &Writer{base: w} 19 | } 20 | 21 | func (w *Writer) Write(data []byte) (int, error) { 22 | if i, err := w.base.Write(data); err != nil { 23 | log.Errorf("w.base.Write: %v", err) 24 | return i, err 25 | } else { 26 | return i, nil 27 | } 28 | } 29 | 30 | func (w *Writer) WriteAny(any interface{}) (int, error) { 31 | return w.WriteValue(reflect.ValueOf(any)) 32 | } 33 | 34 | func (w *Writer) WriteValue(v reflect.Value) (int, error) { 35 | if !v.IsValid() { 36 | return 0, nil 37 | } 38 | 39 | vtyp := v.Type() 40 | kind := vtyp.Kind() 41 | 42 | if kind == reflect.Ptr { 43 | if v.IsNil() { 44 | return 0, nil 45 | } 46 | return w.WriteValue(v.Elem()) 47 | } 48 | 49 | switch kind { 50 | case reflect.Bool: 51 | i := uint32(0) 52 | if v.Bool() { 53 | i = uint32(1) 54 | } 55 | return w.WriteUint32(i) 56 | 57 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: 58 | fallthrough 59 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: 60 | i := uint32(0) 61 | vTo := reflect.ValueOf(&i) 62 | if vtyp.AssignableTo(vTo.Elem().Type()) { 63 | vTo.Elem().Set(v) 64 | } else if vtyp.ConvertibleTo(vTo.Elem().Type()) { 65 | vmid := v.Convert(vTo.Elem().Type()) 66 | vTo.Elem().Set(vmid) 67 | } else { 68 | log.Errorf( 69 | "unable to assign %s to %s", 70 | vtyp.Name(), vTo.Type().Name(), 71 | ) 72 | return 0, fmt.Errorf( 73 | "unable to assign %s to %s", 74 | vtyp.Name(), vTo.Type().Name(), 75 | ) 76 | } 77 | return w.WriteUint32(i) 78 | 79 | case reflect.Int64, reflect.Uint64: 80 | i64 := uint64(0) 81 | vTo := reflect.ValueOf(&i64) 82 | if vtyp.AssignableTo(vTo.Elem().Type()) { 83 | vTo.Elem().Set(v) 84 | } else if vtyp.ConvertibleTo(vTo.Elem().Type()) { 85 | vmid := v.Convert(vTo.Elem().Type()) 86 | vTo.Elem().Set(vmid) 87 | } else { 88 | log.Errorf( 89 | "unable to assign %s to %s", 90 | vtyp.Name(), vTo.Type().Name(), 91 | ) 92 | return 0, fmt.Errorf( 93 | "unable to assign %s to %s", 94 | vtyp.Name(), vTo.Type().Name(), 95 | ) 96 | } 97 | buff := make([]byte, 8) 98 | binary.BigEndian.PutUint64(buff, i64) 99 | return w.Write(buff) 100 | 101 | case reflect.Float32: 102 | i := math.Float32bits(float32(v.Float())) 103 | return w.WriteUint32(i) 104 | 105 | case reflect.Float64: 106 | i64 := math.Float64bits(v.Float()) 107 | buff := make([]byte, 8) 108 | binary.BigEndian.PutUint64(buff, i64) 109 | return w.Write(buff) 110 | 111 | case reflect.Array: 112 | vElTyp := vtyp.Elem() 113 | cnt := v.Len() 114 | 115 | if cnt <= 0 { 116 | return 0, nil 117 | } 118 | 119 | // Special case: [n]byte 120 | if vElTyp.Kind() == reflect.Uint8 { 121 | sTyp := reflect.SliceOf(vElTyp) 122 | slice := reflect.MakeSlice(sTyp, v.Len(), v.Len()) 123 | reflect.Copy(slice, v) 124 | dat := slice.Bytes() 125 | return w.WriteBytesAutoPad(dat) 126 | } 127 | 128 | sizeSent := 0 129 | for i := 0; i < cnt; i++ { 130 | vEl := v.Index(i) 131 | size, err := w.WriteValue(vEl) 132 | if err != nil { 133 | return sizeSent, err 134 | } 135 | sizeSent += size 136 | } 137 | 138 | return sizeSent, nil 139 | 140 | case reflect.Slice: 141 | if v.IsNil() { 142 | return 0, nil 143 | } 144 | 145 | vElTyp := vtyp.Elem() 146 | cnt := v.Len() 147 | 148 | sizeSent := 0 149 | if size, err := w.WriteUint32(uint32(cnt)); err != nil { 150 | return sizeSent, err 151 | } else { 152 | sizeSent += size 153 | } 154 | 155 | // Special case: []byte 156 | if vElTyp.Kind() == reflect.Uint8 { 157 | dat := v.Bytes() 158 | if size, err := w.WriteBytesAutoPad(dat); err != nil { 159 | return sizeSent, err 160 | } else { 161 | sizeSent += size 162 | } 163 | return sizeSent, nil 164 | } 165 | 166 | for i := 0; i < cnt; i++ { 167 | size, err := w.WriteValue(v.Index(i)) 168 | if err != nil { 169 | return sizeSent, err 170 | } 171 | sizeSent += size 172 | } 173 | 174 | return sizeSent, nil 175 | 176 | case reflect.String: 177 | cnt := uint32(v.Len()) 178 | sizeSent := 0 179 | if size, err := w.WriteUint32(cnt); err != nil { 180 | return sizeSent, err 181 | } else { 182 | sizeSent += size 183 | } 184 | 185 | dat := []byte(v.String()) 186 | if size, err := w.WriteBytesAutoPad(dat); err != nil { 187 | return sizeSent, err 188 | } else { 189 | sizeSent += size 190 | } 191 | return sizeSent, nil 192 | 193 | case reflect.Struct: 194 | sizeSent := 0 195 | fieldCount := vtyp.NumField() 196 | for i := 0; i < fieldCount; i++ { 197 | // field := vtyp.Field(i) 198 | fv := v.Field(i) 199 | size, err := w.WriteValue(fv) 200 | if err != nil { 201 | return sizeSent, err 202 | } 203 | sizeSent += size 204 | } 205 | 206 | return sizeSent, nil 207 | 208 | } 209 | 210 | return 0, fmt.Errorf("type not supported: %s", vtyp.Name()) 211 | } 212 | 213 | func (w *Writer) WriteBytesAutoPad(dat []byte) (int, error) { 214 | size := 0 215 | if s, err := w.Write(dat); err != nil { 216 | return size, err 217 | } else { 218 | size += s 219 | } 220 | padLen := Pad(len(dat)) 221 | if padLen > 0 { 222 | pad := make([]byte, padLen) 223 | if s, err := w.Write(pad); err != nil { 224 | return size, err 225 | } else { 226 | size += s 227 | } 228 | } 229 | return size, nil 230 | } 231 | 232 | func (w *Writer) WriteUint32(v uint32) (int, error) { 233 | buff := make([]byte, 4) 234 | binary.BigEndian.PutUint32(buff, v) 235 | return w.Write(buff) 236 | } 237 | 238 | func (w *Writer) Flush() error { 239 | return nil 240 | } 241 | -------------------------------------------------------------------------------- /xdr/writer_test.go: -------------------------------------------------------------------------------- 1 | package xdr 2 | 3 | import ( 4 | "bytes" 5 | // "fmt" 6 | // "math" 7 | "encoding/binary" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestWriter_varSizeBytes(t *testing.T) { 13 | buff := bytes.NewBuffer([]byte{}) 14 | writer := NewWriter(buff) 15 | if size, err := writer.WriteAny([0]byte{}); err != nil { 16 | t.Fatalf("WriteAny(void): %v", err) 17 | } else if size != 0 { 18 | t.Fatalf("WriteAny(void): expects 0 bytes sent. but get %d.", size) 19 | } 20 | } 21 | 22 | func TestWriter_timeDuration(t *testing.T) { 23 | buff := bytes.NewBuffer([]byte{}) 24 | writer := NewWriter(buff) 25 | 26 | dur := time.Hour * 4 27 | if size, err := writer.WriteAny(dur); err != nil { 28 | t.Fatalf("WriteAny(time.Duration): %v", err) 29 | } else if size != 8 { 30 | t.Fatalf("WriteAny(time.Duration): expects 8 bytes sent. but get %d.", size) 31 | } 32 | } 33 | 34 | func TestWriter_Struct(t *testing.T) { 35 | a := &structTestA{ 36 | Iv: 123, 37 | Iv32: 456, 38 | Values: []*structTestB{ 39 | {Value: 789}, 40 | {Value: 10010}, 41 | }, 42 | SingleB: &structTestB{Value: 666}, 43 | PlainB: structTestB{Value: 999}, 44 | Text: "hello", 45 | } 46 | 47 | src := bytes.NewBuffer([]byte{}) 48 | 49 | buff := make([]byte, 8) 50 | binary.BigEndian.PutUint64(buff, a.Iv) 51 | src.Write(buff) 52 | 53 | buff = make([]byte, 4) 54 | binary.BigEndian.PutUint32(buff, a.Iv32) 55 | src.Write(buff) 56 | 57 | buff = make([]byte, 4) 58 | binary.BigEndian.PutUint32(buff, uint32(len(a.Values))) 59 | src.Write(buff) 60 | 61 | for _, v := range a.Values { 62 | buff := make([]byte, 4) 63 | binary.BigEndian.PutUint32(buff, uint32(v.Value)) 64 | src.Write(buff) 65 | } 66 | 67 | buff = make([]byte, 4) 68 | binary.BigEndian.PutUint32(buff, uint32(a.SingleB.Value)) 69 | src.Write(buff) 70 | 71 | // .PlainB 72 | buff = make([]byte, 4) 73 | binary.BigEndian.PutUint32(buff, uint32(a.PlainB.Value)) 74 | src.Write(buff) 75 | 76 | // .Text 77 | buff = make([]byte, 4) 78 | txtb := []byte(a.Text) 79 | binary.BigEndian.PutUint32(buff, uint32(len(txtb))) 80 | src.Write(buff) 81 | src.Write(txtb) 82 | padLen := Pad(len(txtb)) 83 | pad := make([]byte, padLen) 84 | src.Write(pad) 85 | 86 | // --- --- 87 | target := bytes.NewBuffer([]byte{}) 88 | writer := NewWriter(target) 89 | 90 | if size, err := writer.WriteAny(a); err != nil { 91 | t.Fatalf("WriteAny: %v", err) 92 | } else if size != len(src.Bytes()) { 93 | t.Fatalf("expectes size %d but get %d.", len(src.Bytes()), size) 94 | } else { 95 | if !bytes.Equal(src.Bytes(), target.Bytes()) { 96 | t.Fatalf( 97 | "expects: \n%v\nbut gets:\n%v\n", 98 | src.Bytes(), target.Bytes(), 99 | ) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /xdr/xdr.go: -------------------------------------------------------------------------------- 1 | // Xdr packet reader and writer. 2 | package xdr 3 | --------------------------------------------------------------------------------