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,77 @@
1
+ module NFS
2
+ module SUNRPC
3
+ module Client
4
+ @@xid = 0
5
+ @@xid_mutex = Mutex.new
6
+
7
+ def method_missing(name, *args)
8
+ procedure = @version.get_procedure(name)
9
+
10
+ if procedure.nil?
11
+ raise NoMethodError, name.to_s
12
+ end
13
+
14
+ if args.size == 0
15
+ args = [nil]
16
+ end
17
+
18
+ if args.size != 1
19
+ raise ArgumentError
20
+ end
21
+
22
+ xid = nil
23
+
24
+ @@xid_mutex.synchronize do
25
+ xid = @@xid
26
+ @@xid += 1
27
+ end
28
+
29
+ message = RpcMsg.encode({
30
+ xid: xid,
31
+ body: {
32
+ _discriminant: :CALL,
33
+ cbody: {
34
+ rpcvers: 2,
35
+ prog: @program.number,
36
+ vers: @version.number,
37
+ proc: procedure.number,
38
+ cred: {
39
+ flavor: :AUTH_NULL,
40
+ body: ''
41
+ },
42
+ verf: {
43
+ flavor: :AUTH_NULL,
44
+ body: ''
45
+ }
46
+ }
47
+ }
48
+ }) + procedure.encode(args[0])
49
+
50
+ # This will return the result object or raise an exception that
51
+ # contains the cause of the error.
52
+ sendrecv(message) do |result|
53
+ envelope = RpcMsg.decode(result)
54
+
55
+ if envelope[:xid] == xid
56
+ if envelope[:body][:_discriminant] != :REPLY
57
+ raise envelope.inspect
58
+ end
59
+
60
+ if envelope[:body][:rbody][:_discriminant] != :MSG_ACCEPTED
61
+ raise envelope[:body][:rbody].inspect
62
+ end
63
+
64
+ if envelope[:body][:rbody][:areply][:reply_data][:_discriminant] != :SUCCESS
65
+
66
+ raise envelope[:body][:rbody][:areply][:reply_data].inspect
67
+ end
68
+
69
+ procedure.decode(result)
70
+ else
71
+ false # false means keep giving us received messages to inspect
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,56 @@
1
+ module NFS
2
+ module SUNRPC
3
+ class Procedure
4
+ attr_reader :number
5
+
6
+ def initialize(number, returntype, argtype, &block)
7
+ @number = number
8
+ @returntype, @argtype = returntype, argtype
9
+ @block = block
10
+ end
11
+
12
+ def dup
13
+ Procedure.new(@number, @returntype, @argtype, &@block)
14
+ end
15
+
16
+ def on_call(&block)
17
+ @block = block
18
+ end
19
+
20
+ def encode(arg)
21
+ @argtype.encode(arg)
22
+ end
23
+
24
+ def decode(value)
25
+ @returntype.decode(value)
26
+ end
27
+
28
+ def call(arg, cred, verf)
29
+ begin
30
+ arg_object = @argtype.decode(arg)
31
+ rescue
32
+ raise GarbageArguments
33
+ end
34
+
35
+ # Undefined procedures are also unavailable, even if the XDR says it's
36
+ # there. Define your procedures and this won't happen.
37
+ if @block.nil?
38
+ raise ProcedureUnavailable
39
+ end
40
+
41
+ result_object = @block.call(arg_object, cred, verf)
42
+ result = nil
43
+
44
+ begin
45
+ result = @returntype.encode(result_object)
46
+ rescue => e
47
+ NFS.logger.error(e.message)
48
+ NFS.logger.error(e.backtrace.join("\n"))
49
+ raise IgnoreRequest
50
+ end
51
+
52
+ result
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,58 @@
1
+ module NFS
2
+ module SUNRPC
3
+ class Program
4
+ attr_reader :number, :low, :high
5
+
6
+ def initialize(number, &block)
7
+ @number = number
8
+ @versions = {}
9
+ @low = @high = nil
10
+ instance_eval(&block) if block_given?
11
+ end
12
+
13
+ def dup
14
+ self.class.new(@number).tap do |p|
15
+ @versions.each_pair do |number, version|
16
+ p.add_version(number, version.dup)
17
+ end
18
+ end
19
+ end
20
+
21
+ def add_version(number, ver)
22
+ if @low.nil? or number < @low
23
+ @low = number
24
+ end
25
+
26
+ if @high.nil? or number > @high
27
+ @high = number
28
+ end
29
+
30
+ @versions[number] = ver
31
+ end
32
+
33
+ def version(ver, &block)
34
+ add_version(ver, Version.new(ver, &block))
35
+ end
36
+
37
+ def get_version(ver)
38
+ @versions[ver]
39
+ end
40
+
41
+ def each_version(&block)
42
+ @versions.each_value(&block)
43
+ end
44
+
45
+ def on_call(ver, procedure_name, &block)
46
+ @versions[ver].on_call(procedure_name, &block)
47
+ end
48
+
49
+ def call(ver, procedure, arg, cred, verf)
50
+ if !@versions.include?(ver)
51
+ raise ProgramMismatch.new(2, 2)
52
+ end
53
+
54
+ @versions[ver].call(procedure, arg, cred, verf)
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,119 @@
1
+ module NFS
2
+ module SUNRPC
3
+ class Server
4
+ def host
5
+ @server.addr[2]
6
+ end
7
+
8
+ def port
9
+ @server.addr[1]
10
+ end
11
+
12
+ def join
13
+ if !@thread.nil?
14
+ @thread.join
15
+ end
16
+ end
17
+
18
+ def start
19
+ if @thread.nil?
20
+ @thread.start
21
+ end
22
+ end
23
+
24
+ def shutdown
25
+ Thread.kill(@thread)
26
+ @thread = nil
27
+ @server.shutdown
28
+ rescue Errno::ENOTCONN
29
+ end
30
+
31
+ private
32
+
33
+ def run_procedure(data)
34
+ begin
35
+ xid, program_num, version_num, procedure_num, cred,
36
+ verf = decode_envelope(data)
37
+
38
+ program = @programs[program_num]
39
+
40
+ if program.nil?
41
+ raise ProgramUnavailable
42
+ else
43
+ result = program.call(
44
+ version_num, procedure_num, data, cred, verf
45
+ )
46
+
47
+ create_success_envelope(xid, result)
48
+ end
49
+ rescue IgnoreRequest, RequestDenied, AcceptedError => e
50
+ return e.encode(xid)
51
+ end
52
+ end
53
+
54
+ def decode_envelope(data)
55
+ envelope = nil
56
+
57
+ begin
58
+ envelope = RpcMsg.decode(data)
59
+ rescue
60
+ raise IgnoreRequest
61
+ end
62
+
63
+ if envelope[:body][:_discriminant] != :CALL
64
+ raise IgnoreRequest
65
+ end
66
+
67
+ if envelope[:body][:cbody][:rpcvers] != 2
68
+ raise RpcMismatch.new(2, 2, envelope[:xid])
69
+ end
70
+
71
+ cbody = envelope[:body][:cbody]
72
+
73
+ [
74
+ envelope[:xid],
75
+ cbody[:prog],
76
+ cbody[:vers],
77
+ cbody[:proc],
78
+ cbody[:cred],
79
+ cbody[:verf]
80
+ ]
81
+ end
82
+
83
+ def create_success_envelope(xid, result)
84
+ RpcMsg.encode({
85
+ xid: xid,
86
+ body: {
87
+ _discriminant: :REPLY,
88
+ rbody: {
89
+ _discriminant: :MSG_ACCEPTED,
90
+ areply: {
91
+ verf: {
92
+ flavor: :AUTH_NULL,
93
+ body: ''
94
+ },
95
+ reply_data: {
96
+ _discriminant: :SUCCESS,
97
+ results: ''
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }) + result
103
+ end
104
+
105
+ def hash_programs(programs)
106
+ case programs
107
+ when Hash
108
+ programs
109
+ when Array
110
+ programs.each_with_object({}) do |program, result|
111
+ result[program.number] = program
112
+ end
113
+ else
114
+ { programs.number => programs }
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,41 @@
1
+ require 'socket'
2
+ require 'thread'
3
+
4
+ module NFS
5
+ module SUNRPC
6
+ class TCPServer < Server
7
+ def initialize(programs, port = nil, host = '127.0.0.1')
8
+ @server = ::TCPServer.new(host, port)
9
+ @programs = hash_programs(programs)
10
+
11
+ @thread = Thread.new do
12
+ loop do
13
+ Thread.new(@server.accept) do |socket|
14
+ loop do
15
+ frame = socket.recv(4).unpack1('N')
16
+ len = frame & ~0x80000000
17
+ break unless len
18
+
19
+ data = socket.recv(len)
20
+ result = run_procedure(data)
21
+
22
+ if !result.nil?
23
+ result = [result.length | (128 << 24)].pack('N') + result
24
+ socket.send(result, 0)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ if block_given?
32
+ begin
33
+ yield(self)
34
+ ensure
35
+ shutdown
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,42 @@
1
+ require 'socket'
2
+ require 'thread'
3
+
4
+ module NFS
5
+ module SUNRPC
6
+ class UDPServer < Server
7
+ def initialize(programs, port = nil, host = '127.0.0.1')
8
+ @socket = UDPSocket.open
9
+ @socket.bind(host, port)
10
+ socketmutex = Mutex.new
11
+ @programs = hash_programs(programs)
12
+
13
+ @thread = Thread.new do
14
+ loop do
15
+ request = @socket.recvfrom(UDPClient::UDPRecvMTU)
16
+ data = request[0]
17
+ port = request[1][1]
18
+ host = request[1][3]
19
+
20
+ Thread.new do
21
+ result = run_procedure(data)
22
+
23
+ if !result.nil?
24
+ socketmutex.synchronize do
25
+ @socket.send(result, 0, host, port)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ if block_given?
33
+ begin
34
+ yield(self)
35
+ ensure
36
+ shutdown
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,55 @@
1
+ module NFS
2
+ module SUNRPC
3
+ class Version
4
+ attr_reader :number
5
+
6
+ def initialize(number, &block)
7
+ @number = number
8
+ @procedures = {}
9
+ @procedure_names = {}
10
+
11
+ # Add the customary null procedure by default.
12
+ procedure XDR::Void.new, :NULL, 0, XDR::Void.new do
13
+ # do nothing
14
+ end
15
+
16
+ instance_eval(&block) if block_given?
17
+ end
18
+
19
+ def dup
20
+ Version.new(@number).tap do |v|
21
+ @procedure_names.each_pair do |name, procedure|
22
+ v.add_procedure(name, procedure.number, procedure.dup)
23
+ end
24
+ end
25
+ end
26
+
27
+ def add_procedure(name, number, newproc)
28
+ @procedures[number] = newproc
29
+ @procedure_names[name] = newproc
30
+ end
31
+
32
+ # The name is required, but just for documentation.
33
+ def procedure(returntype, name, number, argtype, &block)
34
+ newproc = Procedure.new(number, returntype, argtype, &block)
35
+ add_procedure(name, number, newproc)
36
+ end
37
+
38
+ def get_procedure(procedure_name)
39
+ @procedure_names[procedure_name]
40
+ end
41
+
42
+ def on_call(procedure_name, &block)
43
+ @procedure_names[procedure_name].on_call(&block)
44
+ end
45
+
46
+ def call(p, arg, cred, verf)
47
+ unless @procedures.include?(p)
48
+ raise ProcedureUnavailable
49
+ end
50
+
51
+ @procedures[p].call(arg, cred, verf)
52
+ end
53
+ end
54
+ end
55
+ end