menus.[hc]:
[obnox/wireshark/wip.git] / gtk / main_filter_toolbar.c
1 /* main_filter_toolbar.c
2  * The filter toolbar
3  *
4  * $Id$
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (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, write to the Free Software
22  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23  */
24
25 /*
26  * This file implements the "filter" toolbar for Wireshark.
27  */
28
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32
33 #include <stdio.h>
34 #include <string.h>
35
36 #include <gtk/gtk.h>
37 #include "gtk/old-gtk-compat.h"
38
39 #include "filter_dlg.h"
40 #include "filter_autocomplete.h"
41
42 #include "epan/prefs.h"
43
44 #include "keys.h"
45 #include "gtkglobals.h"
46 #include "stock_icons.h"
47 #include "recent.h"
48
49 #include "main.h"
50 #include "menus.h"
51 #include "main_toolbar.h"
52 #include "main_filter_toolbar.h"
53
54 #ifdef MAIN_MENU_USE_UIMANAGER
55 # define MENU_BAR_PATH_FILE_OPEN                                "/Menubar/FileMenu/Open"
56 # define MENU_BAR_PATH_EDIT_COPY_AS_FLT                         "/Menubar/EditMenu/Copy/AsFilter"
57 # define MENU_BAR_PATH_ANALYZE_DISPLAY_FLT                      "/Menubar/AnalyzeMenu/DisplayFilters"
58 # define MENU_BAR_PATH_ANALYZE_FOLLOW_TCP_STREAM                "/Menubar/AnalyzeMenu/FollowTCPStream"
59 # define MENU_BAR_PATH_ANALYZE_FOLLOW_UDP_STREAM                "/Menubar/AnalyzeMenu/FollowUDPStream"
60 # define MENU_BAR_PATH_ANALYZE_FOLLOW_SSL_STREAM                "/Menubar/AnalyzeMenu/FollowSSLStream"
61 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_SEL                   "/Menubar/AnalyzeMenu/ApplyAsFilter/Selected"
62 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_NOT_SEL               "/Menubar/AnalyzeMenu/ApplyAsFilter/NotSelected"
63 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_AND_SEL               "/Menubar/AnalyzeMenu/ApplyAsFilter/AndSelected"
64 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_OR_SEL                "/Menubar/AnalyzeMenu/ApplyAsFilter/OrSelected"
65 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_AND_NOT_SEL           "/Menubar/AnalyzeMenu/ApplyAsFilter/AndNotSelected"
66 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_OR_NOT_SEL            "/Menubar/AnalyzeMenu/ApplyAsFilter/OrNotSelected"
67
68 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_SEL                   "/Menubar/AnalyzeMenu/PrepareaFilter/Selected"
69 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_NOT_SEL               "/Menubar/AnalyzeMenu/PrepareaFilter/NotSelected"
70 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_AND_SEL               "/Menubar/AnalyzeMenu/PrepareaFilter/AndSelected"
71 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_OR_SEL                "/Menubar/AnalyzeMenu/PrepareaFilter/OrSelected"
72 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_AND_NOT_SEL           "/Menubar/AnalyzeMenu/PrepareaFilter/AndNotSelected"
73 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_OR_NOT_SEL            "/Menubar/AnalyzeMenu/PrepareaFilter/OrNotSelected"
74 #else
75 # define MENU_BAR_PATH_FILE_OPEN                                "/File/Open..."
76 # define MENU_BAR_PATH_EDIT_COPY_AS_FLT                         "/Edit/Copy/As Filter"
77 # define MENU_BAR_PATH_ANALYZE_DISPLAY_FLT                      "/Analyze/Display Filters..."
78 # define MENU_BAR_PATH_ANALYZE_FOLLOW_TCP_STREAM                "/Analyze/Follow TCP Stream"
79 # define MENU_BAR_PATH_ANALYZE_FOLLOW_UDP_STREAM                "/Analyze/Follow UDP Stream"
80 # define MENU_BAR_PATH_ANALYZE_FOLLOW_SSL_STREAM                "/Analyze/Follow SSL Stream"
81 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_SEL                   "/Analyze/Apply as Filter/Selected"
82 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_NOT_SEL               "/Analyze/Apply as Filter/Not Selected"
83 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_AND_SEL               "/Analyze/Apply as Filter/... and Selected"
84 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_OR_SEL                "/Analyze/Apply as Filter/... or Selected"
85 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_AND_NOT_SEL           "/Analyze/Apply as Filter/... and not Selected"
86 # define MENU_BAR_PATH_ANALYZE_APL_AS_FLT_OR_NOT_SEL            "/Analyze/Apply as Filter/... or not Selected"
87
88 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_SEL                   "/Analyze/Prepare a Filter/Selected"
89 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_NOT_SEL               "/Analyze/Prepare a Filter/Not Selected"
90 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_AND_SEL               "/Analyze/Prepare a Filter/... and Selected"
91 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_OR_SEL                "/Analyze/Prepare a Filter/... or Selected"
92 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_AND_NOT_SEL           "/Analyze/Prepare a Filter/... and not Selected"
93 # define MENU_BAR_PATH_ANALYZE_PREP_A_FLT_OR_NOT_SEL            "/Analyze/Prepare a Filter/... or not Selected"
94 # endif /* MAIN_MENU_USE_UIMANAGER */
95
96 #if 0 /* Unused? */
97 # define PACKET_LIST_POPUP_PATH_ANALYZE_FOLLOW_TCP_STREAM       "/PacketListMenuPopup/FollowTCPStream"
98 # define PACKET_LIST_POPUP_PATH_ANALYZE_FOLLOW_UDP_STREAM       "/PacketListMenuPopup/FollowUDPStream"
99 # define PACKET_LIST_POPUP_PATH_ANALYZE_FOLLOW_SSL_STREAM       "/PacketListMenuPopup/FollowSSLStream"
100 # define PACKET_LIST_POPUP_PATH_CONV_FLT_ETH                    "/PacketListMenuPopup/ConversationFilter/Ethernet"
101 # define PACKET_LIST_POPUP_PATH_CONV_FLT_IP                     "/PacketListMenuPopup/ConversationFilter/IP"
102 # define PACKET_LIST_POPUP_PATH_CONV_FLT_TCP                    "/PacketListMenuPopup/ConversationFilter/TCP"
103 # define PACKET_LIST_POPUP_PATH_CONV_FLT_UDP                    "/PacketListMenuPopup/ConversationFilter/UDP"
104 # define PACKET_LIST_POPUP_PATH_CONV_FLT_PN_CBA_SERV            "/PacketListMenuPopup/ConversationFilter/PN-CBA"
105 #endif
106
107 GtkWidget   *main_display_filter_widget=NULL;
108
109 /* Run the current display filter on the current packet set, and
110    redisplay. */
111 static void
112 filter_activate_cb(GtkWidget *w _U_, gpointer data)
113 {
114     const char *s;
115
116     s = gtk_entry_get_text(GTK_ENTRY(data));
117     main_filter_packets(&cfile, s, FALSE);
118 }
119
120 /* Enable both Clear and Apply button when filter is changed */
121 static void
122 filter_changed_cb(GtkWidget *w _U_, gpointer data)
123 {
124     gtk_widget_set_sensitive (g_object_get_data (G_OBJECT(data), E_DFILTER_APPLY_KEY), TRUE);
125     gtk_widget_set_sensitive (g_object_get_data (G_OBJECT(data), E_DFILTER_CLEAR_KEY), TRUE);
126 }
127
128 /* redisplay with no display filter */
129 static void
130 filter_reset_cb(GtkWidget *w, gpointer data _U_)
131 {
132     GtkWidget *filter_te = NULL;
133
134     if ((filter_te = g_object_get_data(G_OBJECT(w), E_DFILTER_TE_KEY))) {
135         gtk_entry_set_text(GTK_ENTRY(filter_te), "");
136     }
137     main_filter_packets(&cfile, NULL, FALSE);
138 }
139
140 GtkWidget *filter_toolbar_new(void)
141 {
142     GtkWidget     *filter_cm;
143     GtkWidget     *filter_te;
144     GtkWidget     *filter_tb;
145     GtkToolItem   *filter_bt, *filter_add_expr_bt, *filter_reset;
146     GtkToolItem   *filter_apply, *item;
147
148
149     /* Display filter construct dialog has an Apply button, and "OK" not
150        only sets our text widget, it activates it (i.e., it causes us to
151        filter the capture). */
152     static construct_args_t args = {
153         "Wireshark: Display Filter",
154         TRUE,
155         TRUE,
156         FALSE
157     };
158
159     /* filter toolbar */
160     filter_tb = gtk_toolbar_new();
161     gtk_orientable_set_orientation(GTK_ORIENTABLE(filter_tb),
162                                 GTK_ORIENTATION_HORIZONTAL);
163
164     g_object_set_data(G_OBJECT(top_level), E_TB_FILTER_KEY, filter_tb);
165     gtk_widget_show(filter_tb);
166
167     /* Create the "Filter:" button */
168     filter_bt = gtk_tool_button_new_from_stock (WIRESHARK_STOCK_DISPLAY_FILTER_ENTRY);
169     g_signal_connect(filter_bt, "clicked", G_CALLBACK(display_filter_construct_cb), &args);
170     gtk_widget_show(GTK_WIDGET (filter_bt));
171     g_object_set_data(G_OBJECT(top_level), E_FILT_BT_PTR_KEY, filter_bt);
172
173     gtk_toolbar_insert(GTK_TOOLBAR(filter_tb),
174                        filter_bt,
175                        -1);
176         gtk_widget_set_tooltip_text( GTK_WIDGET(filter_bt), "Open the \"Display Filter\" dialog, to edit/apply filters");
177
178     /* Create the filter combobox */
179     filter_cm = gtk_combo_box_text_new_with_entry ();
180     filter_te = gtk_bin_get_child(GTK_BIN(filter_cm));
181     main_display_filter_widget=filter_te;
182     g_object_set_data(G_OBJECT(filter_bt), E_FILT_TE_PTR_KEY, filter_te);
183     g_object_set_data(G_OBJECT(filter_te), E_DFILTER_CM_KEY, filter_cm);
184     g_object_set_data(G_OBJECT(top_level), E_DFILTER_CM_KEY, filter_cm);
185     g_signal_connect(filter_te, "activate", G_CALLBACK(filter_activate_cb), filter_te);
186     g_signal_connect(filter_te, "changed", G_CALLBACK(filter_changed_cb), filter_cm);
187     g_signal_connect(filter_te, "changed", G_CALLBACK(filter_te_syntax_check_cb), NULL);
188     g_object_set_data(G_OBJECT(filter_tb), E_FILT_AUTOCOMP_PTR_KEY, NULL);
189     g_object_set_data(G_OBJECT(filter_te), E_FILT_FIELD_USE_STATUSBAR_KEY, "");
190     g_signal_connect(filter_te, "key-press-event", G_CALLBACK (filter_string_te_key_pressed_cb), NULL);
191     g_signal_connect(filter_tb, "key-press-event", G_CALLBACK (filter_parent_dlg_key_pressed_cb), NULL);
192
193     gtk_widget_set_size_request(filter_cm, 400, -1);
194     gtk_widget_show(filter_cm);
195     item = gtk_tool_item_new ();
196     gtk_container_add (GTK_CONTAINER (item), filter_cm);
197     gtk_widget_show (GTK_WIDGET (item));
198
199     gtk_toolbar_insert(GTK_TOOLBAR(filter_tb),
200                        item,
201                        -1);
202
203     /* setting a tooltip for a combobox will do nothing, so add it to the corresponding text entry */
204         gtk_widget_set_tooltip_text(filter_cm,
205         "Enter a display filter, or choose one of your recently used filters. "
206         "The background color of this field is changed by a continuous syntax check "
207         "(green is valid, red is invalid, yellow may have unexpected results).");
208
209     /* Create the "Add Expression..." button, to pop up a dialog
210        for constructing filter comparison expressions. */
211     filter_add_expr_bt = gtk_tool_button_new_from_stock(WIRESHARK_STOCK_ADD_EXPRESSION);
212     g_object_set_data(G_OBJECT(filter_tb), E_FILT_FILTER_TE_KEY, filter_te);
213     g_signal_connect(filter_add_expr_bt, "clicked", G_CALLBACK(filter_add_expr_bt_cb), filter_tb);
214     gtk_widget_show(GTK_WIDGET(filter_add_expr_bt));
215
216     gtk_toolbar_insert(GTK_TOOLBAR(filter_tb),
217                        filter_add_expr_bt,
218                        -1);
219
220         gtk_widget_set_tooltip_text(GTK_WIDGET(filter_add_expr_bt), "Add an expression to this filter string");
221
222     /* Create the "Clear" button */
223     filter_reset = gtk_tool_button_new_from_stock(WIRESHARK_STOCK_CLEAR_EXPRESSION);
224     g_object_set_data(G_OBJECT(filter_reset), E_DFILTER_TE_KEY, filter_te);
225     g_object_set_data (G_OBJECT(filter_cm), E_DFILTER_CLEAR_KEY, filter_reset);
226     g_signal_connect(filter_reset, "clicked", G_CALLBACK(filter_reset_cb), NULL);
227     gtk_widget_set_sensitive (GTK_WIDGET(filter_reset), FALSE);
228     gtk_widget_show(GTK_WIDGET(filter_reset));
229     gtk_toolbar_insert(GTK_TOOLBAR(filter_tb),
230                        filter_reset,
231                        -1);
232
233         gtk_widget_set_tooltip_text(GTK_WIDGET(filter_reset), "Clear this filter string and update the display");
234
235     /* Create the "Apply" button */
236     filter_apply = gtk_tool_button_new_from_stock(WIRESHARK_STOCK_APPLY_EXPRESSION);
237     g_object_set_data(G_OBJECT(filter_apply), E_DFILTER_CM_KEY, filter_cm);
238     g_object_set_data (G_OBJECT(filter_cm), E_DFILTER_APPLY_KEY, filter_apply);
239     g_signal_connect(filter_apply, "clicked", G_CALLBACK(filter_activate_cb), filter_te);
240     gtk_widget_set_sensitive (GTK_WIDGET(filter_apply), FALSE);
241     gtk_widget_show(GTK_WIDGET(filter_apply));
242
243     gtk_toolbar_insert(GTK_TOOLBAR(filter_tb),
244                        filter_apply,
245                        -1);
246
247         gtk_widget_set_tooltip_text(GTK_WIDGET(filter_apply), "Apply this filter string to the display");
248
249     /* Sets the text entry widget pointer as the E_DILTER_TE_KEY data
250      * of any widget that ends up calling a callback which needs
251      * that text entry pointer */
252     set_menu_object_data(MENU_BAR_PATH_FILE_OPEN, E_DFILTER_TE_KEY, filter_te);
253     set_menu_object_data(MENU_BAR_PATH_EDIT_COPY_AS_FLT, E_DFILTER_TE_KEY,
254                          filter_te);
255     set_menu_object_data(MENU_BAR_PATH_ANALYZE_DISPLAY_FLT, E_FILT_TE_PTR_KEY,
256                          filter_te);
257     set_menu_object_data(MENU_BAR_PATH_ANALYZE_FOLLOW_TCP_STREAM, E_DFILTER_TE_KEY,
258                          filter_te);
259     set_menu_object_data(MENU_BAR_PATH_ANALYZE_FOLLOW_UDP_STREAM, E_DFILTER_TE_KEY,
260                          filter_te);
261     set_menu_object_data(MENU_BAR_PATH_ANALYZE_FOLLOW_SSL_STREAM, E_DFILTER_TE_KEY,
262                          filter_te);
263     set_menu_object_data(MENU_BAR_PATH_ANALYZE_APL_AS_FLT_SEL, E_DFILTER_TE_KEY,
264                          filter_te);
265     set_menu_object_data(MENU_BAR_PATH_ANALYZE_APL_AS_FLT_NOT_SEL, E_DFILTER_TE_KEY,
266                          filter_te);
267     set_menu_object_data(MENU_BAR_PATH_ANALYZE_APL_AS_FLT_AND_SEL, E_DFILTER_TE_KEY,
268                          filter_te);
269     set_menu_object_data(MENU_BAR_PATH_ANALYZE_APL_AS_FLT_OR_SEL, E_DFILTER_TE_KEY,
270                          filter_te);
271     set_menu_object_data(MENU_BAR_PATH_ANALYZE_APL_AS_FLT_AND_NOT_SEL, E_DFILTER_TE_KEY,
272                          filter_te);
273     set_menu_object_data(MENU_BAR_PATH_ANALYZE_APL_AS_FLT_OR_NOT_SEL, E_DFILTER_TE_KEY,
274                          filter_te);
275     set_menu_object_data(MENU_BAR_PATH_ANALYZE_PREP_A_FLT_SEL, E_DFILTER_TE_KEY,
276                          filter_te);
277     set_menu_object_data(MENU_BAR_PATH_ANALYZE_PREP_A_FLT_NOT_SEL, E_DFILTER_TE_KEY,
278                          filter_te);
279     set_menu_object_data(MENU_BAR_PATH_ANALYZE_PREP_A_FLT_AND_SEL, E_DFILTER_TE_KEY,
280                          filter_te);
281     set_menu_object_data(MENU_BAR_PATH_ANALYZE_PREP_A_FLT_OR_SEL, E_DFILTER_TE_KEY,
282                          filter_te);
283     set_menu_object_data(MENU_BAR_PATH_ANALYZE_PREP_A_FLT_AND_NOT_SEL, E_DFILTER_TE_KEY,
284                          filter_te);
285     set_menu_object_data(MENU_BAR_PATH_ANALYZE_PREP_A_FLT_OR_NOT_SEL, E_DFILTER_TE_KEY,
286                          filter_te);
287
288     set_toolbar_object_data(E_DFILTER_TE_KEY, filter_te);
289     g_object_set_data(G_OBJECT(popup_menu_object), E_DFILTER_TE_KEY, filter_te);
290
291     /* make current preferences effective */
292     toolbar_redraw_all();
293
294     return filter_tb;
295 }
296
297 static gboolean
298 dfilter_entry_match(GtkWidget *filter_cm, char *s, int *index)
299 {
300     GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX(filter_cm));
301     GtkTreeIter   iter;
302     GValue value = { 0, {{0}}};
303     const char *filter_str;
304     int i;
305
306     i = -1;
307     if (!gtk_tree_model_get_iter_first (model, &iter)){
308         *index = i;
309         return FALSE;
310     }
311     do{
312         i++;
313         gtk_tree_model_get_value (model, &iter, 0, &value);
314         filter_str = g_value_get_string (&value);
315         if(filter_str){
316             if(strcmp(s, filter_str) == 0){
317                 g_value_unset (&value);
318                 *index = i;
319                 return TRUE;
320             }
321         }
322         g_value_unset (&value);
323     }while (gtk_tree_model_iter_next (model, &iter));
324
325     *index = i;
326     return FALSE;
327 }
328
329 /* add a display filter to the combo box */
330 /* Note: a new filter string will not replace an old identical one */
331 static gboolean
332 dfilter_combo_add(GtkWidget *filter_cm, char *s) {
333     int index;
334
335     if(!dfilter_entry_match(filter_cm,s, &index))
336          gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT(filter_cm), s);
337     g_free(s);
338
339     return TRUE;
340 }
341
342
343 /* write all non empty display filters (until maximum count)
344  * of the combo box GList to the user's recent file */
345 void
346 dfilter_recent_combo_write_all(FILE *rf) {
347     GtkWidget *filter_cm = g_object_get_data(G_OBJECT(top_level), E_DFILTER_CM_KEY);
348     GtkTreeModel *model = gtk_combo_box_get_model (GTK_COMBO_BOX(filter_cm));
349     GtkTreeIter   iter;
350     GValue value = { 0, {{0}}};
351     const char *filter_str;
352     guint      max_count = 0;
353
354     if (!gtk_tree_model_get_iter_first (model, &iter))
355         return;
356     do{
357         gtk_tree_model_get_value (model, &iter, 0, &value);
358         filter_str = g_value_get_string (&value);
359         if(filter_str)
360             fprintf (rf, RECENT_KEY_DISPLAY_FILTER ": %s\n", filter_str);
361         g_value_unset (&value);
362
363     }while (gtk_tree_model_iter_next (model, &iter)&& (max_count++ < prefs.gui_recent_df_entries_max));
364
365 }
366
367 /* add a display filter coming from the user's recent file to the dfilter combo box */
368 gboolean
369 dfilter_combo_add_recent(gchar *s) {
370     GtkWidget *filter_cm = g_object_get_data(G_OBJECT(top_level), E_DFILTER_CM_KEY);
371     char      *dup;
372
373     dup = g_strdup(s);
374
375     return dfilter_combo_add(filter_cm, dup);
376 }
377
378 /* call cf_filter_packets() and add this filter string to the recent filter list */
379 gboolean
380 main_filter_packets(capture_file *cf, const gchar *dftext, gboolean force)
381 {
382     GtkWidget *filter_cm = g_object_get_data(G_OBJECT(top_level), E_DFILTER_CM_KEY);
383     gboolean   free_filter = TRUE;
384     char      *s;
385     cf_status_t cf_status;
386
387     s = g_strdup(dftext);
388
389     cf_status = cf_filter_packets(cf, s, force);
390
391     if (cf_status == CF_OK) {
392         gtk_widget_set_sensitive (g_object_get_data (G_OBJECT(filter_cm), E_DFILTER_APPLY_KEY), FALSE);
393         if (!s || strlen (s) == 0) {
394             gtk_widget_set_sensitive (g_object_get_data (G_OBJECT(filter_cm), E_DFILTER_CLEAR_KEY), FALSE);
395         }
396     }
397
398     if (!s)
399         return (cf_status == CF_OK);
400
401     /* GtkCombos don't let us get at their list contents easily, so we maintain
402        our own filter list, and feed it to gtk_combo_set_popdown_strings when
403        a new filter is added. */
404     if (cf_status == CF_OK && strlen(s) > 0) {
405         int index;
406
407         if(!dfilter_entry_match(filter_cm,s, &index)){
408             gtk_combo_box_text_prepend_text(GTK_COMBO_BOX_TEXT(filter_cm), s);
409             index++;
410         }
411         while ((guint)index >= prefs.gui_recent_df_entries_max){
412             gtk_combo_box_text_remove(GTK_COMBO_BOX_TEXT(filter_cm), index);
413             index--;
414         }
415     }
416     if (free_filter)
417         g_free(s);
418
419     return (cf_status == CF_OK);
420 }
421
422