netdev-dpdk: fix mbuf leaks
[cascardo/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])