/src/wireshark/epan/decode_as.c
Line | Count | Source |
1 | | /* decode_as.c |
2 | | * Routines for dissector Decode As handlers |
3 | | * |
4 | | * Wireshark - Network traffic analyzer |
5 | | * By Gerald Combs <gerald@wireshark.org> |
6 | | * Copyright 1998 Gerald Combs |
7 | | * |
8 | | * SPDX-License-Identifier: GPL-2.0-or-later |
9 | | */ |
10 | | |
11 | | #include "config.h" |
12 | | |
13 | | #include <glib.h> |
14 | | |
15 | | #include "decode_as.h" |
16 | | #include <epan/packet.h> |
17 | | #include "prefs.h" |
18 | | #include "prefs-int.h" |
19 | | #include "wsutil/file_util.h" |
20 | | #include "wsutil/filesystem.h" |
21 | | #include <stdio.h> |
22 | | #include <stdlib.h> |
23 | | #include <errno.h> |
24 | | #include <wsutil/ws_assert.h> |
25 | | |
26 | | GList *decode_as_list; |
27 | | |
28 | | void register_decode_as(decode_as_t* reg) |
29 | 1.12k | { |
30 | 1.12k | dissector_table_t decode_table; |
31 | | |
32 | | /* Ensure valid functions */ |
33 | 1.12k | ws_assert(reg->populate_list); |
34 | 1.12k | ws_assert(reg->reset_value); |
35 | 1.12k | ws_assert(reg->change_value); |
36 | | |
37 | 1.12k | decode_table = find_dissector_table(reg->table_name); |
38 | 1.12k | if (decode_table != NULL) |
39 | 1.12k | { |
40 | 1.12k | dissector_table_allow_decode_as(decode_table); |
41 | 1.12k | } |
42 | | |
43 | 1.12k | decode_as_list = g_list_prepend(decode_as_list, reg); |
44 | 1.12k | } |
45 | | |
46 | | static void next_proto_prompt(packet_info *pinfo _U_, char *result) |
47 | 0 | { |
48 | 0 | snprintf(result, MAX_DECODE_AS_PROMPT_LEN, "Next level protocol as"); |
49 | 0 | } |
50 | | |
51 | | static void *next_proto_value(packet_info *pinfo _U_) |
52 | 0 | { |
53 | 0 | return 0; |
54 | 0 | } |
55 | | |
56 | | static build_valid_func next_proto_values[] = { next_proto_value }; |
57 | | static decode_as_value_t next_proto_da_values = |
58 | | { next_proto_prompt, 1, next_proto_values }; |
59 | | |
60 | | dissector_table_t register_decode_as_next_proto(int proto, const char *table_name, const char *ui_name, build_label_func label_func) |
61 | 375 | { |
62 | 375 | decode_as_t *da; |
63 | | |
64 | 375 | dissector_table_t dt = register_dissector_table(table_name, ui_name, proto, FT_NONE, BASE_NONE); |
65 | | |
66 | 375 | da = wmem_new0(wmem_epan_scope(), decode_as_t); |
67 | 375 | da->name = wmem_strdup(wmem_epan_scope(), proto_get_protocol_filter_name(proto)); |
68 | 375 | da->table_name = wmem_strdup(wmem_epan_scope(), table_name); |
69 | 375 | da->num_items = 1; |
70 | 375 | if (label_func == NULL) |
71 | 180 | { |
72 | 180 | da->values = &next_proto_da_values; |
73 | 180 | } |
74 | 195 | else |
75 | 195 | { |
76 | 195 | da->values = wmem_new(wmem_epan_scope(), decode_as_value_t); |
77 | 195 | da->values->label_func = label_func; |
78 | 195 | da->values->num_values = 1; |
79 | 195 | da->values->build_values = next_proto_values; |
80 | 195 | } |
81 | 375 | da->populate_list = decode_as_default_populate_list; |
82 | 375 | da->reset_value = decode_as_default_reset; |
83 | 375 | da->change_value = decode_as_default_change; |
84 | | |
85 | 375 | register_decode_as(da); |
86 | 375 | return dt; |
87 | 375 | } |
88 | | |
89 | | struct decode_as_default_populate |
90 | | { |
91 | | decode_as_add_to_list_func add_to_list; |
92 | | void *ui_element; |
93 | | }; |
94 | | |
95 | | static void |
96 | | decode_proto_add_to_list (const char *table_name, void *value, void *user_data) |
97 | 0 | { |
98 | 0 | struct decode_as_default_populate* populate = (struct decode_as_default_populate*)user_data; |
99 | 0 | const char *dissector_description; |
100 | 0 | int i; |
101 | 0 | dissector_handle_t handle; |
102 | | |
103 | |
|
104 | 0 | handle = (dissector_handle_t)value; |
105 | 0 | dissector_description = dissector_handle_get_description(handle); |
106 | |
|
107 | 0 | i = dissector_handle_get_protocol_index(handle); |
108 | 0 | if (i >= 0 && !proto_is_protocol_enabled(find_protocol_by_id(i))) |
109 | 0 | return; |
110 | | |
111 | 0 | populate->add_to_list(table_name, dissector_description, value, populate->ui_element); |
112 | 0 | } |
113 | | |
114 | | void decode_as_default_populate_list(const char *table_name, decode_as_add_to_list_func add_to_list, void *ui_element) |
115 | 0 | { |
116 | 0 | struct decode_as_default_populate populate; |
117 | |
|
118 | 0 | populate.add_to_list = add_to_list; |
119 | 0 | populate.ui_element = ui_element; |
120 | |
|
121 | 0 | dissector_table_foreach_handle(table_name, decode_proto_add_to_list, &populate); |
122 | 0 | } |
123 | | |
124 | | bool decode_as_default_reset(const char *name, const void *pattern) |
125 | 0 | { |
126 | 0 | switch (get_dissector_table_selector_type(name)) { |
127 | 0 | case FT_UINT8: |
128 | 0 | case FT_UINT16: |
129 | 0 | case FT_UINT24: |
130 | 0 | case FT_UINT32: |
131 | 0 | dissector_reset_uint(name, GPOINTER_TO_UINT(pattern)); |
132 | 0 | return true; |
133 | 0 | case FT_NONE: |
134 | 0 | dissector_reset_payload(name); |
135 | 0 | return true; |
136 | 0 | case FT_STRING: |
137 | 0 | case FT_STRINGZ: |
138 | 0 | case FT_UINT_STRING: |
139 | 0 | case FT_STRINGZPAD: |
140 | 0 | case FT_STRINGZTRUNC: |
141 | 0 | dissector_reset_string(name, (!pattern)?"":(const char *) pattern); |
142 | 0 | return true; |
143 | 0 | default: |
144 | 0 | return false; |
145 | 0 | }; |
146 | |
|
147 | 0 | return true; |
148 | 0 | } |
149 | | |
150 | | bool decode_as_default_change(const char *name, const void *pattern, const void *handle, const char *list_name _U_) |
151 | 0 | { |
152 | 0 | const dissector_handle_t dissector = (const dissector_handle_t)handle; |
153 | 0 | switch (get_dissector_table_selector_type(name)) { |
154 | 0 | case FT_UINT8: |
155 | 0 | case FT_UINT16: |
156 | 0 | case FT_UINT24: |
157 | 0 | case FT_UINT32: |
158 | 0 | dissector_change_uint(name, GPOINTER_TO_UINT(pattern), dissector); |
159 | 0 | return true; |
160 | 0 | case FT_NONE: |
161 | 0 | dissector_change_payload(name, dissector); |
162 | 0 | return true; |
163 | 0 | case FT_STRING: |
164 | 0 | case FT_STRINGZ: |
165 | 0 | case FT_UINT_STRING: |
166 | 0 | case FT_STRINGZPAD: |
167 | 0 | case FT_STRINGZTRUNC: |
168 | 0 | dissector_change_string(name, (!pattern)?"":(const char *) pattern, dissector); |
169 | 0 | return true; |
170 | 0 | default: |
171 | 0 | return false; |
172 | 0 | }; |
173 | |
|
174 | 0 | return true; |
175 | 0 | } |
176 | | |
177 | | /* Some useful utilities for Decode As */ |
178 | | |
179 | | /* |
180 | | * A list of dissectors that need to be reset. |
181 | | */ |
182 | | static GSList *dissector_reset_list; |
183 | | |
184 | | /* |
185 | | * A callback function to parse each "decode as" entry in the file and apply the change |
186 | | */ |
187 | | static prefs_set_pref_e |
188 | | read_set_decode_as_entries(char *key, const char *value, |
189 | | void *user_data, |
190 | | bool return_range_errors _U_) |
191 | 0 | { |
192 | 0 | char *values[4] = {NULL, NULL, NULL, NULL}; |
193 | 0 | char delimiter[4] = {',', ',', ',','\0'}; |
194 | 0 | char *pch; |
195 | 0 | unsigned i, j; |
196 | 0 | GHashTable* processed_entries = (GHashTable*)user_data; |
197 | 0 | dissector_table_t sub_dissectors; |
198 | 0 | prefs_set_pref_e retval = PREFS_SET_OK; |
199 | 0 | bool is_valid = false; |
200 | |
|
201 | 0 | if (strcmp(key, DECODE_AS_ENTRY) == 0) { |
202 | | /* Parse csv into table, selector, initial, current */ |
203 | 0 | for (i = 0; i < 4; i++) { |
204 | 0 | pch = strchr(value, delimiter[i]); |
205 | 0 | if (pch == NULL) { |
206 | 0 | for (j = 0; j < i; j++) { |
207 | 0 | g_free(values[j]); |
208 | 0 | } |
209 | 0 | return PREFS_SET_SYNTAX_ERR; |
210 | 0 | } |
211 | 0 | values[i] = g_strndup(value, pch - value); |
212 | 0 | value = pch + 1; |
213 | 0 | } |
214 | 0 | sub_dissectors = find_dissector_table(values[0]); |
215 | 0 | if (sub_dissectors != NULL) { |
216 | 0 | dissector_handle_t handle; |
217 | 0 | ftenum_t selector_type; |
218 | 0 | pref_t* pref_value; |
219 | 0 | module_t *module; |
220 | 0 | const char* proto_name; |
221 | |
|
222 | 0 | selector_type = dissector_table_get_type(sub_dissectors); |
223 | |
|
224 | 0 | handle = dissector_table_get_dissector_handle(sub_dissectors, values[3]); |
225 | 0 | if (handle != NULL || g_ascii_strcasecmp(values[3], DECODE_AS_NONE) == 0) { |
226 | 0 | is_valid = true; |
227 | 0 | } |
228 | |
|
229 | 0 | if (is_valid) { |
230 | 0 | if (FT_IS_STRING(selector_type)) { |
231 | 0 | dissector_change_string(values[0], values[1], handle); |
232 | 0 | } else { |
233 | 0 | char *p; |
234 | 0 | long long_value; |
235 | |
|
236 | 0 | long_value = strtol(values[1], &p, 0); |
237 | 0 | if (p == values[0] || *p != '\0' || long_value < 0 || |
238 | 0 | (unsigned long)long_value > UINT_MAX) { |
239 | 0 | retval = PREFS_SET_SYNTAX_ERR; |
240 | 0 | is_valid = false; |
241 | 0 | } else { |
242 | 0 | dissector_change_uint(values[0], (unsigned)long_value, handle); |
243 | 0 | } |
244 | | |
245 | | /* Now apply the value data back to dissector table preference */ |
246 | 0 | if (handle != NULL) { |
247 | 0 | proto_name = proto_get_protocol_filter_name(dissector_handle_get_protocol_index(handle)); |
248 | 0 | module = prefs_find_module(proto_name); |
249 | | // values[0] is the dissector table |
250 | 0 | char *pref_name = ws_strdup_printf("%s%s", values[0], dissector_handle_get_pref_suffix(handle)); |
251 | 0 | pref_value = prefs_find_preference(module, pref_name); |
252 | 0 | g_free(pref_name); |
253 | 0 | if (pref_value != NULL) { |
254 | 0 | bool replace = false; |
255 | 0 | if (g_hash_table_lookup(processed_entries, proto_name) == NULL) { |
256 | | /* First decode as entry for this protocol, ranges may be replaced */ |
257 | 0 | replace = true; |
258 | | |
259 | | /* Remember we've processed this protocol */ |
260 | 0 | g_hash_table_insert(processed_entries, (void *)proto_name, (void *)proto_name); |
261 | 0 | } |
262 | |
|
263 | 0 | prefs_add_decode_as_value(pref_value, (unsigned)long_value, replace); |
264 | 0 | module->prefs_changed_flags |= prefs_get_effect_flags(pref_value); |
265 | 0 | } |
266 | 0 | } |
267 | 0 | } |
268 | 0 | } |
269 | 0 | if (is_valid) { |
270 | 0 | decode_build_reset_list(values[0], selector_type, values[1], NULL, NULL); |
271 | 0 | } |
272 | 0 | } else { |
273 | 0 | retval = PREFS_SET_SYNTAX_ERR; |
274 | 0 | } |
275 | |
|
276 | 0 | } else { |
277 | 0 | retval = PREFS_SET_NO_SUCH_PREF; |
278 | 0 | } |
279 | | |
280 | 0 | for (i = 0; i < 4; i++) { |
281 | 0 | g_free(values[i]); |
282 | 0 | } |
283 | 0 | return retval; |
284 | 0 | } |
285 | | |
286 | | void |
287 | | load_decode_as_entries(const char* app_env_var_prefix) |
288 | 15 | { |
289 | 15 | char *daf_path; |
290 | 15 | FILE *daf; |
291 | | |
292 | 15 | decode_clear_all(); |
293 | | |
294 | 15 | daf_path = get_persconffile_path(DECODE_AS_ENTRIES_FILE_NAME, true, app_env_var_prefix); |
295 | 15 | if ((daf = ws_fopen(daf_path, "r")) != NULL) { |
296 | | /* Store saved entries for better range processing */ |
297 | 0 | GHashTable* processed_entries = g_hash_table_new(g_str_hash, g_str_equal); |
298 | 0 | read_prefs_file(daf_path, daf, read_set_decode_as_entries, processed_entries); |
299 | 0 | g_hash_table_destroy(processed_entries); |
300 | 0 | fclose(daf); |
301 | 0 | } |
302 | 15 | g_free(daf_path); |
303 | 15 | } |
304 | | |
305 | | |
306 | | /* Make a sorted list of the entries as we are fetching them from a hash table. Then write it out from the sorted list */ |
307 | | static void |
308 | | decode_as_write_entry (const char *table_name, ftenum_t selector_type, |
309 | | void *key, void *value, void *user_data) |
310 | 0 | { |
311 | 0 | GList **decode_as_rows_list = (GList **)user_data; |
312 | 0 | dissector_handle_t current, initial; |
313 | 0 | const char *current_dissector_name, *initial_dissector_name, *decode_as_row; |
314 | |
|
315 | 0 | current = dtbl_entry_get_handle((dtbl_entry_t *)value); |
316 | 0 | if (current == NULL) |
317 | 0 | current_dissector_name = DECODE_AS_NONE; |
318 | 0 | else |
319 | 0 | current_dissector_name = dissector_handle_get_description(current); |
320 | 0 | initial = dtbl_entry_get_initial_handle((dtbl_entry_t *)value); |
321 | 0 | if (initial == NULL) |
322 | 0 | initial_dissector_name = DECODE_AS_NONE; |
323 | 0 | else |
324 | 0 | initial_dissector_name = dissector_handle_get_description(initial); |
325 | |
|
326 | 0 | switch (selector_type) { |
327 | | |
328 | 0 | case FT_UINT8: |
329 | 0 | case FT_UINT16: |
330 | 0 | case FT_UINT24: |
331 | 0 | case FT_UINT32: |
332 | | /* |
333 | | * XXX - write these in decimal, regardless of the base of |
334 | | * the dissector table's selector, as older versions of |
335 | | * Wireshark used atoi() when reading this file, and |
336 | | * failed to handle hex or octal numbers. |
337 | | * |
338 | | * That will be fixed in future 1.10 and 1.12 releases, |
339 | | * but pre-1.10 releases are at end-of-life and won't |
340 | | * be fixed. |
341 | | */ |
342 | 0 | decode_as_row = ws_strdup_printf( |
343 | 0 | DECODE_AS_ENTRY ": %s,%u,%s,%s\n", |
344 | 0 | table_name, GPOINTER_TO_UINT(key), initial_dissector_name, |
345 | 0 | current_dissector_name); |
346 | 0 | break; |
347 | 0 | case FT_NONE: |
348 | | /* |
349 | | * XXX - Just put a placeholder for the key value. Currently |
350 | | * FT_NONE dissector table uses a single uint value for |
351 | | * a placeholder |
352 | | */ |
353 | 0 | decode_as_row = ws_strdup_printf( |
354 | 0 | DECODE_AS_ENTRY ": %s,0,%s,%s\n", |
355 | 0 | table_name, initial_dissector_name, |
356 | 0 | current_dissector_name); |
357 | 0 | break; |
358 | | |
359 | 0 | case FT_STRING: |
360 | 0 | case FT_STRINGZ: |
361 | 0 | case FT_UINT_STRING: |
362 | 0 | case FT_STRINGZPAD: |
363 | 0 | case FT_STRINGZTRUNC: |
364 | 0 | decode_as_row = ws_strdup_printf( |
365 | 0 | DECODE_AS_ENTRY ": %s,%s,%s,%s\n", |
366 | 0 | table_name, (char *)key, initial_dissector_name, |
367 | 0 | current_dissector_name); |
368 | 0 | break; |
369 | | |
370 | 0 | default: |
371 | 0 | ws_assert_not_reached(); |
372 | 0 | break; |
373 | 0 | } |
374 | | |
375 | | /* Do we need a better sort function ???*/ |
376 | 0 | *decode_as_rows_list = g_list_insert_sorted (*decode_as_rows_list, (void *)decode_as_row, |
377 | 0 | (GCompareFunc)g_ascii_strcasecmp); |
378 | |
|
379 | 0 | } |
380 | | |
381 | | /* Print the sorted rows to File */ |
382 | | static void |
383 | | decode_as_print_rows(void *data, void *user_data) |
384 | 0 | { |
385 | 0 | FILE *da_file = (FILE *)user_data; |
386 | 0 | const char *decode_as_row = (const char *)data; |
387 | |
|
388 | 0 | fprintf(da_file, "%s",decode_as_row); |
389 | |
|
390 | 0 | } |
391 | | int |
392 | | save_decode_as_entries(const char* app_name, const char* app_env_var_prefix, char** err) |
393 | 0 | { |
394 | 0 | char *pf_dir_path; |
395 | 0 | char *daf_path; |
396 | 0 | FILE *da_file; |
397 | 0 | GList *decode_as_rows_list = NULL; |
398 | |
|
399 | 0 | if (create_persconffile_dir(app_env_var_prefix, &pf_dir_path) == -1) { |
400 | 0 | *err = ws_strdup_printf("Can't create directory\n\"%s\"\nfor recent file: %s.", |
401 | 0 | pf_dir_path, g_strerror(errno)); |
402 | 0 | g_free(pf_dir_path); |
403 | 0 | return -1; |
404 | 0 | } |
405 | | |
406 | 0 | daf_path = get_persconffile_path(DECODE_AS_ENTRIES_FILE_NAME, true, app_env_var_prefix); |
407 | 0 | if ((da_file = ws_fopen(daf_path, "w")) == NULL) { |
408 | 0 | *err = ws_strdup_printf("Can't open decode_as_entries file\n\"%s\": %s.", |
409 | 0 | daf_path, g_strerror(errno)); |
410 | 0 | g_free(daf_path); |
411 | 0 | return -1; |
412 | 0 | } |
413 | | |
414 | 0 | fprintf(da_file, "# \"Decode As\" entries file for %s " VERSION ".\n" |
415 | 0 | "#\n" |
416 | 0 | "# This file is regenerated each time \"Decode As\" preferences\n" |
417 | 0 | "# are saved within %s. Making manual changes should be safe,\n" |
418 | 0 | "# however.\n", |
419 | 0 | app_name, app_name); |
420 | |
|
421 | 0 | dissector_all_tables_foreach_changed(decode_as_write_entry, &decode_as_rows_list); |
422 | |
|
423 | 0 | g_list_foreach(decode_as_rows_list, decode_as_print_rows, da_file); |
424 | |
|
425 | 0 | fclose(da_file); |
426 | 0 | g_free(daf_path); |
427 | 0 | g_list_free_full(decode_as_rows_list, g_free); |
428 | |
|
429 | 0 | return 0; |
430 | 0 | } |
431 | | |
432 | | /* |
433 | | * Data structure for tracking which dissector need to be reset. This |
434 | | * structure is necessary as a hash table entry cannot be removed |
435 | | * while a g_hash_table_foreach walk is in progress. |
436 | | */ |
437 | | typedef struct dissector_delete_item { |
438 | | /* The name of the dissector table */ |
439 | | char *ddi_table_name; |
440 | | /* The type of the selector in that dissector table */ |
441 | | ftenum_t ddi_selector_type; |
442 | | /* The selector in the dissector table */ |
443 | | union { |
444 | | unsigned sel_uint; |
445 | | char *sel_string; |
446 | | } ddi_selector; |
447 | | } dissector_delete_item_t; |
448 | | |
449 | | void |
450 | | decode_build_reset_list (const char *table_name, ftenum_t selector_type, |
451 | | void *key, void *value _U_, |
452 | | void *user_data _U_) |
453 | 0 | { |
454 | 0 | dissector_delete_item_t *item; |
455 | |
|
456 | 0 | item = g_new(dissector_delete_item_t,1); |
457 | 0 | item->ddi_table_name = g_strdup(table_name); |
458 | 0 | item->ddi_selector_type = selector_type; |
459 | 0 | switch (selector_type) { |
460 | | |
461 | 0 | case FT_UINT8: |
462 | 0 | case FT_UINT16: |
463 | 0 | case FT_UINT24: |
464 | 0 | case FT_UINT32: |
465 | 0 | item->ddi_selector.sel_uint = GPOINTER_TO_UINT(key); |
466 | 0 | break; |
467 | | |
468 | 0 | case FT_NONE: |
469 | | /* Not really needed, but prevents the assert */ |
470 | 0 | item->ddi_selector.sel_uint = 0; |
471 | 0 | break; |
472 | | |
473 | 0 | case FT_STRING: |
474 | 0 | case FT_STRINGZ: |
475 | 0 | case FT_UINT_STRING: |
476 | 0 | case FT_STRINGZPAD: |
477 | 0 | case FT_STRINGZTRUNC: |
478 | 0 | item->ddi_selector.sel_string = g_strdup((char *)key); |
479 | 0 | break; |
480 | | |
481 | 0 | default: |
482 | 0 | ws_assert_not_reached(); |
483 | 0 | } |
484 | 0 | dissector_reset_list = g_slist_prepend(dissector_reset_list, item); |
485 | 0 | } |
486 | | |
487 | | /* clear all settings */ |
488 | | void |
489 | | decode_clear_all(void) |
490 | 15 | { |
491 | 15 | dissector_delete_item_t *item; |
492 | 15 | GSList *tmp; |
493 | | |
494 | 15 | dissector_all_tables_foreach_changed(decode_build_reset_list, NULL); |
495 | | |
496 | 15 | for (tmp = dissector_reset_list; tmp; tmp = g_slist_next(tmp)) { |
497 | 0 | item = (dissector_delete_item_t *)tmp->data; |
498 | 0 | switch (item->ddi_selector_type) { |
499 | | |
500 | 0 | case FT_UINT8: |
501 | 0 | case FT_UINT16: |
502 | 0 | case FT_UINT24: |
503 | 0 | case FT_UINT32: |
504 | 0 | dissector_reset_uint(item->ddi_table_name, |
505 | 0 | item->ddi_selector.sel_uint); |
506 | 0 | break; |
507 | | |
508 | 0 | case FT_NONE: |
509 | 0 | dissector_reset_payload(item->ddi_table_name); |
510 | 0 | break; |
511 | | |
512 | 0 | case FT_STRING: |
513 | 0 | case FT_STRINGZ: |
514 | 0 | case FT_UINT_STRING: |
515 | 0 | case FT_STRINGZPAD: |
516 | 0 | case FT_STRINGZTRUNC: |
517 | 0 | dissector_reset_string(item->ddi_table_name, |
518 | 0 | item->ddi_selector.sel_string); |
519 | 0 | g_free(item->ddi_selector.sel_string); |
520 | 0 | break; |
521 | | |
522 | 0 | default: |
523 | 0 | ws_assert_not_reached(); |
524 | 0 | } |
525 | 0 | g_free(item->ddi_table_name); |
526 | 0 | g_free(item); |
527 | 0 | } |
528 | 15 | g_slist_free(dissector_reset_list); |
529 | 15 | dissector_reset_list = NULL; |
530 | | |
531 | 1.14k | for (GList* cur = decode_as_list; cur; cur = cur->next) { |
532 | 1.12k | decode_as_t* entry = (decode_as_t*)cur->data; |
533 | 1.12k | if (entry->reset_all != NULL) { |
534 | 15 | entry->reset_all(); |
535 | 15 | } |
536 | 1.12k | } |
537 | 15 | } |
538 | | |
539 | | void |
540 | | decode_cleanup(void) |
541 | 0 | { |
542 | 0 | g_list_free(decode_as_list); |
543 | | decode_as_list = NULL; |
544 | 0 | } |
545 | | |
546 | | /* |
547 | | * Editor modelines |
548 | | * |
549 | | * Local Variables: |
550 | | * c-basic-offset: 4 |
551 | | * tab-width: 8 |
552 | | * indent-tabs-mode: nil |
553 | | * End: |
554 | | * |
555 | | * ex: set shiftwidth=4 tabstop=8 expandtab: |
556 | | * :indentSize=4:tabSize=8:noTabs=true: |
557 | | */ |