]> git.proxmox.com Git - mirror_ovs.git/blob - build-aux/extract-ofp-errors
nroff: Fix style of names.
[mirror_ovs.git] / build-aux / extract-ofp-errors
1 #! /usr/bin/python
2
3 import sys
4 import os.path
5 import re
6
7 macros = {}
8
9 # Map from OpenFlow version number to version ID used in ofp_header.
10 version_map = {"1.0": 0x01,
11 "1.1": 0x02,
12 "1.2": 0x03,
13 "1.3": 0x04,
14 "1.4": 0x05,
15 "1.5": 0x06}
16 version_reverse_map = dict((v, k) for (k, v) in version_map.iteritems())
17
18 token = None
19 line = ""
20 idRe = "[a-zA-Z_][a-zA-Z_0-9]*"
21 tokenRe = "#?" + idRe + "|[0-9]+|."
22 inComment = False
23 inDirective = False
24
25 def open_file(fn):
26 global fileName
27 global inputFile
28 global lineNumber
29 fileName = fn
30 inputFile = open(fileName)
31 lineNumber = 0
32
33 def tryGetLine():
34 global inputFile
35 global line
36 global lineNumber
37 line = inputFile.readline()
38 lineNumber += 1
39 return line != ""
40
41 def getLine():
42 if not tryGetLine():
43 fatal("unexpected end of input")
44
45 def getToken():
46 global token
47 global line
48 global inComment
49 global inDirective
50 while True:
51 line = line.lstrip()
52 if line != "":
53 if line.startswith("/*"):
54 inComment = True
55 line = line[2:]
56 elif inComment:
57 commentEnd = line.find("*/")
58 if commentEnd < 0:
59 line = ""
60 else:
61 inComment = False
62 line = line[commentEnd + 2:]
63 else:
64 match = re.match(tokenRe, line)
65 token = match.group(0)
66 line = line[len(token):]
67 if token.startswith('#'):
68 inDirective = True
69 elif token in macros and not inDirective:
70 line = macros[token] + line
71 continue
72 return True
73 elif inDirective:
74 token = "$"
75 inDirective = False
76 return True
77 else:
78 global lineNumber
79 line = inputFile.readline()
80 lineNumber += 1
81 while line.endswith("\\\n"):
82 line = line[:-2] + inputFile.readline()
83 lineNumber += 1
84 if line == "":
85 if token == None:
86 fatal("unexpected end of input")
87 token = None
88 return False
89
90 n_errors = 0
91 def error(msg):
92 global n_errors
93 sys.stderr.write("%s:%d: %s\n" % (fileName, lineNumber, msg))
94 n_errors += 1
95
96 def fatal(msg):
97 error(msg)
98 sys.exit(1)
99
100 def skipDirective():
101 getToken()
102 while token != '$':
103 getToken()
104
105 def isId(s):
106 return re.match(idRe + "$", s) != None
107
108 def forceId():
109 if not isId(token):
110 fatal("identifier expected")
111
112 def forceInteger():
113 if not re.match('[0-9]+$', token):
114 fatal("integer expected")
115
116 def match(t):
117 if token == t:
118 getToken()
119 return True
120 else:
121 return False
122
123 def forceMatch(t):
124 if not match(t):
125 fatal("%s expected" % t)
126
127 def parseTaggedName():
128 assert token in ('struct', 'union')
129 name = token
130 getToken()
131 forceId()
132 name = "%s %s" % (name, token)
133 getToken()
134 return name
135
136 def print_enum(tag, constants, storage_class):
137 print ("""
138 %(storage_class)sconst char *
139 %(tag)s_to_string(uint16_t value)
140 {
141 switch (value) {\
142 """ % {"tag": tag,
143 "bufferlen": len(tag) + 32,
144 "storage_class": storage_class})
145 for constant in constants:
146 print (" case %s: return \"%s\";" % (constant, constant))
147 print ("""\
148 }
149 return NULL;
150 }\
151 """ % {"tag": tag})
152
153 def usage():
154 argv0 = os.path.basename(sys.argv[0])
155 print ('''\
156 %(argv0)s, for extracting OpenFlow error codes from header files
157 usage: %(argv0)s ERROR_HEADER VENDOR_HEADER
158
159 This program reads VENDOR_HEADER to obtain OpenFlow vendor (aka
160 experimenter IDs), then ERROR_HEADER to obtain OpenFlow error number.
161 It outputs a C source file for translating OpenFlow error codes into
162 strings.
163
164 ERROR_HEADER should point to lib/ofp-errors.h.
165 VENDOR_HEADER should point to include/openflow/openflow-common.h.
166 The output is suitable for use as lib/ofp-errors.inc.\
167 ''' % {"argv0": argv0})
168 sys.exit(0)
169
170 def extract_vendor_ids(fn):
171 global vendor_map
172 vendor_map = {}
173 vendor_loc = {}
174
175 open_file(fn)
176 while tryGetLine():
177 m = re.match(r'#define\s+([A-Z0-9_]+)_VENDOR_ID\s+(0x[0-9a-fA-F]+|[0-9]+)', line)
178 if not m:
179 continue
180
181 name = m.group(1)
182 id_ = int(m.group(2), 0)
183
184 if name in vendor_map:
185 error("%s: duplicate definition of vendor" % name)
186 sys.stderr.write("%s: Here is the location of the previous "
187 "definition.\n" % vendor_loc[name])
188 sys.exit(1)
189
190 vendor_map[name] = id_
191 vendor_loc[name] = "%s:%d" % (fileName, lineNumber)
192
193 if not vendor_map:
194 fatal("%s: no vendor definitions found" % fn)
195
196 inputFile.close()
197
198 vendor_reverse_map = {}
199 for name, id_ in vendor_map.items():
200 if id_ in vendor_reverse_map:
201 fatal("%s: duplicate vendor id for vendors %s and %s"
202 % (id_, vendor_reverse_map[id_], name))
203 vendor_reverse_map[id_] = name
204
205 def extract_ofp_errors(fn):
206 error_types = {}
207
208 comments = []
209 names = []
210 domain = {}
211 reverse = {}
212 for domain_name in version_map.values():
213 domain[domain_name] = {}
214 reverse[domain_name] = {}
215
216 n_errors = 0
217 expected_errors = {}
218
219 open_file(fn)
220
221 while True:
222 getLine()
223 if re.match('enum ofperr', line):
224 break
225
226 while True:
227 getLine()
228 if line.startswith('/*') or not line or line.isspace():
229 continue
230 elif re.match('}', line):
231 break
232
233 if not line.lstrip().startswith('/*'):
234 fatal("unexpected syntax between errors")
235
236 comment = line.lstrip()[2:].strip()
237 while not comment.endswith('*/'):
238 getLine()
239 if line.startswith('/*') or not line or line.isspace():
240 fatal("unexpected syntax within error")
241 comment += ' %s' % line.lstrip('* \t').rstrip(' \t\r\n')
242 comment = comment[:-2].rstrip()
243
244 m = re.match('Expected: (.*)\.$', comment)
245 if m:
246 expected_errors[m.group(1)] = (fileName, lineNumber)
247 continue
248
249 m = re.match('((?:.(?!\. ))+.)\. (.*)$', comment)
250 if not m:
251 fatal("unexpected syntax between errors")
252
253 dsts, comment = m.groups()
254
255 getLine()
256 m = re.match('\s+(?:OFPERR_([A-Z0-9_]+))(\s*=\s*OFPERR_OFS)?,',
257 line)
258 if not m:
259 fatal("syntax error expecting enum value")
260
261 enum = m.group(1)
262 if enum in names:
263 fatal("%s specified twice" % enum)
264
265 comments.append(re.sub('\[[^]]*\]', '', comment))
266 names.append(enum)
267
268 for dst in dsts.split(', '):
269 m = re.match(r'([A-Z]+)([0-9.]+)(\+|-[0-9.]+)?\((\d+)(?:,(\d+))?\)$', dst)
270 if not m:
271 fatal("%r: syntax error in destination" % dst)
272 vendor_name = m.group(1)
273 version1_name = m.group(2)
274 version2_name = m.group(3)
275 type_ = int(m.group(4))
276 if m.group(5):
277 code = int(m.group(5))
278 else:
279 code = None
280
281 if vendor_name not in vendor_map:
282 fatal("%s: unknown vendor" % vendor_name)
283 vendor = vendor_map[vendor_name]
284
285 if version1_name not in version_map:
286 fatal("%s: unknown OpenFlow version" % version1_name)
287 v1 = version_map[version1_name]
288
289 if version2_name is None:
290 v2 = v1
291 elif version2_name == "+":
292 v2 = max(version_map.values())
293 elif version2_name[1:] not in version_map:
294 fatal("%s: unknown OpenFlow version" % version2_name[1:])
295 else:
296 v2 = version_map[version2_name[1:]]
297
298 if v2 < v1:
299 fatal("%s%s: %s precedes %s"
300 % (version1_name, version2_name,
301 version2_name, version1_name))
302
303 if vendor == vendor_map['OF']:
304 # All standard OpenFlow errors have a type and a code.
305 if code is None:
306 fatal("%s: %s domain requires code" % (dst, vendor_name))
307 elif vendor == vendor_map['NX']:
308 # Before OpenFlow 1.2, OVS used a Nicira extension to
309 # define errors that included a type and a code.
310 #
311 # In OpenFlow 1.2 and later, Nicira extension errors
312 # are defined using the OpenFlow experimenter error
313 # mechanism that includes a type but not a code.
314 if v1 < version_map['1.2'] or v2 < version_map['1.2']:
315 if code is None:
316 fatal("%s: NX1.0 and NX1.1 domains require code"
317 % (dst, vendor_name))
318 if v1 >= version_map['1.2'] or v2 >= version_map['1.2']:
319 if code is not None:
320 fatal("%s: NX1.2+ domains do not have codes" % dst)
321 else:
322 # Experimenter extension error for OF1.2+ only.
323 if v1 < version_map['1.2']:
324 fatal("%s: %s domain not supported before OF1.2"
325 % (dst, vendor_name))
326 if code is not None:
327 fatal("%s: %s domains do not have codes"
328 % (dst, vendor_name))
329 if code is None:
330 code = 0
331
332 for version in range(v1, v2 + 1):
333 domain[version].setdefault(vendor, {})
334 domain[version][vendor].setdefault(type_, {})
335 if code in domain[version][vendor][type_]:
336 msg = "%#x,%d,%d in OF%s means both %s and %s" % (
337 vendor, type_, code, version_reverse_map[version],
338 domain[version][vendor][type_][code][0], enum)
339 if msg in expected_errors:
340 del expected_errors[msg]
341 else:
342 error("%s: %s." % (dst, msg))
343 sys.stderr.write("%s:%d: %s: Here is the location "
344 "of the previous definition.\n"
345 % (domain[version][vendor][type_][code][1],
346 domain[version][vendor][type_][code][2],
347 dst))
348 else:
349 domain[version][vendor][type_][code] = (enum, fileName,
350 lineNumber)
351
352 assert enum not in reverse[version]
353 reverse[version][enum] = (vendor, type_, code)
354
355 inputFile.close()
356
357 for fn, ln in expected_errors.values():
358 sys.stderr.write("%s:%d: expected duplicate not used.\n" % (fn, ln))
359 n_errors += 1
360
361 if n_errors:
362 sys.exit(1)
363
364 print ("""\
365 /* Generated automatically; do not modify! -*- buffer-read-only: t -*- */
366
367 #define OFPERR_N_ERRORS %d
368
369 struct ofperr_domain {
370 const char *name;
371 uint8_t version;
372 enum ofperr (*decode)(uint32_t vendor, uint16_t type, uint16_t code);
373 struct triplet errors[OFPERR_N_ERRORS];
374 };
375
376 static const char *error_names[OFPERR_N_ERRORS] = {
377 %s
378 };
379
380 static const char *error_comments[OFPERR_N_ERRORS] = {
381 %s
382 };\
383 """ % (len(names),
384 '\n'.join(' "%s",' % name for name in names),
385 '\n'.join(' "%s",' % re.sub(r'(["\\])', r'\\\1', comment)
386 for comment in comments)))
387
388 def output_domain(map, name, description, version):
389 print ("""
390 static enum ofperr
391 %s_decode(uint32_t vendor, uint16_t type, uint16_t code)
392 {
393 switch (((uint64_t) vendor << 32) | (type << 16) | code) {""" % name)
394 found = set()
395 for enum in names:
396 if enum not in map:
397 continue
398 vendor, type_, code = map[enum]
399 value = (vendor << 32) | (type_ << 16) | code
400 if value in found:
401 continue
402 found.add(value)
403 if vendor:
404 vendor_s = "(%#xULL << 32) | " % vendor
405 else:
406 vendor_s = ""
407 print (" case %s(%d << 16) | %d:" % (vendor_s, type_, code))
408 print (" return OFPERR_%s;" % enum)
409 print ("""\
410 }
411
412 return 0;
413 }""")
414
415 print ("""
416 static const struct ofperr_domain %s = {
417 "%s",
418 %d,
419 %s_decode,
420 {""" % (name, description, version, name))
421 for enum in names:
422 if enum in map:
423 vendor, type_, code = map[enum]
424 if code == None:
425 code = -1
426 print " { %#8x, %2d, %3d }, /* %s */" % (vendor, type_, code, enum)
427 else:
428 print (" { -1, -1, -1 }, /* %s */" % enum)
429 print ("""\
430 },
431 };""")
432
433 for version_name, id_ in version_map.items():
434 var = 'ofperr_of' + re.sub('[^A-Za-z0-9_]', '', version_name)
435 description = "OpenFlow %s" % version_name
436 output_domain(reverse[id_], var, description, id_)
437
438 if __name__ == '__main__':
439 if '--help' in sys.argv:
440 usage()
441 elif len(sys.argv) != 3:
442 sys.stderr.write("exactly two non-options arguments required; "
443 "use --help for help\n")
444 sys.exit(1)
445 else:
446 extract_vendor_ids(sys.argv[2])
447 extract_ofp_errors(sys.argv[1])