nfs-rb 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f440d90e6bd9cfea1f685053754eb92315be08eae3ee3dbe3e836a53aaef8876
4
+ data.tar.gz: d1825c075ce0958424a316b09dd3ea62a0390f63b384f623f614f9e340b0828e
5
+ SHA512:
6
+ metadata.gz: 64b46c26a4c0d7750c3a3fb1c9620d7dba015e867d1a0a010307d0cb6594e05b2aaad53e1f4ca6bda2b51b9e4da2787db6c96855892514a3dbbbe8149aaf7f41
7
+ data.tar.gz: c9f2d5dac95a9628faf95855d1338e44275a431c1802fe3843b413d8ceb2e277a45d85e243f07b844eb2d443f26f11f3d6f135445a3fadd19ae632849407f76b
@@ -0,0 +1,2 @@
1
+ ## 1.0.0
2
+ * Birthday!
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'pry-byebug'
7
+ gem 'rake'
8
+ end
9
+
10
+ group :test do
11
+ gem 'rspec', '~> 3.0'
12
+ end
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Cameron Dutro
4
+
5
+ Adapted from code originally written by Brian Ollenberger, available at https://github.com/bollenberger/nfs. No license accompanied the original code.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
@@ -0,0 +1,73 @@
1
+ ## nfs-rb
2
+
3
+ An NFS v2 server implemented in pure Ruby.
4
+
5
+ Adapted from code written by Brian Ollenberger, available [here](https://github.com/bollenberger/nfs).
6
+
7
+ ## What is NFS?
8
+
9
+ NFS, or "Network File System," is a protocol developed by Sun Microsystems in the 1980s that allows a filesystem to be accessed remotely over a network. MacOS, Windows, and Linux natively support mounting NFS volumes such that they appear alongside normal directories in the operating system's user interface.
10
+
11
+ ## Running the Server
12
+
13
+ First, install the gem by running `gem install nfs-rb`, or add it to your Gemfile.
14
+
15
+ Start the server with the default options by running `nfs-rb`. The various options can be seen by running `nfs-rb --help`:
16
+
17
+ ```
18
+ Usage: nfs-rb [options]
19
+ -d, --dir [DIR] The directory to serve. Defaults to the current directory.
20
+ --host [HOST] The host to bind to. Defaults to 127.0.0.1.
21
+ -p, --port [PORT] The port to bind to. Defaults to 2049.
22
+ -u, --udp Communicate using UDP (default is TCP).
23
+ -v, --verbose Enable verbose logging
24
+ -h, --help Prints this help message
25
+ ```
26
+
27
+ The server can also be started and managed in your Ruby code:
28
+
29
+ ```ruby
30
+ require 'nfs'
31
+
32
+ server = NFS::Server.new(
33
+ dir: '.',
34
+ host: '127.0.0.1',
35
+ port: 2049,
36
+ protocol: :tcp # or :udp
37
+ )
38
+
39
+ # start server, return immediately
40
+ server.start
41
+
42
+ # start server, join to current thread
43
+ server.join
44
+
45
+ # shut server down
46
+ server.shutdown
47
+ ```
48
+
49
+ ## Mounting
50
+
51
+ Once the server is started, mount it using the following command.
52
+
53
+ ```bash
54
+ mount -t nfs -o \
55
+ rsize=8192,wsize=8192,timeo=1,nfsvers=2,proto=tcp,\
56
+ retry=1,port=1234,mountport=1234,hard,intr,nolock \
57
+ 127.0.0.1:/ path/to/mount/location
58
+ ```
59
+
60
+ You should then be able to navigate to /path/to/mount/location and see a listing of all the files available on the networked filesystem.
61
+
62
+ ## Running Tests
63
+
64
+ Run `bundle exec rspec` to run the test suite.
65
+
66
+ ## License
67
+
68
+ Licensed under the MIT license. See LICENSE for details.
69
+
70
+ ## Authors
71
+
72
+ * Cameron C. Dutro: https://github.com/camertron
73
+ * Brian Ollenberger: https://github.com/bollenberger
@@ -0,0 +1,14 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubygems/package_task'
4
+
5
+ require 'nfs'
6
+
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ task default: :spec
10
+
11
+ desc 'Run specs'
12
+ RSpec::Core::RakeTask.new do |t|
13
+ t.pattern = './spec/**/*_spec.rb'
14
+ end
@@ -0,0 +1,52 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require 'nfs'
5
+ require 'logger'
6
+
7
+ options = {}
8
+ default_dir = '.'
9
+ default_host = '127.0.0.1'
10
+ default_port = 2049
11
+ default_protocol = :tcp
12
+ default_log_level = Logger::INFO
13
+
14
+ parser = OptionParser.new do |opts|
15
+ opts.banner = "Usage: nfs-rb [options]"
16
+
17
+ opts.on('-d', '--dir [DIR]', 'The directory to serve. Defaults to the current directory.') do |dir|
18
+ options[:dir] = dir
19
+ end
20
+
21
+ opts.on('-h', '--host [HOST]', "The host to bind to. Defaults to #{default_host}.") do |host|
22
+ options[:host] = host
23
+ end
24
+
25
+ opts.on('-p', '--port [PORT]', "The port to bind to. Defaults to #{default_port}.") do |port|
26
+ options[:port] = port
27
+ end
28
+
29
+ opts.on('-u', '--udp', 'Communicate using UDP (default is TCP).') do
30
+ options[:protocol] = :udp
31
+ end
32
+
33
+ opts.on('-v', '--verbose', 'Enable verbose logging') do |log_level|
34
+ options[:log_level] = Logger::DEBUG
35
+ end
36
+
37
+ opts.on('-h', '--help', 'Prints this help message') do
38
+ puts opts
39
+ exit
40
+ end
41
+ end
42
+
43
+ parser.parse!
44
+
45
+ options[:dir] ||= default_dir
46
+ options[:host] ||= default_host
47
+ options[:port] ||= default_port
48
+ options[:protocol] ||= default_protocol
49
+
50
+ NFS.logger.level = options.delete(:log_level) || default_log_level
51
+ NFS.logger.info("Starting NFS server on #{options[:host]}:#{options[:port]}, serving from '#{options[:dir]}'")
52
+ NFS::Server.new(**options).join
@@ -0,0 +1,17 @@
1
+ module NFS
2
+ class << self
3
+ attr_accessor :logger
4
+ end
5
+
6
+ autoload :DefaultLogger, 'nfs/default_logger'
7
+ autoload :FileProxy, 'nfs/file_proxy'
8
+ autoload :Filehandle, 'nfs/filehandle'
9
+ autoload :Mount, 'nfs/mount'
10
+ autoload :NFS, 'nfs/nfs'
11
+ autoload :Handler, 'nfs/handler'
12
+ autoload :Server, 'nfs/server'
13
+ autoload :SUNRPC, 'nfs/sunrpc'
14
+ autoload :XDR, 'nfs/xdr'
15
+ end
16
+
17
+ NFS.logger = NFS::DefaultLogger.new(STDOUT)
@@ -0,0 +1,13 @@
1
+ require 'logger'
2
+
3
+ module NFS
4
+ class DefaultLogger < Logger
5
+ def initialize(*args)
6
+ super
7
+
8
+ self.formatter = proc do |severity, datetime, _progname, msg|
9
+ "[#{datetime} nfs-rb] #{severity} -- : #{msg}\n"
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,117 @@
1
+ module NFS
2
+ class FileProxy < ::File
3
+ class << self
4
+ def new(*args, &block)
5
+ super(*args, &block)._nfs_setup
6
+ end
7
+
8
+ def open(*args)
9
+ f = super(*args)._nfs_setup
10
+
11
+ if block_given?
12
+ begin
13
+ return yield(f)
14
+ ensure
15
+ f.close
16
+ end
17
+ end
18
+
19
+ f
20
+ end
21
+ end
22
+
23
+ def _nfs_setup
24
+ @absolute_path = File.expand_path(path)
25
+ @looked_up = {}
26
+ self
27
+ end
28
+
29
+ def create(name, mode, uid, gid)
30
+ f = nil
31
+
32
+ begin
33
+ f = self.class.new(_lookup(name), File::RDWR | File::CREAT, mode)
34
+ rescue
35
+ f = self.class.new(_lookup(name), File::RDONLY | File::CREAT, mode)
36
+ end
37
+
38
+ stat = f.lstat
39
+
40
+ @looked_up[[stat.ino, name]] = f
41
+
42
+ [f, stat]
43
+ end
44
+
45
+ def _lookup(name)
46
+ File.expand_path(name, @absolute_path)
47
+ end
48
+
49
+ def lookup(name)
50
+ f = nil
51
+
52
+ begin
53
+ f = self.class.new(_lookup(name), File::RDWR)
54
+ rescue
55
+ f = self.class.new(_lookup(name), File::RDONLY)
56
+ end
57
+
58
+ stat = f.lstat
59
+ key = [stat.ino, name]
60
+
61
+ if @looked_up.include?(key)
62
+ @looked_up[key]
63
+ else
64
+ @looked_up[key] = f
65
+ end
66
+ end
67
+
68
+ def delete(name)
69
+ File.delete(_lookup(name))
70
+ end
71
+
72
+ def rename(from_name, to_dir, to_name)
73
+ File.rename(_lookup(from_name), to_dir._lookup(to_name))
74
+ end
75
+
76
+ def link(dir, name)
77
+ File.link(@absolute_path, dir._lookup(name))
78
+ end
79
+
80
+ def symlink(name, to_name)
81
+ File.symlink(to_name, _lookup(name))
82
+ end
83
+
84
+ def readlink
85
+ File.readlink(@absolute_path)
86
+ end
87
+
88
+ def mkdir(name, mode, uid, gid)
89
+ path = _lookup(name)
90
+ Dir.mkdir(path, mode)
91
+
92
+ f = self.class.new(path)
93
+ #f.chown(uid, gid)
94
+
95
+ stat = f.lstat
96
+ @looked_up[[stat.ino, name]] = f
97
+
98
+ [f, stat]
99
+ end
100
+
101
+ def rmdir(name)
102
+ Dir.delete(_lookup(name))
103
+ end
104
+
105
+ def unlink(name)
106
+ File.unlink(_lookup(name))
107
+ end
108
+
109
+ def entries
110
+ Dir.entries(@absolute_path)
111
+ end
112
+
113
+ def utime(atime, mtime)
114
+ File.utime(atime, mtime, @absolute_path)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,24 @@
1
+ module NFS
2
+ class Filehandle < ::String
3
+ def initialize
4
+ super("\0".b * NFS::FHSIZE)
5
+ end
6
+
7
+ def increment!
8
+ size.times do |i|
9
+ self[i] += 1
10
+ return self if self[i] != 0
11
+ end
12
+
13
+ self
14
+ end
15
+
16
+ def [](idx)
17
+ getbyte(idx)
18
+ end
19
+
20
+ def []=(idx, newval)
21
+ setbyte(idx, newval)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,570 @@
1
+ module NFS
2
+ class Handler
3
+ def initialize(root = nil, fsid = 0)
4
+ @mount_prog = Mount::MOUNTPROG.dup
5
+ @mount_vers = Mount::MOUNTVERS
6
+ @nfs_prog = NFS::NFS_PROGRAM.dup
7
+ @nfs_vers = NFS::NFS_VERSION
8
+
9
+ @exports = {}
10
+ @fh_table = {}
11
+ @file_objects = {}
12
+ @next_fh = Filehandle.new
13
+
14
+ @fsid = fsid
15
+
16
+ if !root.nil?
17
+ export('/', root)
18
+ end
19
+
20
+ define_mount_procedures
21
+ define_nfs_procedures
22
+
23
+ instance_eval(&block) if block_given?
24
+ end
25
+
26
+ def programs
27
+ [@mount_prog, @nfs_prog]
28
+ end
29
+
30
+ def export(path, file)
31
+ @exports[path] = add_filehandle(file)
32
+ end
33
+
34
+ def add_filehandle(file)
35
+ if @file_objects.include?(file)
36
+ @file_objects[file]
37
+ else
38
+ @next_fh.dup.tap do |fh|
39
+ @fh_table[fh] = file
40
+ @file_objects[file] = fh
41
+ @next_fh.increment!
42
+ end
43
+ end
44
+ end
45
+
46
+ def handle_errors
47
+ begin
48
+ yield
49
+ rescue Errno::EPERM
50
+ { :_discriminant => :NFSERR_PERM }
51
+ rescue Errno::ENOENT
52
+ { _discriminant: :NFSERR_NOENT }
53
+ rescue Errno::EIO
54
+ { _discriminant: :NFSERR_IO }
55
+ rescue Errno::ENXIO
56
+ { _discriminant: :NFSERR_NXIO }
57
+ rescue Errno::EACCES
58
+ { _discriminant: :NFSERR_ACCES }
59
+ rescue Errno::EEXIST
60
+ { _discriminant: :NFSERR_EXIST }
61
+ rescue Errno::ENODEV
62
+ { _discriminant: :NFSERR_NODEV }
63
+ rescue Errno::ENOTDIR
64
+ { _discriminant: :NFSERR_NOTDIR }
65
+ rescue Errno::EISDIR
66
+ { _discriminant: :NFSERR_ISDIR }
67
+ rescue Errno::EINVAL
68
+ { _discriminant: :NFSERR_INVAL }
69
+ rescue Errno::EFBIG
70
+ { _discriminant: :NFSERR_FBIG }
71
+ rescue Errno::ENOSPC
72
+ { _discriminant: :NFSERR_NOSPC }
73
+ rescue Errno::EROFS
74
+ { _discriminant: :NFSERR_ROFS }
75
+ rescue Errno::ENAMETOOLONG
76
+ { _discriminant: :NFSERR_NAMETOOLONG }
77
+ rescue Errno::ENOTEMPTY
78
+ { _discriminant: :NFSERR_NOTEMPTY }
79
+ rescue Errno::EDQUOT
80
+ { _discriminant: :NFSERR_DQUOT }
81
+ rescue Errno::ESTALE
82
+ { _discriminant: :NFSERR_STALE }
83
+ rescue => e
84
+ # LOG
85
+ ::NFS.logger.error(e.message)
86
+ ::NFS.logger.error(e.backtrace.join("\n"))
87
+ { _discriminant: :NFSERR_IO }
88
+ end
89
+ end
90
+
91
+ def define_mount_procedures
92
+ @mount_prog.on_call(@mount_vers, :MNT) do |arg, auth, verf|
93
+ if @exports.include?(arg)
94
+ ::NFS.logger.info("MNT #{arg}")
95
+
96
+ {
97
+ _discriminant: :NFS_OK,
98
+ fhs_fhandle: {
99
+ data: @exports[arg]
100
+ }
101
+ }
102
+ else
103
+ { _discriminant: :NFSERR_ACCES }
104
+ end
105
+ end
106
+
107
+ @mount_prog.on_call(@mount_vers, :DUMP) do |arg, auth, verf|
108
+ ::NFS.logger.info('DUMP')
109
+ nil
110
+ end
111
+
112
+ @mount_prog.on_call(@mount_vers, :UMNT) do |arg, auth, verf|
113
+ ::NFS.logger.info("UMNT #{arg}")
114
+ end
115
+
116
+ @mount_prog.on_call(@mount_vers, :UMNTALL) do |arg, auth, verf|
117
+ ::NFS.logger.info("UMNTALL #{arg}")
118
+ end
119
+
120
+ export = proc do |arg, auth, verf|
121
+ ::NFS.logger.info('EXPORT')
122
+ result = nil
123
+
124
+ @exports.each_key do |name|
125
+ result = {
126
+ ex_dir: name,
127
+ ex_groups: nil,
128
+ ex_next: result
129
+ }
130
+ end
131
+
132
+ result
133
+ end
134
+
135
+ @mount_prog.on_call(@mount_vers, :EXPORT, &export)
136
+ @mount_prog.on_call(@mount_vers, :EXPORTALL, &export)
137
+ end
138
+
139
+ # Convert Ruby Stat object to an NFS fattr
140
+ def convert_attrs(attrs)
141
+ type = :NFNON
142
+ mode = attrs.mode
143
+
144
+ if attrs.file?
145
+ type = :NFREG
146
+ mode |= NFS::MODE_REG
147
+ elsif attrs.directory?
148
+ type = :NFDIR
149
+ mode |= NFS::MODE_DIR
150
+ elsif attrs.blockdev?
151
+ type = :NFBLK
152
+ mode |= NFS::MODE_BLK
153
+ elsif attrs.chardev?
154
+ type = :NFCHR
155
+ mode |= NFS::MODE_CHR
156
+ elsif attrs.symlink?
157
+ type = :NFLNK
158
+ mode |= NFS::MODE_LNK
159
+ elsif attrs.socket?
160
+ type = :NFSOCK
161
+ mode |= NFS::MODE_SOCK
162
+ end
163
+
164
+ {
165
+ type: type,
166
+ mode: mode,
167
+ nlink: attrs.nlink,
168
+ uid: attrs.uid,
169
+ gid: attrs.gid,
170
+ size: attrs.size,
171
+ blocksize: attrs.blksize,
172
+ rdev: attrs.rdev,
173
+ blocks: attrs.blocks,
174
+ fsid: @fsid,
175
+ fileid: attrs.ino,
176
+ atime: {
177
+ seconds: attrs.atime.tv_sec,
178
+ useconds: attrs.atime.tv_usec
179
+ },
180
+ mtime: {
181
+ seconds: attrs.mtime.tv_sec,
182
+ useconds: attrs.mtime.tv_usec
183
+ },
184
+ ctime: {
185
+ seconds: attrs.ctime.tv_sec,
186
+ useconds: attrs.ctime.tv_usec
187
+ }
188
+ }
189
+ end
190
+
191
+ def define_nfs_procedures
192
+ @nfs_prog.on_call(@nfs_vers, :GETATTR) do |arg, auth, verf|
193
+ handle_errors do
194
+ f = @fh_table[arg[:data]]
195
+ attrs = f.lstat
196
+
197
+ ::NFS.logger.info("GETATTR #{f.path}")
198
+
199
+ {
200
+ _discriminant: :NFS_OK,
201
+ attributes: convert_attrs(attrs)
202
+ }
203
+ end
204
+ end
205
+
206
+ @nfs_prog.on_call(@nfs_vers, :SETATTR) do |arg, auth, verf|
207
+ changes = []
208
+
209
+ handle_errors do
210
+ f = @fh_table[arg[:file][:data]]
211
+ attrs = convert_attrs(f.lstat)
212
+
213
+ # Get -1 represented as an unsigned integer. The sattr fields
214
+ # are -1 to represent that they should not be changed.
215
+ neg_one = 4294967295
216
+
217
+ # Start with the mode. Setattr won't change the type of a file
218
+ # and apparently some NFS clients don't set the type, so mask
219
+ # that part out to keep what we have already.
220
+ if arg[:attributes][:mode] != neg_one
221
+ attrs[:mode] &= ~07777
222
+ attrs[:mode] |= 07777 & arg[:attributes][:mode]
223
+
224
+ new_mode = arg[:attributes][:mode] & 07777
225
+ changes << "mode: #{new_mode.to_s(8).rjust(5, '0')}"
226
+ f.chmod(new_mode)
227
+ end
228
+
229
+ # Next do the UID and GID
230
+ if arg[:attributes][:uid] != neg_one or
231
+ arg[:attributes][:gid] != neg_one
232
+
233
+ uid = arg[:attributes][:uid]
234
+ gid = arg[:attributes][:gid]
235
+
236
+ if uid == neg_one
237
+ uid = attrs[:uid]
238
+ end
239
+
240
+ if gid == neg_one
241
+ gid = attrs[:gid]
242
+ end
243
+
244
+ attrs[:uid] = uid
245
+ attrs[:gid] = gid
246
+
247
+ changes << "uid: #{uid}"
248
+ changes << "gid: #{gid}"
249
+
250
+ f.chown(uid, gid)
251
+ end
252
+
253
+ # Set size (truncate)
254
+ if arg[:attributes][:size] != neg_one
255
+ attrs[:size] = arg[:attributes][:size]
256
+ changes << "size: #{attrs[:size]}"
257
+ f.truncate(arg[:attributes][:size])
258
+ end
259
+
260
+ # Set time
261
+ if arg[:attributes][:atime][:seconds] != neg_one or
262
+ arg[:attributes][:mtime][:seconds] != neg_one
263
+
264
+ atime = arg[:attributes][:atime]
265
+ mtime = arg[:attributes][:mtime]
266
+
267
+ if atime[:seconds] == neg_one
268
+ atime = attrs[:atime]
269
+ end
270
+
271
+ if mtime[:seconds] == neg_one
272
+ mtime = attrs[:mtime]
273
+ end
274
+
275
+ attrs[:atime] = atime
276
+ attrs[:mtime] = mtime
277
+
278
+ atime = Time.at(atime[:seconds], atime[:useconds])
279
+ mtime = Time.at(mtime[:seconds], mtime[:useconds])
280
+
281
+ changes << "atime: #{atime}"
282
+ changes << "mtime: #{mtime}"
283
+
284
+ f.utime(atime, mtime)
285
+ end
286
+
287
+ ::NFS.logger.info("SETATTR #{f} #{changes.join(', ')}")
288
+
289
+ {
290
+ _discriminant: :NFS_OK,
291
+ attributes: attrs
292
+ }
293
+ end
294
+ end
295
+
296
+ @nfs_prog.on_call(@nfs_vers, :ROOT) do |arg, auth, verf|
297
+ ::NFS.logger.info('ROOT')
298
+ # obsolete
299
+ end
300
+
301
+ @nfs_prog.on_call(@nfs_vers, :LOOKUP) do |arg, auth, verf|
302
+ handle_errors do
303
+ f = @fh_table[arg[:dir][:data]].lookup(arg[:name])
304
+ ::NFS.logger.info("LOOKUP #{f.path}")
305
+ fh = add_filehandle(f)
306
+ attrs = f.lstat
307
+
308
+ result = {
309
+ _discriminant: :NFS_OK,
310
+ diropres: {
311
+ file: {
312
+ data: fh
313
+ },
314
+ attributes: convert_attrs(attrs)
315
+ }
316
+ }
317
+
318
+ result
319
+ end
320
+ end
321
+
322
+ @nfs_prog.on_call(@nfs_vers, :READLINK) do |arg, auth, verf|
323
+ handle_errors do
324
+ f = @fh_table[arg[:data]]
325
+ ::NFS.logger.info("READLINK #{f.path}")
326
+ result = f.readlink
327
+
328
+ {
329
+ _discriminant: :NFS_OK,
330
+ data: result
331
+ }
332
+ end
333
+ end
334
+
335
+ @nfs_prog.on_call(@nfs_vers, :READ) do |arg, auth, verf|
336
+ handle_errors do
337
+ f = @fh_table[arg[:file][:data]]
338
+ ::NFS.logger.info("READ #{f.path}")
339
+ attrs = f.lstat
340
+ f.pos = arg[:offset]
341
+ result = f.read(arg[:count])
342
+
343
+ {
344
+ _discriminant: :NFS_OK,
345
+ reply: {
346
+ attributes: convert_attrs(attrs),
347
+ data: result
348
+ }
349
+ }
350
+ end
351
+ end
352
+
353
+ @nfs_prog.on_call(@nfs_vers, :WRITECACHE) do |arg, auth, verf|
354
+ ::NFS.logger.info('WRITECACHE')
355
+ end
356
+
357
+ @nfs_prog.on_call(@nfs_vers, :WRITE) do |arg, auth, verf|
358
+
359
+ handle_errors do
360
+ f = @fh_table[arg[:file][:data]]
361
+ ::NFS.logger.info("WRITE #{f.path}")
362
+ f.pos = arg[:offset]
363
+ f.write(arg[:data])
364
+ f.flush
365
+ attrs = f.lstat
366
+
367
+ {
368
+ _discriminant: :NFS_OK,
369
+ attributes: convert_attrs(attrs)
370
+ }
371
+ end
372
+ end
373
+
374
+ @nfs_prog.on_call(@nfs_vers, :CREATE) do |arg, auth, verf|
375
+ handle_errors do
376
+ dir = @fh_table[arg[:where][:dir][:data]]
377
+ name = arg[:where][:name]
378
+ ::NFS.logger.info("CREATE #{name}")
379
+
380
+ f, attrs = dir.create(
381
+ name,
382
+ arg[:attributes][:mode], arg[:attributes][:uid],
383
+ arg[:attributes][:gid]
384
+ )
385
+
386
+ fh = add_filehandle(f)
387
+
388
+ {
389
+ _discriminant: :NFS_OK,
390
+ diropres: {
391
+ file: {
392
+ data: fh
393
+ },
394
+ attributes: convert_attrs(attrs)
395
+ }
396
+ }
397
+ end
398
+ end
399
+
400
+ @nfs_prog.on_call(@nfs_vers, :REMOVE) do |arg, auth, verf|
401
+ (handle_errors do
402
+ dir = @fh_table[arg[:dir][:data]]
403
+ name = arg[:name]
404
+ ::NFS.logger.info("REMOVE #{name}")
405
+ dir.unlink(name)
406
+
407
+ { _discriminant: :NFS_OK }
408
+ end)[:_discriminant]
409
+ end
410
+
411
+ @nfs_prog.on_call(@nfs_vers, :RENAME) do |arg, auth, verf|
412
+ (handle_errors do
413
+ from_dir = @fh_table[arg[:from][:dir][:data]]
414
+ from_name = arg[:from][:name]
415
+ to_dir = @fh_table[arg[:to][:dir][:data]]
416
+ to_name = arg[:to][:name]
417
+
418
+ ::NFS.logger.info(
419
+ "RENAME #{File.join(from_dir.path, from_name)} -> #{File.join(to_dir.path, to_name)}"
420
+ )
421
+
422
+ from_dir.rename(from_name, to_dir, to_name)
423
+
424
+ { _discriminant: :NFS_OK }
425
+ end)[:_discriminant]
426
+ end
427
+
428
+ @nfs_prog.on_call(@nfs_vers, :LINK) do |arg, auth, verf|
429
+ (handle_errors do
430
+ from = @fh_table[arg[:from][:data]]
431
+ to_dir = @fh_table[arg[:to][:dir][:data]]
432
+ to_name = arg[:to][:name]
433
+
434
+ ::NFS.logger.info("LINK #{from.path} -> #{File.join(to_dir.path, to_name)}")
435
+
436
+ from.link(to_dir, to_name)
437
+
438
+ { _discriminant: :NFS_OK }
439
+ end)[:_discriminant]
440
+ end
441
+
442
+ @nfs_prog.on_call(@nfs_vers, :SYMLINK) do |arg, auth, verf|
443
+ (handle_errors do
444
+ dir = @fh_table[arg[:from][:dir][:data]]
445
+ name = arg[:from][:name]
446
+ to_name = arg[:to]
447
+ attrs = arg[:attributes]
448
+
449
+ ::NFS.logger.info("SYMLINK #{File.join(dir.path, name)} -> #{to_name}")
450
+
451
+ dir.symlink(name, to_name)
452
+
453
+ { _discriminant: :NFS_OK }
454
+ end)[:_discriminant]
455
+ end
456
+
457
+ @nfs_prog.on_call(@nfs_vers, :MKDIR) do |arg, auth, verf|
458
+ handle_errors do
459
+ dir = @fh_table[arg[:where][:dir][:data]]
460
+ name = arg[:where][:name]
461
+
462
+ ::NFS.logger.info("MKDIR #{name}")
463
+
464
+ f, attrs = dir.mkdir(name, arg[:attributes][:mode],
465
+ arg[:attributes][:uid], arg[:attributes][:gid])
466
+
467
+ fh = add_filehandle(f)
468
+
469
+ {
470
+ _discriminant: :NFS_OK,
471
+ diropres: {
472
+ file: {
473
+ data: fh
474
+ },
475
+ attributes: convert_attrs(attrs)
476
+ }
477
+ }
478
+ end
479
+ end
480
+
481
+ @nfs_prog.on_call(@nfs_vers, :RMDIR) do |arg, auth, verf|
482
+ (handle_errors do
483
+ dir = @fh_table[arg[:dir][:data]]
484
+ name = arg[:name]
485
+ ::NFS.logger.info("RMDIR #{name}")
486
+ dir.rmdir(name)
487
+
488
+ { _discriminant: :NFS_OK}
489
+ end)[:_discriminant]
490
+ end
491
+
492
+ @nfs_prog.on_call(@nfs_vers, :READDIR) do |arg, auth, verf|
493
+ handle_errors do
494
+ dir = @fh_table[arg[:dir][:data]]
495
+ ::NFS.logger.info("READDIR #{dir.path}")
496
+
497
+ cookie = arg[:cookie]
498
+ count = arg[:count]
499
+
500
+ need_bytes = 16 + 12
501
+
502
+ entries = dir.entries
503
+
504
+ result_entries = nil
505
+ last_entry = nil
506
+
507
+ while cookie < entries.size && need_bytes < count
508
+ need_bytes += NFS::Filename.encode(entries[cookie]).size
509
+
510
+ next_entry = {
511
+ fileid: 1,
512
+ name: entries[cookie],
513
+ cookie: cookie
514
+ }
515
+
516
+ if not last_entry.nil?
517
+ last_entry[:nextentry] = next_entry
518
+ last_entry = next_entry
519
+ end
520
+
521
+ if result_entries.nil?
522
+ result_entries = next_entry
523
+ last_entry = next_entry
524
+ end
525
+
526
+ cookie += 1
527
+ need_bytes += 16
528
+ end
529
+
530
+ eof = :TRUE
531
+
532
+ if need_bytes > count
533
+ eof = :FALSE
534
+ end
535
+
536
+ if not last_entry.nil?
537
+ last_entry[:nextentry] = nil
538
+ end
539
+
540
+ {
541
+ _discriminant: :NFS_OK,
542
+ reply: {
543
+ entries: result_entries,
544
+ eof: eof
545
+ }
546
+ }
547
+ end
548
+ end
549
+
550
+ @nfs_prog.on_call(@nfs_vers, :STATFS) do |arg, auth, verf|
551
+ ::NFS.logger.info('STATFS')
552
+
553
+ handle_errors do
554
+ {
555
+ _discriminant: :NFS_OK,
556
+ reply: {
557
+ tsize: 1024,
558
+ bsize: 1024,
559
+ blocks: 100,
560
+ bfree: 100,
561
+ bavail: 100
562
+ }
563
+ }
564
+ end
565
+ end
566
+ end
567
+
568
+ attr_reader :root
569
+ end
570
+ end