]> git.proxmox.com Git - ovs.git/blob - build-aux/extract-ofp-fields
tests: avoid non-posix options to wc
[ovs.git] / build-aux / extract-ofp-fields
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 line = ""
11
12 # Maps from user-friendly version number to its protocol encoding.
13 VERSION = {"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 = dict((v,k) for k, v in VERSION.items())
20
21 TYPES = {"u8": (1, False),
22 "be16": (2, False),
23 "be32": (4, False),
24 "MAC": (6, False),
25 "be64": (8, False),
26 "be128": (16, False),
27 "tunnelMD": (124, True)}
28
29 FORMATTING = {"decimal": ("MFS_DECIMAL", 1, 8),
30 "hexadecimal": ("MFS_HEXADECIMAL", 1, 127),
31 "ct state": ("MFS_CT_STATE", 4, 4),
32 "Ethernet": ("MFS_ETHERNET", 6, 6),
33 "IPv4": ("MFS_IPV4", 4, 4),
34 "IPv6": ("MFS_IPV6", 16, 16),
35 "OpenFlow 1.0 port": ("MFS_OFP_PORT", 2, 2),
36 "OpenFlow 1.1+ port": ("MFS_OFP_PORT_OXM", 4, 4),
37 "frag": ("MFS_FRAG", 1, 1),
38 "tunnel flags": ("MFS_TNL_FLAGS", 2, 2),
39 "TCP flags": ("MFS_TCP_FLAGS", 2, 2),
40 "packet type": ("MFS_PACKET_TYPE", 4, 4)}
41
42 PREREQS = {"none": "MFP_NONE",
43 "Ethernet": "MFP_ETHERNET",
44 "ARP": "MFP_ARP",
45 "VLAN VID": "MFP_VLAN_VID",
46 "IPv4": "MFP_IPV4",
47 "IPv6": "MFP_IPV6",
48 "IPv4/IPv6": "MFP_IP_ANY",
49 "CT": "MFP_CT_VALID",
50 "MPLS": "MFP_MPLS",
51 "TCP": "MFP_TCP",
52 "UDP": "MFP_UDP",
53 "SCTP": "MFP_SCTP",
54 "ICMPv4": "MFP_ICMPV4",
55 "ICMPv6": "MFP_ICMPV6",
56 "ND": "MFP_ND",
57 "ND solicit": "MFP_ND_SOLICIT",
58 "ND advert": "MFP_ND_ADVERT"}
59
60 # Maps a name prefix into an (experimenter ID, class) pair, so:
61 #
62 # - Standard OXM classes are written as (0, <oxm_class>)
63 #
64 # - Experimenter OXM classes are written as (<oxm_vender>, 0xffff)
65 #
66 # If a name matches more than one prefix, the longest one is used.
67 OXM_CLASSES = {"NXM_OF_": (0, 0x0000),
68 "NXM_NX_": (0, 0x0001),
69 "OXM_OF_": (0, 0x8000),
70 "OXM_OF_PKT_REG": (0, 0x8001),
71 "ONFOXM_ET_": (0x4f4e4600, 0xffff),
72
73 # This is the experimenter OXM class for Nicira, which is the
74 # one that OVS would be using instead of NXM_OF_ and NXM_NX_
75 # if OVS didn't have those grandfathered in. It is currently
76 # used only to test support for experimenter OXM, since there
77 # are barely any real uses of experimenter OXM in the wild.
78 "NXOXM_ET_": (0x00002320, 0xffff)}
79
80
81 def oxm_name_to_class(name):
82 prefix = ''
83 class_ = None
84 for p, c in OXM_CLASSES.items():
85 if name.startswith(p) and len(p) > len(prefix):
86 prefix = p
87 class_ = c
88 return class_
89
90
91 def decode_version_range(range):
92 if range in VERSION:
93 return (VERSION[range], VERSION[range])
94 elif range.endswith('+'):
95 return (VERSION[range[:-1]], max(VERSION.values()))
96 else:
97 a, b = re.match(r'^([^-]+)-([^-]+)$', range).groups()
98 return (VERSION[a], VERSION[b])
99
100
101 def get_line():
102 global line
103 global line_number
104 line = input_file.readline()
105 line_number += 1
106 if line == "":
107 fatal("unexpected end of input")
108
109
110 n_errors = 0
111
112
113 def error(msg):
114 global n_errors
115 sys.stderr.write("%s:%d: %s\n" % (file_name, line_number, msg))
116 n_errors += 1
117
118
119 def fatal(msg):
120 error(msg)
121 sys.exit(1)
122
123
124 def usage():
125 argv0 = os.path.basename(sys.argv[0])
126 print('''\
127 %(argv0)s, for extracting OpenFlow field properties from meta-flow.h
128 usage: %(argv0)s INPUT [--meta-flow | --nx-match]
129 where INPUT points to lib/meta-flow.h in the source directory.
130 Depending on the option given, the output written to stdout is intended to be
131 saved either as lib/meta-flow.inc or lib/nx-match.inc for the respective C
132 file to #include.\
133 ''' % {"argv0": argv0})
134 sys.exit(0)
135
136
137 def make_sizeof(s):
138 m = re.match(r'(.*) up to (.*)', s)
139 if m:
140 struct, member = m.groups()
141 return "offsetof(%s, %s)" % (struct, member)
142 else:
143 return "sizeof(%s)" % s
144
145
146 def parse_oxms(s, prefix, n_bytes):
147 if s == 'none':
148 return ()
149
150 return tuple(parse_oxm(s2.strip(), prefix, n_bytes) for s2 in s.split(','))
151
152
153 match_types = dict()
154
155
156 def parse_oxm(s, prefix, n_bytes):
157 global match_types
158
159 m = re.match('([A-Z0-9_]+)\(([0-9]+)\) since(?: OF(1\.[0-9]+) and)? v([12]\.[0-9]+)$', s)
160 if not m:
161 fatal("%s: syntax error parsing %s" % (s, prefix))
162
163 name, oxm_type, of_version, ovs_version = m.groups()
164
165 class_ = oxm_name_to_class(name)
166 if class_ is None:
167 fatal("unknown OXM class for %s" % name)
168 oxm_vendor, oxm_class = class_
169
170 if class_ in match_types:
171 if oxm_type in match_types[class_]:
172 fatal("duplicate match type for %s (conflicts with %s)" %
173 (name, match_types[class_][oxm_type]))
174 else:
175 match_types[class_] = dict()
176 match_types[class_][oxm_type] = name
177
178 # Normally the oxm_length is the size of the field, but for experimenter
179 # OXMs oxm_length also includes the 4-byte experimenter ID.
180 oxm_length = n_bytes
181 if oxm_class == 0xffff:
182 oxm_length += 4
183
184 header = (oxm_vendor, oxm_class, int(oxm_type), oxm_length)
185
186 if of_version:
187 if of_version not in VERSION:
188 fatal("%s: unknown OpenFlow version %s" % (name, of_version))
189 of_version_nr = VERSION[of_version]
190 if of_version_nr < VERSION['1.2']:
191 fatal("%s: claimed version %s predates OXM" % (name, of_version))
192 elif prefix == 'OXM':
193 fatal("%s: missing OpenFlow version number" % name)
194 else:
195 of_version_nr = 0
196
197 return (header, name, of_version_nr, ovs_version)
198
199
200 def parse_field(mff, comment):
201 f = {'mff': mff}
202
203 # First line of comment is the field name.
204 m = re.match(r'"([^"]+)"(?:\s+\(aka "([^"]+)"\))?(?:\s+\(.*\))?\.', comment[0])
205 if not m:
206 fatal("%s lacks field name" % mff)
207 f['name'], f['extra_name'] = m.groups()
208
209 # Find the last blank line the comment. The field definitions
210 # start after that.
211 blank = None
212 for i in range(len(comment)):
213 if not comment[i]:
214 blank = i
215 if not blank:
216 fatal("%s: missing blank line in comment" % mff)
217
218 d = {}
219 for key in ("Type", "Maskable", "Formatting", "Prerequisites",
220 "Access", "Prefix lookup member",
221 "OXM", "NXM", "OF1.0", "OF1.1"):
222 d[key] = None
223 for fline in comment[blank + 1:]:
224 m = re.match(r'([^:]+):\s+(.*)\.$', fline)
225 if not m:
226 fatal("%s: syntax error parsing key-value pair as part of %s"
227 % (fline, mff))
228 key, value = m.groups()
229 if key not in d:
230 fatal("%s: unknown key" % key)
231 elif key == 'Code point':
232 d[key] += [value]
233 elif d[key] is not None:
234 fatal("%s: duplicate key" % key)
235 d[key] = value
236 for key, value in d.items():
237 if not value and key not in ("OF1.0", "OF1.1",
238 "Prefix lookup member", "Notes"):
239 fatal("%s: missing %s" % (mff, key))
240
241 m = re.match(r'([a-zA-Z0-9]+)(?: \(low ([0-9]+) bits\))?$', d['Type'])
242 if not m:
243 fatal("%s: syntax error in type" % mff)
244 type_ = m.group(1)
245 if type_ not in TYPES:
246 fatal("%s: unknown type %s" % (mff, d['Type']))
247
248 f['n_bytes'] = TYPES[type_][0]
249 if m.group(2):
250 f['n_bits'] = int(m.group(2))
251 if f['n_bits'] > f['n_bytes'] * 8:
252 fatal("%s: more bits (%d) than field size (%d)"
253 % (mff, f['n_bits'], 8 * f['n_bytes']))
254 else:
255 f['n_bits'] = 8 * f['n_bytes']
256 f['variable'] = TYPES[type_][1]
257
258 if d['Maskable'] == 'no':
259 f['mask'] = 'MFM_NONE'
260 elif d['Maskable'] == 'bitwise':
261 f['mask'] = 'MFM_FULLY'
262 else:
263 fatal("%s: unknown maskable %s" % (mff, d['Maskable']))
264
265 fmt = FORMATTING.get(d['Formatting'])
266 if not fmt:
267 fatal("%s: unknown format %s" % (mff, d['Formatting']))
268 f['formatting'] = d['Formatting']
269 if f['n_bytes'] < fmt[1] or f['n_bytes'] > fmt[2]:
270 fatal("%s: %d-byte field can't be formatted as %s"
271 % (mff, f['n_bytes'], d['Formatting']))
272 f['string'] = fmt[0]
273
274 f['prereqs'] = d['Prerequisites']
275 if f['prereqs'] not in PREREQS:
276 fatal("%s: unknown prerequisites %s" % (mff, d['Prerequisites']))
277
278 if d['Access'] == 'read-only':
279 f['writable'] = False
280 elif d['Access'] == 'read/write':
281 f['writable'] = True
282 else:
283 fatal("%s: unknown access %s" % (mff, d['Access']))
284
285 f['OF1.0'] = d['OF1.0']
286 if not d['OF1.0'] in (None, 'exact match', 'CIDR mask'):
287 fatal("%s: unknown OF1.0 match type %s" % (mff, d['OF1.0']))
288
289 f['OF1.1'] = d['OF1.1']
290 if not d['OF1.1'] in (None, 'exact match', 'bitwise mask'):
291 fatal("%s: unknown OF1.1 match type %s" % (mff, d['OF1.1']))
292
293 f['OXM'] = (parse_oxms(d['OXM'], 'OXM', f['n_bytes']) +
294 parse_oxms(d['NXM'], 'NXM', f['n_bytes']))
295
296 f['prefix'] = d["Prefix lookup member"]
297
298 return f
299
300
301 def protocols_to_c(protocols):
302 if protocols == set(['of10', 'of11', 'oxm']):
303 return 'OFPUTIL_P_ANY'
304 elif protocols == set(['of11', 'oxm']):
305 return 'OFPUTIL_P_NXM_OF11_UP'
306 elif protocols == set(['oxm']):
307 return 'OFPUTIL_P_NXM_OXM_ANY'
308 elif protocols == set([]):
309 return 'OFPUTIL_P_NONE'
310 else:
311 assert False
312
313
314 def autogen_c_comment():
315 return [
316 "/* Generated automatically; do not modify! -*- buffer-read-only: t -*- */",
317 ""]
318
319 def make_meta_flow(meta_flow_h):
320 fields = extract_ofp_fields(meta_flow_h)
321 output = autogen_c_comment()
322 for f in fields:
323 output += ["{"]
324 output += [" %s," % f['mff']]
325 if f['extra_name']:
326 output += [" \"%s\", \"%s\"," % (f['name'], f['extra_name'])]
327 else:
328 output += [" \"%s\", NULL," % f['name']]
329
330 if f['variable']:
331 variable = 'true'
332 else:
333 variable = 'false'
334 output += [" %d, %d, %s," % (f['n_bytes'], f['n_bits'], variable)]
335
336 if f['writable']:
337 rw = 'true'
338 else:
339 rw = 'false'
340 output += [" %s, %s, %s, %s, false,"
341 % (f['mask'], f['string'], PREREQS[f['prereqs']], rw)]
342
343 oxm = f['OXM']
344 of10 = f['OF1.0']
345 of11 = f['OF1.1']
346 if f['mff'] in ('MFF_DL_VLAN', 'MFF_DL_VLAN_PCP'):
347 # MFF_DL_VLAN and MFF_DL_VLAN_PCP don't exactly correspond to
348 # OF1.1, nor do they have NXM or OXM assignments, but their
349 # meanings can be expressed in every protocol, which is the goal of
350 # this member.
351 protocols = set(["of10", "of11", "oxm"])
352 else:
353 protocols = set([])
354 if of10:
355 protocols |= set(["of10"])
356 if of11:
357 protocols |= set(["of11"])
358 if oxm:
359 protocols |= set(["oxm"])
360
361 if f['mask'] == 'MFM_FULLY':
362 cidr_protocols = protocols.copy()
363 bitwise_protocols = protocols.copy()
364
365 if of10 == 'exact match':
366 bitwise_protocols -= set(['of10'])
367 cidr_protocols -= set(['of10'])
368 elif of10 == 'CIDR mask':
369 bitwise_protocols -= set(['of10'])
370 else:
371 assert of10 is None
372
373 if of11 == 'exact match':
374 bitwise_protocols -= set(['of11'])
375 cidr_protocols -= set(['of11'])
376 else:
377 assert of11 in (None, 'bitwise mask')
378 else:
379 assert f['mask'] == 'MFM_NONE'
380 cidr_protocols = set([])
381 bitwise_protocols = set([])
382
383 output += [" %s," % protocols_to_c(protocols)]
384 output += [" %s," % protocols_to_c(cidr_protocols)]
385 output += [" %s," % protocols_to_c(bitwise_protocols)]
386
387 if f['prefix']:
388 output += [" FLOW_U32OFS(%s)," % f['prefix']]
389 else:
390 output += [" -1, /* not usable for prefix lookup */"]
391
392 output += ["},"]
393 for oline in output:
394 print(oline)
395
396
397 def make_nx_match(meta_flow_h):
398 fields = extract_ofp_fields(meta_flow_h)
399 output = autogen_c_comment()
400 print("static struct nxm_field_index all_nxm_fields[] = {")
401 for f in fields:
402 # Sort by OpenFlow version number (nx-match.c depends on this).
403 for oxm in sorted(f['OXM'], key=lambda x: x[2]):
404 header = ("NXM_HEADER(0x%x,0x%x,%s,0,%d)" % oxm[0])
405 print("""{ .nf = { %s, %d, "%s", %s } },""" % (
406 header, oxm[2], oxm[1], f['mff']))
407 print("};")
408 for oline in output:
409 print(oline)
410
411
412 def extract_ofp_fields(fn):
413 global file_name
414 global input_file
415 global line_number
416 global line
417
418 file_name = fn
419 input_file = open(file_name)
420 line_number = 0
421
422 fields = []
423
424 while True:
425 get_line()
426 if re.match('enum.*mf_field_id', line):
427 break
428
429 while True:
430 get_line()
431 first_line_number = line_number
432 here = '%s:%d' % (file_name, line_number)
433 if (line.startswith('/*')
434 or line.startswith(' *')
435 or line.startswith('#')
436 or not line
437 or line.isspace()):
438 continue
439 elif re.match('}', line) or re.match('\s+MFF_N_IDS', line):
440 break
441
442 # Parse the comment preceding an MFF_ constant into 'comment',
443 # one line to an array element.
444 line = line.strip()
445 if not line.startswith('/*'):
446 fatal("unexpected syntax between fields")
447 line = line[1:]
448 comment = []
449 end = False
450 while not end:
451 line = line.strip()
452 if line.startswith('*/'):
453 get_line()
454 break
455 if not line.startswith('*'):
456 fatal("unexpected syntax within field")
457
458 line = line[1:]
459 if line.startswith(' '):
460 line = line[1:]
461 if line.startswith(' ') and comment:
462 continuation = True
463 line = line.lstrip()
464 else:
465 continuation = False
466
467 if line.endswith('*/'):
468 line = line[:-2].rstrip()
469 end = True
470 else:
471 end = False
472
473 if continuation:
474 comment[-1] += " " + line
475 else:
476 comment += [line]
477 get_line()
478
479 # Drop blank lines at each end of comment.
480 while comment and not comment[0]:
481 comment = comment[1:]
482 while comment and not comment[-1]:
483 comment = comment[:-1]
484
485 # Parse the MFF_ constant(s).
486 mffs = []
487 while True:
488 m = re.match('\s+(MFF_[A-Z0-9_]+),?\s?$', line)
489 if not m:
490 break
491 mffs += [m.group(1)]
492 get_line()
493 if not mffs:
494 fatal("unexpected syntax looking for MFF_ constants")
495
496 if len(mffs) > 1 or '<N>' in comment[0]:
497 for mff in mffs:
498 # Extract trailing integer.
499 m = re.match('.*[^0-9]([0-9]+)$', mff)
500 if not m:
501 fatal("%s lacks numeric suffix in register group" % mff)
502 n = m.group(1)
503
504 # Search-and-replace <N> within the comment,
505 # and drop lines that have <x> for x != n.
506 instance = []
507 for x in comment:
508 y = x.replace('<N>', n)
509 if re.search('<[0-9]+>', y):
510 if ('<%s>' % n) not in y:
511 continue
512 y = re.sub('<[0-9]+>', '', y)
513 instance += [y.strip()]
514 fields += [parse_field(mff, instance)]
515 else:
516 fields += [parse_field(mffs[0], comment)]
517 continue
518
519 input_file.close()
520
521 if n_errors:
522 sys.exit(1)
523
524 return fields
525 \f
526 ## ------------------------ ##
527 ## Documentation Generation ##
528 ## ------------------------ ##
529
530 def field_to_xml(field_node, f, body, summary):
531 f["used"] = True
532
533 # Summary.
534 if field_node.hasAttribute('internal'):
535 return
536
537 min_of_version = None
538 min_ovs_version = None
539 for header, name, of_version_nr, ovs_version_s in f['OXM']:
540 if (not name.startswith('NXM')
541 and (min_ovs_version is None or of_version_nr < min_of_version)):
542 min_of_version = of_version_nr
543 ovs_version = [int(x) for x in ovs_version_s.split('.')]
544 if min_ovs_version is None or ovs_version < min_ovs_version:
545 min_ovs_version = ovs_version
546 summary += ["\\fB%s\\fR" % f["name"]]
547 if f["extra_name"]:
548 summary += [" aka \\fB%s\\fR" % f["extra_name"]]
549 summary += [";%d" % f["n_bytes"]]
550 if f["n_bits"] != 8 * f["n_bytes"]:
551 summary += [" (low %d bits)" % f["n_bits"]]
552 summary += [";%s;" % {"MFM_NONE": "no", "MFM_FULLY": "yes"}[f["mask"]]]
553 summary += ["%s;" % {True: "yes", False: "no"}[f["writable"]]]
554 summary += ["%s;" % f["prereqs"]]
555 support = []
556 if min_of_version is not None:
557 support += ["OF %s+" % VERSION_REVERSE[min_of_version]]
558 if min_ovs_version is not None:
559 support += ["OVS %s+" % '.'.join([str(x) for x in min_ovs_version])]
560 summary += ' and '.join(support)
561 summary += ["\n"]
562
563 # Full description.
564 if field_node.hasAttribute('hidden'):
565 return
566
567 title = field_node.attributes['title'].nodeValue
568
569 body += [""".PP
570 \\fB%s Field\\fR
571 .TS
572 tab(;);
573 l lx.
574 """ % title]
575
576 body += ["Name:;\\fB%s\\fR" % f["name"]]
577 if f["extra_name"]:
578 body += [" (aka \\fB%s\\fR)" % f["extra_name"]]
579 body += ['\n']
580
581 body += ["Width:;"]
582 if f["n_bits"] != 8 * f["n_bytes"]:
583 body += ["%d bits (only the least-significant %d bits "
584 "may be nonzero)" % (f["n_bytes"] * 8, f["n_bits"])]
585 elif f["n_bits"] <= 128:
586 body += ["%d bits" % f["n_bits"]]
587 else:
588 body += ["%d bits (%d bytes)" % (f["n_bits"], f["n_bits"] / 8)]
589 body += ['\n']
590
591 body += ["Format:;%s\n" % f["formatting"]]
592
593 masks = {"MFM_NONE": "not maskable",
594 "MFM_FULLY": "arbitrary bitwise masks"}
595 body += ["Masking:;%s\n" % masks[f["mask"]]]
596 body += ["Prerequisites:;%s\n" % f["prereqs"]]
597
598 access = {True: "read/write",
599 False: "read-only"}[f["writable"]]
600 body += ["Access:;%s\n" % access]
601
602 of10 = {None: "not supported",
603 "exact match": "yes (exact match only)",
604 "CIDR mask": "yes (CIDR match only)"}
605 body += ["OpenFlow 1.0:;%s\n" % of10[f["OF1.0"]]]
606
607 of11 = {None: "not supported",
608 "exact match": "yes (exact match only)",
609 "bitwise mask": "yes"}
610 body += ["OpenFlow 1.1:;%s\n" % of11[f["OF1.1"]]]
611
612 oxms = []
613 for header, name, of_version_nr, ovs_version in [x for x in sorted(f['OXM'], key=lambda x: x[2]) if not x[1].startswith('NXM')]:
614 of_version = VERSION_REVERSE[of_version_nr]
615 oxms += [r"\fB%s\fR (%d) since OpenFlow %s and Open vSwitch %s" % (name, header[2], of_version, ovs_version)]
616 if not oxms:
617 oxms = ['none']
618 body += ['OXM:;T{\n%s\nT}\n' % r'\[char59] '.join(oxms)]
619
620 nxms = []
621 for header, name, of_version_nr, ovs_version in [x for x in sorted(f['OXM'], key=lambda x: x[2]) if x[1].startswith('NXM')]:
622 nxms += [r"\fB%s\fR (%d) since Open vSwitch %s" % (name, header[2], ovs_version)]
623 if not nxms:
624 nxms = ['none']
625 body += ['NXM:;T{\n%s\nT}\n' % r'\[char59] '.join(nxms)]
626
627 body += [".TE\n"]
628
629 body += ['.PP\n']
630 body += [build.nroff.block_xml_to_nroff(field_node.childNodes)]
631
632 def group_xml_to_nroff(group_node, fields):
633 title = group_node.attributes['title'].nodeValue
634
635 summary = []
636 body = []
637 for node in group_node.childNodes:
638 if node.nodeType == node.ELEMENT_NODE and node.tagName == 'field':
639 id_ = node.attributes['id'].nodeValue
640 field_to_xml(node, fields[id_], body, summary)
641 else:
642 body += [build.nroff.block_xml_to_nroff([node])]
643
644 content = [
645 '.bp\n',
646 '.SH \"%s\"\n' % build.nroff.text_to_nroff(title.upper() + " FIELDS"),
647 '.SS "Summary:"\n',
648 '.TS\n',
649 'tab(;);\n',
650 'l l l l l l l.\n',
651 'Name;Bytes;Mask;RW?;Prereqs;NXM/OXM Support\n',
652 '\_;\_;\_;\_;\_;\_\n']
653 content += summary
654 content += ['.TE\n']
655 content += body
656 return ''.join(content)
657
658 def make_oxm_classes_xml(document):
659 s = '''tab(;);
660 l l l.
661 Prefix;Vendor;Class
662 \_;\_;\_
663 '''
664 for key in sorted(OXM_CLASSES, key=OXM_CLASSES.get):
665 vendor, class_ = OXM_CLASSES.get(key)
666 s += r"\fB%s\fR;" % key.rstrip('_')
667 if vendor:
668 s += r"\fL0x%08x\fR;" % vendor
669 else:
670 s += "(none);"
671 s += r"\fL0x%04x\fR;" % class_
672 s += "\n"
673 e = document.createElement('tbl')
674 e.appendChild(document.createTextNode(s))
675 return e
676
677 def recursively_replace(node, name, replacement):
678 for child in node.childNodes:
679 if child.nodeType == node.ELEMENT_NODE:
680 if child.tagName == name:
681 node.replaceChild(replacement, child)
682 else:
683 recursively_replace(child, name, replacement)
684
685 def make_ovs_fields(meta_flow_h, meta_flow_xml):
686 fields = extract_ofp_fields(meta_flow_h)
687 fields_map = {}
688 for f in fields:
689 fields_map[f['mff']] = f
690
691 document = xml.dom.minidom.parse(meta_flow_xml)
692 doc = document.documentElement
693
694 global version
695 if version == None:
696 version = "UNKNOWN"
697
698 print('''\
699 '\\" tp
700 .\\" -*- mode: troff; coding: utf-8 -*-
701 .TH "ovs\-fields" 7 "%s" "Open vSwitch" "Open vSwitch Manual"
702 .fp 5 L CR \\" Make fixed-width font available as \\fL.
703 .de ST
704 . PP
705 . RS -0.15in
706 . I "\\\\$1"
707 . RE
708 ..
709
710 .de SU
711 . PP
712 . I "\\\\$1"
713 ..
714
715 .de IQ
716 . br
717 . ns
718 . IP "\\\\$1"
719 ..
720
721 .de TQ
722 . br
723 . ns
724 . TP "\\\\$1"
725 ..
726 .de URL
727 \\\\$2 \\(laURL: \\\\$1 \\(ra\\\\$3
728 ..
729 .if \\n[.g] .mso www.tmac
730 .SH NAME
731 ovs\-fields \- protocol header fields in OpenFlow and Open vSwitch
732 .
733 .PP
734 ''' % version)
735
736 recursively_replace(doc, 'oxm_classes', make_oxm_classes_xml(document))
737
738 s = ''
739 for node in doc.childNodes:
740 if node.nodeType == node.ELEMENT_NODE and node.tagName == "group":
741 s += group_xml_to_nroff(node, fields_map)
742 elif node.nodeType == node.TEXT_NODE:
743 assert node.data.isspace()
744 elif node.nodeType == node.COMMENT_NODE:
745 pass
746 else:
747 s += build.nroff.block_xml_to_nroff([node])
748
749 for f in fields:
750 if "used" not in f:
751 fatal("%s: field not documented" % f["mff"])
752 if n_errors:
753 sys.exit(1)
754
755 output = []
756 for oline in s.split("\n"):
757 oline = oline.strip()
758
759 # Life is easier with nroff if we don't try to feed it Unicode.
760 # Fortunately, we only use a few characters outside the ASCII range.
761 oline = oline.replace(u'\u2208', r'\[mo]')
762 oline = oline.replace(u'\u2260', r'\[!=]')
763 oline = oline.replace(u'\u2264', r'\[<=]')
764 oline = oline.replace(u'\u2265', r'\[>=]')
765 oline = oline.replace(u'\u00d7', r'\[mu]')
766 if len(oline):
767 output += [oline]
768
769 # nroff tends to ignore .bp requests if they come after .PP requests,
770 # so remove .PPs that precede .bp.
771 for i in range(len(output)):
772 if output[i] == '.bp':
773 j = i - 1
774 while j >= 0 and output[j] == '.PP':
775 output[j] = None
776 j -= 1
777 for i in range(len(output)):
778 if output[i] is not None:
779 print(output[i])
780 \f
781 ## ------------ ##
782 ## Main Program ##
783 ## ------------ ##
784
785 if __name__ == "__main__":
786 argv0 = sys.argv[0]
787 try:
788 options, args = getopt.gnu_getopt(sys.argv[1:], 'h',
789 ['help', 'ovs-version='])
790 except getopt.GetoptError as geo:
791 sys.stderr.write("%s: %s\n" % (argv0, geo.msg))
792 sys.exit(1)
793
794 global version
795 version = None
796 for key, value in options:
797 if key in ['-h', '--help']:
798 usage()
799 elif key == '--ovs-version':
800 version = value
801 else:
802 sys.exit(0)
803
804 if not args:
805 sys.stderr.write("%s: missing command argument "
806 "(use --help for help)\n" % argv0)
807 sys.exit(1)
808
809 commands = {"meta-flow": (make_meta_flow, 1),
810 "nx-match": (make_nx_match, 1),
811 "ovs-fields": (make_ovs_fields, 2)}
812
813 if not args[0] in commands:
814 sys.stderr.write("%s: unknown command \"%s\" "
815 "(use --help for help)\n" % (argv0, args[0]))
816 sys.exit(1)
817
818 func, n_args = commands[args[0]]
819 if len(args) - 1 != n_args:
820 sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
821 "provided\n"
822 % (argv0, args[0], n_args, len(args) - 1))
823 sys.exit(1)
824
825 func(*args[1:])