]> git.proxmox.com Git - mirror_ovs.git/blob - build-aux/extract-ofp-actions
dist-docs: Fix bugs in text to HTML conversion.
[mirror_ovs.git] / build-aux / extract-ofp-actions
1 #! /usr/bin/python
2
3 import getopt
4 import sys
5 import os.path
6 import re
7 import xml.dom.minidom
8 import build.nroff
9
10 OFP_ACTION_ALIGN = 8
11
12 # Map from OpenFlow version number to version ID used in ofp_header.
13 version_map = {"1.0": 0x01,
14 "1.1": 0x02,
15 "1.2": 0x03,
16 "1.3": 0x04,
17 "1.4": 0x05,
18 "1.5": 0x06}
19 version_reverse_map = dict((v, k) for (k, v) in version_map.items())
20
21 # Map from vendor name to the length of the action header.
22 vendor_map = {"OF": (0x00000000, 4),
23 "ONF": (0x4f4e4600, 10),
24 "NX": (0x00002320, 10)}
25
26 # Basic types used in action arguments.
27 types = {}
28 types['uint8_t'] = {"size": 1, "align": 1, "ntoh": None, "hton": None}
29 types['ovs_be16'] = {"size": 2, "align": 2, "ntoh": "ntohs", "hton": "htons"}
30 types['ovs_be32'] = {"size": 4, "align": 4, "ntoh": "ntohl", "hton": "htonl"}
31 types['ovs_be64'] = {"size": 8, "align": 8, "ntoh": "ntohll", "hton": "htonll"}
32 types['uint16_t'] = {"size": 2, "align": 2, "ntoh": None, "hton": None}
33 types['uint32_t'] = {"size": 4, "align": 4, "ntoh": None, "hton": None}
34 types['uint64_t'] = {"size": 8, "align": 8, "ntoh": None, "hton": None}
35
36 line = ""
37
38 arg_structs = set()
39
40 def round_up(x, y):
41 return int((x + (y - 1)) / y) * y
42
43 def open_file(fn):
44 global file_name
45 global input_file
46 global line_number
47 file_name = fn
48 input_file = open(file_name)
49 line_number = 0
50
51 def get_line():
52 global input_file
53 global line
54 global line_number
55 line = input_file.readline()
56 line_number += 1
57 if line == "":
58 fatal("unexpected end of input")
59 return line
60
61 n_errors = 0
62 def error(msg):
63 global n_errors
64 sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
65 n_errors += 1
66
67 def fatal(msg):
68 error(msg)
69 sys.exit(1)
70
71 def usage():
72 argv0 = os.path.basename(sys.argv[0])
73 print('''\
74 %(argv0)s, for extracting OpenFlow action data
75 usage: %(argv0)s [prototypes | definitions] OFP-ACTIONS.c
76 usage: %(argv0)s ovs-actions OVS-ACTIONS.XML
77
78 Commands:
79
80 prototypes OFP-ACTIONS.C
81 Reads ofp-actions.c and prints a set of prototypes to #include early in
82 ofp-actions.c.
83
84 definitions OFP-ACTIONS.C
85 Reads ofp-actions.c and prints a set of definitions to #include late in
86 ofp-actions.c.
87
88 ovs-actions OVS-ACTIONS.XML
89 Reads ovs-actions.xml and prints documentation in troff format.\
90 ''' % {"argv0": argv0})
91 sys.exit(0)
92
93 def extract_ofp_actions(fn, definitions):
94 error_types = {}
95
96 comments = []
97 names = []
98 domain = {}
99 for code, size in vendor_map.values():
100 domain[code] = {}
101 enums = {}
102
103 n_errors = 0
104
105 open_file(fn)
106
107 while True:
108 get_line()
109 if re.match('enum ofp_raw_action_type {', line):
110 break
111
112 while True:
113 get_line()
114 if line.startswith('/*') or not line or line.isspace():
115 continue
116 elif re.match('}', line):
117 break
118
119 if not line.lstrip().startswith('/*'):
120 fatal("unexpected syntax between actions")
121
122 comment = line.lstrip()[2:].strip()
123 while not comment.endswith('*/'):
124 get_line()
125 if line.startswith('/*') or not line or line.isspace():
126 fatal("unexpected syntax within action")
127 comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
128 comment = re.sub('\[[^]]*\]', '', comment)
129 comment = comment[:-2].rstrip()
130
131 m = re.match('([^:]+):\s+(.*)$', comment)
132 if not m:
133 fatal("unexpected syntax between actions")
134
135 dsts = m.group(1)
136 argtypes = m.group(2).strip().replace('.', '', 1)
137
138 if 'VLMFF' in argtypes:
139 arg_vl_mff_map = True
140 else:
141 arg_vl_mff_map = False
142 argtype = argtypes.replace('VLMFF', '', 1).rstrip()
143
144 get_line()
145 m = re.match(r'\s+(([A-Z]+)_RAW([0-9]*)_([A-Z0-9_]+)),?', line)
146 if not m:
147 fatal("syntax error expecting enum value")
148
149 enum = m.group(1)
150 if enum in names:
151 fatal("%s specified twice" % enum)
152
153 names.append(enum)
154
155 for dst in dsts.split(', '):
156 m = re.match(r'([A-Z]+)([0-9.]+)(\+|-[0-9.]+)?(?:\((\d+)\))(?: is deprecated \(([^)]+)\))?$', dst)
157 if not m:
158 fatal("%r: syntax error in destination" % dst)
159 vendor_name = m.group(1)
160 version1_name = m.group(2)
161 version2_name = m.group(3)
162 type_ = int(m.group(4))
163 deprecation = m.group(5)
164
165 if vendor_name not in vendor_map:
166 fatal("%s: unknown vendor" % vendor_name)
167 vendor = vendor_map[vendor_name][0]
168
169 if version1_name not in version_map:
170 fatal("%s: unknown OpenFlow version" % version1_name)
171 v1 = version_map[version1_name]
172
173 if version2_name is None:
174 v2 = v1
175 elif version2_name == "+":
176 v2 = max(version_map.values())
177 elif version2_name[1:] not in version_map:
178 fatal("%s: unknown OpenFlow version" % version2_name[1:])
179 else:
180 v2 = version_map[version2_name[1:]]
181
182 if v2 < v1:
183 fatal("%s%s: %s precedes %s"
184 % (version1_name, version2_name,
185 version2_name, version1_name))
186
187 for version in range(v1, v2 + 1):
188 domain[vendor].setdefault(type_, {})
189 if version in domain[vendor][type_]:
190 v = domain[vendor][type_][version]
191 msg = "%#x,%d in OF%s means both %s and %s" % (
192 vendor, type_, version_reverse_map[version],
193 v["enum"], enum)
194 error("%s: %s." % (dst, msg))
195 sys.stderr.write("%s:%d: %s: Here is the location "
196 "of the previous definition.\n"
197 % (v["file_name"], v["line_number"],
198 dst))
199 n_errors += 1
200 else:
201 header_len = vendor_map[vendor_name][1]
202
203 base_argtype = argtype.replace(', ..', '', 1)
204 if base_argtype in types:
205 arg_align = types[base_argtype]['align']
206 arg_len = types[base_argtype]['size']
207 arg_ofs = round_up(header_len, arg_align)
208 min_length = round_up(arg_ofs + arg_len,
209 OFP_ACTION_ALIGN)
210 elif base_argtype == 'void':
211 min_length = round_up(header_len, OFP_ACTION_ALIGN)
212 arg_len = 0
213 arg_ofs = 0
214 elif re.match(r'struct [a-zA-Z0-9_]+$', base_argtype):
215 min_length = 'sizeof(%s)' % base_argtype
216 arg_structs.add(base_argtype)
217 arg_len = 0
218 arg_ofs = 0
219 # should also emit OFP_ACTION_ALIGN assertion
220 else:
221 fatal("bad argument type %s" % argtype)
222
223 ellipsis = argtype != base_argtype
224 if ellipsis:
225 max_length = '65536 - OFP_ACTION_ALIGN'
226 else:
227 max_length = min_length
228
229 info = {"enum": enum, # 0
230 "deprecation": deprecation, # 1
231 "file_name": file_name, # 2
232 "line_number": line_number, # 3
233 "min_length": min_length, # 4
234 "max_length": max_length, # 5
235 "arg_ofs": arg_ofs, # 6
236 "arg_len": arg_len, # 7
237 "base_argtype": base_argtype, # 8
238 "arg_vl_mff_map": arg_vl_mff_map, # 9
239 "version": version, # 10
240 "type": type_} # 11
241 domain[vendor][type_][version] = info
242
243 enums.setdefault(enum, [])
244 enums[enum].append(info)
245
246 input_file.close()
247
248 if n_errors:
249 sys.exit(1)
250
251 print("""\
252 /* Generated automatically; do not modify! -*- buffer-read-only: t -*- */
253 """)
254
255 if definitions:
256 print("/* Verify that structs used as actions are reasonable sizes. */")
257 for s in sorted(arg_structs):
258 print("BUILD_ASSERT_DECL(sizeof(%s) %% OFP_ACTION_ALIGN == 0);" % s)
259
260 print("\nstatic struct ofpact_raw_instance all_raw_instances[] = {")
261 for vendor in domain:
262 for type_ in domain[vendor]:
263 for version in domain[vendor][type_]:
264 d = domain[vendor][type_][version]
265 print(" { { 0x%08x, %2d, 0x%02x }, " % (
266 vendor, type_, version))
267 print(" %s," % d["enum"])
268 print(" HMAP_NODE_NULL_INITIALIZER,")
269 print(" HMAP_NODE_NULL_INITIALIZER,")
270 print(" %s," % d["min_length"])
271 print(" %s," % d["max_length"])
272 print(" %s," % d["arg_ofs"])
273 print(" %s," % d["arg_len"])
274 print(" \"%s\"," % re.sub('_RAW[0-9]*', '', d["enum"], 1))
275 if d["deprecation"]:
276 print(" \"%s\"," % re.sub(r'(["\\])', r'\\\1', d["deprecation"]))
277 else:
278 print(" NULL,")
279 print(" },")
280 print("};")
281
282 for versions in enums.values():
283 need_ofp_version = False
284 for v in versions:
285 assert v["arg_len"] == versions[0]["arg_len"]
286 assert v["base_argtype"] == versions[0]["base_argtype"]
287 if (v["min_length"] != versions[0]["min_length"] or
288 v["arg_ofs"] != versions[0]["arg_ofs"] or
289 v["type"] != versions[0]["type"]):
290 need_ofp_version = True
291 base_argtype = versions[0]["base_argtype"]
292
293 decl = "static inline "
294 if base_argtype.startswith('struct'):
295 decl += "%s *" %base_argtype
296 else:
297 decl += "void"
298 decl += "\nput_%s(struct ofpbuf *openflow" % versions[0]["enum"].replace('_RAW', '', 1)
299 if need_ofp_version:
300 decl += ", enum ofp_version version"
301 if base_argtype != 'void' and not base_argtype.startswith('struct'):
302 decl += ", %s arg" % base_argtype
303 decl += ")"
304 if definitions:
305 decl += "{\n"
306 decl += " "
307 if base_argtype.startswith('struct'):
308 decl += "return "
309 decl += "ofpact_put_raw(openflow, "
310 if need_ofp_version:
311 decl += "version"
312 else:
313 decl += "%s" % versions[0]["version"]
314 decl += ", %s, " % versions[0]["enum"]
315 if base_argtype.startswith('struct') or base_argtype == 'void':
316 decl += "0"
317 else:
318 ntoh = types[base_argtype]['ntoh']
319 if ntoh:
320 decl += "%s(arg)" % ntoh
321 else:
322 decl += "arg"
323 decl += ");\n"
324 decl += "}"
325 else:
326 decl += ";"
327 print(decl)
328 print("")
329
330 if definitions:
331 print("""\
332 static enum ofperr
333 ofpact_decode(const struct ofp_action_header *a, enum ofp_raw_action_type raw,
334 enum ofp_version version, uint64_t arg,
335 const struct vl_mff_map *vl_mff_map,
336 uint64_t *tlv_bitmap, struct ofpbuf *out)
337 {
338 switch (raw) {\
339 """)
340 for versions in enums.values():
341 enum = versions[0]["enum"]
342 print(" case %s:" % enum)
343 base_argtype = versions[0]["base_argtype"]
344 arg_vl_mff_map = versions[0]["arg_vl_mff_map"]
345 if base_argtype == 'void':
346 print(" return decode_%s(out);" % enum)
347 else:
348 if base_argtype.startswith('struct'):
349 arg = "ALIGNED_CAST(const %s *, a)" % base_argtype
350 else:
351 hton = types[base_argtype]['hton']
352 if hton:
353 arg = "%s(arg)" % hton
354 else:
355 arg = "arg"
356 if arg_vl_mff_map:
357 print(" return decode_%s(%s, version, vl_mff_map, tlv_bitmap, out);" % (enum, arg))
358 else:
359 print(" return decode_%s(%s, version, out);" % (enum, arg))
360 print("")
361 print("""\
362 default:
363 OVS_NOT_REACHED();
364 }
365 }\
366 """)
367 else:
368 for versions in enums.values():
369 enum = versions[0]["enum"]
370 prototype = "static enum ofperr decode_%s(" % enum
371 base_argtype = versions[0]["base_argtype"]
372 arg_vl_mff_map = versions[0]["arg_vl_mff_map"]
373 if base_argtype != 'void':
374 if base_argtype.startswith('struct'):
375 prototype += "const %s *, enum ofp_version, " % base_argtype
376 else:
377 prototype += "%s, enum ofp_version, " % base_argtype
378 if arg_vl_mff_map:
379 prototype += 'const struct vl_mff_map *, uint64_t *, '
380 prototype += "struct ofpbuf *);"
381 print(prototype)
382
383 print("""
384 static enum ofperr ofpact_decode(const struct ofp_action_header *,
385 enum ofp_raw_action_type raw,
386 enum ofp_version version,
387 uint64_t arg, const struct vl_mff_map *vl_mff_map,
388 uint64_t *tlv_bitmap, struct ofpbuf *out);
389 """)
390 \f
391 ## ------------------------ ##
392 ## Documentation Generation ##
393 ## ------------------------ ##
394
395 def action_to_xml(action_node, body):
396 syntax = 0
397 for node in action_node.childNodes:
398 if node.nodeType == node.ELEMENT_NODE and node.tagName == 'syntax':
399 if body[-1].strip() == '.PP':
400 del body[-1]
401 if syntax:
402 body += ['.IQ\n']
403 else:
404 body += ['.IP "\\fBSyntax:\\fR"\n']
405 body += [build.nroff.inline_xml_to_nroff(x, r'\fR')
406 for x in node.childNodes] + ['\n']
407 syntax += 1
408 elif (node.nodeType == node.ELEMENT_NODE
409 and node.tagName == 'conformance'):
410 body += ['.IP "\\fBConformance:\\fR"\n']
411 body += [build.nroff.block_xml_to_nroff(node.childNodes)]
412 else:
413 body += [build.nroff.block_xml_to_nroff([node])]
414
415 def group_xml_to_nroff(group_node):
416 title = group_node.attributes['title'].nodeValue
417
418 body = []
419 for node in group_node.childNodes:
420 if node.nodeType == node.ELEMENT_NODE and node.tagName == 'action':
421 action_to_xml(node, body)
422 else:
423 body += [build.nroff.block_xml_to_nroff([node])]
424
425 content = [
426 '.bp\n',
427 '.SH \"%s\"\n' % build.nroff.text_to_nroff(title.upper())]
428 content += body
429 return ''.join(content)
430
431 def make_ovs_actions(ovs_actions_xml):
432 document = xml.dom.minidom.parse(ovs_actions_xml)
433 doc = document.documentElement
434
435 global version
436 if version == None:
437 version = "UNKNOWN"
438
439 print('''\
440 '\\" tp
441 .\\" -*- mode: troff; coding: utf-8 -*-
442 .TH "ovs\-actions" 7 "%s" "Open vSwitch" "Open vSwitch Manual"
443 .fp 5 L CR \\" Make fixed-width font available as \\fL.
444 .de ST
445 . PP
446 . RS -0.15in
447 . I "\\\\$1"
448 . RE
449 ..
450
451 .de SU
452 . PP
453 . I "\\\\$1"
454 ..
455
456 .de IQ
457 . br
458 . ns
459 . IP "\\\\$1"
460 ..
461
462 .de TQ
463 . br
464 . ns
465 . TP "\\\\$1"
466 ..
467 .de URL
468 \\\\$2 \\(laURL: \\\\$1 \\(ra\\\\$3
469 ..
470 .if \\n[.g] .mso www.tmac
471 .SH NAME
472 ovs\-actions \- OpenFlow actions and instructions with Open vSwitch extensions
473 .
474 .PP
475 ''' % version)
476
477 s = ''
478 for node in doc.childNodes:
479 if node.nodeType == node.ELEMENT_NODE and node.tagName == "group":
480 s += group_xml_to_nroff(node)
481 elif node.nodeType == node.TEXT_NODE:
482 assert node.data.isspace()
483 elif node.nodeType == node.COMMENT_NODE:
484 pass
485 else:
486 s += build.nroff.block_xml_to_nroff([node])
487
488 if n_errors:
489 sys.exit(1)
490
491 output = []
492 for oline in s.split("\n"):
493 oline = oline.strip()
494
495 # Life is easier with nroff if we don't try to feed it Unicode.
496 # Fortunately, we only use a few characters outside the ASCII range.
497 oline = oline.replace(u'\u2208', r'\[mo]')
498 oline = oline.replace(u'\u2260', r'\[!=]')
499 oline = oline.replace(u'\u2264', r'\[<=]')
500 oline = oline.replace(u'\u2265', r'\[>=]')
501 oline = oline.replace(u'\u00d7', r'\[mu]')
502 if len(oline):
503 output += [oline]
504
505 # nroff tends to ignore .bp requests if they come after .PP requests,
506 # so remove .PPs that precede .bp.
507 for i in range(len(output)):
508 if output[i] == '.bp':
509 j = i - 1
510 while j >= 0 and output[j] == '.PP':
511 output[j] = None
512 j -= 1
513 for i in range(len(output)):
514 if output[i] is not None:
515 print(output[i])
516
517 \f
518 ## ------------ ##
519 ## Main Program ##
520 ## ------------ ##
521
522 if __name__ == '__main__':
523 argv0 = sys.argv[0]
524 try:
525 options, args = getopt.gnu_getopt(sys.argv[1:], 'h',
526 ['help', 'ovs-version='])
527 except getopt.GetoptError as geo:
528 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
529 sys.exit(1)
530
531 global version
532 version = None
533 for key, value in options:
534 if key in ['-h', '--help']:
535 usage()
536 elif key == '--ovs-version':
537 version = value
538 else:
539 sys.exit(0)
540
541 if not args:
542 sys.stderr.write("%s: missing command argument "
543 "(use --help for help)\n" % argv0)
544 sys.exit(1)
545
546 commands = {"prototypes": (lambda fn: extract_ofp_actions(fn, False), 1),
547 "definitions": (lambda fn: extract_ofp_actions(fn, True), 1),
548 "ovs-actions": (make_ovs_actions, 1)}
549
550 if not args[0] in commands:
551 sys.stderr.write("%s: unknown command \"%s\" "
552 "(use --help for help)\n" % (argv0, args[0]))
553 sys.exit(1)
554
555 func, n_args = commands[args[0]]
556 if len(args) - 1 != n_args:
557 sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
558 "provided\n"
559 % (argv0, args[0], n_args, len(args) - 1))
560 sys.exit(1)
561
562 func(*args[1:])