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 |
|
---|
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)
|
---|