]> git.proxmox.com Git - mirror_frr.git/blob - lib/command_py.c
Merge pull request #9489 from opensourcerouting/pim-restruct-20210825
[mirror_frr.git] / lib / command_py.c
1 /*
2 * clippy (CLI preparator in python) wrapper for FRR command_graph
3 * Copyright (C) 2016-2017 David Lamparter for NetDEF, Inc.
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; see the file COPYING; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 /* note: this wrapper is intended to be used as build-time helper. while
21 * it should be generally correct and proper, there may be the occasional
22 * memory leak or SEGV for things that haven't been well-tested.
23 */
24
25 /* This file is "exempt" from having
26 #include "config.h"
27 * as the first include statement because Python.h also does environment
28 * setup & these trample over each other.
29 */
30
31 #include <Python.h>
32 #include "structmember.h"
33 #include <string.h>
34 #include <stdlib.h>
35
36 #include "command_graph.h"
37 #include "clippy.h"
38
39 struct wrap_graph;
40 static PyObject *graph_to_pyobj(struct wrap_graph *graph,
41 struct graph_node *gn);
42
43 /*
44 * nodes are wrapped as follows:
45 * - instances can only be acquired from a graph
46 * - the same node will return the same wrapper object (they're buffered
47 * through "idx")
48 * - a reference is held onto the graph
49 * - fields are copied for easy access with PyMemberDef
50 */
51 struct wrap_graph_node {
52 PyObject_HEAD
53
54 bool allowrepeat;
55 const char *type;
56
57 bool deprecated;
58 bool hidden;
59 const char *text;
60 const char *desc;
61 const char *varname;
62 long long min, max;
63
64 struct graph_node *node;
65 struct wrap_graph *wgraph;
66 size_t idx;
67 };
68
69 /*
70 * graphs are wrapped as follows:
71 * - they can only be created by parsing a definition string
72 * - there's a table here for the wrapped nodes (nodewrappers), indexed
73 * by "idx" (corresponds to node's position in graph's table of nodes)
74 * - graphs do NOT hold references to nodes (would be circular)
75 */
76 struct wrap_graph {
77 PyObject_HEAD
78
79 char *definition;
80 struct graph *graph;
81 struct wrap_graph_node **nodewrappers;
82 };
83
84 static PyObject *refuse_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
85 {
86 PyErr_SetString(PyExc_ValueError,
87 "cannot create instances of this type");
88 return NULL;
89 }
90
91 #define member(name, type) \
92 { \
93 (char *)#name, type, offsetof(struct wrap_graph_node, name), \
94 READONLY, (char *)#name " (" #type ")" \
95 }
96 static PyMemberDef members_graph_node[] = {
97 member(allowrepeat, T_BOOL), member(type, T_STRING),
98 member(deprecated, T_BOOL), member(hidden, T_BOOL),
99 member(text, T_STRING), member(desc, T_STRING),
100 member(min, T_LONGLONG), member(max, T_LONGLONG),
101 member(varname, T_STRING), {},
102 };
103 #undef member
104
105 /*
106 * node.next() -- returns list of all "next" nodes.
107 * this will include circles if the graph has them.
108 */
109 static PyObject *graph_node_next(PyObject *self, PyObject *args)
110 {
111 struct wrap_graph_node *wrap = (struct wrap_graph_node *)self;
112 PyObject *pylist;
113
114 if (wrap->node->data
115 && ((struct cmd_token *)wrap->node->data)->type == END_TKN)
116 return PyList_New(0);
117 pylist = PyList_New(vector_active(wrap->node->to));
118 for (size_t i = 0; i < vector_active(wrap->node->to); i++) {
119 struct graph_node *gn = vector_slot(wrap->node->to, i);
120 PyList_SetItem(pylist, i, graph_to_pyobj(wrap->wgraph, gn));
121 }
122 return pylist;
123 };
124
125 /*
126 * node.join() -- return FORK's JOIN node or None
127 */
128 static PyObject *graph_node_join(PyObject *self, PyObject *args)
129 {
130 struct wrap_graph_node *wrap = (struct wrap_graph_node *)self;
131
132 if (!wrap->node->data
133 || ((struct cmd_token *)wrap->node->data)->type == END_TKN)
134 Py_RETURN_NONE;
135
136 struct cmd_token *tok = wrap->node->data;
137 if (tok->type != FORK_TKN)
138 Py_RETURN_NONE;
139
140 return graph_to_pyobj(wrap->wgraph, tok->forkjoin);
141 };
142
143 static PyMethodDef methods_graph_node[] = {
144 {"next", graph_node_next, METH_NOARGS, "outbound graph edge list"},
145 {"join", graph_node_join, METH_NOARGS, "outbound join node"},
146 {}};
147
148 static void graph_node_wrap_free(void *arg)
149 {
150 struct wrap_graph_node *wrap = arg;
151 wrap->wgraph->nodewrappers[wrap->idx] = NULL;
152 Py_DECREF(wrap->wgraph);
153 }
154
155 static PyTypeObject typeobj_graph_node = {
156 PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.GraphNode",
157 .tp_basicsize = sizeof(struct wrap_graph_node),
158 .tp_flags = Py_TPFLAGS_DEFAULT,
159 .tp_doc = "struct graph_node *",
160 .tp_new = refuse_new,
161 .tp_free = graph_node_wrap_free,
162 .tp_members = members_graph_node,
163 .tp_methods = methods_graph_node,
164 };
165
166 static PyObject *graph_to_pyobj(struct wrap_graph *wgraph,
167 struct graph_node *gn)
168 {
169 struct wrap_graph_node *wrap;
170 size_t i;
171
172 for (i = 0; i < vector_active(wgraph->graph->nodes); i++)
173 if (vector_slot(wgraph->graph->nodes, i) == gn)
174 break;
175 if (i == vector_active(wgraph->graph->nodes)) {
176 PyErr_SetString(PyExc_ValueError, "cannot find node in graph");
177 return NULL;
178 }
179 if (wgraph->nodewrappers[i]) {
180 PyObject *obj = (PyObject *)wgraph->nodewrappers[i];
181 Py_INCREF(obj);
182 return obj;
183 }
184
185 wrap = (struct wrap_graph_node *)typeobj_graph_node.tp_alloc(
186 &typeobj_graph_node, 0);
187 if (!wrap)
188 return NULL;
189 wgraph->nodewrappers[i] = wrap;
190 Py_INCREF(wgraph);
191
192 wrap->idx = i;
193 wrap->wgraph = wgraph;
194 wrap->node = gn;
195 wrap->type = "NULL";
196 wrap->allowrepeat = false;
197 if (gn->data) {
198 struct cmd_token *tok = gn->data;
199 switch (tok->type) {
200 #define item(x) \
201 case x: \
202 wrap->type = #x; \
203 break /* no semicolon */
204
205 item(WORD_TKN); // words
206 item(VARIABLE_TKN); // almost anything
207 item(RANGE_TKN); // integer range
208 item(IPV4_TKN); // IPV4 addresses
209 item(IPV4_PREFIX_TKN); // IPV4 network prefixes
210 item(IPV6_TKN); // IPV6 prefixes
211 item(IPV6_PREFIX_TKN); // IPV6 network prefixes
212 item(MAC_TKN); // MAC address
213 item(MAC_PREFIX_TKN); // MAC address with mask
214
215 /* plumbing types */
216 item(FORK_TKN);
217 item(JOIN_TKN);
218 item(START_TKN);
219 item(END_TKN);
220 item(NEG_ONLY_TKN);
221 #undef item
222 default:
223 wrap->type = "???";
224 }
225
226 wrap->deprecated = (tok->attr == CMD_ATTR_DEPRECATED);
227 wrap->hidden = (tok->attr == CMD_ATTR_HIDDEN);
228 wrap->text = tok->text;
229 wrap->desc = tok->desc;
230 wrap->varname = tok->varname;
231 wrap->min = tok->min;
232 wrap->max = tok->max;
233 wrap->allowrepeat = tok->allowrepeat;
234 }
235
236 return (PyObject *)wrap;
237 }
238
239 #define member(name, type) \
240 { \
241 (char *)#name, type, offsetof(struct wrap_graph, name), \
242 READONLY, (char *)#name " (" #type ")" \
243 }
244 static PyMemberDef members_graph[] = {
245 member(definition, T_STRING),
246 {},
247 };
248 #undef member
249
250 /* graph.first() - root node */
251 static PyObject *graph_first(PyObject *self, PyObject *args)
252 {
253 struct wrap_graph *gwrap = (struct wrap_graph *)self;
254 struct graph_node *gn = vector_slot(gwrap->graph->nodes, 0);
255 return graph_to_pyobj(gwrap, gn);
256 };
257
258 static PyMethodDef methods_graph[] = {
259 {"first", graph_first, METH_NOARGS, "first graph node"},
260 {}};
261
262 static PyObject *graph_parse(PyTypeObject *type, PyObject *args,
263 PyObject *kwds);
264
265 static void graph_wrap_free(void *arg)
266 {
267 struct wrap_graph *wgraph = arg;
268
269 graph_delete_graph(wgraph->graph);
270 free(wgraph->nodewrappers);
271 free(wgraph->definition);
272 }
273
274 static PyTypeObject typeobj_graph = {
275 PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_clippy.Graph",
276 .tp_basicsize = sizeof(struct wrap_graph),
277 .tp_flags = Py_TPFLAGS_DEFAULT,
278 .tp_doc = "struct graph *",
279 .tp_new = graph_parse,
280 .tp_free = graph_wrap_free,
281 .tp_members = members_graph,
282 .tp_methods = methods_graph,
283 };
284
285 /* top call / entrypoint for python code */
286 static PyObject *graph_parse(PyTypeObject *type, PyObject *args, PyObject *kwds)
287 {
288 const char *def, *doc = NULL;
289 struct wrap_graph *gwrap;
290 static const char *kwnames[] = {"cmddef", "doc", NULL};
291
292 gwrap = (struct wrap_graph *)typeobj_graph.tp_alloc(&typeobj_graph, 0);
293 if (!gwrap)
294 return NULL;
295
296 if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|s", (char **)kwnames,
297 &def, &doc))
298 return NULL;
299
300 struct graph *graph = graph_new();
301 struct cmd_token *token = cmd_token_new(START_TKN, 0, NULL, NULL);
302 graph_new_node(graph, token, (void (*)(void *)) & cmd_token_del);
303
304 struct cmd_element cmd = {.string = def, .doc = doc};
305 cmd_graph_parse(graph, &cmd);
306 cmd_graph_names(graph);
307
308 gwrap->graph = graph;
309 gwrap->definition = strdup(def);
310 gwrap->nodewrappers = calloc(vector_active(graph->nodes),
311 sizeof(gwrap->nodewrappers[0]));
312 return (PyObject *)gwrap;
313 }
314
315 static PyMethodDef clippy_methods[] = {
316 {"parse", clippy_parse, METH_VARARGS, "Parse a C file"},
317 {NULL, NULL, 0, NULL}};
318
319 #if PY_MAJOR_VERSION >= 3
320 static struct PyModuleDef pymoddef_clippy = {
321 PyModuleDef_HEAD_INIT,
322 "_clippy",
323 NULL, /* docstring */
324 -1,
325 clippy_methods,
326 };
327 #define modcreate() PyModule_Create(&pymoddef_clippy)
328 #define initret(val) return val;
329 #else
330 #define modcreate() Py_InitModule("_clippy", clippy_methods)
331 #define initret(val) \
332 do { \
333 if (!val) \
334 Py_FatalError("initialization failure"); \
335 return; \
336 } while (0)
337 #endif
338
339 #pragma GCC diagnostic ignored "-Wstrict-aliasing"
340 PyMODINIT_FUNC command_py_init(void)
341 {
342 PyObject *pymod;
343
344 if (PyType_Ready(&typeobj_graph_node) < 0)
345 initret(NULL);
346 if (PyType_Ready(&typeobj_graph) < 0)
347 initret(NULL);
348
349 pymod = modcreate();
350 if (!pymod)
351 initret(NULL);
352
353 Py_INCREF(&typeobj_graph_node);
354 PyModule_AddObject(pymod, "GraphNode", (PyObject *)&typeobj_graph_node);
355 Py_INCREF(&typeobj_graph);
356 PyModule_AddObject(pymod, "Graph", (PyObject *)&typeobj_graph);
357 if (!elf_py_init(pymod))
358 initret(NULL);
359 initret(pymod);
360 }