Merge branch 'master' of ssh://jra@git.samba.org/data/git/samba
[kai/samba-autobuild/.git] / source4 / scripting / bin / w32err_code.py
1 #!/usr/bin/python
2
3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Kamen Mazdrashki <kamen.mazdrashki@postpath.com> 2009
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 """Import generete werror.h/doserr.c files from WSPP HTML"""
21
22 import re
23 import os
24 import sys
25 import urllib
26 import pprint
27 from xml.dom import minidom
28 from optparse import OptionParser, OptionGroup
29
30 _wspp_werror_url = 'http://msdn.microsoft.com/en-us/library/cc231199%28PROT.10%29.aspx'
31
32 class WerrorHtmlParser(object):
33     """
34     Parses HTML from WSPP documentation generating dictionary of
35     dictionaries with following keys:
36       - "err_hex" - hex number (as string)
37       - "err_name" - error name
38       - "err_desc" - error long description
39     For the key of returned dictionary err_hex is used,
40     i.e. "hex-error-code-str" => {error dictionary object}
41     """
42
43     ERROR_PREFIX = ['ERROR_', 'NERR_', 'FRS_', 'RPC_', 'EPT_', 'OR_', 'WAIT_TIMEOUT']
44     ERROR_REPLACE = ['ERROR_']
45
46     def __init__(self, opt):
47         self.opt = opt
48         self._errors_skipped = []
49         pass
50
51     def _is_error_code_name(self, err_name):
52         for pref in self.ERROR_PREFIX:
53             if err_name.startswith(pref):
54                 return True
55         return False
56
57     def _make_werr_name(self, err_name):
58         err_name = err_name.upper()
59         for pref in self.ERROR_REPLACE:
60             if err_name.startswith(pref):
61                 return err_name.replace(pref, 'WERR_', 1)
62         return 'WERR_' + err_name
63
64     def parse_url(self, url):
65         errors = {}
66         html = self._load_url(url)
67
68         # let minidom to parse the tree, should be:
69         # table -> tr -> td
70         #     p -> [hex code, br, error code]
71         #     p -> [description]
72         table_node = minidom.parseString(html)
73         for row_node in table_node.getElementsByTagName("tr"):
74             # verify we got right number of td elements
75             td_nodes = row_node.getElementsByTagName('td')
76             if len(td_nodes) != 2:
77                 continue
78             # now get the real data
79             p_nodes = row_node.getElementsByTagName('p')
80             if len(p_nodes) != 2:   continue
81             if len(p_nodes[0].childNodes) != 3: continue
82             if len(p_nodes[1].childNodes) != 1: continue
83             err_hex = str(p_nodes[0].childNodes[0].nodeValue)
84             err_name = str(p_nodes[0].childNodes[2].nodeValue)
85             err_desc = p_nodes[1].childNodes[0].nodeValue.encode('utf-8')
86             err_desc = err_desc.replace('"', '\\"').replace("\'", "\\'")
87             # do some checking
88             if not err_hex.startswith('0x'):    continue
89             if not self._is_error_code_name(err_name):
90                 self._errors_skipped.append("%s - %s - %d" % (err_name, err_hex, int(err_hex, 16)))
91                 continue
92             # create entry
93             err_name = self._make_werr_name(err_name)
94             err_def = {'err_hex': err_hex,
95                        'err_name': err_name,
96                        'err_desc': err_desc,
97                        'code': int(err_hex, 16)}
98             errors[err_def['code']] = err_def
99
100         # print skipped errors
101         if self.opt.print_skipped and len(self._errors_skipped):
102             print "\nErrors skipped during HTML parsing:"
103             pprint.pprint(self._errors_skipped)
104             print "\n"
105
106         return errors
107
108     def _load_url(self, url):
109         html_str = ""
110         try:
111             fp = urllib.urlopen(url)
112             for line in fp:
113                 html_str += line.strip()
114             fp.close()
115         except IOError, e:
116             print "error loading url: " + e.strerror
117             pass
118
119         # currently ERROR codes are rendered as table
120         # locate table chunk with ERROR_SUCCESS
121         html = [x for x in html_str.split('<table ') if "ERROR_SUCCESS" in x]
122         html = '<table ' + html[0]
123         pos = html.find('</table>')
124         if pos == -1:
125             return '';
126         html = html[:pos] + '</table>'
127
128         # html clean up
129         html = re.sub(r'<a[^>]*>(.*?)</a>', r'\1', html)
130
131         return html
132
133
134 class WerrorGenerator(object):
135     """
136     provides methods to generate parts of werror.h and doserr.c files
137     """
138
139     FNAME_WERRORS   = 'w32errors.lst'
140     FNAME_WERROR_DEFS  = 'werror_defs.h'
141     FNAME_DOSERR_DEFS = 'doserr_defs.c'
142     FNAME_DOSERR_DESC = 'doserr_desc.c'
143
144     def __init__(self, opt):
145         self.opt = opt
146         self._out_dir = opt.out_dir
147         pass
148
149     def _open_out_file(self, fname):
150         fname = os.path.join(self._out_dir, fname)
151         return open(fname, "w")
152
153     def _gen_werrors_list(self, errors):
154         """uses 'errors' dictionary to display list of Win32 Errors"""
155
156         fp = self._open_out_file(self.FNAME_WERRORS)
157         for err_code in sorted(errors.keys()):
158             err_name = errors[err_code]['err_name']
159             fp.write(err_name)
160             fp.write("\n")
161         fp.close()
162
163     def _gen_werror_defs(self, errors):
164         """uses 'errors' dictionary to generate werror.h file"""
165
166         fp = self._open_out_file(self.FNAME_WERROR_DEFS)
167         for err_code in sorted(errors.keys()):
168             err_name = errors[err_code]['err_name']
169             err_hex = errors[err_code]['err_hex']
170             fp.write('#define %s\tW_ERROR(%s)' % (err_name, err_hex))
171             fp.write("\n")
172         fp.close()
173
174     def _gen_doserr_defs(self, errors):
175         """uses 'errors' dictionary to generate defines in doserr.c file"""
176
177         fp = self._open_out_file(self.FNAME_DOSERR_DEFS)
178         for err_code in sorted(errors.keys()):
179             err_name = errors[err_code]['err_name']
180             fp.write('\t{ "%s", %s },' % (err_name, err_name))
181             fp.write("\n")
182         fp.close()
183
184     def _gen_doserr_descriptions(self, errors):
185         """uses 'errors' dictionary to generate descriptions in doserr.c file"""
186
187         fp = self._open_out_file(self.FNAME_DOSERR_DESC)
188         for err_code in sorted(errors.keys()):
189             err_name = errors[err_code]['err_name']
190             fp.write('\t{ %s, "%s" },' % (err_name, errors[err_code]['err_desc']))
191             fp.write("\n")
192         fp.close()
193
194     def _lookup_error_by_name(self, err_name, defined_errors):
195         for err in defined_errors.itervalues():
196             if err['err_name'] == err_name:
197                 return err
198         return None
199
200     def _filter_errors(self, errors, defined_errors):
201         """
202         returns tuple (new_erros, diff_code_errors, diff_name_errors)
203         new_errors - dictionary of errors not in defined_errors
204         diff_code_errors - list of errors found in defined_errors
205                            but with different value
206         diff_name_errors - list of errors found with same code in
207                             defined_errors, but with different name
208         Most critical is diff_code_errors list to be empty!
209         """
210         new_errors = {}
211         diff_code_errors = []
212         diff_name_errors = []
213         for err_def in errors.itervalues():
214             add_error = True
215             # try get defined error by code
216             if defined_errors.has_key(err_def['code']):
217                 old_err = defined_errors[err_def['code']]
218                 if err_def['err_name'] != old_err['err_name']:
219                     warning = {'msg': 'New and Old errors has different error names',
220                                'err_new': err_def,
221                                'err_old': old_err}
222                     diff_name_errors.append(warning)
223
224             # sanity check for errors with same name but different values
225             old_err = self._lookup_error_by_name(err_def['err_name'], defined_errors)
226             if old_err:
227                 if err_def['code'] != old_err['code']:
228                     warning = {'msg': 'New and Old error defs has different error value',
229                                'err_new': err_def,
230                                'err_old': old_err}
231                     diff_code_errors.append(warning)
232                 # exclude error already defined with same name
233                 add_error = False
234             # do add the error in new_errors if everything is fine
235             if add_error:
236                 new_errors[err_def['code']] = err_def
237             pass
238         return (new_errors, diff_code_errors, diff_name_errors)
239
240     def generate(self, errors):
241         # load already defined error codes
242         werr_parser = WerrorParser(self.opt)
243         (defined_errors,
244          no_value_errors) = werr_parser.load_err_codes(self.opt.werror_file)
245         if not defined_errors:
246             print "\nUnable to load existing errors file: %s" % self.opt.werror_file
247             sys.exit(1)
248         if self.opt.verbose and len(no_value_errors):
249             print "\nWarning: there are errors defines using macro value:"
250             pprint.pprint(no_value_errors)
251             print ""
252         # filter generated error codes
253         (new_errors,
254          diff_code_errors,
255          diff_name_errors) = self._filter_errors(errors, defined_errors)
256         if diff_code_errors:
257             print("\nFound %d errors with same names but different error values! Aborting."
258                   % len(diff_code_errors))
259             pprint.pprint(diff_code_errors)
260             sys.exit(2)
261
262         if diff_name_errors:
263             print("\nFound %d errors with same values but different names (should be normal)"
264                   % len(diff_name_errors))
265             pprint.pprint(diff_name_errors)
266
267         # finally generate output files
268         self._gen_werror_defs(new_errors)
269         self._gen_doserr_defs(new_errors)
270         self._gen_werrors_list(errors)
271         self._gen_doserr_descriptions(errors)
272         pass
273
274 class WerrorParser(object):
275     """
276     Parses errors defined in werror.h file
277     """
278
279     def __init__(self, opt):
280         self.opt = opt
281         pass
282
283     def _parse_werror_line(self, line):
284         m = re.match('#define[ \t]*(.*?)[ \t]*W_ERROR\((.*?)\)', line)
285         if not m or (len(m.groups()) != 2):
286             return None
287         if len(m.group(1)) == 0:
288             return None
289         if str(m.group(2)).startswith('0x'):
290             err_code = int(m.group(2), 16)
291         elif m.group(2).isdigit():
292             err_code = int(m.group(2))
293         else:
294             self.err_no_values.append(line)
295             return None
296         return {'err_name': str(m.group(1)),
297                 'err_hex': "0x%08X" % err_code,
298                 'code': err_code}
299         pass
300
301     def load_err_codes(self, fname):
302         """
303         Returns tuple of:
304             dictionary of "hex_err_code" => {code, name}
305                 "hex_err_code" is string
306                 "code" is int value for the error
307             list of errors that was ignored for some reason
308         """
309         # reset internal variables
310         self.err_no_values = []
311         err_codes = {}
312         fp = open(fname)
313         for line in fp.readlines():
314             err_def = self._parse_werror_line(line)
315             if err_def:
316                 err_codes[err_def['code']] = err_def
317         fp.close();
318         return (err_codes, self.err_no_values)
319
320
321
322 def _generate_files(opt):
323     parser = WerrorHtmlParser(opt)
324     errors = parser.parse_url(opt.url)
325
326     out = WerrorGenerator(opt)
327     out.generate(errors)
328     pass
329
330
331 if __name__ == '__main__':
332     _cur_dir = os.path.abspath(os.path.dirname(__file__))
333     opt_parser = OptionParser(usage="usage: %prog [options]", version="%prog 0.3")
334     opt_group = OptionGroup(opt_parser, "Main options")
335     opt_group.add_option("--url", dest="url",
336                          default=_wspp_werror_url,
337                          help="url for w32 error codes html - may be local file")
338     opt_group.add_option("--out", dest="out_dir",
339                          default=_cur_dir,
340                          help="output dir for generated files")
341     opt_group.add_option("--werror", dest="werror_file",
342                          default=os.path.join(_cur_dir, 'werror.h'),
343                          help="path to werror.h file")
344     opt_group.add_option("--print_skipped",
345                         action="store_true", dest="print_skipped", default=False,
346                         help="print errors skipped during HTML parsing")
347     opt_group.add_option("-q", "--quiet",
348                         action="store_false", dest="verbose", default=True,
349                         help="don't print warnings to stdout")
350
351     opt_parser.add_option_group(opt_group)
352
353     (options, args) = opt_parser.parse_args()
354
355     # add some options to be used internally
356     options.err_defs_file = os.path.join(options.out_dir, WerrorGenerator.FNAME_WERROR_DEFS)
357     options.dos_defs_file = os.path.join(options.out_dir, WerrorGenerator.FNAME_DOSERR_DEFS)
358     options.dos_desc_file = os.path.join(options.out_dir, WerrorGenerator.FNAME_DOSERR_DESC)
359
360     # check options
361     _generate_files(options)