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