nfs-rb 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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