659bcb38f1b1b7b31fd715e092449699d5c762af
[metze/wireshark/wip.git] / tools / generate-sysdig-event.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 #
4 # Wireshark - Network traffic analyzer
5 # By Gerald Combs <gerald@wireshark.org>
6 # Copyright 1998 Gerald Combs
7 #
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License
10 # as published by the Free Software Foundation; either version 2
11 # of the License, or (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #
22 '''\
23 Generate Sysdig event dissector sections from the sysdig sources.
24
25 Reads driver/event_table.c and driver/ppm_events_public.h and generates
26 corresponding dissection code in packet-sysdig-event.c. Updates are
27 performed in-place in the dissector code.
28
29 Requires an Internet connection. Assets are loaded from GitHub over HTTPS.
30 '''
31
32 import os
33 import os.path
34 import re
35 import urllib2
36 import sys
37
38 sysdig_repo_pfx = 'https://raw.githubusercontent.com/draios/sysdig/0.5.0/'
39
40 ppm_ev_pub = urllib2.urlopen(sysdig_repo_pfx + 'driver/ppm_events_public.h')
41 ppm_ev_pub_lines = ppm_ev_pub.readlines()
42 ppm_ev_pub.close()
43
44 ppme_re = re.compile('^\s+PPME_([A-Z0-9_]+_[EX])\s*=\s*([0-9]+)\s*,')
45
46 event_info_d = {}
47
48 def get_event_defines():
49     event_d = {}
50     for line in ppm_ev_pub_lines:
51         m = ppme_re.match(line)
52         if m:
53             event_d[int(m.group(2))] = m.group(1)
54     return event_d
55
56 ppm_ev_table = urllib2.urlopen(sysdig_repo_pfx + 'driver/event_table.c')
57 ppm_ev_table_lines = ppm_ev_table.readlines()
58 ppm_ev_table.close()
59
60 hf_d = {}
61
62 event_info_re = re.compile('^\s+/\*\s*PPME_.*\*\/\s*{\s*"([A-Za-z0-9_]+)"\s*,[^,]+,[^,]+,\s*([0-9]+)\s*[,{}]')
63 event_param_re = re.compile('{\s*"([A-Za-z0-9_]+)"\s*,\s*PT_([A-Z0-9_]+)\s*,\s*PF_([A-Z0-9_]+)\s*[,}]')
64
65 def get_event_names():
66     '''Return a contiguous list of event names. Names are lower case.'''
67     event_name_l = []
68     for line in ppm_ev_table_lines:
69         ei = event_info_re.match(line)
70         if ei:
71             event_name_l.append(ei.group(1))
72     return event_name_l
73
74 # PT_xxx to FT_xxx
75 pt_to_ft = {
76     'BYTEBUF': 'BYTES',
77     'CHARBUF': 'STRING',
78     'FD': 'INT64',
79     'FSPATH': 'STRING',
80 }
81
82 def get_event_params():
83     '''Return a list of dictionaries containing event names and parameter info.'''
84     event_param_l = []
85     event_num = 0
86     force_string_l = ['args', 'env']
87     for line in ppm_ev_table_lines:
88         ei = event_info_re.match(line)
89         ep = event_param_re.findall(line)
90         if ei and ep:
91             src_param_count = int(ei.group(2))
92             if len(ep) != src_param_count:
93                 err_msg = '{}: found {} parameters. Expected {}. Params: {}'.format(
94                     ei.group(1), len(ep), src_param_count, repr(ep))
95                 raise NameError(err_msg)
96             for p in ep:
97                 if p[0] in force_string_l:
98                     param_type = 'STRING'
99                 elif p[1] in pt_to_ft:
100                     param_type = pt_to_ft[p[1]]
101                 elif p[0] == 'flags' and p[1].startswith('INT') and 'HEX' in p[2]:
102                     param_type = 'U' + p[1]
103                 elif 'INT' in p[1]:
104                     # Ints
105                     param_type = p[1]
106                 else:
107                     # Fall back to bytes
108                     param_type = 'BYTES'
109
110                 if p[2] == 'NA':
111                     if 'INT' in param_type:
112                         param_format = 'DEC'
113                     else:
114                         param_format = 'NONE'
115                 elif param_type == 'BYTES':
116                     param_format = 'NONE'
117                 else:
118                     param_format = p[2]
119                 param_d = {
120                     'event_name': ei.group(1),
121                     'event_num': event_num,
122                     'param_name': p[0],
123                     'param_type': param_type,
124                     'param_format': param_format,
125                 }
126                 event_param_l.append(param_d)
127         if ei:
128             event_num += 1
129     return event_param_l
130
131 def param_to_hf_name(param):
132     return 'hf_param_{}_{}'.format(param['param_name'], param['param_type'].lower())
133
134 def param_to_value_string_name(param):
135     return '{}_{}_vals'.format(param['param_name'], param['param_type'].lower())
136
137 def get_param_desc(param):
138     # Try to coerce event names and parameters into human-friendly
139     # strings.
140     # XXX This could use some work.
141
142     # Specific descriptions. Event name + parameter name.
143     param_descs = {
144         'accept.queuepct': 'Accept queue per connection',
145         'execve.args': 'Program arguments',
146         'execve.comm': 'Command',
147         'execve.cwd': 'Current working directory',
148     }
149     # General descriptions. Event name only.
150     event_descs = {
151         'ioctl': 'I/O control',
152     }
153
154     event_name = param['event_name']
155     param_id = '{}.{}'.format(event_name, param['param_name'])
156     if param_id in param_descs:
157         param_desc = param_descs[param_id]
158     elif event_name in event_descs:
159         param_desc = '{}: {}'.format(event_descs[event_name], param['param_name'])
160     else:
161         param_desc = param['param_name']
162     return param_desc
163
164 def main():
165     # Event list
166     event_d = get_event_defines()
167     event_nums = event_d.keys()
168     event_nums.sort()
169
170     event_name_l = get_event_names()
171     event_param_l = get_event_params()
172
173     hf_d = {}
174     for param in event_param_l:
175         hf_name = param_to_hf_name(param)
176         hf_d[hf_name] = param
177
178     idx_id_to_name = { '': 'no' }
179     parameter_index_l = []
180
181     for en in range (0, len(event_nums)):
182         param_id = ''
183         param_l = []
184         event_var = event_d[en].lower()
185         for param in event_param_l:
186             if param['event_num'] == en:
187                 hf_name = param_to_hf_name(param)
188                 param_l.append(hf_name)
189                 param_id += ':' + param['param_name'] + '_' + param['param_type']
190
191         ei_str = ''
192         if param_id not in idx_id_to_name:
193             idx_id_to_name[param_id] = event_var
194             ei_str = 'static const int *{}_indexes[] = {{ &{}, NULL }};'.format(
195                 event_var,
196                 ', &'.join(param_l)
197             )
198         else:
199             ei_str = '#define {}_indexes {}_indexes'.format(event_var, idx_id_to_name[param_id])
200
201         parameter_index_l.append(ei_str)
202
203     dissector_path = os.path.join(os.path.dirname(__file__),
204         '..', 'epan', 'dissectors', 'packet-sysdig-event.c')
205     dissector_f = open(dissector_path, 'r')
206     dissector_lines = list(dissector_f)
207     dissector_f = open(dissector_path, 'w+')
208
209     # Strip out old content
210     strip_re_l = []
211     strip_re_l.append(re.compile('^static\s+int\s+hf_param_.*;'))
212     strip_re_l.append(re.compile('^#define\s+EVT_STR_[A-Z0-9_]+\s+"[A-Za-z0-9_]+"'))
213     strip_re_l.append(re.compile('^#define\s+EVT_[A-Z0-9_]+\s+[0-9]+'))
214     strip_re_l.append(re.compile('^\s*{\s*EVT_[A-Z0-9_]+\s*,\s*EVT_STR_[A-Z0-9_]+\s*}'))
215     strip_re_l.append(re.compile('^static\s+const\s+int\s+\*\s*[a-z0-9_]+_[ex]_indexes\[\]\s*=\s*\{\s*&hf_param_.*NULL\s*\}\s*;'))
216     strip_re_l.append(re.compile('^\s*#define\s+[a-z0-9_]+_[ex]_indexes\s+[a-z0-9_]+_indexes'))
217     strip_re_l.append(re.compile('^\s*\{\s*EVT_[A-Z0-9_]+_[EX]\s*,\s*[a-z0-9_]+_[ex]_indexes\s*}\s*,'))
218     strip_re_l.append(re.compile('^\s*{\s*&hf_param_.*},')) # Must all be on one line
219
220     for strip_re in strip_re_l:
221         dissector_lines = [l for l in dissector_lines if not strip_re.search(l)]
222
223     # Find our value strings
224     value_string_re = re.compile('static\s+const\s+value_string\s+([A-Za-z0-9_]+_vals)')
225     value_string_l = []
226     for line in dissector_lines:
227         vs = value_string_re.match(line)
228         if vs:
229             value_string_l.append(vs.group(1))
230
231     # Add in new content after comments.
232
233     header_fields_c = 'Header fields'
234     header_fields_re = re.compile('/\*\s+' + header_fields_c, flags = re.IGNORECASE)
235     header_fields_l = []
236     for hf_name in sorted(hf_d.keys()):
237         header_fields_l.append('static int {} = -1;'.format(hf_name))
238
239     event_names_c = 'Event names'
240     event_names_re = re.compile('/\*\s+' + event_names_c, flags = re.IGNORECASE)
241     event_names_l = []
242     event_str_l = list(set(event_name_l))
243     event_str_l.sort()
244     for evt_str in event_str_l:
245         event_names_l.append('#define EVT_STR_{0:24s} "{1:s}"'.format(evt_str.upper(), evt_str))
246
247     event_definitions_c = 'Event definitions'
248     event_definitions_re = re.compile('/\*\s+' + event_definitions_c, flags = re.IGNORECASE)
249     event_definitions_l = []
250     for evt in event_nums:
251         event_definitions_l.append('#define EVT_{0:24s} {1:3d}'.format(event_d[evt], evt))
252
253     value_strings_c = 'Value strings'
254     value_strings_re = re.compile('/\*\s+' + value_strings_c, flags = re.IGNORECASE)
255     value_strings_l = []
256     for evt in event_nums:
257         evt_num = 'EVT_{},'.format(event_d[evt])
258         evt_str = 'EVT_STR_' + event_name_l[evt].upper()
259         value_strings_l.append('    {{ {0:<32s} {1:s} }},'.format(evt_num, evt_str))
260
261     parameter_index_c = 'Parameter indexes'
262     parameter_index_re = re.compile('/\*\s+' + parameter_index_c, flags = re.IGNORECASE)
263     # parameter_index_l defined above.
264
265     event_tree_c = 'Event tree'
266     event_tree_re = re.compile('/\*\s+' + event_tree_c, flags = re.IGNORECASE)
267     event_tree_l = []
268     for evt in event_nums:
269         evt_num = 'EVT_{}'.format(event_d[evt])
270         evt_idx = '{}_indexes'.format(event_d[evt].lower())
271         event_tree_l.append('    {{ {}, {} }},'.format(evt_num, evt_idx))
272
273     header_field_reg_c = 'Header field registration'
274     header_field_reg_re = re.compile('/\*\s+' + header_field_reg_c, flags = re.IGNORECASE)
275     header_field_reg_l = []
276     for hf_name in sorted(hf_d.keys()):
277         param = hf_d[hf_name]
278         event_name = param['event_name']
279         param_desc = get_param_desc(param)
280         param_name = param['param_name']
281         param_type = param['param_type']
282         param_format = param['param_format']
283         fieldconvert = 'NULL'
284         vs_name = param_to_value_string_name(param)
285         if vs_name in value_string_l and 'INT' in param_type:
286             fieldconvert = 'VALS({})'.format(vs_name)
287         header_field_reg_l.append('        {{ &{}, {{ "{}", "sysdig.param.{}.{}", FT_{}, BASE_{}, {}, 0, NULL, HFILL }} }},'.format(
288             hf_name,
289             param_desc,
290             event_name,
291             param_name,
292             param_type,
293             param_format,
294             fieldconvert
295             ))
296
297     for line in dissector_lines:
298         fill_comment = None
299         fill_l = []
300
301         if header_fields_re.match(line):
302             fill_comment = header_fields_c
303             fill_l = header_fields_l
304         elif event_names_re.match(line):
305             fill_comment = event_names_c
306             fill_l = event_names_l
307         elif event_definitions_re.match(line):
308             fill_comment = event_definitions_c
309             fill_l = event_definitions_l
310         elif value_strings_re.match(line):
311             fill_comment = value_strings_c
312             fill_l = value_strings_l
313         elif parameter_index_re.match(line):
314             fill_comment = parameter_index_c
315             fill_l = parameter_index_l
316         elif event_tree_re.match(line):
317             fill_comment = event_tree_c
318             fill_l = event_tree_l
319         elif header_field_reg_re.match(line):
320             fill_comment = header_field_reg_c
321             fill_l = header_field_reg_l
322
323         if fill_comment is not None:
324             # Write our comment followed by the content
325             print('Generating {}, {:d} lines'.format(fill_comment, len(fill_l)))
326             dissector_f.write('/* {}. Automatically generated by tools/{} */\n'.format(
327                 fill_comment,
328                 os.path.basename(__file__)
329                 ))
330             for line in fill_l:
331                 dissector_f.write('{}\n'.format(line))
332             # Fill each section only once
333             del fill_l[:]
334         else:
335             # Existing content
336             dissector_f.write(line)
337
338     dissector_f.close()
339
340 #
341 # On with the show
342 #
343
344 if __name__ == "__main__":
345     sys.exit(main())