bf57fefc228d080721cd5631ab1a07229630fc2b
[samba.git] / source4 / scripting / bin / gen_ntstatus.py
1 #!/usr/bin/env python
2
3 #
4 # Unix SMB/CIFS implementation.
5 #
6 # HRESULT Error definitions
7 #
8 # Copyright (C) Noel Power <noel.power@suse.com> 2014
9 #
10 # This program is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the GNU General Public License
21 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
22 #
23
24 import sys, os.path, io, string
25
26 # parsed error data
27 Errors = []
28 # error definitions to output
29 ErrorsToUse = []
30 ErrorsToCreatDescFor = []
31
32 # some lookup dictionaries
33 DefineToErrCode = {};
34 ErrCodeToDefine = {};
35
36 # error data model
37 class ErrorDef:
38
39     def __init__(self):
40         self.err_code = None
41         self.err_define = None
42         self.err_string = ""
43         self.linenum = ""
44
45 def escapeString( input ):
46     output = input.replace('"','\\"')
47     output = output.replace("\\<","\\\\<")
48     output = output.replace('\t',"")
49     return output
50
51 def transformErrorName( error_name ):
52     new_name = error_name
53     if error_name.startswith("STATUS_"):
54         error_name = error_name.replace("STATUS_","",1)
55     elif error_name.startswith("RPC_NT_"):
56         error_name = error_name.replace("RPC_NT_","RPC_",1)
57     elif error_name.startswith("EPT_NT_"):
58         error_name = error_name.replace("EPT_NT_","EPT_",1)
59     new_name = "NT_STATUS_" + error_name
60     return new_name
61
62 def parseErrorDescriptions( file_contents, isWinError ):
63     count = 0
64     for line in file_contents:
65         if line == None or line == '\t' or line == "" or line == '\n':
66             continue
67         content = line.strip().split(None,1)
68         # start new error definition ?
69         if line.startswith("0x"):
70             newError = ErrorDef()
71             newError.err_code = int(content[0],0)
72             # escape the usual suspects
73             if len(content) > 1:
74                 newError.err_string = escapeString(content[1])
75             newError.linenum = count
76             newError.isWinError = isWinError
77             Errors.append(newError)
78         else:
79             if len(Errors) == 0:
80                 print "Error parsing file as line %d"%count
81                 sys.exit()
82             err = Errors[-1]
83             if err.err_define == None:
84                 err.err_define = transformErrorName(content[0])
85             else:
86                 if len(content) > 0:
87                     desc =  escapeString(line.strip())
88                     if len(desc):
89                         if err.err_string == "":
90                             err.err_string = desc
91                         else:
92                             err.err_string = err.err_string + " " + desc
93             count = count + 1
94     print "parsed %d lines generated %d error definitions"%(count,len(Errors))
95
96 def parseErrCodeString(error_code_string):
97     # we could develop this more and *really* parse it but realistically 
98     # we are only interested in NT_STATUS( mask | code ) or NT_STATUS( code )
99     parts = error_code_string.split('|',1)
100     code = None
101     try:
102         if len(parts) > 1:
103             if len(parts) > 2: #something weird, better warn
104                 print "warning something weird unexpected errorcode format ->%s<-"%error_code_string
105             code = int(parts[0],0) | int(parts[1],0)
106         else:
107             code = int(error_code_string,0)
108     except:
109         pass
110     return code
111
112 def parseHeaderFile(file_contents):
113     count = 0
114     for line in file_contents:
115         contents = line.strip().split(None,2)
116         err_code_string = None
117         err_code = None
118
119         if len(contents) > 2:
120             if contents[0] == "#define" and contents[2].startswith("NT_STATUS("):
121                 # hairy parsing of lines like
122                 # "#define SOMETHING NT_STATUS( num1 | num2 )" etc...
123                 err_code_string = contents[2].split('(')[1].split(')')[0]
124                 err_code = parseErrCodeString( err_code_string ) 
125                 if  err_code != None:
126                     const_define = contents[1]
127 #                    print "%s 0x%x"%(const_define, err_code)
128                     DefineToErrCode[const_define] = err_code
129                     ErrCodeToDefine[err_code] = const_define
130                 else:
131                     print "warning: failed to process line[%d] ->%s<-"%(count,line)
132         count = count + 1
133     print "read %d error declarations from header file"%len(ErrCodeToDefine)
134
135 def generateHeaderFile(out_file):
136     out_file.write("\n\n")
137     out_file.write("/*\n")
138     out_file.write(" * New descriptions for new errors generated from\n")
139     out_file.write(" * [MS-ERREF] http://msdn.microsoft.com/en-us/library/cc704588.aspx\n")
140     out_file.write(" */\n\n")
141     for err in ErrorsToUse:
142         line = "#define {0:49} NT_STATUS(0x{1:08X})\n".format(err.err_define ,err.err_code)
143         out_file.write(line)
144
145
146 def generateSourceFile(out_file):
147     out_file.write("/*\n")
148     out_file.write(" * New descriptions for existing errors generated from\n")
149     out_file.write(" * [MS-ERREF] http://msdn.microsoft.com/en-us/library/cc704588.aspx\n")
150     out_file.write(" */\n")
151     for err in ErrorsToCreatDescFor:
152         out_file.write("        { N_(\"%s\"), %s },\n"%(err.err_string, err.err_define))
153     out_file.write("\n\n")
154     out_file.write("/*\n")
155     out_file.write(" * New descriptions for new errors generated from\n")
156     out_file.write(" * [MS-ERREF] http://msdn.microsoft.com/en-us/library/cc704588.aspx\n")
157     out_file.write(" */\n")
158     for err in ErrorsToUse:
159         out_file.write("        { N_(\"%s\"), %s },\n"%(err.err_string, err.err_define))
160     out_file.write("\n\n");
161     out_file.write("/*\n")
162     out_file.write(" * New descriptions for new errors generated from\n")
163     out_file.write(" * [MS-ERREF] http://msdn.microsoft.com/en-us/library/cc704588.aspx\n")
164     out_file.write(" */\n")
165     for err in ErrorsToUse:
166         out_file.write("        { \"%s\", %s },\n"%(err.err_define, err.err_define))
167
168 def def_in_list(define, err_def_with_desc):
169     for item in err_def_with_desc:
170         if item.strip() == define:
171             return True
172     return False
173
174 def processErrorDescription(err_def_with_desc):
175     print "processing error descriptions...."
176     count = 0 
177     for err in Errors:
178         # do we have an error with this error code  already ?
179         if ErrCodeToDefine.has_key(err.err_code):
180             already_has_desc = def_in_list(ErrCodeToDefine[err.err_code], err_def_with_desc)
181             # no 'full' error description for this error code so create a new
182             # one
183             if already_has_desc == False:
184                 # synthesise a new Error object to create desc from
185                 new_error = ErrorDef()
186                 new_error.err_define =  ErrCodeToDefine[err.err_code]
187                 new_error.err_code = err.err_code
188                 new_error.err_string = err.err_string
189                 new_error.linenum = err.linenum
190                 ErrorsToCreatDescFor.append(new_error)
191             count = count + 1
192         else:
193            ErrorsToUse.append(err) 
194     if count > 0:
195         print "skipped %d existing definitions"%count
196     print "imported %d new error definitions"%(len(ErrorsToUse))
197     print "created %d new error descriptions for existing errors"%(len(ErrorsToCreatDescFor))
198
199 # Very simple script to generate files ntstatus.c & ntstatus.h, these
200 # files contain generated content used to add to the existing content
201 # of files nterr.c & ntstatus.h. 
202 # The script takes 3 inputs
203 # 1. location of the existing ntstatus.h (which is used to build a list of
204 #     existing error defines
205 # 2. a text file, format which is very simple and is just the content of a 
206 #    html table ( such as that found in
207 #    http://msdn.microsoft.com/en-us/library/cc231200.aspx ) copied and
208 #    pasted into a text file
209 # 3. finally a text file containing names of the error defines (1 per line)
210 #    that already are in ntstatus.h/nterr.c but that only have the 'short'
211 #    error description ( short error description is where the description is
212 #    the name of the error itself e.g. "NT_STATUS_SUCCESS" etc.
213
214 def main ():
215     input_file1 = None;
216     input_file2 = None;
217     filename = "ntstatus"
218     headerfile_name = filename + ".h"
219     sourcefile_name = filename + ".c"
220     if len(sys.argv) > 3:
221         input_file1 =  sys.argv[1]
222         input_file2 =  sys.argv[2]
223         input_file3 =  sys.argv[3]
224     else:
225         print "usage: %s headerfile winerrorfile existing_short_descs"%(sys.argv[0])
226         sys.exit()
227
228     # read in the data
229     file_contents = open(input_file1,"r")
230     parseHeaderFile(file_contents)
231     file_contents = open(input_file2,"r")
232     parseErrorDescriptions(file_contents, False)
233     file_contents = open(input_file3,"r")
234     has_already_desc = file_contents.readlines()
235     processErrorDescription(has_already_desc)
236     print "writing new headerfile: %s"%headerfile_name
237     out_file = open(headerfile_name,"w")
238     generateHeaderFile(out_file)
239     out_file.close()
240     print "writing new headerfile: %s"%sourcefile_name
241     out_file = open(sourcefile_name,"w")
242     generateSourceFile(out_file)
243     out_file.close()
244
245 if __name__ == '__main__':
246
247     main()