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,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