diff options
Diffstat (limited to 'lib/command_py.c')
-rw-r--r-- | lib/command_py.c | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/lib/command_py.c b/lib/command_py.c new file mode 100644 index 0000000..f8abcf8 --- /dev/null +++ b/lib/command_py.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * clippy (CLI preparator in python) wrapper for FRR command_graph + * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc. + */ + +/* note: this wrapper is intended to be used as build-time helper. while + * it should be generally correct and proper, there may be the occasional + * memory leak or SEGV for things that haven't been well-tested. + */ + +/* This file is "exempt" from having +#include "config.h" + * as the first include statement because Python.h also does environment + * setup & these trample over each other. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include <Python.h> +#include "structmember.h" +#include <string.h> +#include <stdlib.h> + +#include "command_graph.h" +#include "clippy.h" + +struct wrap_graph; +static PyObject *graph_to_pyobj(struct wrap_graph *graph, + struct graph_node *gn); + +/* + * nodes are wrapped as follows: + * - instances can only be acquired from a graph + * - the same node will return the same wrapper object (they're buffered + * through "idx") + * - a reference is held onto the graph + * - fields are copied for easy access with PyMemberDef + */ +struct wrap_graph_node { + PyObject_HEAD + + bool allowrepeat; + const char *type; + + bool deprecated; + bool hidden; + const char *text; + const char *desc; + const char *varname; + long long min, max; + + struct graph_node *node; + struct wrap_graph *wgraph; + size_t idx; +}; + +/* + * graphs are wrapped as follows: + * - they can only be created by parsing a definition string + * - there's a table here for the wrapped nodes (nodewrappers), indexed + * by "idx" (corresponds to node's position in graph's table of nodes) + * - graphs do NOT hold references to nodes (would be circular) + */ +struct wrap_graph { + PyObject_HEAD + + char *definition; + struct graph *graph; + struct wrap_graph_node **nodewrappers; +}; + +static PyObject *refuse_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + PyErr_SetString(PyExc_ValueError, + "cannot create instances of this type"); + return NULL; +} + +#define member(name, type) \ + { \ + (char *)#name, type, offsetof(struct wrap_graph_node, name), \ + READONLY, (char *)#name " (" #type ")" \ + } +static PyMemberDef members_graph_node[] = { + member(allowrepeat, T_BOOL), member(type, T_STRING), + member(deprecated, T_BOOL), member(hidden, T_BOOL), + member(text, T_STRING), member(desc, T_STRING), + member(min, T_LONGLONG), member(max, T_LONGLONG), + member(varname, T_STRING), {}, +}; +#undef member + +/* + * node.next() -- returns list of all "next" nodes. + * this will include circles if the graph has them. + */ +static PyObject *graph_node_next(PyObject *self, PyObject *args) +{ + struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; + PyObject *pylist; + + if (wrap->node->data + && ((struct cmd_token *)wrap->node->data)->type == END_TKN) + return PyList_New(0); + pylist = PyList_New(vector_active(wrap->node->to)); + for (size_t i = 0; i < vector_active(wrap->node->to); i++) { + struct graph_node *gn = vector_slot(wrap->node->to, i); + PyList_SetItem(pylist, i, graph_to_pyobj(wrap->wgraph, gn)); + } + return pylist; +}; + +/* + * node.join() -- return FORK's JOIN node or None + */ +static PyObject *graph_node_join(PyObject *self, PyObject *args) +{ + struct wrap_graph_node *wrap = (struct wrap_graph_node *)self; + + if (!wrap->node->data + || ((struct cmd_token *)wrap->node->data)->type == END_TKN) + Py_RETURN_NONE; + + struct cmd_token *tok = wrap->node->data; + if (tok->type != FORK_TKN) + Py_RETURN_NONE; + + return graph_to_pyobj(wrap->wgraph, tok->forkjoin); +}; + +static PyMethodDef methods_graph_node[] = { + {"next", graph_node_next, METH_NOARGS, "outbound graph edge list"}, + {"join", graph_node_join, METH_NOARGS, "outbound join node"}, + {}}; + +static void graph_node_wrap_free(void *arg) +{ + struct wrap_graph_node *wrap = arg; + wrap->wgraph->nodewrappers[wrap->idx] = NULL; + Py_DECREF(wrap->wgraph); +} + +static PyTypeObject typeobj_graph_node = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.GraphNode", + .tp_basicsize = sizeof(struct wrap_graph_node), + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "struct graph_node *", + .tp_new = refuse_new, + .tp_free = graph_node_wrap_free, + .tp_members = members_graph_node, + .tp_methods = methods_graph_node, +}; + +static PyObject *graph_to_pyobj(struct wrap_graph *wgraph, + struct graph_node *gn) +{ + struct wrap_graph_node *wrap; + size_t i; + + for (i = 0; i < vector_active(wgraph->graph->nodes); i++) + if (vector_slot(wgraph->graph->nodes, i) == gn) + break; + if (i == vector_active(wgraph->graph->nodes)) { + PyErr_SetString(PyExc_ValueError, "cannot find node in graph"); + return NULL; + } + if (wgraph->nodewrappers[i]) { + PyObject *obj = (PyObject *)wgraph->nodewrappers[i]; + Py_INCREF(obj); + return obj; + } + + wrap = (struct wrap_graph_node *)typeobj_graph_node.tp_alloc( + &typeobj_graph_node, 0); + if (!wrap) + return NULL; + wgraph->nodewrappers[i] = wrap; + Py_INCREF(wgraph); + + wrap->idx = i; + wrap->wgraph = wgraph; + wrap->node = gn; + wrap->type = "NULL"; + wrap->allowrepeat = false; + if (gn->data) { + struct cmd_token *tok = gn->data; + switch (tok->type) { +#define item(x) \ + case x: \ + wrap->type = #x; \ + break /* no semicolon */ + + item(WORD_TKN); // words + item(VARIABLE_TKN); // almost anything + item(RANGE_TKN); // integer range + item(IPV4_TKN); // IPV4 addresses + item(IPV4_PREFIX_TKN); // IPV4 network prefixes + item(IPV6_TKN); // IPV6 prefixes + item(IPV6_PREFIX_TKN); // IPV6 network prefixes + item(MAC_TKN); // MAC address + item(MAC_PREFIX_TKN); // MAC address with mask + item(ASNUM_TKN); // ASNUM + + /* plumbing types */ + item(FORK_TKN); + item(JOIN_TKN); + item(START_TKN); + item(END_TKN); + item(NEG_ONLY_TKN); +#undef item + default: + wrap->type = "???"; + } + + wrap->deprecated = !!(tok->attr & CMD_ATTR_DEPRECATED); + wrap->hidden = !!(tok->attr & CMD_ATTR_HIDDEN); + wrap->text = tok->text; + wrap->desc = tok->desc; + wrap->varname = tok->varname; + wrap->min = tok->min; + wrap->max = tok->max; + wrap->allowrepeat = tok->allowrepeat; + } + + return (PyObject *)wrap; +} + +#define member(name, type) \ + { \ + (char *)#name, type, offsetof(struct wrap_graph, name), \ + READONLY, (char *)#name " (" #type ")" \ + } +static PyMemberDef members_graph[] = { + member(definition, T_STRING), + {}, +}; +#undef member + +/* graph.first() - root node */ +static PyObject *graph_first(PyObject *self, PyObject *args) +{ + struct wrap_graph *gwrap = (struct wrap_graph *)self; + struct graph_node *gn = vector_slot(gwrap->graph->nodes, 0); + return graph_to_pyobj(gwrap, gn); +}; + +static PyMethodDef methods_graph[] = { + {"first", graph_first, METH_NOARGS, "first graph node"}, + {}}; + +static PyObject *graph_parse(PyTypeObject *type, PyObject *args, + PyObject *kwds); + +static void graph_wrap_free(void *arg) +{ + struct wrap_graph *wgraph = arg; + + graph_delete_graph(wgraph->graph); + free(wgraph->nodewrappers); + free(wgraph->definition); +} + +static PyTypeObject typeobj_graph = { + PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.Graph", + .tp_basicsize = sizeof(struct wrap_graph), + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_doc = "struct graph *", + .tp_new = graph_parse, + .tp_free = graph_wrap_free, + .tp_members = members_graph, + .tp_methods = methods_graph, +}; + +/* top call / entrypoint for python code */ +static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + const char *def, *doc = NULL; + struct wrap_graph *gwrap; + static const char *kwnames[] = {"cmddef", "doc", NULL}; + + gwrap = (struct wrap_graph *)typeobj_graph.tp_alloc(&typeobj_graph, 0); + if (!gwrap) + return NULL; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", (char **)kwnames, + &def, &doc)) + return NULL; + + struct graph *graph = graph_new(); + struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL); + graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del); + + struct cmd_element cmd = {.string = def, .doc = doc}; + cmd_graph_parse(graph, &cmd); + cmd_graph_names(graph); + + gwrap->graph = graph; + gwrap->definition = strdup(def); + gwrap->nodewrappers = calloc(vector_active(graph->nodes), + sizeof(gwrap->nodewrappers[0])); + return (PyObject *)gwrap; +} + +static PyMethodDef clippy_methods[] = { + {"parse", clippy_parse, METH_VARARGS, "Parse a C file"}, + {NULL, NULL, 0, NULL}}; + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef pymoddef_clippy = { + PyModuleDef_HEAD_INIT, + "_clippy", + NULL, /* docstring */ + -1, + clippy_methods, +}; +#define modcreate() PyModule_Create(&pymoddef_clippy) +#define initret(val) return val; +#else +#define modcreate() Py_InitModule("_clippy", clippy_methods) +#define initret(val) \ + do { \ + if (!val) \ + Py_FatalError("initialization failure"); \ + return; \ + } while (0) +#endif + +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +PyMODINIT_FUNC command_py_init(void) +{ + PyObject *pymod; + + if (PyType_Ready(&typeobj_graph_node) < 0) + initret(NULL); + if (PyType_Ready(&typeobj_graph) < 0) + initret(NULL); + + pymod = modcreate(); + if (!pymod) + initret(NULL); + + if (PyModule_AddIntMacro(pymod, CMD_ATTR_YANG) + || PyModule_AddIntMacro(pymod, CMD_ATTR_HIDDEN) + || PyModule_AddIntMacro(pymod, CMD_ATTR_DEPRECATED) + || PyModule_AddIntMacro(pymod, CMD_ATTR_NOSH)) + initret(NULL); + + Py_INCREF(&typeobj_graph_node); + PyModule_AddObject(pymod, "GraphNode", (PyObject *)&typeobj_graph_node); + Py_INCREF(&typeobj_graph); + PyModule_AddObject(pymod, "Graph", (PyObject *)&typeobj_graph); + if (!elf_py_init(pymod)) + initret(NULL); + initret(pymod); +} |