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