source: trunk/server/source4/scripting/bin/w32err_code.py

Last change on this file was 745, checked in by Silvan Scherrer, 13 years ago

Samba Server: updated trunk to 3.6.0

File size: 13.5 KB
Line 
1#!/usr/bin/env 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
22import re
23import os
24import sys
25import urllib
26import pprint
27from xml.dom import minidom
28from optparse import OptionParser, OptionGroup
29
30_wspp_werror_url = 'http://msdn.microsoft.com/en-us/library/cc231199%28PROT.10%29.aspx'
31
32class 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
134class 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
274class 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
322def _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
331if __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)
Note: See TracBrowser for help on using the repository browser.