s4:rpc_server: call irpc_add_name() at startup (bug #9905)
[Samba.git] / source4 / scripting / bin / w32err_code.py
blob4ca6413e942c47994f17848cbc0c8ef9e533119c
1 #!/usr/bin/env python
3 # Unix SMB/CIFS implementation.
4 # Copyright (C) Kamen Mazdrashki <kamen.mazdrashki@postpath.com> 2009
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.
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.
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/>.
20 """Import generete werror.h/doserr.c files from WSPP HTML"""
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
30 _wspp_werror_url = 'http://msdn.microsoft.com/en-us/library/cc231199%28PROT.10%29.aspx'
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 """
43 ERROR_PREFIX = ['ERROR_', 'NERR_', 'FRS_', 'RPC_', 'EPT_', 'OR_', 'WAIT_TIMEOUT']
44 ERROR_REPLACE = ['ERROR_']
46 def __init__(self, opt):
47 self.opt = opt
48 self._errors_skipped = []
49 pass
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
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
64 def parse_url(self, url):
65 errors = {}
66 html = self._load_url(url)
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
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"
106 return errors
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
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>'
128 # html clean up
129 html = re.sub(r'<a[^>]*>(.*?)</a>', r'\1', html)
131 return html
134 class WerrorGenerator(object):
136 provides methods to generate parts of werror.h and doserr.c files
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'
144 def __init__(self, opt):
145 self.opt = opt
146 self._out_dir = opt.out_dir
147 pass
149 def _open_out_file(self, fname):
150 fname = os.path.join(self._out_dir, fname)
151 return open(fname, "w")
153 def _gen_werrors_list(self, errors):
154 """uses 'errors' dictionary to display list of Win32 Errors"""
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()
163 def _gen_werror_defs(self, errors):
164 """uses 'errors' dictionary to generate werror.h file"""
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()
174 def _gen_doserr_defs(self, errors):
175 """uses 'errors' dictionary to generate defines in doserr.c file"""
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()
184 def _gen_doserr_descriptions(self, errors):
185 """uses 'errors' dictionary to generate descriptions in doserr.c file"""
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()
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
200 def _filter_errors(self, errors, defined_errors):
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!
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)
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)
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)
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)
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
274 class WerrorParser(object):
276 Parses errors defined in werror.h file
279 def __init__(self, opt):
280 self.opt = opt
281 pass
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
301 def load_err_codes(self, fname):
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
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)
322 def _generate_files(opt):
323 parser = WerrorHtmlParser(opt)
324 errors = parser.parse_url(opt.url)
326 out = WerrorGenerator(opt)
327 out.generate(errors)
328 pass
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")
351 opt_parser.add_option_group(opt_group)
353 (options, args) = opt_parser.parse_args()
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)
360 # check options
361 _generate_files(options)