]> www.infradead.org Git - users/dhowells/kafs-utils.git/commitdiff
Rewrite rxgen in python using the ply module master
authorDavid Howells <dhowells@redhat.com>
Fri, 4 Sep 2015 10:22:01 +0000 (11:22 +0100)
committerDavid Howells <dhowells@redhat.com>
Sun, 20 Sep 2015 21:53:55 +0000 (22:53 +0100)
Signed-off-by: David Howells <dhowells@redhat.com>
rxgen/emit_c_struct.py [new file with mode: 0644]
rxgen/emit_c_sync_funcs.pm
rxgen/emit_c_sync_funcs.py [new file with mode: 0644]
rxgen/emit_py_module.py [new file with mode: 0644]
rxgen/emit_py_sync_funcs.py [new file with mode: 0644]
rxgen/emit_py_types.py [new file with mode: 0644]
rxgen/rxgen.py [new file with mode: 0755]
rxgen/rxgen_bits.py [new file with mode: 0644]

diff --git a/rxgen/emit_c_struct.py b/rxgen/emit_c_struct.py
new file mode 100644 (file)
index 0000000..2078cda
--- /dev/null
@@ -0,0 +1,121 @@
+#!/usr/bin/python3
+#
+# Emit C structure
+# -*- coding: utf-8 -*-
+
+__copyright__ = """
+Copyright (C) 2015 Red Hat, Inc. All Rights Reserved.
+Written by David Howells (dhowells@redhat.com)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public Licence version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public Licence for more details.
+
+You should have received a copy of the GNU General Public Licence
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+from rxgen_bits import *
+
+###############################################################################
+#
+# Emit structure encoders and decoders predeclarations
+#
+###############################################################################
+def emit_struct_encdec_decl(o, struct):
+    o.rxsrc("/* ", struct.name, " XDR size ", struct.xdr_size, " */\n")
+
+###############################################################################
+#
+# Emit structure encoders and decoders
+#
+###############################################################################
+def emit_struct_encdec(o, struct):
+    # Write out a C structure definition for this type
+    o.rxhdr("struct ", struct.name, " {\n")
+    for m in struct.members:
+        ty = m.typespec
+        o.where(struct.name + "::" + m.name)
+        if ty.is_single():
+            o.rxhdr("\t", ty.c_name, "\t", m.name)
+        elif ty.is_int_array() or ty.is_struct_array():
+            o.rxhdr("\t", ty.c_name, "\t", m.name, "[", ty.dim, "]")
+        else:
+            o.error("Unsupported type '", ty, "'\n")
+        o.rxhdr(";\n")
+    o.rxhdr("};\n")
+
+    # Write an encoding function
+    o.rxhdr("extern void rxgen_encode_", struct.name,
+            "(struct rx_call *call, const struct ", struct.name, " *p);\n")
+
+    o.rxsrc("void rxgen_encode_", struct.name,
+            "(struct rx_call *call, const struct ", struct.name, " *p)\n")
+    o.rxsrc("{\n")
+
+    for m in struct.members:
+        ty = m.typespec
+        if ty.is_array():
+            o.rxsrc("\tint i;\n\n")
+            break
+
+    for m in struct.members:
+        ty = m.typespec
+        o.where(struct.name + "::" + m.name)
+        if ty.is_single_int32():
+            o.rxsrc("\trxrpc_enc(call, p->", m.name, ");\n")
+        elif ty.is_single_struct():
+            o.rxsrc("\trxgen_encode_", ty.name, "(call, &p->", m.name, ");\n")
+        elif ty.is_array():
+            o.rxsrc("\tfor (i = 0; i < ", ty.dim.name, "; i++)\n")
+            if ty.is_int32_array():
+                o.rxsrc("\t\trxrpc_enc(call, p->", m.name, "[i]);\n")
+            elif ty.is_struct_array():
+                o.rxsrc("\t\trxgen_encode_", ty.name, "(call, &p->", m.name, "[i]);\n")
+            else:
+                o.error("No encoding for array type '", ty, "'")
+        else:
+            o.error("No encoding for type '", ty, "'")
+
+    o.rxsrc("}\n")
+    o.rxsrc("\n")
+
+    # Write a decoding function
+    o.rxhdr("extern void rxgen_decode_", struct.name,
+            "(struct rx_call *call, struct ", struct.name, " *p);\n")
+
+    o.rxsrc("void rxgen_decode_", struct.name,
+            "(struct rx_call *call, struct ", struct.name, " *p)\n")
+    o.rxsrc("{\n")
+
+    for m in struct.members:
+        ty = m.typespec
+        if ty.is_array():
+            o.rxsrc("\tint i;\n\n")
+            break
+
+    for m in struct.members:
+        ty = m.typespec
+        o.where(struct.name + "::" + m.name)
+        if ty.is_single_int32():
+            o.rxsrc("\tp->", m.name, " = rxrpc_dec(call);\n")
+        elif ty.is_single_struct():
+            o.rxsrc("\trxgen_decode_", ty.name, "(call, &p->", m.name, ");\n")
+        elif ty.is_array():
+            o.rxsrc("\tfor (i = 0; i < ", ty.dim.name, "; i++)\n")
+            if ty.is_int32_array():
+                o.rxsrc("\t\tp->", m.name, "[i] = rxrpc_dec(call);\n")
+            elif ty.is_struct_array():
+                o.rxsrc("\t\trxgen_decode_", ty.name, "(call, &p->", m.name, "[i]);\n")
+            else:
+                o.error("No decoding for array type '", ty, "'")
+        else:
+            o.error("No decoding for type '", ty, "'")
+
+    o.rxsrc("}\n")
index 493eeacb474b971a445eff1e8b50306f970481dc..942bce5448d8c89a960ede18417f20bb81c2c74c 100644 (file)
@@ -639,7 +639,7 @@ sub emit_func_decode($$$$)
            }
        }
 
-       if ($phase->{type} ne "blob" || $phase->{type} ne "bulk") {
+       if ($phase->{type} ne "blob" && $phase->{type} ne "bulk") {
            print RXOUT "\t\tif (rxrpc_post_dec(call) < 0)\n";
            print RXOUT "\t\t\treturn -1;\n";
        }
@@ -764,20 +764,6 @@ sub emit_func_send($$)
        } elsif ($p->{class} eq "blob") {
            print RXOUT "\trxrpc_enc_blob(call, ", $p->{name}, ", nr__", $p->{name}, ");\n";
            print RXOUT "\trxrpc_enc_align(call);\n";
-       } elsif ($p->{class} eq "blob") {
-           print RXOUT "\trxrpc_enc(call, nr__", $p->{name}, ");\n";
-           print RXOUT "\tcall->blob_size = nr__", $p->{name}, ";\n";
-           print RXOUT "\tfor (call->blob_offset = 0; call->blob_offset < call->blob_size; call->blob_offset++) {\n";
-           if ($p->{elem}->{class} eq "struct") {
-               print RXOUT "\t\tstruct ", $p->{elem}->{type}, " x;\n";
-           } else {
-               print RXOUT "\t\t", $p->{elem}->{type}, " x;\n";
-           }
-           print RXOUT "\t\tcall->blob = &x;\n";
-           print RXOUT "\t\tif (get__", $p->{name}, "(call, token__", $p->{name}, ") < 0)\n";
-           print RXOUT "\t\t\tgoto error;\n";
-           die $p->{where}, "No decoding for array type '$type'";
-           print RXOUT "\t}\n";
        } elsif ($p->{class} eq "bulk") {
            print RXOUT "\trxrpc_enc(call, nr__", $p->{name}, ");\n";
            print RXOUT "\tcall->bulk_count = nr__", $p->{name}, ";\n";
diff --git a/rxgen/emit_c_sync_funcs.py b/rxgen/emit_c_sync_funcs.py
new file mode 100644 (file)
index 0000000..79da197
--- /dev/null
@@ -0,0 +1,555 @@
+#!/usr/bin/python3
+#
+# Emit C synchronous functions
+# -*- coding: utf-8 -*-
+
+__copyright__ = """
+Copyright (C) 2015 Red Hat, Inc. All Rights Reserved.
+Written by David Howells (dhowells@redhat.com)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public Licence version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public Licence for more details.
+
+You should have received a copy of the GNU General Public Licence
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+from rxgen_bits import *
+
+###############################################################################
+#
+# Calculate the C function prototypes
+#
+###############################################################################
+def emit_func_prototype(o, func):
+    # Function prototype lists (we add commas and the closing bracket later)
+    protos = list()
+    protos.append("int " + func.name + "(\n")
+    send_request_protos = list()
+    send_response_protos = list()
+    recv_request_protos = list()
+    recv_response_protos = list()
+
+    # Arguments to pass when sending a call or processing a reply
+    send_args = list()
+    recv_args = list()
+
+    for p in func.params:
+        o.where(func.name + ":" + p.name)
+        ty = p.typespec
+        enclines = list()
+        declines = list()
+        args = list()
+
+        if ty.is_array():
+            raise RuntimeError("Array arg not supported")
+        elif ty.is_bulk():
+            # Encode
+            if ty.is_bulk_struct():
+                proto = "int (*get__" + p.name + ")(struct rx_call *call, void *token)"
+            else:
+                proto = "int (*get__" + p.name + ")(struct rx_call *call, void *token)"
+
+            enclines.append(proto)
+            enclines.append("void *token__" + p.name)
+            enclines.append("size_t nr__" + p.name)
+            args.append("get__" + p.name)
+            args.append("token__" + p.name)
+            args.append("nr__" + p.name)
+
+            # Decode
+            if ty.is_bulk_struct():
+                proto = "int (*alloc__" + p.name + ")(struct rx_call *call, void **token)"
+                args.append("alloc__" + p.name)
+            else:
+                proto = "int (*store__" + p.name + ")(struct rx_call *call, void **token)"
+                args.append("store__" + p.name)
+
+            declines.append(proto)
+            declines.append("void *token__" + p.name)
+            declines.append("size_t nr__" + p.name)
+            args.append("token__" + p.name)
+        elif ty.is_single_blob():
+            # Do we want to insert a "*" in the following?
+            proto = ty.c_name + " *" + p.name
+            enclines.append("size_t nr__" + p.name)
+            enclines.append("const " + proto)
+
+            declines.append("size_t nr__" + p.name)
+            declines.append("void *token__" + p.name)
+            declines.append("int (*alloc__" + p.name + ")(struct rx_call *call, void **token)")
+            args.append("nr__" + p.name)
+            args.append(p.name)
+            args.append("alloc__" + p.name)
+        else:
+            enc_const = ""
+            if not ty.is_single_int():
+                enc_const = "const "
+            proto = ty.c_name + " "
+            if not ty.is_single_int():
+                proto += "*"
+            proto += p.name
+            enclines.append(enc_const + proto)
+            declines.append(proto)
+            args.append(p.name)
+
+        if p.direction == xdr_direction.IN or p.direction == xdr_direction.INOUT:
+            send_request_protos += enclines
+            recv_request_protos += declines
+            send_args += args
+        if p.direction == xdr_direction.OUT or p.direction == xdr_direction.INOUT:
+            send_response_protos += enclines
+            recv_response_protos += declines
+            recv_args += args
+
+    o.rxhdr("\n")
+    o.rxhdr("/*\n")
+    o.rxhdr(" * ", func.name, "\n")
+    o.rxhdr(" */\n")
+
+    if recv_request_protos:
+        o.rxhdr("struct ", func.name, "_request {\n")
+        for p in recv_request_protos:
+            o.rxhdr("\t", p, ";\n")
+        o.rxhdr("};\n")
+
+    o.rxhdr("\n")
+    if recv_response_protos:
+        o.rxhdr("struct ", func.name, "_response {\n")
+        for p in recv_response_protos:
+            o.rxhdr("\t", p, ";\n")
+        o.rxhdr("};\n")
+
+    # # Terminate each line with a comma, excepting the last, which we terminate
+    # # with a closing bracket.
+    # for (my $i = 1; $i < $#protos; $i++:
+    #   protos[$i] .= ",\n")
+    # }
+    # protos[$#protos] .= ")")
+
+    # for (my $i = 1; $i < $#send_protos; $i++:
+    #   $send_protos[$i] .= ",\n")
+    # }
+    # $send_protos[$#send_protos] .= ")")
+
+    # for (my $i = 1; $i < $#recv_protos; $i++:
+    #   $recv_protos[$i] .= ",\n")
+    # }
+    # $recv_protos[$#recv_protos] .= ")")
+
+    func.protos = protos
+    func.send_request_protos = send_request_protos
+    func.recv_request_protos = recv_request_protos
+    func.send_response_protos = send_response_protos
+    func.recv_response_protos = recv_response_protos
+    func.send_args = send_args
+    func.recv_args = recv_args
+
+###############################################################################
+#
+# Emit a function to decode a block in a way that can be used from asynchronous
+# code.  The opcode is expected to have been removed from the incoming call on
+# the server side.
+#
+###############################################################################
+class decode_phase:
+    def __init__(self, form="flat", size=0, xdr_size=0, name=None):
+        self.form = form
+        self.size = size
+        self.params = list()
+        self.xdr_size = xdr_size
+        self.elem_count = 0
+        self.name = name
+
+def emit_func_decode(o, func, side, subname, params):
+    # We fetch the data in a number of phases.  Each phase receives a chunk of
+    # data of a certain size.  A phase's size might be dependent on a variable
+    # in the previous phase.  Variable-sized bulk arrays are split across
+    # multiple phases, with the length being at the end of the first phase and
+    # the data in the second.
+    phases = list()
+    phase = None
+    have_bulk = False
+
+    for p in params:
+        o.where(func.name + ":" + p.name)
+        ty = p.typespec
+
+        if not phase:
+            phase = decode_phase()
+            phases.append(phase)
+
+        if ty.is_single_int() or ty.is_single_struct():
+            phase.size += ty.xdr_size
+            phase.params.append(p)
+        elif ty.is_single_blob():
+            have_bulk = True
+
+            # Blob objects begin with a size for which we institute a special
+            # parameter
+            phase.elem_count = phase.size
+            phase.size += 4
+
+            count_type = xdr_type(o.xdr, base=o.xdr.get_type("uint32_t"))
+            pseudoparam = xdr_member("nr__" + p.name, count_type, o.xdr)
+            pseudoparam.special = "blob_size"
+            phase.params.append(pseudoparam)
+
+            # Create a new phase
+            phase = decode_phase(form="blob", name=p.name, size=4, xdr_size=ty.xdr_size)
+            phase.params.append(p)
+            phases.append(phase)
+            phase = None
+
+        elif ty.is_bulk():
+            have_bulk = True
+
+            # Bulk objects begin with an element count for which we institute a
+            # special parameter
+            phase.elem_count = phase.size
+            phase.size +=  4
+
+            count_type = xdr_type(o.xdr, base=o.xdr.get_type("uint32_t"))
+            pseudoparam = xdr_member("nr__" + p.name, count_type, o.xdr)
+            pseudoparam.special = "bulk_size"
+            phase.params.append(pseudoparam)
+
+            # Create a new phase
+            phase = decode_phase(form="bulk", name=p.name, size=4, xdr_size=ty.xdr_size)
+            phase.params.append(p)
+            phases.append(phase)
+
+            # We don't want to be asking recvmsg() for one object at a time if
+            # they're really small.
+            n_buf = 1
+            if ty.xdr_size < 1020:
+                n_buf = int(1020 / ty.xdr_size)
+            n_buf *= ty.xdr_size
+            phase.size = ty.xdr_size
+            phase = None
+
+        else:
+            raise RuntimeError("Reply array not supported")
+
+    # Function definition and arguments
+    o.rxsrc("\n")
+    o.rxsrc("static int rxgen_decode_", func.name, "_", subname, "(struct rx_call *call)\n")
+    o.rxsrc("{\n")
+
+    if not params:
+        o.rxsrc("\treturn 0;\n")
+        o.rxsrc("}\n")
+        return
+
+    # Local variables
+    o.rxsrc("\tstruct ", func.name, "_", subname, " *obj = call->decoder_private;\n")
+    o.rxsrc("\tunsigned count;\n")
+    o.rxsrc("\tunsigned phase = call->phase;\n")
+
+    # Deal with each phase
+    o.rxsrc("\n")
+    if have_bulk:
+        o.rxsrc("select_phase:\n")
+    o.rxsrc("\tcount = call->data_count;\n")
+    o.rxsrc("\tswitch (phase) {\n")
+
+    o.rxsrc("\tcase 0:\n")
+
+    next_phase_id = 1
+    for phase in phases:
+        phase.phase_id = next_phase_id
+        next_phase_id += 1
+
+    phase_goto_label = None
+    for phase in phases:
+        phase_id = phase.phase_id
+        o.rxsrc("\n")
+        o.rxsrc("\t\t/* --- Phase ", phase_id, " --- */\n")
+
+        if phase_goto_label == phase_id:
+            o.rxsrc("\tphase_", phase_id, ":\n")
+            phase_goto_label = None
+
+        # Determine how big bulk objects are
+        if phase.form == "blob":
+            p = phase.params[0]
+            o.rxsrc("\t\tcall->blob_size = obj->nr__", p.name, ";\n")
+            o.rxsrc("\t\tcall->blob_offset = UINT_MAX;\n")
+            o.rxsrc("\t\tif (obj->alloc__", p.name, "(call, &obj->token__", p.name, ") < 0)\n")
+            o.rxsrc("\t\t\treturn -1;\n")
+            o.rxsrc("\t\tif (call->blob_size == 0)\n")
+            o.rxsrc("\t\t\tgoto phase_", phase_id + 1, ";\n")
+            phase_goto_label = phase_id + 1
+            o.rxsrc("\t\tcall->blob_offset = 0;\n")
+        elif phase.form == "bulk":
+            p = phase.params[0]
+            o.rxsrc("\t\tcall->bulk_count = obj->nr__", p.name, ";\n")
+            o.rxsrc("\t\tcall->bulk_index = UINT_MAX;\n")
+
+            if ty.is_bulk_int():
+                o.rxsrc("\t\tif (obj->store__", p.name, "(call, &obj->token__", p.name, ") < 0)\n")
+            else:
+                o.rxsrc("\t\tif (obj->alloc__", p.name, "(call, &obj->token__", p.name, ") < 0)\n")
+
+            o.rxsrc("\t\t\treturn -1;\n")
+            o.rxsrc("\t\tif (call->bulk_count == 0)\n")
+            o.rxsrc("\t\t\tgoto phase_", phase_id + 1, ";\n")
+            phase_goto_label = phase_id + 1
+            o.rxsrc("\t\tcall->bulk_index = 0;\n")
+        else:
+            o.rxsrc("\t\tcall->need_size = ", phase.size, ";\n")
+
+        # Entry point for a phase
+        o.rxsrc("\t\tcall->phase = ", phase_id, ";\n")
+        o.rxsrc("\tcase ", phase_id, ":\n")
+
+        o.rxsrc("\t\tif (count < ", phase.size, ")")
+        if phase.form == "bulk" and phase.xdr_size <= 512:
+            o.rxsrc(" {\n")
+            o.rxsrc("\t\t\tunsigned n = call->bulk_count - call->bulk_index;\n")
+            o.rxsrc("\t\t\tn = MIN(n, ", int(1024 / phase.xdr_size), ");\n")
+            o.rxsrc("\t\t\tcall->need_size = n * ", phase.xdr_size, ";\n")
+            o.rxsrc("\t\t\treturn 1;\n")
+            o.rxsrc("\t\t}")
+        else:
+            o.rxsrc("\n")
+            o.rxsrc("\t\t\treturn 1;\n")
+
+        # Unmarshal the data
+        o.rxsrc("\n")
+        for p in phase.params:
+            o.where(func.name + ":" + p.name)
+            ty = p.typespec
+            if p.special == None:
+                pass
+            elif p.special == "blob_size" or p.special == "bulk_size":
+                o.rxsrc("\t\tobj->", p.name, " = rxrpc_dec(call);\n")
+                continue
+            else:
+                raise RuntimeError
+
+            if ty.is_bulk_int():
+                if ty.is_bulk_int32():
+                    o.rxsrc("\t\tcall->bulk_u32 = rxrpc_dec(call);\n")
+                    o.rxsrc("\t\tif (obj->store__", p.name, "(call, &obj->token__", p.name, ") < 0)\n")
+                elif ty.is_bulk_int64():
+                    o.rxsrc("\t\tcall->bulk_u64  = (uint64_t)rxrpc_dec(call) << 32;\n")
+                    o.rxsrc("\t\tcall->bulk_u64 |= (uint64_t)rxrpc_dec(call);\n")
+                    o.rxsrc("\t\tif (obj->store__", p.name, "(call, &obj->token__", p.name, ") < 0)\n")
+                else:
+                    raise RuntimeError
+                o.rxsrc("\t\t\treturn -1;\n")
+                o.rxsrc("\t\tcall->bulk_index++;\n")
+
+            elif ty.is_bulk_struct():
+                o.rxsrc("\t\tif (obj->alloc__", p.name, "(call, &obj->token__", p.name, ") < 0)\n")
+                o.rxsrc("\t\t\treturn -1;\n")
+                o.rxsrc("\t\trxgen_decode_", ty.name, "(call, call->bulk_item);\n")
+                o.rxsrc("\t\tcall->bulk_index++;\n")
+            elif ty.is_single_blob():
+                o.rxsrc("\t\trxrpc_dec_blob(call);\n")
+                o.rxsrc("\t\trxrpc_dec_align(call);\n")
+            elif ty.is_single_int32():
+                o.rxsrc("\t\tobj->", p.name, " = rxrpc_dec(call);\n")
+            elif ty.is_single_int64():
+                o.rxsrc("\t\tobj->", p.name, "  = (uint64_t)rxrpc_dec(call) << 32;\n")
+                o.rxsrc("\t\tobj->", p.name, " |= (uint64_t)rxrpc_dec(call);\n")
+            elif ty.is_single_struct():
+                o.rxsrc("\t\trxgen_decode_", ty.name, "(call, obj->", p.name, ");\n")
+            else:
+                raise RuntimeError("Unsupported type in decode " + str(ty))
+
+            if ty.is_single_blob():
+                o.rxsrc("\t\tif (rxrpc_post_dec(call) < 0)\n")
+                o.rxsrc("\t\t\treturn -1;\n")
+                o.rxsrc("\t\tif (call->blob_offset < call->blob_size) {\n")
+                o.rxsrc("\t\t\tphase = ", phase_id, ";\n")
+                o.rxsrc("\t\t\tgoto select_phase;\n")
+                o.rxsrc("\t\t}\n")
+            elif ty.is_bulk():
+                o.rxsrc("\t\tif (rxrpc_post_dec(call) < 0)\n")
+                o.rxsrc("\t\t\treturn -1;\n")
+                o.rxsrc("\t\tif (call->bulk_index < call->bulk_count) {\n")
+                o.rxsrc("\t\t\tphase = ", phase_id, ";\n")
+                o.rxsrc("\t\t\tgoto select_phase;\n")
+                o.rxsrc("\t\t}\n")
+
+        # --- TODO: Check the following condition as it must always be true
+        if phase.form != "blob" or phase.form != "bulk":
+            o.rxsrc("\t\tif (rxrpc_post_dec(call) < 0)\n")
+            o.rxsrc("\t\t\treturn -1;\n")
+
+    o.rxsrc("\n")
+    o.rxsrc("\t\t/* --- Phase ", next_phase_id, " --- */\n")
+    if phase_goto_label:
+        o.rxsrc("\tphase_", next_phase_id, ":\n")
+    o.rxsrc("\t\tcall->phase = ", next_phase_id, ";\n")
+    o.rxsrc("\t\tcall->need_size = 0;\n")
+    o.rxsrc("\tdefault:\n")
+    o.rxsrc("\t\treturn 0;\n")
+    o.rxsrc("\t}\n")
+    o.rxsrc("}\n")
+
+
+###############################################################################
+#
+# Emit a function to encode and dispatch a request or a response
+#
+###############################################################################
+def emit_func_send(o, func, what):
+    # Function definition and arguments
+    if what == "request":
+        protos = func.send_request_protos
+        params = func.request
+        bad_ret = "NULL"
+    else:
+        protos = func.send_response_protos
+        params = func.response
+        bad_ret = "-1"
+
+    o.rxsrc("\n")
+    if what == "request":
+        o.rxsrc("struct rx_call *", func.name + "(\n")
+        o.rxsrc("\tstruct rx_connection *z_conn")
+    else:
+        o.rxsrc("int respond_to_", func.name + "(\n")
+        o.rxsrc("\tstruct rx_call *call")
+
+    for proto in protos:
+        o.rxsrc(",\n\t", proto)
+
+    if what == "request" and func.response:
+        o.rxsrc(",\n")
+        o.rxsrc("\tstruct ", func.name, "_response *response")
+
+    o.rxsrc(")\n")
+    o.rxsrc("{\n")
+
+    if what == "request":
+        o.rxsrc("\tstruct rx_call *call;\n")
+
+    blob_params = list()
+    bulk_params = list()
+    for p in params:
+        ty = p.typespec
+        if ty.is_single_blob():
+            blob_params.append(p)
+        if ty.is_bulk():
+            bulk_params.append(p)
+
+    # Local variables
+    o.rxsrc("\tint ret;\n")
+
+    # Check lengths
+    if blob_params or bulk_params:
+        o.rxsrc("\n")
+        o.rxsrc("\tif (")
+        first = True
+        for p in blob_params:
+            o.where(func.name + ":" + p.name)
+            ty = p.typespec
+            if first:
+                first = False
+            else:
+                o.rxsrc(" ||\n\t    ")
+            o.rxsrc("!", p.name)
+            if ty.max_size:
+                o.rxsrc(" || nr__", p.name, " > ", ty.max_size.name)
+
+        for p in bulk_params:
+            o.where(func.name + ":" + p.name)
+            ty = p.typespec
+            if first:
+                first = False
+            else:
+                o.rxsrc(" ||\n\t    ")
+            o.rxsrc("!get__", p.name)
+            if ty.max_size:
+                o.rxsrc(" || nr__", p.name, " > ", ty.max_size.name)
+
+        o.rxsrc(") {\n")
+        o.rxsrc("\t\terrno = EINVAL;\n")
+        o.rxsrc("\t\treturn ", bad_ret, ";\n")
+        o.rxsrc("\t};\n")
+
+    # Allocate call
+    if what == "request":
+        o.rxsrc("\n")
+        o.rxsrc("\tcall = rxrpc_alloc_call(z_conn, 0);\n")
+        o.rxsrc("\tif (!call)\n")
+        o.rxsrc("\t\treturn ", bad_ret, ";\n")
+        o.rxsrc("\tcall->decoder = rxgen_decode_", func.name, "_response;\n")
+        if func.response:
+            o.rxsrc("\tcall->decoder_private = response;\n")
+
+    # Marshal the data
+    if what == "request" or params:
+        o.rxsrc("\n")
+    if what == "request":
+        o.rxsrc("\trxrpc_enc(call, ", func.opcode.name, ");\n")
+
+    for p in params:
+        o.where(func.name + ":" + p.name)
+        ty = p.typespec
+        if ty.is_single_int32():
+            o.rxsrc("\trxrpc_enc(call, ", p.name, ");\n")
+        elif ty.is_single_int64():
+            o.rxsrc("\trxrpc_enc(call, (uint32_t)", p.name, ");\n")
+            o.rxsrc("\trxrpc_enc(call, (uint32_t)(", p.name, " >> 32));\n")
+        elif ty.is_single_struct():
+            o.rxsrc("\trxgen_encode_", ty.name, "(call, ", p.name, ");\n")
+        elif ty.is_single_blob():
+            o.rxsrc("\trxrpc_enc_blob(call, ", p.name, ", nr__", p.name, ");\n")
+            o.rxsrc("\trxrpc_enc_align(call);\n")
+        elif ty.is_bulk():
+            o.rxsrc("\trxrpc_enc(call, nr__", p.name, ");\n")
+            o.rxsrc("\tcall->bulk_count = nr__", p.name, ";\n")
+            o.rxsrc("\tfor (call->bulk_index = 0; call->bulk_index < call->bulk_count; call->bulk_index++) {\n")
+            o.rxsrc("\t\t", ty.c_name, " x;\n")
+
+            o.rxsrc("\t\tcall->bulk_item = &x;\n")
+            o.rxsrc("\t\tif (get__", p.name, "(call, token__", p.name, ") < 0)\n")
+            o.rxsrc("\t\t\tgoto error;\n")
+            if ty.is_bulk_int32():
+                if not ty.name.startswith("u"):
+                    o.rxsrc("\t\trxrpc_enc(call, (u", ty.name, ")x);\n")
+                else:
+                    o.rxsrc("\t\trxrpc_enc(call, x);\n")
+            elif ty.is_bulk_int64():
+                o.rxsrc("\t\trxrpc_enc(call, (uint32_t)", p.name, ");\n")
+                o.rxsrc("\t\trxrpc_enc(call, (uint32_t)(", p.name, " >> 32));\n")
+            elif ty.is_bulk_struct():
+                o.rxsrc("\t\trxgen_encode_", ty.name, "(call, &x);\n")
+            else:
+                raise RuntimeError("No decoding for array type '" + str(ty) + "'")
+
+            o.rxsrc("\t}\n")
+        else:
+            raise RuntimeError("Unsupported param encoding")
+
+    o.rxsrc("\tif (rxrpc_post_enc(call) < 0)\n")
+    o.rxsrc("\t\tgoto error;\n")
+    o.rxsrc("\tcall->more_send = 0;\n")
+
+    # Send the message
+    o.rxsrc("\n")
+    o.rxsrc("\tret = rxrpc_send_data(call);\n")
+    o.rxsrc("\tif (ret < 0)\n")
+    o.rxsrc("\t\tgoto error;\n")
+    if what == "request":
+        o.rxsrc("\treturn call;\n")
+    else:
+        o.rxsrc("\treturn 0;\n")
+
+    o.rxsrc("\n")
+    o.rxsrc("error:\n")
+    o.rxsrc("\trxrpc_terminate_call(call, 0);\n")
+    o.rxsrc("\treturn ", bad_ret, ";\n")
+    o.rxsrc("}\n")
diff --git a/rxgen/emit_py_module.py b/rxgen/emit_py_module.py
new file mode 100644 (file)
index 0000000..d2277cb
--- /dev/null
@@ -0,0 +1,175 @@
+#!/usr/bin/python3
+#
+# Emit python module definition.
+# -*- coding: utf-8 -*-
+
+__copyright__ = """
+Copyright (C) 2015 Red Hat, Inc. All Rights Reserved.
+Written by David Howells (dhowells@redhat.com)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public Licence version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public Licence for more details.
+
+You should have received a copy of the GNU General Public Licence
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+from rxgen_bits import *
+
+###############################################################################
+#
+# Emit python module definition.
+#
+###############################################################################
+def emit_py_module(o):
+    # We want an exception we can raise when we get a remote abort
+    o.pyhdr("extern PyObject *kafs_remote_abort;\n")
+
+    o.pysrc("\n")
+    o.pysrc("/*\n")
+    o.pysrc(" * The remote-abort exception.\n")
+    o.pysrc(" */\n")
+    o.pysrc("PyObject *kafs_remote_abort;\n")
+
+    pkgnames = list(o.xdr.packages.keys())
+    pkgnames.sort()
+
+    for pkgname in pkgnames:
+        pkg = o.xdr.packages[pkgname]
+        codes = pkg.abort_codes
+        if codes:
+            o.pysrc("PyObject *kafs_", pkg.name, "_abort;\n")
+
+    o.pysrc("\n")
+    o.pyhdr("extern struct kafs_abort_list kafs_abort_map[", o.xdr.abort_count, "];\n")
+
+    index = 0
+    o.pysrc("struct kafs_abort_list kafs_abort_map[", o.xdr.abort_count, "] = {\n")
+    abort_ids = list(o.xdr.abort_ids.keys())
+    abort_ids.sort()
+    for aid in abort_ids:
+        abort = o.xdr.abort_ids[aid]
+        o.pysrc("\t{ .id = ", abort.name)
+        #if abort.msg:
+        #    o.pysrc(", .msg = \"", abort.msg, "\"")
+        o.pysrc(" }, /* ", abort.u32, " */\n")
+        abort.index = index
+        index += 1
+
+    o.pysrc("};\n\n")
+
+    # Emit python structure wrapper static method table
+    o.pysrc("\n")
+    o.pysrc("/*\n")
+    o.pysrc(" * The static methods.\n")
+    o.pysrc(" */\n")
+    o.pysrc("static PyMethodDef module_methods[] = {\n")
+
+    o.pysrc("\t{\"rx_new_connection\", (PyCFunction)kafs_py_rx_new_connection, METH_VARARGS, \"\" },\n")
+    o.pysrc("\t{\"afs_string_to_key\", (PyCFunction)kafs_py_string_to_key, METH_VARARGS, \"\" },\n")
+
+    for pyf in o.xdr.py_func_defs:
+        o.pysrc("\t{\"", pyf.name, "\", (PyCFunction)", pyf.c_func, ", METH_VARARGS,")
+        o.pysrc(" \"", pyf.doc, "\" },\n")
+
+    o.pysrc("\t{}\n")
+    o.pysrc("};\n")
+
+    # Emit python structure wrapper loader
+    o.pysrc("\n")
+
+    o.pysrc("static PyModuleDef kafs_module = {\n")
+    o.pysrc("\t.m_base = PyModuleDef_HEAD_INIT,\n")
+    o.pysrc("\t.m_name = \"kafs\",\n")
+    o.pysrc("\t.m_doc = \"AFS stuff.\",\n")
+    o.pysrc("\t.m_size = -1,\n")
+    o.pysrc("\t.m_methods = module_methods,\n")
+    o.pysrc("};\n")
+
+    o.pyhdr("\n")
+    o.pyhdr("extern PyObject *pykafs_load_wrappers(void);\n")
+
+    o.pysrc("\n")
+    o.pysrc("PyObject *pykafs_load_wrappers(void)\n")
+    o.pysrc("{\n")
+    o.pysrc("\tPyObject *m;\n")
+
+    # Load types
+    if o.xdr.py_type_defs:
+        o.pysrc("\tif (")
+        o.pysrc("PyType_Ready(&py_rx_connectionType) < 0 ||\n\t    ")
+        o.pysrc("PyType_Ready(&py_rx_split_infoType) < 0")
+        for pyt in o.xdr.py_type_defs:
+            o.pysrc(" ||\n\t    ")
+            o.pysrc("PyType_Ready(&", pyt.c_type, ") < 0")
+        o.pysrc(")\n")
+        o.pysrc("\t\treturn NULL;\n")
+
+
+    o.pysrc("\n")
+    o.pysrc("\tm = PyModule_Create(&kafs_module);\n")
+    o.pysrc("\tif (!m)\n")
+    o.pysrc("\t\treturn NULL;\n")
+
+    if o.xdr.constants:
+        o.pysrc("\n")
+        con_names = list(o.xdr.constants.keys())
+        con_names.sort()
+        for c in con_names:
+            o.pysrc("\tPyModule_AddIntConstant(m, \"", c, "\", ", c, ");\n")
+
+    if o.xdr.py_type_defs:
+        o.pysrc("\n")
+        for pyt in o.xdr.py_type_defs:
+            o.pysrc("\tPy_INCREF(&", pyt.c_type, ");\n")
+            o.pysrc("\tPyModule_AddObject(m, \"", pyt.name, "\", (PyObject *)&", pyt.c_type, ");\n")
+
+    # Emit a base remote abort class that all others can be subclassed off
+    o.pysrc("\n")
+    o.pysrc("\tkafs_remote_abort = PyErr_NewException(\"kafs.RemoteAbort\", NULL, NULL);\n")
+    o.pysrc("\tif (!kafs_remote_abort)\n")
+    o.pysrc("\t\treturn NULL;\n")
+    o.pysrc("\tPy_INCREF(kafs_remote_abort);\n")
+    o.pysrc("\tPyModule_AddObject(m, \"RemoteAbort\", kafs_remote_abort);\n")
+
+    for pkgname in pkgnames:
+        pkg = o.xdr.packages[pkgname]
+        abort_codes = pkg.abort_codes
+        if not abort_codes:
+            continue
+
+        pkg_abort = pkg.name + "Abort"
+        pkg_sym = "kafs_" + pkg.name + "_abort"
+
+        o.pysrc("\n")
+        o.pysrc("\t", pkg_sym, " = PyErr_NewException(\"kafs.", pkg_abort, "\", kafs_remote_abort, NULL);\n")
+        o.pysrc("\tif (!", pkg_sym, ")\n")
+        o.pysrc("\t\treturn NULL;\n")
+        o.pysrc("\tPy_INCREF(", pkg_sym, ");\n")
+        o.pysrc("\tPyModule_AddObject(m, \"", pkg_abort, "\", ", pkg_sym, ");\n")
+
+        def get_constant_value(abort):
+            return abort.u32
+        abort_codes.sort(key=get_constant_value)
+
+        for abort in abort_codes:
+            abort_name = "Abort" + abort.name
+            abort_var = "kafs_abort_map[" + str(abort.index) + "].obj"
+
+            o.pysrc("\n")
+            o.pysrc("\t", abort_var, " = PyErr_NewException(\"kafs.", abort_name, "\", ", pkg_sym, ", NULL);\n")
+            o.pysrc("\tif (!", abort_var, ")\n")
+            o.pysrc("\t\treturn NULL;\n")
+            o.pysrc("\tPy_INCREF(", abort_var, ");\n")
+            o.pysrc("\tPyModule_AddObject(m, \"", abort_name, "\", ", abort_var, ");\n")
+
+    o.pysrc("\n")
+    o.pysrc("\treturn m;\n")
+    o.pysrc("}\n")
diff --git a/rxgen/emit_py_sync_funcs.py b/rxgen/emit_py_sync_funcs.py
new file mode 100644 (file)
index 0000000..1c8ac71
--- /dev/null
@@ -0,0 +1,752 @@
+#!/usr/bin/python3
+#
+# Emit C synchronous functions
+# -*- coding: utf-8 -*-
+
+__copyright__ = """
+Copyright (C) 2015 Red Hat, Inc. All Rights Reserved.
+Written by David Howells (dhowells@redhat.com)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public Licence version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public Licence for more details.
+
+You should have received a copy of the GNU General Public Licence
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+from rxgen_bits import *
+
+class decode_phase:
+    def __init__(self, form="flat", size=0, xdr_size=0, name=None):
+        self.form = form
+        self.size = size
+        self.params = list()
+        self.xdr_size = xdr_size
+        self.elem_count = 0
+        self.name = name
+
+bulk_get_helpers = dict();
+bulk_set_helpers = dict();
+
+c_to_py_type_map = dict([("char",     "T_CHAR"),
+                         ("int8_t",   "T_BYTE"),
+                         ("int16_t",  "T_SHORT"),
+                         ("int32_t",  "T_INT"),
+                         ("int64_t",  "T_LONGLONG"),
+                         ("uint8_t",  "T_UBYTE"),
+                         ("uint16_t", "T_USHORT"),
+                         ("uint32_t", "T_UINT"),
+                         ("uint64_t", "T_ULONGLONG")
+                     ])
+
+###############################################################################
+#
+# Emit python objects to represent received parameter sets and received
+# response sets for RPC calls.
+#
+###############################################################################
+def emit_py_func_param_object(o, func, way):
+    struct_req = "py_" + func.name + "_" + way;
+    basic_params = list()
+    complex_params = list()
+    params = list()
+    division = ""
+
+    global c_to_py_type_map
+
+    t = py_type_def(func.name + "_" + way, struct_req + "Type")
+    o.xdr.py_type_defs.append(t)
+
+    if way == "request":
+        params = func.request
+        division = "calls"
+    else:
+        params = func.response
+        division = "responses"
+
+    # Define a C structure to hold the python object header and the data.
+    o.pyhdr("\n")
+    o.pyhdr("struct ", struct_req, " {\n")
+    o.pyhdr("\tstruct py_rx_", way, " common;\n")
+    if params:
+        have_opaque = False
+        o.pyhdr("\tstruct {\n")
+        for p in params:
+            o.where(func.name + ":" + p.name)
+            ty = p.typespec
+            if ty.is_single_int():
+                basic_params.append(p)
+                o.pyhdr("\t\t", ty.name, "\t", p.name, ";\n")
+            else:
+                complex_params.append(p)
+                o.pyhdr("\t\tPyObject\t*", p.name, ";\n")
+
+            if ty.is_single_opaque():
+                have_opaque = True
+
+        o.pyhdr("\t} x;\n")
+        if have_opaque:
+            o.pyhdr("\tPy_buffer dec_buf;\n")
+
+    o.pyhdr("};\n")
+
+    # We need to have a new function if the object is to be allocatable by the
+    # Python interpreter
+    o.pysrc("\n")
+    o.pysrc("static PyObject *\n")
+    o.pysrc(struct_req, "_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds)\n")
+    o.pysrc("{\n")
+    o.pysrc("\tPyObject *obj;\n")
+    o.pysrc("\n")
+    o.pysrc("\tobj = subtype->tp_alloc(subtype, 1);\n")
+    if params:
+        o.pysrc("\tif (obj) {\n")
+        o.pysrc("\t\tstruct ", struct_req, " *self = (struct ", struct_req, " *)obj;\n")
+        o.pysrc("\t\tmemset(&self->x, 0, sizeof(self->x));\n")
+        o.pysrc("\t}\n")
+    o.pysrc("\treturn obj;\n")
+    o.pysrc("}\n")
+
+    # We have to have a deallocation function
+    o.pysrc("\n")
+    o.pysrc("static void ", struct_req, "_dealloc(struct ", struct_req, " *self)\n")
+    o.pysrc("{\n")
+    for p in complex_params:
+        o.pysrc("\tPy_XDECREF(self->x.", p.name, ");\n")
+    o.pysrc("\tPy_TYPE(self)->tp_free((PyObject *)self);\n")
+    o.pysrc("}\n")
+
+    # All elements are made directly accessible to the Python interpreter,
+    # either as integer types or as object types.
+    if params:
+        o.pysrc("\n")
+        o.pysrc("static PyMemberDef ", struct_req, "_members[] = {\n")
+        for p in params:
+            o.where(func.name + ":" + p.name)
+            ty = p.typespec
+            o.pysrc("\t{ \"", p.name, "\", ")
+            if ty.is_single_blob():    o.pysrc("T_OBJECT_EX")
+            elif ty.is_bulk():         o.pysrc("T_OBJECT_EX")
+            elif ty.is_single_int():   o.pysrc(c_to_py_type_map[ty.name])
+            else:
+                o.pysrc("T_OBJECT_EX")
+            o.pysrc(", offsetof(struct ", struct_req, ", x.", p.name, "), 0, \"\"},\n")
+
+        o.pysrc("\t{}\n")
+        o.pysrc("};\n")
+
+    # Emit the Python type definition
+    o.pysrc("\n")
+    o.pysrc("static PyTypeObject ", struct_req, "Type = {\n")
+    o.pysrc("\tPyVarObject_HEAD_INIT(NULL, 0)\n")
+    o.pysrc("\t\"kafs.", func.name, "_", way, "\",\t\t/*tp_name*/\n")
+    o.pysrc("\tsizeof(struct ", struct_req, "),\t/*tp_basicsize*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_itemsize*/\n")
+    o.pysrc("\t(destructor)", struct_req, "_dealloc, /*tp_dealloc*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_print*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_getattr*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_setattr*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_compare*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_repr*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_as_number*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_as_sequence*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_as_mapping*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_hash */\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_call */\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_str*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_getattro*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_setattro*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_as_buffer*/\n")
+    o.pysrc("\tPy_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/\n")
+    o.pysrc("\t\"\",\t\t\t\t/* tp_doc */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_traverse */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_clear */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_richcompare */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_weaklistoffset */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_iter */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_iternext */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_methods */\n")
+    if params:
+        o.pysrc("\t", struct_req, "_members,\n")
+    else:
+        o.pysrc("\t0,\t\t\t\t/* tp_members */\n")
+
+    o.pysrc("\t0,\t\t\t\t/* tp_getset */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_base */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_dict */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_descr_get */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_descr_set */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_dictoffset */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_init */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_alloc */\n")
+    o.pysrc("\t", struct_req, "_new,\n")
+    o.pysrc("};\n")
+
+
+###############################################################################
+#
+# Emit functions to help deal with bulk lists
+#
+###############################################################################
+def emit_py_func_bulk_helper(o, func):
+    for p in func.params:
+        o.where(func.name + ":" + p.name)
+        ty = p.typespec
+        if not ty.is_bulk():
+            continue
+
+        # Data encoding
+        if ty.name not in bulk_get_helpers:
+            bulk_get_helpers[ty.name] = True
+
+            o.pysrc("\n")
+            o.pysrc("static __attribute__((unused))\n")
+            o.pysrc("int py_encode_bulk_", ty.name, "(struct rx_call *call, PyObject *list)\n")
+            o.pysrc("{\n")
+            o.pysrc("\tPyObject *item;\n")
+            o.pysrc("\tunsigned count, i;\n")
+            o.pysrc("\n")
+            o.pysrc("\tcount = PyList_Size(list);\n")
+            o.pysrc("\trxrpc_enc(call, count);\n")
+            o.pysrc("\n")
+            o.pysrc("\tfor (i = 0; i < count; i++) {\n")
+            o.pysrc("\t\titem = PyList_GetItem(list, i);\n")
+            o.pysrc("\t\tif (!item)\n")
+            o.pysrc("\t\t\treturn -1;\n")
+
+            o.pysrc("\n")
+            if ty.is_bulk_int():
+                o.pysrc("\t\tif (!PyLong_Check(item)) {\n")
+                o.pysrc("\t\t\tPyErr_SetString(PyExc_TypeError, \"Expected list of ", ty.name, "\");\n")
+                o.pysrc("\t\t\treturn -1;\n")
+                o.pysrc("\t\t}\n")
+            else:
+                o.pysrc("\t\tif (py_premarshal_", ty.name, "(item))\n")
+                o.pysrc("\t\t\treturn -1;\n")
+
+            if ty.is_bulk_int():
+                if ty.name == "int64_t":
+                    o.pysrc("\t\tuint64_t x = PyLong_AsLongLong(item);\n")
+                    o.pysrc("\t\trxrpc_enc(call, x >> 32);\n")
+                    o.pysrc("\t\trxrpc_enc(call, x);\n")
+                elif ty.name == "uint64_t":
+                    o.pysrc("\t\tuint64_t x = PyLong_AsUnsignedLongLong(item);\n")
+                    o.pysrc("\t\trxrpc_enc(call, x >> 32);\n")
+                    o.pysrc("\t\trxrpc_enc(call, x);\n")
+                elif ty.name.startswith("int"):
+                    o.pysrc("\t\trxrpc_enc(call, PyLong_AsLong(item));\n")
+                elif ty.name.startswith("uint") or ty.name.startswith("char"):
+                    o.pysrc("\t\trxrpc_enc(call, PyLong_AsUnsignedLong(item));\n")
+                else:
+                    raise RuntimeError
+            else:
+                o.pysrc("\t\trxgen_encode_", ty.name, "(call, &((struct py_", ty.name, " *)item)->x);\n")
+
+            o.pysrc("\t}\n")
+            o.pysrc("\treturn 0;\n")
+            o.pysrc("}\n")
+
+###############################################################################
+#
+# Emit a python wrapper function to make a simple synchronous call
+#
+###############################################################################
+def emit_py_func_simple_sync_call(o, func):
+
+    o.xdr.py_func_defs.append(py_func_def(func.name, "kafs_" + func.name))
+
+    o.pysrc("\n")
+    o.pysrc("PyObject *\n")
+    o.pysrc("kafs_", func.name, "(PyObject *_self, PyObject *args)\n")
+    o.pysrc("{\n")
+
+    # Local variable declarations representing parameters to send
+    o.pysrc("\tstruct rx_call *call;\n")
+    o.pysrc("\tstruct py_rx_connection *z_conn;\n")
+    o.pysrc("\tstruct py_", func.name, "_response *response;\n")
+    for p in func.request:
+        o.where(func.name + ":" + p.name)
+        ty = p.typespec
+        if ty.is_single_blob():
+            o.pysrc("\tPy_buffer param_", p.name, ";\n")
+        elif ty.is_single_int():
+            o.pysrc("\t", ty.name, " param_", p.name, ";\n")
+        elif ty.is_single_struct():
+            o.pysrc("\tstruct py_", ty.name, " *param_", p.name, ";\n")
+        elif ty.is_bulk():
+            if p.direction != "OUT":
+                o.pysrc("\tPyObject *param_", p.name, ";\n")
+        else:
+            raise RuntimeError("Unsupported type \"" + str(ty) + "\"")
+
+    if func.split:
+        o.pysrc("\tPyObject *split_callback, *split_info;\n")
+    o.pysrc("\tPyObject *res = NULL;\n")
+    o.pysrc("\tint ret;\n")
+
+    # Make use of the tuple parser to extract the arguments and check their
+    # types for us.
+    o.pysrc("\n")
+    o.pysrc("\tif (!PyArg_ParseTuple(args, \"O!")
+
+    for p in func.request:
+        o.where(func.name + ":" + p.name)
+        ty = p.typespec
+        if ty.is_bulk():               o.pysrc("O!")
+        elif ty.is_array():            raise RuntimeError
+        elif ty.name == "int8_t":      o.pysrc("B")
+        elif ty.name == "int16_t":     o.pysrc("h")
+        elif ty.name == "int32_t":     o.pysrc("i")
+        elif ty.name == "int64_t":     o.pysrc("L")
+        elif ty.name == "uint8_t":     o.pysrc("b")
+        elif ty.name == "uint16_t":    o.pysrc("H")
+        elif ty.name == "uint32_t":    o.pysrc("I")
+        elif ty.name == "uint64_t":    o.pysrc("K")
+        elif ty.is_single_struct():    o.pysrc("O!")
+        elif ty.is_single_string():    o.pysrc("s*")
+        elif ty.is_single_opaque():    o.pysrc("z*")
+        else:
+            raise RuntimeError("No py parse for param")
+
+    if func.split:
+        o.pysrc("O")
+    o.pysrc("\",\n")
+    o.pysrc("\t\t\t      &py_rx_connectionType, &z_conn")
+
+    for p in func.request:
+        o.where(func.name + ":" + p.name)
+        ty = p.typespec
+        o.pysrc(",\n")
+        o.pysrc("\t\t\t      ")
+        if ty.is_single_int():
+            o.pysrc("&param_", p.name)
+        elif ty.is_single_struct():
+            o.pysrc("&py_", ty.name, "Type, &param_", p.name)
+        elif ty.is_single_blob():
+            o.pysrc("&param_", p.name)
+        elif ty.is_bulk():
+            o.pysrc("&PyList_Type, &param_", p.name)
+        else:
+            raise RuntimeError(": Unsupported type \"" + str(ty) + "\"")
+
+    if func.split:
+        o.pysrc(",\n\t\t\t      &split_callback")
+    o.pysrc("))\n")
+    o.pysrc("\t\treturn NULL;\n")
+
+    if func.split:
+        o.pysrc("\n")
+        o.pysrc("\tsplit_info = py_rxgen_split_client_prepare();\n")
+        o.pysrc("\tif (!split_info)\n")
+        o.pysrc("\t\treturn NULL;\n")
+
+    o.pysrc("\n")
+    o.pysrc("\tcall = rxrpc_alloc_call(z_conn->x, 0);\n")
+    o.pysrc("\tif (!call) {\n")
+    if func.split:
+        o.pysrc("\t\tPy_XDECREF(split_info);\n");
+    o.pysrc("\t\treturn PyErr_NoMemory();\n")
+    o.pysrc("\t}\n")
+    o.pysrc("\tcall->decoder_cleanup = py_rxgen_decoder_cleanup;\n")
+    if func.split:
+        o.pysrc("\tpy_rxgen_split_client_set(call, split_callback, split_info);\n")
+
+    # Marshal the arguments
+    o.pysrc("\n")
+    o.pysrc("\trxrpc_enc(call, ", func.opcode.name, ");\n")
+    for p in func.request:
+        o.where(func.name + ":" + p.name)
+        ty = p.typespec
+        if ty.is_blob():
+            dim = -1
+            if ty.max_size:
+                dim = ty.max_size.name
+            o.pysrc("\tif (py_enc_buffer(call, &param_", p.name, ", ", dim, ") < 0) {\n")
+            o.pysrc("\t\trxrpc_terminate_call(call, EINVAL);\n")
+            o.pysrc("\t\treturn NULL;\n")
+            o.pysrc("\t}\n")
+        elif ty.is_bulk():
+            o.pysrc("\tif (py_encode_bulk_", ty.name, "(call, param_", p.name, ") < 0)\n")
+            o.pysrc("\t\tgoto error;\n")
+        elif ty.is_single_int32():
+            o.pysrc("\trxrpc_enc(call, param_", p.name, ");\n")
+        elif ty.is_single_int64():
+            o.pysrc("\trxrpc_enc(call, param_", p.name, " >> 32);\n")
+            o.pysrc("\trxrpc_enc(call, param_", p.name, ");\n")
+        elif ty.is_single_struct():
+            o.pysrc("\tif (py_premarshal_", ty.name, "((PyObject *)param_", p.name, ")) {\n")
+            o.pysrc("\t\trxrpc_terminate_call(call, EINVAL);\n")
+            o.pysrc("\t\treturn NULL;\n")
+            o.pysrc("\t}\n")
+            o.pysrc("\trxgen_encode_", ty.name, "(call, &param_", p.name, "->x);\n")
+        else:
+            raise RuntimeError("Unsupported type in decode " + str(ty))
+
+    o.pysrc("\tif (rxrpc_post_enc(call) < 0)\n")
+    o.pysrc("\t\tgoto error_no_res;\n")
+
+    # Allocate a reply object
+    o.pysrc("\n")
+    o.pysrc("\tres = _PyObject_New(&py_", func.name, "_responseType);\n")
+    o.pysrc("\tresponse = (struct py_", func.name, "_response *)res;\n")
+    o.pysrc("\tif (!response)\n")
+    o.pysrc("\t\tgoto enomem;\n")
+    if func.response:
+        o.pysrc("\tmemset(&response->x, 0, sizeof(response->x));\n")
+    o.pysrc("\tcall->decoder = py_", func.name, "_decode_response;\n")
+    o.pysrc("\tcall->decoder_private = response;\n")
+
+    # Transmit the split data
+    if func.split:
+        o.pysrc("\tif (py_rxgen_split_transmit(call) < 0)\n")
+        o.pysrc("\t\tgoto error_no_res;\n")
+    else:
+        o.pysrc("\tcall->more_send = 0;\n")
+
+        # Make the call
+        o.pysrc("\n")
+        o.pysrc("\tret = rxrpc_send_data(call);\n")
+        o.pysrc("\tif (ret == -1)\n")
+        o.pysrc("\t\tgoto error;\n")
+
+    # Wait for the reply
+    #
+    # If we're dealing with a split function or are in asynchronous mode, we
+    # need to return the call here.
+    #
+    o.pysrc("\n")
+    o.pysrc("\tret = rxrpc_run_sync_call(call);\n")
+    o.pysrc("\tif (ret == -1)\n")
+    o.pysrc("\t\tgoto error;\n")
+
+    # Successful return
+    o.pysrc("\n")
+    o.pysrc("\trxrpc_terminate_call(call, 0);\n")
+    o.pysrc("\treturn res;\n")
+
+    # Error cleanups
+    o.pysrc("\n")
+    o.pysrc("error:\n")
+    o.pysrc("\tPy_XDECREF(res);\n")
+    o.pysrc("error_no_res:\n")
+    o.pysrc("\tif (errno == ENOMEM)\n")
+    o.pysrc("enomem:\n")
+    o.pysrc("\t\tres = PyErr_NoMemory();\n")
+    o.pysrc("\telse if (errno == ECONNABORTED)\n")
+    o.pysrc("\t\tres = py_rxgen_received_abort(call);\n")
+    o.pysrc("\telse\n")
+    o.pysrc("\t\tres = PyErr_SetFromErrno(PyExc_IOError);\n")
+    o.pysrc("\trxrpc_terminate_call(call, ENOMEM);\n")
+    o.pysrc("\treturn res;\n")
+
+    # End the function
+    o.pysrc("}\n")
+
+###############################################################################
+#
+# Emit a function to decode a block into a python object in a way that can be
+# used from asynchronous code.  The opcode is expected to have been removed
+# from the incoming call on the server side.
+#
+###############################################################################
+def emit_py_func_decode(o, func, side, subname, params):
+    ptr = "obj->"
+
+    # We fetch the data in a number of phases.  Each phase receives a chunk of
+    # data of a certain size.  A phase's size might be dependent on a variable
+    # in the previous phase.  Variable-sized bulk arrays are split across
+    # multiple phases, with the length being at the end of the first phase and
+    # the data in the second.
+    #
+    # We also need to interpolate a phase to deal with decoding split-op
+    # auxiliary data.  This comes last when decoding the request and first when
+    # decoding the response.
+    #
+    phases = list()
+    phase = None
+    have_bulk = False
+    want_item = False
+
+    if func.split and subname == "response":
+        phase = decode_phase(form="split",
+                             size="py_rxgen_split_receive(call)")
+        phases.append(phase)
+        phase = None
+        have_bulk = True
+
+    for p in params:
+        o.where(func.name + ":" + p.name)
+        ty = p.typespec
+
+        if not phase:
+            phase = decode_phase(form="flat")
+            phases.append(phase)
+
+        if ty.is_single_int() or ty.is_single_struct():
+            phase.size += ty.xdr_size
+            phase.params.append(p)
+        elif ty.is_single_blob():
+            have_bulk = True
+
+            # Bulk objects begin with an element count
+            phase.elem_count = phase.size
+            phase.size += 4
+
+            count_type = xdr_type(o.xdr, base=o.xdr.get_type("uint32_t"))
+            pseudoparam = xdr_member("nr__" + p.name, count_type, o.xdr)
+            pseudoparam.special = "blob_size"
+            phase.params.append(pseudoparam)
+
+            # Create a new phase
+            phase = decode_phase(form="blob", name=p.name, size=4, xdr_size=ty.xdr_size)
+            phase.params.append(p)
+            phases.append(phase)
+
+            # We don't want to be asking recvmsg() for one object at a time if
+            # they're really small.
+            phase.size = ty.xdr_size
+            phase = None
+        elif ty.is_bulk():
+            have_bulk = True
+
+            # Bulk objects begin with an element count
+            phase.elem_count = phase.size
+            phase.size += 4
+
+            count_type = xdr_type(o.xdr, base=o.xdr.get_type("uint32_t"))
+            pseudoparam = xdr_member("nr__" + p.name, count_type, o.xdr)
+            pseudoparam.special = "bulk_size"
+            phase.params.append(pseudoparam)
+
+            # Create a new phase
+            phase = decode_phase(form="bulk", name=p.name, size=4, xdr_size=ty.xdr_size)
+            phase.params.append(p)
+            phases.append(phase)
+
+            want_item = True
+
+            # We don't want to be asking recvmsg() for one object at a time if
+            # they're really small.
+            phase.size = ty.xdr_size
+            phase = None
+        else:
+            raise RuntimeError("Reply array not supported")
+
+    if func.split and subname == "request":
+        phase = decode_phase(form="split",
+                             size="py_rxgen_split_receive(call)")
+        phases.append(phase)
+        phase = None
+        have_bulk = True
+
+    # Function definition and arguments
+    o.pysrc("\n")
+    o.pysrc("int py_", func.name, "_decode_", subname, "(struct rx_call *call)\n")
+    o.pysrc("{\n")
+
+    if not params and not func.split:
+        o.pysrc("\treturn 0;\n")
+        o.pysrc("}\n")
+        return
+
+    # Local variables
+    if params:
+        o.pysrc("\tstruct py_", func.name, "_", subname, " *obj = call->decoder_private;\n")
+    if want_item:
+        o.pysrc("\tPyObject *item;\n")
+    o.pysrc("\tunsigned phase = call->phase;\n")
+    o.pysrc("\tunsigned count;\n")
+
+    # Deal with each phase
+    o.pysrc("\n")
+    if have_bulk:
+        o.pysrc("select_phase:\n")
+    o.pysrc("\tcount = call->data_count;\n")
+    #o.pysrc("\tprintf(\"-- Phase %u (%u) --\\n\", phase, count);\n")
+    o.pysrc("\tswitch (phase) {\n")
+
+    o.pysrc("\tcase 0:\n")
+
+    next_phase_id = 1
+    for phase in phases:
+        phase.phase_id = next_phase_id
+        next_phase_id += 1
+
+    phase_goto_label = None
+    for phase in phases:
+        phase_id = phase.phase_id
+        o.pysrc("\n")
+        o.pysrc("\t\t/* --- Phase ", phase_id, " --- */\n")
+
+        if phase_goto_label == phase_id:
+            o.pysrc("\tphase_", phase_id, ":\n")
+            phase_goto_label = None
+
+        # Determine how big bulk objects are
+        if phase.form == "blob":
+            p = phase.params[0]
+            ty = p.typespec
+            if ty.is_single_string():
+                o.pysrc("\t\tswitch (py_dec_init_string(call, &obj->x.", p.name, ")) {\n")
+            elif ty.is_single_opaque():
+                o.pysrc("\t\tobj->x.", p.name, " = PyByteArray_FromStringAndSize(\"\", 0);\n")
+                o.pysrc("\t\tif (!obj->x.", p.name, ")\n")
+                o.pysrc("\t\t\treturn -1;\n")
+                o.pysrc("\t\tif (PyByteArray_Resize(obj->x.", p.name, ", call->blob_size) == -1)\n")
+                o.pysrc("\t\t\treturn -1;\n")
+
+                o.pysrc("\t\tswitch (py_dec_init_opaque(call, obj->x.", p.name, ")) {\n")
+            else:
+                raise RuntimeError("Unsupported blob type " + str(ty))
+
+            o.pysrc("\t\tcase -1: return -1;\n")
+            o.pysrc("\t\tcase  0: goto phase_", phase_id + 1, ";\n")
+            o.pysrc("\t\tcase  1: break;\n")
+            o.pysrc("\t\t}\n")
+            phase_goto_label = phase_id + 1
+
+        elif phase.form == "bulk":
+            p = phase.params[0]
+            ty = p.typespec
+            if ty.is_bulk_int() or ty.is_bulk_struct():
+                o.pysrc("\t\tobj->x.", p.name, " = PyList_New(call->bulk_count);\n")
+                o.pysrc("\t\tif (!obj->x.", p.name, ")\n")
+                o.pysrc("\t\t\treturn -1;\n")
+            else:
+                raise RuntimeError
+
+            o.pysrc("\t\tif (call->bulk_count == 0)\n")
+            o.pysrc("\t\t\tgoto phase_", phase_id + 1, ";\n")
+            phase_goto_label = phase_id + 1
+            o.pysrc("\t\tcall->bulk_index = 0;\n")
+
+        # Entry point for a phase
+        elif phase.form == "split":
+            o.pysrc("\t\tif (py_rxgen_split_receive(call, 1) < 0)\n")
+            o.pysrc("\t\t\treturn -1;\n")
+            o.pysrc("\t\tif (call->need_size == 0)\n")
+            o.pysrc("\t\t\tgoto phase_", phase_id + 1, ";\n")
+            phase_goto_label = phase_id + 1
+
+        o.pysrc("\t\tcall->phase = ", phase_id, ";\n")
+        o.pysrc("\tcase ", phase_id, ":\n")
+
+        if phase.form != "split":
+            o.pysrc("\t\tcall->need_size = ", phase.size, ";\n")
+            o.pysrc("\t\tif (count < call->need_size)\n")
+            o.pysrc("\t\t\treturn 1;\n")
+        else:
+            o.pysrc("\t\tif (call->need_size == UINT_MAX ? count == 0 : count < call->need_size) {\n")
+            #o.pysrc("\t\t\tprintf(\"NEED %u (phase %u)\\n\", call->need_size, phase);\n")
+            o.pysrc("\t\t\treturn 1;\n")
+            o.pysrc("\t\t}\n")
+
+        # Unmarshal the data
+        o.pysrc("\n")
+        for p in phase.params:
+            o.where(func.name + ":" + p.name)
+            ty = p.typespec
+            if p.special == None:
+                pass
+            elif p.special == "blob_size":
+                o.pysrc("\t\tcall->blob_size = rxrpc_dec(call);\n")
+                continue
+            elif p.special == "bulk_size":
+                o.pysrc("\t\tcall->bulk_count = rxrpc_dec(call);\n")
+                continue
+            else:
+                raise RuntimeError
+
+            if ty.is_bulk():
+                if ty.is_bulk_struct():
+                    o.pysrc("\t\titem = py_decode_", ty.name, "(call);\n")
+                elif ty.is_bulk_int32() and ty.name.startswith("u"):
+                    o.pysrc("\t\titem = PyLong_FromUnsignedLong((", ty.name, ")rxrpc_dec(call));\n")
+                elif ty.is_bulk_int32():
+                    o.pysrc("\t\titem = PyLong_FromLong((", ty.name, ")rxrpc_dec(call));\n")
+                elif ty.is_bulk_int64() and ty.name.startswith("u"):
+                    o.pysrc("\t\tcall->bulk_u64  = (uint64_t)rxrpc_dec(call) << 32;\n")
+                    o.pysrc("\t\tcall->bulk_u64 |= (uint64_t)rxrpc_dec(call);\n")
+                    o.pysrc("\t\titem = PyLong_FromUnsignedLongLong(call->bulk_u64);\n")
+                elif ty.is_bulk_int64():
+                    o.pysrc("\t\tcall->bulk_s64  = (int64_t)rxrpc_dec(call) << 32;\n")
+                    o.pysrc("\t\tcall->bulk_s64 |= (int64_t)rxrpc_dec(call);\n")
+                    o.pysrc("\t\titem = PyLong_FromLongLong(call->bulk_s64);\n")
+                else:
+                    raise RuntimeError
+
+                o.pysrc("\t\tif (!item)\n")
+                o.pysrc("\t\t\treturn -1;\n")
+                o.pysrc("\t\tif (PyList_SetItem(obj->x.", p.name, ", call->bulk_index, item) < 0)\n")
+                o.pysrc("\t\t\treturn -1;\n")
+                o.pysrc("\t\tcall->bulk_index++;\n")
+
+            elif ty.is_single_blob():
+                if ty.is_single_string():
+                    o.pysrc("\t\tswitch (py_dec_into_string(call)) {\n")
+                else:
+                    o.pysrc("\t\tswitch (py_dec_into_buffer(call)) {\n")
+                o.pysrc("\t\tcase -1: return -1;\n")
+                o.pysrc("\t\tcase  0: break;\n")
+                o.pysrc("\t\tcase  1: phase = ", phase_id, "; goto select_phase;\n")
+                o.pysrc("\t\t}\n")
+            elif ty.is_single_int32():
+                o.pysrc("\t\tobj->x.", p.name, " = (", ty.name, ")rxrpc_dec(call);\n")
+            elif ty.is_single_int64():
+                o.pysrc("\t\tobj->x.", p.name, " = (", ty.name, ")rxrpc_dec(call) << 32;\n")
+                o.pysrc("\t\tobj->x.", p.name, " |= (", ty.name, ")rxrpc_dec(call) << 32;\n")
+            elif ty.is_single_struct():
+                o.pysrc("\t\tobj->x.", p.name, " = py_decode_", ty.name, "(call);\n")
+            else:
+                raise RuntimeError("Unsupported type in decode")
+
+            if ty.is_single_string():
+                o.pysrc("\t\tif (rxrpc_post_dec(call) < 0)\n")
+                o.pysrc("\t\t\treturn -1;\n")
+                o.pysrc("\t\tif (call->blob_offset < call->blob_size) {\n")
+                o.pysrc("\t\t\tphase = ", phase_id, ";\n")
+                o.pysrc("\t\t\tgoto select_phase;\n")
+                o.pysrc("\t\t}\n")
+            if ty.is_bulk():
+                o.pysrc("\t\tif (rxrpc_post_dec(call) < 0)\n")
+                o.pysrc("\t\t\treturn -1;\n")
+                o.pysrc("\t\tif (call->bulk_index < call->bulk_count) {\n")
+                o.pysrc("\t\t\tphase = ", phase_id, ";\n")
+                o.pysrc("\t\t\tgoto select_phase;\n")
+                o.pysrc("\t\t}\n")
+
+        if phase.form == "split":
+            o.pysrc("\t\tswitch (py_rxgen_split_receive(call, 0)) {\n")
+            o.pysrc("\t\tcase -1: return -1;\n")
+            o.pysrc("\t\tcase  0: break;\n")
+            o.pysrc("\t\tcase  1: phase = ", phase_id, "; goto select_phase;\n")
+            o.pysrc("\t\t}\n")
+            #o.pysrc("\t\tif (rxrpc_post_dec(call) < 0)\n")
+            #o.pysrc("\t\t\treturn -1;\n")
+            #o.pysrc("\t\tif (call->need_size != 0) {\n")
+            #o.pysrc("\t\t\tphase = ", phase_id, ";\n")
+            #o.pysrc("\t\t\tgoto select_phase;\n")
+            #o.pysrc("\t\t}\n")
+
+        if phase.form != "bulk" and phase.form != "blob":
+            o.pysrc("\t\tif (rxrpc_post_dec(call) < 0)\n")
+            o.pysrc("\t\t\treturn -1;\n")
+
+    o.pysrc("\n")
+    o.pysrc("\t\t/* --- Phase ", next_phase_id, " --- */\n")
+    if phase_goto_label:
+        o.pysrc("\tphase_", next_phase_id, ":\n")
+    o.pysrc("\t\tcall->phase = ", next_phase_id, ";\n")
+    o.pysrc("\t\tcall->need_size = 0;\n")
+    o.pysrc("\tdefault:\n")
+    o.pysrc("\t\treturn 0;\n")
+    o.pysrc("\t}\n")
+
+    o.pysrc("}\n")
diff --git a/rxgen/emit_py_types.py b/rxgen/emit_py_types.py
new file mode 100644 (file)
index 0000000..529303a
--- /dev/null
@@ -0,0 +1,387 @@
+# Emission of Python type wrappers
+# -*- coding: utf-8 -*-
+
+__copyright__ = """
+Copyright (C) 2015 Red Hat, Inc. All Rights Reserved.
+Written by David Howells (dhowells@redhat.com)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public Licence version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public Licence for more details.
+
+You should have received a copy of the GNU General Public Licence
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+from rxgen_bits import *
+
+###############################################################################
+#
+# Emit python type wrapper declarations
+#
+###############################################################################
+def emit_py_type_wrapper_decls(o, s):
+    o.pysrc("static PyTypeObject py_", s.name, "Type;\n")
+
+
+###############################################################################
+#
+# Emit python type wrappers for C structs.
+#
+###############################################################################
+def emit_py_type_wrapper(o, struct):
+    o.xdr.py_type_defs.append(py_type_def(struct.name, "py_" + struct.name + "Type"))
+
+    # Divide the struct members into single ints, single structs, char arrays
+    # (strings) and other arrays
+    single_ints = list();
+    single_structs = list();
+    char_arrays = list();
+    arrays = list();
+    for m in struct.members:
+        ty = m.typespec
+        o.where(struct.name + "::" + m.name)
+        if ty.is_char_array():
+            char_arrays.append(m)
+        elif ty.is_single_basic():
+            single_ints.append(m)
+        elif ty.is_single_struct():
+            single_structs.append(m)
+        elif ty.is_array():
+            arrays.append(m)
+        else:
+            o.error(": Unsupported struct member type")
+
+    # Write a python wrapper struct
+    #
+    # We have a copy of the raw struct and we also have caches for python
+    # objects for non-integer, non-array bits of the struct.  We populate the
+    # caches when these bits are called for and then fold their contents back
+    # into the raw struct when we're about to marshal it.
+    #
+    o.pyhdr("\n")
+    o.pyhdr("struct py_", struct.name, " {\n")
+    o.pyhdr("\tPyObject_HEAD\n")
+    o.pyhdr("\tstruct ", struct.name, " x;\n")
+    if single_structs or arrays:
+        o.pyhdr("\tstruct {\n")
+        for m in single_structs + arrays:
+            o.pyhdr("\t\tPyObject *", m.name, ";\n")
+        o.pyhdr("\t} c;\n")
+    o.pyhdr("};\n")
+
+    # We need to have a new function if the object is to be allocatable by the
+    # Python interpreter
+    o.pysrc("\n")
+    o.pysrc("static PyObject *\n")
+    o.pysrc("py_", struct.name, "_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds)\n")
+    o.pysrc("{\n")
+    o.pysrc("\treturn subtype->tp_alloc(subtype, 1);\n;")
+    o.pysrc("}\n")
+
+    # We have to have a deallocation function
+    o.pysrc("\n")
+    o.pysrc("static void\n")
+    o.pysrc("py_", struct.name, "_dealloc(struct py_", struct.name, " *self)\n")
+    o.pysrc("{\n")
+    for m in single_structs + arrays:
+        o.pysrc("\tPy_XDECREF(self->c.", m.name, ");\n")
+    o.pysrc("\tPy_TYPE(self)->tp_free((PyObject *)self);\n")
+    o.pysrc("}\n")
+
+    # Any integer non-array elements are made directly accessible to the Python
+    # interpreter
+    if single_ints:
+        o.pysrc("\n")
+        o.pysrc("static PyMemberDef py_", struct.name, "_members[] = {\n")
+        for m in single_ints:
+            ty = m.typespec
+            o.where(struct.name + "::" + m.name)
+            o.pysrc("\t{ \"", m.name, "\", ")
+            if ty.name == "char":
+                o.pysrc("T_CHAR")
+            elif ty.name == "int8_t":
+                o.pysrc("T_BYTE")
+            elif ty.name == "int16_t":
+                o.pysrc("T_SHORT")
+            elif ty.name == "int32_t":
+                o.pysrc("T_INT")
+            elif ty.name == "int64_t":
+                o.pysrc("T_LONGLONG")
+            elif ty.name == "uint8_t":
+                o.pysrc("T_UBYTE")
+            elif ty.name == "uint16_t":
+                o.pysrc("T_USHORT")
+            elif ty.name == "uint32_t":
+                o.pysrc("T_UINT")
+            elif ty.name == "uint64_t":
+                o.pysrc("T_ULONGLONG")
+            else:
+                o.error(": Unsupported type \"", ty.name, "\"")
+            o.pysrc(", offsetof(struct py_", struct.name, ", x.", m.name, "), 0, \"\"},\n")
+        o.pysrc("\t{}\n")
+        o.pysrc("};\n")
+
+    # Non-single integer elements need to be turned into their respective
+    # Python types and returned.
+
+    # Array elements have to be accessed through ->tp_[sg]etattro() as
+    # tuples (int[]/uint[]/struct[]) or strings (char[])
+    #
+    attro_list = char_arrays + single_structs + arrays
+    if attro_list:
+        # The attribute get function
+        o.pysrc("\n")
+        o.pysrc("static PyObject *\n")
+        o.pysrc("py_", struct.name, "_getattro(PyObject *_self, PyObject *name)\n")
+        o.pysrc("{\n")
+        o.pysrc("\tstruct py_", struct.name, " *self = (struct py_", struct.name, " *)_self;\n")
+        o.pysrc("\n")
+        o.pysrc("\tif (PyUnicode_Check(name)) {\n")
+
+        for m in attro_list:
+            ty = m.typespec
+            o.where(struct.name + "::" + m.name)
+            o.pysrc("\t\tif (PyUnicode_CompareWithASCIIString(name, \"", m.name, "\") == 0)\n")
+            if ty.is_single_struct():
+                o.pysrc("\t\t\treturn py_rxgen_get_struct(&self->x.", m.name, ",\n")
+                o.pysrc("\t\t\t\t\t\t   &self->c.", m.name, ",\n")
+                o.pysrc("\t\t\t\t\t\t   py_data_to_", ty.name, ");\n")
+            elif not ty.is_array():
+                raise RuntimeError("Unsupported basic type \"" + str(ty) + "\"")
+            elif ty.is_struct_array():
+                o.pysrc("\t\t\treturn py_rxgen_get_structs(&self->x.", m.name, ", ", ty.dim.name, ",\n")
+                o.pysrc("\t\t\t\t\t\t    sizeof(struct ", ty.name, "),\n")
+                o.pysrc("\t\t\t\t\t\t    &self->c.", m.name, ",\n")
+                o.pysrc("\t\t\t\t\t\t    py_data_to_", ty.name, ");\n")
+            elif not ty.is_int_array():
+                o.error(": Unsupported array type class \"", ty, "\"")
+            elif ty.name == "char":
+                o.pysrc("\t\t\treturn py_rxgen_get_string(&self->x.", m.name, ", ", ty.dim.name, ");\n")
+            elif ty.name == "uint8_t":
+                o.pysrc("\t\t\treturn py_rxgen_get_uint8(&self->x.", m.name, ", ", ty.dim.name, ",\n")
+                o.pysrc("\t\t\t\t\t\t  &self->c.", m.name, ");\n")
+            elif ty.name == "uint16_t":
+                o.pysrc("\t\t\treturn py_rxgen_get_uint16(&self->x.", m.name, ", ", ty.dim.name, ",\n")
+                o.pysrc("\t\t\t\t\t\t   &self->c.", m.name, ");\n")
+            elif ty.name == "uint32_t":
+                o.pysrc("\t\t\treturn py_rxgen_get_uint32(&self->x.", m.name, ", ", ty.dim.name, ",\n")
+                o.pysrc("\t\t\t\t\t\t   &self->c.", m.name, ");\n")
+            elif ty.name == "int8_t":
+                o.pysrc("\t\t\treturn py_rxgen_get_int8(&self->x.", m.name, ", ", ty.dim.name, ",\n")
+                o.pysrc("\t\t\t\t\t\t  &self->c.", m.name, ");\n")
+            elif ty.name == "int16_t":
+                o.pysrc("\t\t\treturn py_rxgen_get_int16(&self->x.", m.name, ", ", ty.dim.name, ",\n")
+                o.pysrc("\t\t\t\t\t\t   &self->c.", m.name, ");\n")
+            elif ty.name == "int32_t":
+                o.pysrc("\t\t\treturn py_rxgen_get_int32(&self->x.", m.name, ", ", ty.dim.name, ",\n")
+                o.pysrc("\t\t\t\t\t\t   &self->c.", m.name, ");\n")
+            else:
+                raise RuntimeError("Unsupported array type \"" + str(ty) + "\"")
+
+        o.pysrc("\t}\n")
+        o.pysrc("\n")
+        o.pysrc("\treturn PyObject_GenericGetAttr(_self, name);\n")
+        o.pysrc("}\n")
+        o.pysrc("\n")
+
+        # The attribute set function
+        o.pysrc("static int\n")
+        o.pysrc("py_", struct.name, "_setattro(PyObject *_self, PyObject *name, PyObject *val)\n")
+        o.pysrc("{\n")
+        o.pysrc("\tstruct py_", struct.name, " *self = (struct py_", struct.name, " *)_self;\n")
+        o.pysrc("\n")
+        o.pysrc("\tif (PyUnicode_Check(name)) {\n")
+
+        for m in attro_list:
+            ty = m.typespec
+            o.where(struct.name + "::" + m.name)
+            o.pysrc("\t\tif (PyUnicode_CompareWithASCIIString(name, \"", m.name, "\") == 0)\n")
+            if ty.is_single_struct():
+                o.pysrc("\t\t\treturn py_rxgen_set_struct(&self->c.", m.name, ",\n")
+                o.pysrc("\t\t\t\t\t\t   &py_", ty.name, "Type, val);\n")
+            elif not ty.is_array():
+                raise RuntimeError("Unsupported basic type \"" + str(ty) + "\"")
+            elif ty.is_char_array():
+                o.pysrc("\t\t\treturn py_rxgen_set_string(&self->x.", m.name, ", ", ty.dim.name, ", val);\n")
+            elif ty.is_int_array() or ty.is_struct_array():
+                o.pysrc("\t\t\treturn py_rxgen_set_array(", ty.dim.name, ", &self->c.", m.name, ", val);\n")
+            else:
+                raise RuntimeError("Unsupported array type \"" + str(ty) + "\"")
+
+        o.pysrc("\t}\n")
+        o.pysrc("\n")
+        o.pysrc("\treturn PyObject_GenericSetAttr(_self, name, val);\n")
+        o.pysrc("}\n")
+        o.pysrc("\n")
+
+    # Emit the Python type definition
+    o.pysrc("static PyTypeObject py_", struct.name, "Type = {\n")
+    o.pysrc("\tPyVarObject_HEAD_INIT(NULL, 0)\n")
+    o.pysrc("\t\"kafs.", struct.name, "\",\t\t/*tp_name*/\n")
+    o.pysrc("\tsizeof(struct py_", struct.name, "),\t/*tp_basicsize*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_itemsize*/\n")
+    o.pysrc("\t(destructor)py_", struct.name, "_dealloc, /*tp_dealloc*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_print*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_getattr*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_setattr*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_compare*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_repr*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_as_number*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_as_sequence*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_as_mapping*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_hash */\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_call*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_str*/\n")
+    if attro_list:
+        o.pysrc("\tpy_", struct.name, "_getattro,\n")
+        o.pysrc("\tpy_", struct.name, "_setattro,\n")
+    else:
+        o.pysrc("\t0,\t\t\t\t/*tp_getattro*/\n")
+        o.pysrc("\t0,\t\t\t\t/*tp_setattro*/\n")
+    o.pysrc("\t0,\t\t\t\t/*tp_as_buffer*/\n")
+    o.pysrc("\tPy_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/\n")
+    o.pysrc("\t\"\",\t\t\t/* tp_doc */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_traverse */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_clear */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_richcompare */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_weaklistoffset */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_iter */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_iternext */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_methods */\n")
+    if single_ints:
+        o.pysrc("\tpy_", struct.name, "_members,\n")
+    else:
+        o.pysrc("\t0,\t\t\t/* tp_members */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_getset */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_base */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_dict */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_descr_get */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_descr_set */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_dictoffset */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_init */\n")
+    o.pysrc("\t0,\t\t\t\t/* tp_alloc */\n")
+    o.pysrc("\tpy_", struct.name, "_new,\n")
+    o.pysrc("};\n")
+
+    # Emit a function to allocate such a type
+    o.pyhdr("extern PyObject *kafs_new_py_", struct.name, "(PyObject *, PyObject *);\n")
+
+    o.pysrc("\n")
+    o.pysrc("PyObject *\n")
+    o.pysrc("kafs_new_py_", struct.name, "(PyObject *_self, PyObject *args)\n")
+    o.pysrc("{\n")
+    o.pysrc("\tPyObject *obj = _PyObject_New(&py_", struct.name, "Type);\n")
+    o.pysrc("\tstruct py_", struct.name, " *self = (struct py_", struct.name, " *)obj;\n")
+    o.pysrc("\tif (!obj)\n")
+    o.pysrc("\t\treturn PyExc_MemoryError;\n")
+    o.pysrc("\tmemset(&self->x, 0, sizeof(self->x));\n")
+    if single_structs or arrays:
+        o.pysrc("\tmemset(&self->c, 0, sizeof(self->c));\n")
+    o.pysrc("\treturn obj;\n")
+    o.pysrc("}\n")
+
+    # Emit a function to create an object of this type from raw data
+    o.pyhdr("extern PyObject *py_data_to_", struct.name, "(const void *);\n")
+
+    o.pysrc("\n")
+    o.pysrc("PyObject *py_data_to_", struct.name, "(const void *data)\n")
+    o.pysrc("{\n")
+    o.pysrc("\tPyObject *obj = _PyObject_New(&py_", struct.name, "Type);\n")
+    o.pysrc("\tstruct py_", struct.name, " *self = (struct py_", struct.name, " *)obj;\n")
+    o.pysrc("\tif (!obj)\n")
+    o.pysrc("\t\treturn PyExc_MemoryError;\n")
+    o.pysrc("\tmemcpy(&self->x, data, sizeof(self->x));\n")
+    if single_structs or arrays:
+        o.pysrc("\tmemset(&self->c, 0, sizeof(self->c));\n")
+    o.pysrc("\treturn obj;\n")
+    o.pysrc("}\n")
+
+    # Emit a function to unmarshal on object of this type.
+    o.pysrc("\n")
+    o.pysrc("PyObject *py_decode_", struct.name, "(struct rx_call *call)\n")
+    o.pysrc("{\n")
+    o.pysrc("\tPyObject *obj = _PyObject_New(&py_", struct.name, "Type);\n")
+    o.pysrc("\tstruct py_", struct.name, " *self = (struct py_", struct.name, " *)obj;\n")
+    o.pysrc("\tif (!obj)\n")
+    o.pysrc("\t\treturn PyExc_MemoryError;\n")
+    o.pysrc("\trxgen_decode_", struct.name, "(call, &self->x);\n")
+    if single_structs or arrays:
+        o.pysrc("\tmemset(&self->c, 0, sizeof(self->c));\n")
+    o.pysrc("\treturn obj;\n")
+    o.pysrc("}\n")
+
+    # Emit a function to premarshal such a type.  This checks the Python object
+    # type and folds the contents of the cached Python objects into their raw
+    # fields.
+    #
+    o.pyhdr("extern int py_premarshal_", struct.name, "(PyObject *);\n")
+
+    o.pysrc("\n")
+    o.pysrc("int py_premarshal_", struct.name, "(PyObject *_self)\n")
+    o.pysrc("{\n")
+    o.pysrc("\tstruct py_", struct.name, " *self = (struct py_", struct.name, " *)_self;\n")
+
+    # Check that the type we've been given is the right one
+    o.pysrc("\n")
+    o.pysrc("\tif (!PyObject_TypeCheck(self, &py_", struct.name, "Type)) {\n")
+    o.pysrc("\t\tPyErr_Format(PyExc_TypeError, \"Expected object of type ", struct.name, "\");\n")
+    o.pysrc("\t\treturn -1;\n")
+    o.pysrc("\t}\n")
+
+    if single_structs or arrays:
+        o.pysrc("\n")
+        first = 1
+        for m in single_structs + arrays:
+            ty = m.typespec
+            o.where(struct.name + "::" + m.name)
+            if first:
+                o.pysrc("\tif (")
+                first = 0
+            else:
+                o.pysrc(" ||\n")
+                o.pysrc("\t    ")
+
+            if ty.is_single_struct():
+                o.pysrc("py_rxgen_premarshal_struct(&self->x.", m.name, ",\n")
+                o.pysrc("\t\t\t\t      sizeof(struct ", ty.name, "),\n")
+                o.pysrc("\t\t\t\t      offsetof(struct py_", ty.name, ", x),\n")
+                o.pysrc("\t\t\t\t      self->c.", m.name, ",\n")
+                o.pysrc("\t\t\t\t      py_premarshal_", ty.name, ") < 0")
+            elif not ty.is_array():
+                raise RuntimeError("Unsupported basic type \"" + str(ty) + "\"")
+            elif ty.is_struct_array():
+                o.pysrc("py_rxgen_premarshal_structs(&self->x.", m.name, ",\n")
+                o.pysrc("\t\t\t\t\t", ty.dim.name, ", sizeof(struct ", ty.name, "),\n")
+                o.pysrc("\t\t\t\t\toffsetof(struct py_", ty.name, ", x),\n")
+                o.pysrc("\t\t\t\t\tself->c.", m.name, ",\n")
+                o.pysrc("\t\t\t\t\tpy_premarshal_", ty.name, ") < 0")
+            elif not ty.is_int_array():
+                raise RuntimeError("Unsupported array type \"", ty, "\"")
+            elif ty.name == "uint8_t":
+                o.pysrc("py_rxgen_premarshal_uint8(&self->x.", m.name, ", ", ty.dim.name, ", self->c.", m.name, ") < 0")
+            elif ty.name == "uint16_t":
+                o.pysrc("py_rxgen_premarshal_uint16(&self->x.", m.name, ", ", ty.dim.name, ", self->c.", m.name, ") < 0")
+            elif ty.name == "uint32_t":
+                o.pysrc("py_rxgen_premarshal_uint32(&self->x.", m.name, ", ", ty.dim.name, ", self->c.", m.name, ") < 0")
+            elif ty.name == "int8_t":
+                o.pysrc("py_rxgen_premarshal_int8(&self->x.", m.name, ", ", ty.dim.name, ", self->c.", m.name, ") < 0")
+            elif ty.name == "int16_t":
+                o.pysrc("py_rxgen_premarshal_int16(&self->x.", m.name, ", ", ty.dim.name, ", self->c.", m.name, ") < 0")
+            elif ty.name == "int32_t":
+                o.pysrc("py_rxgen_premarshal_int32(&self->x.", m.name, ", ", ty.dim.name, ", self->c.", m.name, ") < 0")
+            else:
+                o.error(": Unsupported array type \"", ty.name, "\"")
+
+        o.pysrc(")\n")
+        o.pysrc("\t\treturn -1;\n")
+
+    o.pysrc("\treturn 0;\n")
+    o.pysrc("}\n")
+
diff --git a/rxgen/rxgen.py b/rxgen/rxgen.py
new file mode 100755 (executable)
index 0000000..7142037
--- /dev/null
@@ -0,0 +1,745 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+#
+# Tool for processing an RxRPC-based RPC API definition in a C header file to
+# produce (un)marshalling code and RPC functions to implement that API.
+#
+# It also produces a python module containing wrappers for the types, RPC
+# functions and constants in the API definition.
+
+__copyright__ = """
+Copyright (C) 2015 Red Hat, Inc. All Rights Reserved.
+Written by David Howells (dhowells@redhat.com)
+
+Yacc & Lex bits based on pynfs rpcgen.py code:
+
+    Written by Fred Isaman <iisaman@citi.umich.edu>
+    Copyright (C) 2004 University of Michigan, Center for
+                       Information Technology Integration
+    Based on version written by Peter Astrand <peter@cendio.se>
+    Copyright (C) 2001 Cendio Systems AB (http://www.cendio.se)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public Licence version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public Licence for more details.
+
+You should have received a copy of the GNU General Public Licence
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+import sys
+import keyword
+import time
+import os
+from rxgen_bits import *
+from emit_c_struct import *
+from emit_c_sync_funcs import *
+from emit_py_types import *
+from emit_py_sync_funcs import *
+from emit_py_module import *
+
+xdr = None              # Current context
+
+##########################################################################
+#                                                                        #
+#                            Lexical analysis                            #
+#                                                                        #
+##########################################################################
+import ply.lex as lex
+
+basic_types = (
+    ( "char",      "char",      xdr_basic.int32,   4 ),
+    ( "int8_t",    "int8_t",    xdr_basic.int32,   4 ),
+    ( "int16_t",   "int16_t",   xdr_basic.int32,   4 ),
+    ( "int32_t",   "int32_t",   xdr_basic.int32,   4 ),
+    ( "int64_t",   "int64_t",   xdr_basic.int64,   8 ),
+    ( "uint8_t",   "uint8_t",   xdr_basic.int32,   4 ),
+    ( "uint16_t",  "uint16_t",  xdr_basic.int32,   4 ),
+    ( "uint32_t",  "uint32_t",  xdr_basic.int32,   4 ),
+    ( "uint64_t",  "uint64_t",  xdr_basic.int64,   8 ),
+    ( "string",    "char",      xdr_basic.string,  4 ),
+    ( "opaque",    "void",      xdr_basic.opaque,  4 ),
+    )
+
+def load_basic_types(xdr):
+    global basic_types
+    for i in basic_types:
+        name = i[0]
+        t = xdr_type(xdr, name=name, c_name=i[1], basic=i[2], xdr_size=i[3])
+        xdr.types[name] = t
+
+keywords = (
+    "IN",
+    "INOUT",
+    "OUT",
+    "const",
+    "enum",
+    "multi",
+    "package",
+    "split",
+    "struct",
+    "typedef",
+) + tuple([i[0] for i in basic_types])
+
+# Required by lex.  Each token also allows a function t_<token>.
+tokens = tuple([t.upper() for t in keywords]) + (
+    "ID", "CONST10", "CONST8", "CONST16",
+    # ( ) [ ] { }
+    "LPAREN", "RPAREN", "LSQUARE", "RSQUARE", "LBRACE", "RBRACE",
+    # ; : < > * = ,
+    "SEMI", "LT", "GT", "STAR", "EQUALS", "COMMA",
+    "BEGIN_ERROR_CODES", "END_ERROR_CODES", "NEWFILE"
+)
+
+# t_<name> functions are used by lex.  They are called with t.value==<match
+# of rule in comment>, and t.type==<name>.  They expect a return value
+# with attribute type=<token>
+
+# Tell lexer to ignore Whitespace.
+t_ignore = " \t"
+
+def t_NEWFILE(t):
+    r'__NEWFILE__ [^\n]*'
+    t.lexer.source = t.value[12:]
+    t.lexer.lineno = 0
+    return t
+
+def t_ID(t):
+    r'[A-Za-z][A-Za-z0-9_]*'
+    if t.value in keywords:
+        t.type = t.value.upper()
+    return t
+
+def t_CONST16(t):
+    r'0x[0-9a-fA-F]+'
+    return t
+
+def t_CONST8(t):
+    r'0[0-7]+'
+    return t
+
+def t_CONST10(t):
+    r'-?(([1-9]\d*)|0)'
+    return t
+
+# Tokens
+t_LPAREN           = r'\('
+t_RPAREN           = r'\)'
+t_LSQUARE          = r'\['
+t_RSQUARE          = r'\]'
+t_LBRACE           = r'\{'
+t_RBRACE           = r'\}'
+t_SEMI             = r';'
+t_LT               = r'<'
+t_GT               = r'>'
+t_STAR             = r'\*'
+t_EQUALS           = r'='
+t_COMMA            = r','
+t_BEGIN_ERROR_CODES = r'__BEGIN_ERROR_CODES__'
+t_END_ERROR_CODES  = r'__END_ERROR_CODES__'
+
+def t_newline(t):
+    r'\n+'
+    t.lexer.lineno += t.value.count("\n")
+
+def t_error(t):
+    print("Illegal character {:s} at {:d} type {:s}".format(repr(t.value[0]),
+                                                            t.lineno, t.type))
+    t.lexer.skip(1)
+
+# Build the lexer
+lex.lex(debug=0)
+
+##########################################################################
+#                                                                        #
+#                          Yacc Parsing Info                             #
+#                                                                        #
+##########################################################################
+
+def p_specification(t):
+    '''specification : NEWFILE definition_list'''
+
+def p_definition_list(t):
+    '''definition_list : definition definition_list
+                       | empty'''
+
+def p_definition(t):
+    '''definition : package_def
+                  | constant_def
+                  | enum_def
+                  | type_def
+                  | error_code_list_def
+                  | proc_spec'''
+
+def p_package_def(t):
+    '''package_def : PACKAGE ID'''
+    prefix = t[2]
+    name = t[2]
+    if name.endswith("_"):
+        name = name[:-1]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    xdr.add_package(name, prefix)
+
+###############################################################################
+#
+# Constants and values
+#
+def p_constant_def(t):
+    '''constant_def : CONST ID EQUALS constant SEMI'''
+    name = t[2]
+    value = t[4]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    t[0] = xdr.add_constant(name, value)
+
+def p_constant(t):
+    '''constant : CONST10
+                | CONST8
+                | CONST16'''
+    value = t[1]
+    if len(value) > 9:
+        value = value + 'L'
+    global xdr
+    xdr.lineno = t.lineno(1)
+    t[0] = xdr.add_number(value)
+
+def p_value_1(t):
+    '''value : constant'''
+    t[0] = t[1]
+
+def p_value_2(t):
+    '''value : ID'''
+    name = t[1]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    t[0] = xdr.get_constant(name)
+
+def p_optional_value(t):
+    '''optional_value : value
+                      | empty'''
+    # return value or None.
+    t[0] = t[1]
+
+def p_constant_list_1(t):
+    '''constant_list  : constant_def constant_list'''
+    t[0] = [t[1]] + t[2]
+
+def p_constant_list_2(t):
+    '''constant_list  : constant_def'''
+    t[0] = [t[1]]
+
+def p_error_code_list_def(t):
+    '''error_code_list_def : BEGIN_ERROR_CODES constant_list END_ERROR_CODES'''
+    global xdr
+    xdr.lineno = t.lineno(1)
+    xdr.add_error_codes(t[2])
+
+def p_enum_def(t):
+    '''enum_def       : ENUM ID LBRACE enum_list RBRACE SEMI'''
+    '''enum_def       : ENUM ID LBRACE enum_list COMMA RBRACE SEMI'''
+    name = t[2]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    enum = xdr_type(xdr, base=xdr.get_type("int32_t"))
+    t[0] = xdr.add_type(name, enum)
+
+def p_enum_list_1(t):
+    '''enum_list      : enum_term'''
+    t[0] = [t[1]]
+
+def p_enum_list_2(t):
+    '''enum_list      : enum_term COMMA enum_list'''
+    t[0] = [t[1]] + t[3]
+
+def p_enum_term(t):
+    '''enum_term      : ID EQUALS constant'''
+    name = t[1]
+    value = t[3]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    t[0] = xdr.add_constant(name, value)
+
+###############################################################################
+#
+# Type definition
+#
+def p_type_def_1(t):
+    '''type_def : TYPEDEF declaration SEMI'''
+    name = t[2].name
+    typespec = t[2].typespec
+    global xdr
+    xdr.lineno = t.lineno(1)
+    alias = xdr_type_alias(name, typespec, xdr)
+    xdr.add_type_alias(name, alias)
+    #xdr.debug("Typedef", name)
+
+def p_type_def_2(t):
+    '''type_def : STRUCT ID struct_body SEMI'''
+    name = t[2]
+    body = t[3]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    if name in xdr.structs:
+        xdr.error("Structure {:s} already exists".format(name))
+    else:
+        s = xdr_type(xdr, name=name, c_name="struct " + name,
+                     basic=xdr_basic.struct, members=body)
+        xdr.structs[name] = s
+        xdr.all_structs.append(s)
+        xdr.add_type(name, s)
+    xdr.debug("New struct", name)
+
+###############################################################################
+#
+# Type specification
+#
+def p_declaration_1(t):
+    '''declaration : type_specifier ID'''
+    typespec = t[1]
+    name = t[2]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    t[0] = xdr_member(name, typespec, xdr);
+
+def p_declaration_2(t):
+    '''declaration : type_specifier STAR ID'''
+    # We ignore the pointer type marker
+    typespec = t[1]
+    name = t[3]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    ty = xdr_type(xdr, base=typespec)
+    t[0] = xdr_member(name, ty, xdr)
+
+def p_declaration_3(t):
+    '''declaration : type_specifier ID LSQUARE value RSQUARE'''
+    typespec = t[1]
+    name = t[2]
+    array_size = t[4]
+    if not isinstance(typespec, xdr_type):
+        raise RuntimeError("Type is not type" + str(typespec))
+    global xdr
+    xdr.lineno = t.lineno(1)
+    ty = xdr_type(xdr, base=typespec, array=xdr_array.fixed, dim=array_size)
+    t[0] = xdr_member(name, ty, xdr)
+
+def p_declaration_4(t):
+    '''declaration : type_specifier ID LT optional_value GT'''
+    typespec = t[1]
+    name = t[2]
+    max_size = t[4]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    if typespec.is_single_blob():
+        ty = xdr_type(xdr, base=typespec, max_size=max_size)
+    else:
+        ty = xdr_type(xdr, base=typespec, array=xdr_array.bulk, max_size=max_size)
+    t[0] = xdr_member(name, ty, xdr)
+
+def p_declaration_5(t):
+    '''declaration : type_specifier STAR ID LT optional_value GT'''
+    typespec = t[1]
+    name = t[3]
+    max_size = t[5]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    if typespec.is_single_blob():
+        ty = xdr_type(xdr, base=typespec, max_size=max_size)
+    else:
+        ty = xdr_type(xdr, base=typespec, array=xdr_array.bulk, max_size=max_size)
+    t[0] = xdr_member(name, ty, xdr)
+
+def p_declaration_6(t):
+    '''declaration : type_specifier LT optional_value GT STAR ID'''
+    typespec = t[1]
+    max_size = t[3]
+    name = t[6]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    ty = xdr_type(xdr, base=typespec, array=xdr_array.bulk, max_size=max_size)
+    t[0] = xdr_member(name, ty, xdr)
+
+def p_type_specifier_1(t):
+    '''type_specifier : CHAR
+                      | INT8_T
+                      | INT16_T
+                      | INT32_T
+                      | INT64_T
+                      | UINT8_T
+                      | UINT16_T
+                      | UINT32_T
+                      | UINT64_T
+                      | STRING
+                      | OPAQUE'''
+    name = t[1]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    t[0] = xdr.types[name]
+    if not isinstance(t[0], xdr_type):
+        raise RuntimeError("Type is not type" + typespec);
+
+def p_type_specifier_2(t):
+    '''type_specifier : ID'''
+    name = t[1]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    t[0] = xdr.get_type(t[1])
+
+def p_type_specifier_3(t):
+    '''type_specifier : struct_type_spec'''
+    t[0] = t[1]
+
+def p_type_specifier_4(t):
+    '''type_specifier : STRUCT ID'''
+    name = t[2]
+    global xdr
+    xdr.lineno = t.lineno(1)
+    t[0] = xdr.get_type(name)
+
+
+###############################################################################
+#
+# Structure specification
+#
+def p_struct_type_spec(t):
+    '''struct_type_spec : STRUCT struct_body'''
+    t[0] = xdr_type(xdr, name="<anon struct {:s}>".format(t.lineno), c_name="struct",
+                    compound=xdr_compound.struct, basic_type="struct", members=t[2])
+
+def p_struct_body(t):
+    '''struct_body : LBRACE member_list RBRACE'''
+    t[0] = t[2]
+
+def p_member_list_1(t):
+    '''member_list : declaration SEMI'''
+    t[0] = [t[1]]
+
+def p_member_list_2(t):
+    '''member_list : declaration SEMI member_list'''
+    t[0] = [t[1]] + t[3]
+
+###############################################################################
+#
+# Procedure specification
+#
+def p_proc_spec_1(t):
+    '''proc_spec : ID LPAREN parameters RPAREN EQUALS value SEMI'''
+    name = t[1]
+    params = t[3]
+    op = t[6]
+    global xdr
+    proc = xdr_proc(name, xdr, params, op)
+    xdr.add_proc(proc)
+
+def p_proc_spec_2(t):
+    '''proc_spec : ID LPAREN parameters RPAREN SPLIT EQUALS value SEMI'''
+    name = t[1]
+    params = t[3]
+    op = t[7]
+    global xdr
+    proc = xdr_proc(name, xdr, params, op, split=True)
+    xdr.add_proc(proc)
+
+def p_proc_spec_3(t):
+    '''proc_spec : ID LPAREN parameters RPAREN MULTI EQUALS value SEMI'''
+    name = t[1]
+    params = t[3]
+    op = t[7]
+    global xdr
+    proc = xdr_proc(name, xdr, params, op, multi=True)
+    xdr.add_proc(proc)
+
+def p_parameters_0(t):
+    '''parameters : '''
+    t[0] = []
+
+def p_parameters_1(t):
+    '''parameters : parameter'''
+    t[0] = [t[1]]
+
+def p_parameters_2(t):
+    '''parameters : parameter COMMA parameters'''
+    t[0] = [t[1]] + t[3]
+
+def p_parameter_1(t):
+    '''parameter : IN declaration'''
+    t[2].direction = xdr_direction.IN
+    t[0] = t[2]
+
+def p_parameter_2(t):
+    '''parameter : INOUT declaration'''
+    t[2].direction = xdr_direction.INOUT
+    t[0] = t[2]
+
+def p_parameter_3(t):
+    '''parameter : OUT declaration'''
+    t[2].direction = xdr_direction.OUT
+    t[0] = t[2]
+
+###############################################################################
+#
+# Miscellany
+#
+def p_empty(t):
+    'empty :'
+
+def p_error(t):
+    global error_occurred
+    error_occurred = True
+    if t:
+        print(t)
+        print("Syntax error at '{:s}' (lineno {:d})".format(t.value, t.lineno))
+    else:
+        print("Syntax error: unexpectedly hit EOF")
+
+##########################################################################
+#                                                                        #
+#                          Global Variables                              #
+#                                                                        #
+##########################################################################
+
+error_occurred = False # Parsing of infile status
+
+
+
+##########################################################################
+#                                                                        #
+#                       C Preprocessor                                   #
+#                                                                        #
+##########################################################################
+def cpp(data, filename):
+    # Remove C comments
+    lines = data.splitlines()
+    inside_C_comment = False
+    inside_error_code_list = False
+
+    for i in range(0, len(lines)):
+        l = lines[i]
+        begin = None
+        if inside_C_comment:
+            begin = 0
+        cursor = 0
+
+        # We need to capture the error code list so that we can build a set of
+        # Python exceptions for it - but the only way to do that is to snoop
+        # the comments
+        #
+        if not inside_C_comment:
+            if l == "/* Error codes */" and not inside_error_code_list:
+                inside_error_code_list = True
+                lines[i] = "__BEGIN_ERROR_CODES__"
+                continue
+
+            if inside_error_code_list and l == "":
+                inside_error_code_list = False
+                lines[i] = "__END_ERROR_CODES__"
+                continue
+
+        while True:
+            if inside_C_comment:
+                p = l.find("*/", cursor)
+                if p == -1:
+                    l = l[:begin]
+                    lines[i] = l
+                    break
+                l = l[:begin] + l[p + 2:]
+                lines[i] = l
+                cursor = begin
+                begin = None
+                inside_C_comment = False
+            else:
+                p = l.find("/*");
+                if p == -1:
+                    break
+                inside_C_comment = True
+                begin = p
+                cursor += 2
+
+    if inside_error_code_list:
+        lines.append("__END_ERROR_CODES__")
+
+    # Remove C++ comments
+    for i in range(0, len(lines)):
+        l = lines[i]
+        p = l.find("//")
+        if p != -1:
+            lines[i] = l[:p]
+
+    # Remove directives
+    skipping = False
+    for i in range(0, len(lines)):
+        l = lines[i]
+        if skipping:
+            if l == "#endif":
+                skipping = False;
+            lines[i] = ""
+            continue
+        if l == "#if 0":
+            skipping = True;
+            lines[i] = ""
+            continue
+        if (l.startswith("#include") or
+            l.startswith("#define") or
+            l.startswith("%")):
+            lines[i] = ""
+            continue
+
+    lines.insert(0, "__NEWFILE__ {:s}".format(filename))
+    return "\n".join(lines)
+
+##########################################################################
+#                                                                        #
+#                          Main Loop                                     #
+#                                                                        #
+##########################################################################
+xdr = xdr_context()
+load_basic_types(xdr)
+xdr.add_constant("RXRPC_SECURITY_PLAIN",   "0")
+xdr.add_constant("RXRPC_SECURITY_AUTH",    "1")
+xdr.add_constant("RXRPC_SECURITY_ENCRYPT", "2")
+
+# Parse the input data with yacc
+import ply.yacc as yacc
+yacc.yacc(debug=0)
+
+def parse(infile, debug=False):
+    global xdr
+    xdr.source = infile
+    xdr.lineno = 0
+
+    f = open(infile)
+    data = f.read()
+    f.close()
+
+    data = cpp(data, infile)
+
+    print("Parsing", infile);
+    global yacc
+    yacc.parse(data, debug=debug)
+
+    if error_occurred:
+        print
+        print("Error occurred, did not write output files")
+        return False
+    return True
+
+#
+# Section: main
+#
+if __name__ == "__main__":
+    if len(sys.argv) < 2:
+        print("Usage: {:s} <filename>*".format(sys.argv[0]))
+        sys.exit(1)
+
+    for f in sys.argv[1:]:
+        if not parse(f):
+            break
+
+    xdr.finished_parsing()
+
+    o = file_generator(xdr)
+    o.rxhdr("/* AUTOGENERATED */\n")
+    #o.rxhdr("#define _XOPEN_SOURCE\n";
+    o.rxhdr("#include <stdint.h>\n")
+    o.rxhdr("#include \"rxgen.h\"\n")
+
+    o.rxsrc("/* AUTOGENERATED */\n")
+    o.rxsrc("#include \"afs_xg.h\"\n")
+    o.rxsrc("#include <stdio.h>\n")
+    o.rxsrc("#include <stdlib.h>\n")
+    o.rxsrc("#include <string.h>\n")
+    o.rxsrc("#include <unistd.h>\n")
+    o.rxsrc("#include <errno.h>\n")
+    o.rxsrc("#include <sys/socket.h>\n")
+    o.rxsrc("#include <sys/param.h>\n")
+    o.rxsrc("#include <arpa/inet.h>\n")
+    o.rxsrc("\n")
+
+    o.pyhdr("/* AUTOGENERATED */\n")
+    o.pyhdr("#include <Python.h>\n")
+    o.pyhdr("#include \"afs_xg.h\"\n")
+    o.pyhdr("#include \"py_rxgen.h\"\n")
+
+    o.pysrc("/* AUTOGENERATED */\n")
+    o.pysrc("#include <Python.h>\n")
+    o.pysrc("#include \"structmember.h\"\n")
+    o.pysrc("#include \"afs_py.h\"\n")
+    o.pysrc("#include <arpa/inet.h>\n")
+    o.pysrc("\n")
+
+    # Declare constants
+    o.rxhdr("\n")
+    for name in xdr.all_constants:
+        c = xdr.constants[name]
+        o.rxhdr("#define ", c.name, " ", c.value, "\n")
+
+    # Declare structure types
+    for s in xdr.all_structs:
+        emit_struct_encdec_decl(o, s)
+        emit_py_type_wrapper_decls(o, s)
+
+    for s in xdr.all_structs:
+        emit_struct_encdec(o, s);
+        emit_py_type_wrapper(o, s);
+
+    # Emit RPC call functions.  For this we need to classify parameters according
+    # to input and output usage and work out how big the RPC messages will be.
+    #
+    for f in xdr.funcs:
+        # Dump the banner comment block
+        o.rxsrc("/*\n")
+        o.rxsrc(" * RPC Call ", f.name, "\n")
+        o.rxsrc(" */\n")
+
+        # Find the Operation ID
+        if not f.opcode:
+            raise RuntimeError("Operation ID unspecified for " + f.name)
+
+        # Filter the parameters into request and response
+        f.request = list()
+        f.response = list()
+
+        for p in f.params:
+            o.where(f.name + ":" + p.name)
+            ty = p.typespec
+            if ty.is_single_basic():
+                pass
+            elif ty.is_struct():
+                assert(ty.xdr_size)
+            elif ty.is_single_blob():
+                # Could validate max_size attribute
+                pass
+            elif ty.is_bulk():
+                assert(ty.xdr_size)
+            else:
+                raise RuntimeError("Unsupported param type \"" + str(ty) + "\"")
+
+            if p.direction == xdr_direction.IN:
+                f.request.append(p)
+            elif p.direction == xdr_direction.OUT:
+                f.response.append(p)
+            elif p.direction == xdr_direction.INOUT:
+                f.request.append(p)
+                f.response.append(p)
+
+        emit_func_prototype(o, f)
+        emit_func_decode(o, f, "client", "response", f.response)
+        emit_func_send(o, f, "request")
+        #emit_func_decode(f, "server", "request", request)
+        #emit_func_send(f, "response")
+
+        emit_py_func_param_object(o, f, "request")
+        emit_py_func_param_object(o, f, "response")
+        emit_py_func_bulk_helper(o, f)
+        emit_py_func_decode(o, f, "client", "response", f.response)
+        emit_py_func_decode(o, f, "server", "request", f.request)
+        emit_py_func_simple_sync_call(o, f)
+
+    emit_py_module(o);
diff --git a/rxgen/rxgen_bits.py b/rxgen/rxgen_bits.py
new file mode 100644 (file)
index 0000000..a8ad012
--- /dev/null
@@ -0,0 +1,550 @@
+# Bits for rxgen implementation
+# -*- coding: utf-8 -*-
+
+__copyright__ = """
+Copyright (C) 2015 Red Hat, Inc. All Rights Reserved.
+Written by David Howells (dhowells@redhat.com)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public Licence version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public Licence for more details.
+
+You should have received a copy of the GNU General Public Licence
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+import sys
+from enum import Enum
+
+class xdr_basic(Enum):
+    int32 = 1
+    int64 = 2
+    string = 3
+    opaque = 4
+    struct = 5
+
+class xdr_array(Enum):
+    # single object is represented by None
+    fixed = 1
+    bulk = 2
+
+class xdr_direction(Enum):
+    IN = 1
+    OUT = 2
+    INOUT = 3
+
+class xdr_context:
+    def __init__(self):
+        self.source = None              # Current source file
+        self.lineno = None              # Current line number
+        self.types = dict()             # List of types
+        self.typedefs = dict()          # List of type aliases
+        self.structs = dict()           # Structure definitions
+        self.struct_sizes = dict()      # Structure sizes
+        self.all_structs = list()       # Structures in order of declaration
+        self.funcs = list()             # Functions in declaration order
+        self.func_names = dict()        # Function name uniquifier
+        self.constants = dict()         # Constants
+        self.packages = dict()          # Packages
+        self.pkg = None                 # Current package
+        self.abort_syms = dict()        # Abort symbol to code map
+        self.abort_ids = dict()         # Abort code to symbol map
+        self.abort_count = 0            # Number of abort codes
+        self.error_codes = False        # True if parsing error code list
+        self.py_type_defs = list()      # Python type definitions
+        self.py_func_defs = list()      # Python function definitions
+        self.debug_level = False
+        self.zero = xdr_constant("0", "0", self)
+        self.typespecs = dict()
+
+    def debug(self, *s):
+        if (self.debug_level):
+            print(*s)
+
+    def error(self, *s):
+        print("{:s}:{:d}: Error".format(self.source, self.lineno), *s)
+
+    def add_package(self, name, prefix):
+        if name in self.packages:
+            self.error("Package {:s} already exists".format(name))
+        else:
+            pkg = xdr_package(name, prefix, self)
+            self.packages[name] = pkg
+            self.pkg = pkg
+            self.debug("New package", name)
+
+    def add_error_codes(self, codes):
+        self.abort_count += len(codes)
+        self.pkg.abort_codes += codes
+        for code in codes:
+            if not isinstance(code, xdr_constant):
+                raise TypeError("Expecting xdr_constant, got " + str(type(code)))
+            self.abort_syms[code.name] = code
+            value = code.value
+            suffix = ""
+            if value.endswith("L"):
+                value = value[0:-1]
+                suffix = "L"
+            if int(value) < 0:
+                value = str(int(value) + 0x100000000)
+            code.u32 = int(value)
+            self.abort_ids[code.u32] = code
+
+    def add_constant(self, name, value):
+        if name in self.constants:
+            self.error("Constant {:s} already exists".format(name))
+        elif isinstance(value, xdr_constant):
+            self.constants[name] = xdr_constant(name, value.value, self)
+        else:
+            self.constants[name] = xdr_constant(name, value, self)
+            self.debug("New constant", name)
+        return self.constants[name]
+
+    def add_number(self, value):
+        if value == 0:
+            return self.zero
+        return xdr_constant(value, value, self)
+
+    def get_constant(self, name):
+        if name not in self.constants:
+            self.error("Constant {:s} undefined".format(name))
+            return self.zero
+        return self.constants[name]
+    
+    def add_type(self, name, typespec):
+        if name in self.types:
+            self.error("Type {:s} already exists".format(name))
+        else:
+            self.types[name] = typespec
+            self.debug("New type", name)
+        return self.types[name]
+    
+    def add_type_alias(self, name, alias):
+        if name in self.typedefs:
+            self.error("Type alias {:s} already exists".format(name))
+            return self.typedefs[name]
+        if name in self.structs:
+            self.error("struct {:s} already exists, cannot shadow with alias".format(name))
+            return alias
+
+        self.typedefs[name] = alias
+        self.debug("New typedef", name)
+
+    def get_type(self, name):
+        if name in self.types:
+            typespec = self.types[name]
+        elif name in self.typedefs:
+            typespec = self.typedefs[name].typespec
+        else:
+            raise RuntimeError("Undefined type requested" + name);
+        if not isinstance(typespec, xdr_type):
+            raise TypeError("Retrieved type object is not xdr_type" + name + str(typespec));
+        typespec.referenced = True
+        return typespec
+
+    def add_proc(self, proc):
+        if not isinstance(proc, xdr_proc):
+            raise KeyError("proc is not an xdr_proc", name, proc);
+        name = proc.name
+        if name in self.func_names:
+            self.error("Proc {:s} already exists".format(name))
+        else:
+            self.func_names[name] = proc
+            self.funcs.append(proc)
+            self.debug("New proc", name)
+        return self.func_names[name]
+
+    def finished_parsing(self):
+        self.all_constants = list(self.constants.keys())
+        self.all_constants.sort()
+        self.all_types = list(self.types.keys())
+        self.all_types.sort()
+
+
+###############################################################################
+#
+# Token base class
+#
+###############################################################################
+class token_base(object):
+    def __init__(self):
+        self.source = None
+        self.lineno = None
+        self.name = None
+        self.type = None
+
+    def __str__(self):
+        return "{:s} {:s} at {:s}:{:d}".format(
+            self.type, self.name, self.source, self.lineno)
+
+###############################################################################
+#
+# XDR package
+#
+###############################################################################
+class xdr_package(token_base):
+    """The result of 'package ID'"""
+    def __init__(self, name, prefix, xdr):
+        self.type = 'package'
+        self.name = name
+        self.source = xdr.source
+        self.lineno = xdr.lineno
+        self.prefix = prefix
+        self.abort_codes = list()
+
+###############################################################################
+#
+# XDR constant
+#
+###############################################################################
+class xdr_constant(token_base):
+    """The result of 'CONST ID EQUALS constant SEMI'"""
+    def __init__(self, name, value, xdr):
+        self.type = 'const'
+        self.name = name
+        self.value = value
+        self.source = xdr.source
+        self.lineno = xdr.lineno
+        if not isinstance(value, str):
+            raise RuntimeError("Value should be a string");
+
+    def get_value(self):
+        if isinstance(self.value, xdr_constant):
+            return self.value.get_value()
+        return int(self.value)
+
+    def __str__(self):
+        return self.value
+
+    def __repr__(self):
+        return "constant {:s}={:s} at {:s}:{:d}".format(
+            self.name, self.value, self.source, self.lineno)
+
+###############################################################################
+#
+# XDR type definition
+#
+# Each type is specified by a hash of the following elements:
+#
+#       name            Name of non-anonymous type (char, {u,}int{8,16,32,64}_t, struct name)
+#       basic           Basic XDR type (int32, int64, string, opaque, struct)
+#       array           Array class (single, array, bulk)
+#       dim             Number of elements in fixed-size array (or None)
+#       max_size        Max elements in bulk array (if array/bulk)
+#       members         Members of struct
+#       xdr_size        Size of XDR encoded object
+#       source/lineno   Where defined in which file
+#
+# Members/parameters take a copy of their parent type's hash and add:
+#
+#       name            Member or parameter name (overrides type name)
+#       direction       Direction of parameter (IN, OUT or INOUT)
+#
+###############################################################################
+class xdr_type(token_base):
+    def __init__(self, xdr, base=None, name=None, c_name=None,
+                 basic=None, array=None, dim=None, max_size=None,
+                 xdr_size=None,
+                 members=None):
+        if not isinstance(xdr, xdr_context):
+            raise TypeError("XDR context isn't")
+        if array:
+            if not isinstance(array, xdr_array):
+                raise TypeError("Invalid array class")
+            if array == xdr_array.fixed and not dim:
+                raise RuntimeError("Dimension required for fixed-size array")
+            if dim and max_size:
+                raise RuntimeError("Can't be both variable and fixed-size array")
+        elif basic == "string" or basic == "opaque" or base and base.is_blob():
+            if dim:
+                raise RuntimeError("Can't specify fixed dimension limits on string/opaque")
+        else:
+            if dim or max_size:
+                raise RuntimeError("Can't specify dimension limits on non-array")
+
+        self.source = xdr.source
+        self.lineno = xdr.lineno
+        self.referenced = False
+
+        if base:
+            if name:
+               raise RuntimeError("Type basic name can't be changed on an extant type")
+            if c_name:
+               raise RuntimeError("Type C name can't be changed on an extant type")
+            if basic:
+               raise RuntimeError("Basic type can't be changed on an extant type")
+            if members:
+               raise RuntimeError("Members can't be added to an extant type")
+            if not isinstance(base, xdr_type):
+                raise TypeError("Base type is not a type", type(base))
+            base.referenced = True
+            self.referenced = True
+            self.name = base.name
+            self.c_name = base.c_name
+            self.basic = base.basic
+            self.array = base.array
+            self.dim = base.dim
+            self.max_size = base.max_size
+            self.xdr_size = base.xdr_size
+            self.members = base.members
+
+            if array:
+                if base.array:
+                    xdr.error("Array-of-array not supported")
+                self.array = array
+                self.dim = dim
+                self.max_size = max_size
+                if self.xdr_size and dim:
+                    self.xdr_size *= int(str(dim))
+            elif self.is_single_blob() and max_size:
+                if self.max_size:
+                    xdr.error("Maximum size already set on string/opaque")
+                self.max_size = max_size
+        else:
+            if name and not isinstance(name, str):
+                raise TypeError("Type name is not a string")
+            if c_name and not isinstance(c_name, str):
+                raise TypeError("C type name is not a string")
+            if basic and not isinstance(basic, xdr_basic):
+                raise TypeError("Invalid basic XDR type")
+            self.name = name
+            self.c_name = c_name
+            self.basic = basic
+            self.array = array
+            self.xdr_size = xdr_size
+            self.array = array
+            self.dim = dim
+            self.max_size = max_size
+            self.members = members
+            self.xdr_size = xdr_size
+            if members:
+                if not isinstance(members, list):
+                    raise RuntimeError("Members should be a list")
+                self.xdr_size = 0
+                for i in members:
+                    if i.typespec.xdr_size:
+                        self.xdr_size += i.typespec.xdr_size
+
+        if not self.basic:
+           raise RuntimeError("basic type unset")
+
+        typespec = str(self)
+        if typespec not in xdr.typespecs:
+            xdr.typespecs[typespec] = self
+
+
+    def is_int(self):
+        return self.basic == xdr_basic.int32 or self.basic == xdr_basic.int64
+
+    def is_int32(self):
+        return self.basic == xdr_basic.int32
+
+    def is_int64(self):
+        return self.basic == xdr_basic.int64
+
+    def is_string(self):
+        return self.basic == xdr_basic.string
+
+    def is_opaque(self):
+        return self.basic == xdr_basic.opaque
+
+    def is_struct(self):
+        return self.basic == xdr_basic.struct
+
+    def is_blob(self):
+        return self.is_string() or self.is_opaque()
+    
+    def is_single_int(self):
+        return not self.array and self.is_int()
+
+    def is_single_int32(self):
+        return not self.array and self.is_int32()
+
+    def is_single_int64(self):
+        return not self.array and self.is_int64()
+
+    def is_single_string(self):
+        return not self.array and self.is_string()
+
+    def is_single_opaque(self):
+        return not self.array and self.is_opaque()
+
+    def is_single_blob(self):
+        return not self.array and self.is_blob()
+
+    def is_single_basic(self):
+        return not self.array and not self.is_struct()
+
+    def is_single_struct(self):
+        return not self.array and self.is_struct()
+
+    def is_single(self):
+        return not self.array
+
+    def is_array(self):
+        return self.array == xdr_array.fixed
+
+    def is_char_array(self):
+        return self.array == xdr_array.fixed and self.name == "char"
+
+    def is_int32_array(self):
+        return self.array == xdr_array.fixed and self.is_int32()
+
+    def is_int_array(self):
+        return self.array == xdr_array.fixed and self.is_int()
+
+    def is_struct_array(self):
+        return self.array == xdr_array.fixed and self.is_struct()
+
+    def is_bulk(self):
+        return self.array == xdr_array.bulk
+
+    def is_bulk_char(self):
+        return self.is_bulk() and self.name == "char"
+
+    def is_bulk_int32(self):
+        return self.is_bulk() and self.is_int32()
+
+    def is_bulk_int64(self):
+        return self.is_bulk() and self.is_int64()
+
+    def is_bulk_int(self):
+        return self.is_bulk() and self.is_int()
+
+    def is_bulk_struct(self):
+        return self.is_bulk() and self.is_struct()
+
+    def __str__(self):
+        t = self.name
+        if self.is_array():
+            t += "[{:s}]".format(str(self.dim))
+        elif self.is_bulk() or self.is_blob():
+            if self.max_size:
+                t += "<{:s}>".format(str(self.max_size))
+            else:
+                t += "<>"
+        return t
+    
+###############################################################################
+#
+# XDR structure member definition
+#
+###############################################################################
+class xdr_member(token_base):
+    """A structure member"""
+    def __init__(self, name, typespec, xdr):
+        self.typespec = typespec
+        self.name = name
+        self.source = xdr.source
+        self.lineno = xdr.lineno
+        self.special = None
+    
+###############################################################################
+#
+# XDR type alias definition
+#
+###############################################################################
+class xdr_type_alias(token_base):
+    """A type alias"""
+    def __init__(self, name, typespec, xdr):
+        if not isinstance(typespec, xdr_type):
+            raise TypeError("Base type is not a type")
+        self.name = name
+        self.typespec = typespec
+        self.source = xdr.source
+        self.lineno = xdr.lineno
+
+###############################################################################
+#
+# XDR procedure member definition
+#
+###############################################################################
+class xdr_proc(token_base):
+    """An XDR procedure"""
+    def __init__(self, name, xdr, params, opcode, multi=False, split=False):
+        self.name = xdr.pkg.name + "_" + name
+        self.source = xdr.source
+        self.lineno = xdr.lineno
+        self.params = params
+        self.opcode = opcode
+        self.multi = multi
+        self.split = split
+
+###############################################################################
+#
+# Generated file writer class
+#
+###############################################################################
+class file_generator:
+    """File generator class"""
+    def __init__(self, xdr):
+        self.xdr = xdr
+        self._rxhdr = open("2_afs_xg.h", "w", encoding="utf-8")
+        self._rxsrc = open("2_afs_xg.c", "w", encoding="utf-8")
+        self._pyhdr = open("2_afs_py.h", "w", encoding="utf-8")
+        self._pysrc = open("2_afs_py.c", "w", encoding="utf-8")
+
+    def rxhdr(self, *va):
+        for i in va:
+            self._rxhdr.write(str(i))
+
+    def rxsrc(self, *va):
+        for i in va:
+            self._rxsrc.write(str(i))
+
+    def pyhdr(self, *va):
+        for i in va:
+            self._pyhdr.write(str(i))
+
+    def pysrc(self, *va):
+        for i in va:
+            self._pysrc.write(str(i))
+
+    def rxhdrf(self, fmt, *va):
+        self._rxhdr.write(fmt.format(*va))
+
+    def rxsrcf(self, fmt, *va):
+        self._rxsrc.write(fmt.format(*va))
+
+    def pyhdrf(self, fmt, *va):
+        self._pyhdr.write(fmt.format(*va))
+
+    def pysrcf(self, fmt, *va):
+        self._pysrc.write(fmt.format(*va))
+
+    def where(self, loc):
+        self._where = loc + ": "
+        
+    def error(self, *va):
+        if self._where:
+            sys.stdout.write(self._where)
+        for i in va:
+            sys.stdout.write(str(i))
+        sys.stdout.write("\n")
+
+###############################################################################
+#
+# Python type def
+#
+###############################################################################
+class py_type_def:
+    def __init__(self, name, c_type):
+        self.name = name
+        self.c_type = c_type
+
+###############################################################################
+#
+# Python function def
+#
+###############################################################################
+class py_func_def:
+    def __init__(self, name, c_func, doc=""):
+        self.name = name
+        self.c_func = c_func
+        self.doc = doc