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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +12 -0
- data/LICENSE +23 -0
- data/README.md +73 -0
- data/Rakefile +14 -0
- data/bin/nfs-rb +52 -0
- data/lib/nfs.rb +17 -0
- data/lib/nfs/default_logger.rb +13 -0
- data/lib/nfs/file_proxy.rb +117 -0
- data/lib/nfs/filehandle.rb +24 -0
- data/lib/nfs/handler.rb +570 -0
- data/lib/nfs/mount.rb +94 -0
- data/lib/nfs/nfs.rb +259 -0
- data/lib/nfs/server.rb +39 -0
- data/lib/nfs/sunrpc.rb +365 -0
- data/lib/nfs/sunrpc/client.rb +77 -0
- data/lib/nfs/sunrpc/procedure.rb +56 -0
- data/lib/nfs/sunrpc/program.rb +58 -0
- data/lib/nfs/sunrpc/server.rb +119 -0
- data/lib/nfs/sunrpc/tcp_server.rb +41 -0
- data/lib/nfs/sunrpc/udp_server.rb +42 -0
- data/lib/nfs/sunrpc/version.rb +55 -0
- data/lib/nfs/version.rb +3 -0
- data/lib/nfs/xdr.rb +300 -0
- data/nfs-rb.gemspec +17 -0
- data/spec/nfs_spec.rb +29 -0
- data/spec/orig_dir/file1.txt +1 -0
- data/spec/orig_dir/file2.txt +3 -0
- data/spec/run_server.rb +10 -0
- data/spec/spec_helper.rb +44 -0
- metadata +74 -0
data/lib/nfs/version.rb
ADDED
data/lib/nfs/xdr.rb
ADDED
@@ -0,0 +1,300 @@
|
|
1
|
+
# Ruby XDR. XDR codec for Ruby. Based on RFC 4506.
|
2
|
+
|
3
|
+
module NFS
|
4
|
+
module XDR
|
5
|
+
class Void
|
6
|
+
def encode(value)
|
7
|
+
''
|
8
|
+
end
|
9
|
+
|
10
|
+
def decode(string)
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class SignedInteger
|
16
|
+
def encode(value)
|
17
|
+
[value].pack('N')
|
18
|
+
end
|
19
|
+
|
20
|
+
def decode(string)
|
21
|
+
string.slice!(0..3).unpack('N').pack('I').unpack('i')[0]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class UnsignedInteger
|
26
|
+
def encode(value)
|
27
|
+
[value].pack('N')
|
28
|
+
end
|
29
|
+
|
30
|
+
def decode(string)
|
31
|
+
string.slice!(0..3).unpack('N')[0]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Enumeration < SignedInteger
|
36
|
+
def initialize(&block)
|
37
|
+
@values = {}
|
38
|
+
@names = {}
|
39
|
+
|
40
|
+
if block_given?
|
41
|
+
instance_eval(&block)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def name(v_name, value)
|
46
|
+
@values[v_name] = value
|
47
|
+
@names[value] = v_name
|
48
|
+
end
|
49
|
+
|
50
|
+
def encode(name)
|
51
|
+
super(@values[name])
|
52
|
+
end
|
53
|
+
|
54
|
+
def decode(string)
|
55
|
+
@names[super(string)]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Boolean < Enumeration
|
60
|
+
def initialize
|
61
|
+
super
|
62
|
+
|
63
|
+
name :TRUE, 1
|
64
|
+
name :FALSE, 0
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class FloatNum
|
69
|
+
def encode(value)
|
70
|
+
[value].pack('g')
|
71
|
+
end
|
72
|
+
|
73
|
+
def decode(string)
|
74
|
+
string.slice!(0..3).unpack('g')[0]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class DoubleNum
|
79
|
+
def encode(value)
|
80
|
+
[value].pack('G')
|
81
|
+
end
|
82
|
+
|
83
|
+
def decode(string)
|
84
|
+
string.slice!(0..7).unpack('G')[0]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.pad(n, align)
|
89
|
+
r = n % align
|
90
|
+
r = align if r == 0
|
91
|
+
n + align - r
|
92
|
+
end
|
93
|
+
|
94
|
+
class FixedString
|
95
|
+
def initialize(n)
|
96
|
+
@n = n
|
97
|
+
end
|
98
|
+
|
99
|
+
def encode(value)
|
100
|
+
[value.to_s].pack('a' + XDR.pad(@n, 4).to_s)
|
101
|
+
end
|
102
|
+
|
103
|
+
def decode(string)
|
104
|
+
superstring = string.slice!(0, XDR.pad(@n, 4))
|
105
|
+
superstring.nil? ? '' : superstring[0, @n]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class DynamicString
|
110
|
+
def initialize(n = nil)
|
111
|
+
@n = n
|
112
|
+
@length = UnsignedInteger.new
|
113
|
+
end
|
114
|
+
|
115
|
+
def encode(value)
|
116
|
+
value = value.to_s
|
117
|
+
n = value.size
|
118
|
+
n = @n if !@n.nil? && @n < n
|
119
|
+
@length.encode(n) + [value].pack('a' + XDR::pad(n, 4).to_s)
|
120
|
+
end
|
121
|
+
|
122
|
+
def decode(string)
|
123
|
+
length = @length.decode(string)
|
124
|
+
superstring = string.slice!(0, XDR.pad(length, 4))
|
125
|
+
superstring.nil? ? '' : superstring[0, length]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class FixedOpaque < FixedString
|
130
|
+
end
|
131
|
+
|
132
|
+
class Opaque < DynamicString
|
133
|
+
end
|
134
|
+
|
135
|
+
class FixedArray
|
136
|
+
def initialize(type, n)
|
137
|
+
@type, @n = type, n
|
138
|
+
end
|
139
|
+
|
140
|
+
def encode(value)
|
141
|
+
n.times do |i|
|
142
|
+
@type.encode(value[i])
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def decode(string)
|
147
|
+
Array.new(n) do
|
148
|
+
@type.decode(string)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class DynamicArray
|
154
|
+
def initialize(type, n)
|
155
|
+
@type, @n = type, n
|
156
|
+
@length = UnsignedInteger.new
|
157
|
+
end
|
158
|
+
|
159
|
+
def encode(value)
|
160
|
+
n = value.size
|
161
|
+
|
162
|
+
if !@n.nil? && @n < n
|
163
|
+
n = @n
|
164
|
+
end
|
165
|
+
|
166
|
+
result = @length.encode(n)
|
167
|
+
|
168
|
+
n.times do |i|
|
169
|
+
result << @type.encode(value[i])
|
170
|
+
end
|
171
|
+
|
172
|
+
result
|
173
|
+
end
|
174
|
+
|
175
|
+
def decode(string)
|
176
|
+
length = @length.decode(string)
|
177
|
+
|
178
|
+
Array.new(length) do
|
179
|
+
@type.decode(string)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class Optional < DynamicArray
|
185
|
+
def initialize(type)
|
186
|
+
super(type, 1)
|
187
|
+
end
|
188
|
+
|
189
|
+
def encode(value)
|
190
|
+
if value.nil?
|
191
|
+
super([])
|
192
|
+
else
|
193
|
+
super([value])
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def decode(string)
|
198
|
+
result = super(string)
|
199
|
+
|
200
|
+
if result.empty?
|
201
|
+
nil
|
202
|
+
else
|
203
|
+
result[0]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
class Structure
|
209
|
+
def initialize(&block)
|
210
|
+
@components = []
|
211
|
+
@names = []
|
212
|
+
instance_eval(&block) if block_given?
|
213
|
+
end
|
214
|
+
|
215
|
+
def component(name, type)
|
216
|
+
@components << [name, type]
|
217
|
+
@names << name
|
218
|
+
end
|
219
|
+
|
220
|
+
def encode(value)
|
221
|
+
''.tap do |result|
|
222
|
+
@components.each do |component|
|
223
|
+
unless value.include?(component[0])
|
224
|
+
raise 'missing structure component ' + component[0].to_s
|
225
|
+
end
|
226
|
+
|
227
|
+
result << component[1].encode(value[component[0]])
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def decode(string)
|
233
|
+
@components.each_with_object({}) do |component, result|
|
234
|
+
result[component[0]] = component[1].decode(string)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
# Each arm of the union is represented as a struct
|
240
|
+
class Union
|
241
|
+
def initialize(disc_type, &block)
|
242
|
+
@disc_type = disc_type
|
243
|
+
@arms = {}
|
244
|
+
@default_arm = nil
|
245
|
+
instance_eval(&block) if block_given?
|
246
|
+
end
|
247
|
+
|
248
|
+
# Add an arm
|
249
|
+
def arm(disc_value, struct = nil, &block)
|
250
|
+
if block_given?
|
251
|
+
struct = Structure.new(&block)
|
252
|
+
end
|
253
|
+
|
254
|
+
@arms[disc_value] = struct
|
255
|
+
end
|
256
|
+
|
257
|
+
# Set the default arm
|
258
|
+
def default(struct = nil, &block)
|
259
|
+
if block_given?
|
260
|
+
struct = Structure.new(&block)
|
261
|
+
end
|
262
|
+
|
263
|
+
@default_arm = struct
|
264
|
+
end
|
265
|
+
|
266
|
+
def encode(struct)
|
267
|
+
disc = struct[:_discriminant]
|
268
|
+
arm = @default_arm
|
269
|
+
arm = @arms[disc] if @arms.include?(disc)
|
270
|
+
result = @disc_type.encode(disc)
|
271
|
+
|
272
|
+
unless arm.nil?
|
273
|
+
result << arm.encode(struct)
|
274
|
+
end
|
275
|
+
|
276
|
+
result
|
277
|
+
end
|
278
|
+
|
279
|
+
def decode(string)
|
280
|
+
disc = @disc_type.decode(string)
|
281
|
+
arm = @default_arm
|
282
|
+
|
283
|
+
if @arms.include?(disc)
|
284
|
+
arm = @arms[disc]
|
285
|
+
end
|
286
|
+
|
287
|
+
result = nil
|
288
|
+
|
289
|
+
if arm.nil?
|
290
|
+
result = {}
|
291
|
+
else
|
292
|
+
result = arm.decode(string)
|
293
|
+
end
|
294
|
+
|
295
|
+
result[:_discriminant] = disc
|
296
|
+
result
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
data/nfs-rb.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$:.unshift File.join(File.dirname(__FILE__), 'lib')
|
2
|
+
require 'nfs/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'nfs-rb'
|
6
|
+
s.version = ::NFS::VERSION
|
7
|
+
s.authors = ['Cameron Dutro', 'Brian Ollenberger']
|
8
|
+
s.email = ['camertron@gmail.com']
|
9
|
+
s.homepage = 'http://github.com/camertron/nfs'
|
10
|
+
s.description = s.summary = 'An NFS v2 server implemented in pure Ruby.'
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.require_path = 'lib'
|
13
|
+
|
14
|
+
s.executables << 'nfs-rb'
|
15
|
+
|
16
|
+
s.files = Dir['{lib,spec}/**/*', 'Gemfile', 'LICENSE', 'CHANGELOG.md', 'README.md', 'Rakefile', 'nfs-rb.gemspec']
|
17
|
+
end
|
data/spec/nfs_spec.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'NFS' do
|
4
|
+
it 'supports directory listing' do
|
5
|
+
files = Dir.chdir($dest_dir) do
|
6
|
+
Dir.glob("*.*")
|
7
|
+
end
|
8
|
+
|
9
|
+
expect(files.sort).to eq(['file1.txt', 'file2.txt'].sort)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'supports reading files' do
|
13
|
+
%w(file1.txt file2.txt).each do |file|
|
14
|
+
orig_contents = File.read(File.join($orig_dir, file))
|
15
|
+
dest_contents = File.read(File.join($dest_dir, file))
|
16
|
+
expect(dest_contents).to eq(orig_contents)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'supports file stats' do
|
21
|
+
%w(file1.txt file2.txt).each do |file|
|
22
|
+
orig_stat = File.lstat(File.join($orig_dir, 'file1.txt'))
|
23
|
+
dest_stat = File.lstat(File.join($dest_dir, 'file1.txt'))
|
24
|
+
expect(orig_stat.size).to eq(dest_stat.size)
|
25
|
+
expect(orig_stat.mtime.to_i).to eq(dest_stat.mtime.to_i)
|
26
|
+
expect(orig_stat.ctime.to_i).to eq(dest_stat.ctime.to_i)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Hello world!
|
data/spec/run_server.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
$:.push(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
require 'rspec'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'nfs'
|
8
|
+
|
9
|
+
RSpec.configure do |config|
|
10
|
+
config.before(:suite) do
|
11
|
+
$orig_dir = File.expand_path(File.join('.', 'orig_dir'), __dir__)
|
12
|
+
$dest_dir = File.expand_path(File.join('.', 'dest_dir'), __dir__)
|
13
|
+
|
14
|
+
FileUtils.mkdir_p($dest_dir)
|
15
|
+
|
16
|
+
$server_pid = Process.spawn(
|
17
|
+
"bundle exec ruby #{File.join('spec', 'run_server.rb')}"
|
18
|
+
)
|
19
|
+
|
20
|
+
system(
|
21
|
+
"mount -t nfs -o "\
|
22
|
+
'rsize=8192,wsize=8192,timeo=1,nfsvers=2,proto=tcp,'\
|
23
|
+
"port=1234,mountport=1234,"\
|
24
|
+
"hard,intr,nolock 127.0.0.1:/ "\
|
25
|
+
"#{$dest_dir}"
|
26
|
+
)
|
27
|
+
|
28
|
+
if $?.exitstatus != 0
|
29
|
+
fail "Unable to mount NFS volume at #{$dest_dir}"
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
config.after(:suite) do
|
35
|
+
system("umount #{$dest_dir}")
|
36
|
+
|
37
|
+
if $?.exitstatus != 0
|
38
|
+
fail "Unable to unmount NFS volume at #{$dest_dir}"
|
39
|
+
exit 2
|
40
|
+
end
|
41
|
+
|
42
|
+
Process.kill('HUP', $server_pid)
|
43
|
+
end
|
44
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nfs-rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Cameron Dutro
|
8
|
+
- Brian Ollenberger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2020-08-16 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: An NFS v2 server implemented in pure Ruby.
|
15
|
+
email:
|
16
|
+
- camertron@gmail.com
|
17
|
+
executables:
|
18
|
+
- nfs-rb
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- CHANGELOG.md
|
23
|
+
- Gemfile
|
24
|
+
- LICENSE
|
25
|
+
- README.md
|
26
|
+
- Rakefile
|
27
|
+
- bin/nfs-rb
|
28
|
+
- lib/nfs.rb
|
29
|
+
- lib/nfs/default_logger.rb
|
30
|
+
- lib/nfs/file_proxy.rb
|
31
|
+
- lib/nfs/filehandle.rb
|
32
|
+
- lib/nfs/handler.rb
|
33
|
+
- lib/nfs/mount.rb
|
34
|
+
- lib/nfs/nfs.rb
|
35
|
+
- lib/nfs/server.rb
|
36
|
+
- lib/nfs/sunrpc.rb
|
37
|
+
- lib/nfs/sunrpc/client.rb
|
38
|
+
- lib/nfs/sunrpc/procedure.rb
|
39
|
+
- lib/nfs/sunrpc/program.rb
|
40
|
+
- lib/nfs/sunrpc/server.rb
|
41
|
+
- lib/nfs/sunrpc/tcp_server.rb
|
42
|
+
- lib/nfs/sunrpc/udp_server.rb
|
43
|
+
- lib/nfs/sunrpc/version.rb
|
44
|
+
- lib/nfs/version.rb
|
45
|
+
- lib/nfs/xdr.rb
|
46
|
+
- nfs-rb.gemspec
|
47
|
+
- spec/nfs_spec.rb
|
48
|
+
- spec/orig_dir/file1.txt
|
49
|
+
- spec/orig_dir/file2.txt
|
50
|
+
- spec/run_server.rb
|
51
|
+
- spec/spec_helper.rb
|
52
|
+
homepage: http://github.com/camertron/nfs
|
53
|
+
licenses: []
|
54
|
+
metadata: {}
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
requirements: []
|
70
|
+
rubygems_version: 3.1.4
|
71
|
+
signing_key:
|
72
|
+
specification_version: 4
|
73
|
+
summary: An NFS v2 server implemented in pure Ruby.
|
74
|
+
test_files: []
|