]> git.proxmox.com Git - mirror_ovs.git/blob - build-aux/extract-ofp-errors
getopt_long: Fix broken sequence of casts in __UNCONST macor.
[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['NX']:
304 if v1 >= version_map['1.2'] or v2 >= version_map['1.2']:
305 if code is not None:
306 fatal("%s: NX1.2+ domains do not have codes" % dst)
307 code = 0
308 elif vendor != vendor_map['OF']:
309 if code is not None:
310 fatal("%s: %s domains do not have codes" % vendor_name)
311
312 for version in range(v1, v2 + 1):
313 domain[version].setdefault(vendor, {})
314 domain[version][vendor].setdefault(type_, {})
315 if code in domain[version][vendor][type_]:
316 msg = "%#x,%d,%d in OF%s means both %s and %s" % (
317 vendor, type_, code, version_reverse_map[version],
318 domain[version][vendor][type_][code][0], enum)
319 if msg in expected_errors:
320 del expected_errors[msg]
321 else:
322 error("%s: %s." % (dst, msg))
323 sys.stderr.write("%s:%d: %s: Here is the location "
324 "of the previous definition.\n"
325 % (domain[version][vendor][type_][code][1],
326 domain[version][vendor][type_][code][2],
327 dst))
328 else:
329 domain[version][vendor][type_][code] = (enum, fileName,
330 lineNumber)
331
332 assert enum not in reverse[version]
333 reverse[version][enum] = (vendor, type_, code)
334
335 inputFile.close()
336
337 for fn, ln in expected_errors.values():
338 sys.stderr.write("%s:%d: expected duplicate not used.\n" % (fn, ln))
339 n_errors += 1
340
341 if n_errors:
342 sys.exit(1)
343
344 print ("""\
345 /* Generated automatically; do not modify! -*- buffer-read-only: t -*- */
346
347 #define OFPERR_N_ERRORS %d
348
349 struct ofperr_domain {
350 const char *name;
351 uint8_t version;
352 enum ofperr (*decode)(uint32_t vendor, uint16_t type, uint16_t code);
353 struct triplet errors[OFPERR_N_ERRORS];
354 };
355
356 static const char *error_names[OFPERR_N_ERRORS] = {
357 %s
358 };
359
360 static const char *error_comments[OFPERR_N_ERRORS] = {
361 %s
362 };\
363 """ % (len(names),
364 '\n'.join(' "%s",' % name for name in names),
365 '\n'.join(' "%s",' % re.sub(r'(["\\])', r'\\\1', comment)
366 for comment in comments)))
367
368 def output_domain(map, name, description, version):
369 print ("""
370 static enum ofperr
371 %s_decode(uint32_t vendor, uint16_t type, uint16_t code)
372 {
373 switch (((uint64_t) vendor << 32) | (type << 16) | code) {""" % name)
374 found = set()
375 for enum in names:
376 if enum not in map:
377 continue
378 vendor, type_, code = map[enum]
379 if code is None:
380 continue
381 value = (vendor << 32) | (type_ << 16) | code
382 if value in found:
383 continue
384 found.add(value)
385 if vendor:
386 vendor_s = "(%#xULL << 32) | " % vendor
387 else:
388 vendor_s = ""
389 print (" case %s(%d << 16) | %d:" % (vendor_s, type_, code))
390 print (" return OFPERR_%s;" % enum)
391 print ("""\
392 }
393
394 return 0;
395 }""")
396
397 print ("""
398 static const struct ofperr_domain %s = {
399 "%s",
400 %d,
401 %s_decode,
402 {""" % (name, description, version, name))
403 for enum in names:
404 if enum in map:
405 vendor, type_, code = map[enum]
406 if code == None:
407 code = -1
408 print " { %#8x, %2d, %3d }, /* %s */" % (vendor, type_, code, enum)
409 else:
410 print (" { -1, -1, -1 }, /* %s */" % enum)
411 print ("""\
412 },
413 };""")
414
415 for version_name, id_ in version_map.items():
416 var = 'ofperr_of' + re.sub('[^A-Za-z0-9_]', '', version_name)
417 description = "OpenFlow %s" % version_name
418 output_domain(reverse[id_], var, description, id_)
419
420 if __name__ == '__main__':
421 if '--help' in sys.argv:
422 usage()
423 elif len(sys.argv) != 3:
424 sys.stderr.write("exactly two non-options arguments required; "
425 "use --help for help\n")
426 sys.exit(1)
427 else:
428 extract_vendor_ids(sys.argv[2])
429 extract_ofp_errors(sys.argv[1])