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