Implement plugins status save/restore :
[obnox/wireshark/wip.git] / prefs.c
1 /* prefs.c
2  * Routines for handling preferences
3  *
4  * $Id: prefs.c,v 1.29 2000/01/03 06:29:32 guy Exp $
5  *
6  * Ethereal - Network traffic analyzer
7  * By Gerald Combs <gerald@zing.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * 
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  * 
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  * 
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 # include "config.h"
28 #endif
29
30 #ifdef HAVE_SYS_TYPES_H
31 #include <sys/types.h>
32 #endif
33
34 #ifdef HAVE_DIRECT_H
35 #include <direct.h>
36 #endif
37
38 #include <stdlib.h>
39 #include <ctype.h>
40 #include <errno.h>
41
42 #ifdef HAVE_UNISTD_H
43 #include <unistd.h>
44 #endif
45
46 #include <sys/stat.h>
47
48 #include "globals.h"
49 #include "packet.h"
50 #include "file.h"
51 #include "prefs.h"
52 #include "column.h"
53 #include "print.h"
54
55 /* Internal functions */
56 static int    set_pref(gchar*, gchar*);
57 static GList *get_string_list(gchar *);
58 static void   clear_string_list(GList *);
59
60 #define PF_NAME "preferences"
61
62 static int init_prefs = 1;
63 static gchar *pf_path = NULL;
64
65 e_prefs prefs;
66
67 gchar   *gui_ptree_line_style_text[] =
68         { "NONE", "SOLID", "DOTTED", "TABBED", NULL };
69
70 gchar   *gui_ptree_expander_style_text[] =
71         { "NONE", "SQUARE", "TRIANGLE", "CIRCULAR", NULL };
72
73
74 /* Parse through a list of comma-separated, quoted strings.  Return a
75    list of the string data */
76 static GList *
77 get_string_list(gchar *str) {
78   enum { PRE_QUOT, IN_QUOT, POST_QUOT };
79
80   gint      state = PRE_QUOT, i = 0, j = 0;
81   gboolean  backslash = FALSE;
82   gchar     cur_c, *slstr = NULL;
83   GList    *sl = NULL;
84   
85   while ((cur_c = str[i]) != '\0') {
86     if (cur_c == '"' && ! backslash) {
87       switch (state) {
88         case PRE_QUOT:
89           state = IN_QUOT;
90           slstr = (gchar *) g_malloc(sizeof(gchar) * COL_MAX_LEN);
91           j = 0;
92           break;
93         case IN_QUOT:
94           state  = POST_QUOT;
95           slstr[j] = '\0';
96           sl = g_list_append(sl, slstr);
97           break;
98         case POST_QUOT:
99           clear_string_list(sl);
100           return NULL;
101           break;
102         default:
103           break;
104       }
105     } else if (cur_c == '\\' && ! backslash) {
106       backslash = TRUE;
107     } else if (cur_c == ',' && state == POST_QUOT) {
108       state = PRE_QUOT;
109     } else if (state == IN_QUOT && j < COL_MAX_LEN) {
110       slstr[j] = str[i];
111       j++;
112     }
113     i++;
114   }
115   if (state != POST_QUOT) {
116     clear_string_list(sl);
117   }
118   return(sl);
119 }
120
121 void
122 clear_string_list(GList *sl) {
123   GList *l = sl;
124   
125   while (l) {
126     g_free(l->data);
127     l = g_list_remove_link(l, l);
128   }
129 }
130
131 /* Takes an string and a pointer to an array of strings, and a default int value.
132  * The array must be terminated by a NULL string. If the string is found in the array
133  * of strings, the index of that string in the array is returned. Otherwise, the
134  * default value that was passed as the third argument is returned.
135  */
136 static int
137 find_index_from_string_array(char *needle, char **haystack, int default_value)
138 {
139         int i = 0;
140
141         while (haystack[i] != NULL) {
142                 if (strcmp(needle, haystack[i]) == 0) {
143                         return i;
144                 }
145                 i++;    
146         }
147         return default_value;
148 }
149
150 /* Preferences file format:
151  * - Configuration directives start at the beginning of the line, and 
152  *   are terminated with a colon.
153  * - Directives can be continued on the next line by preceding them with
154  *   whitespace.
155  *
156  * Example:
157
158 # This is a comment line
159 print.command: lpr
160 print.file: /a/very/long/path/
161         to/ethereal-out.ps
162  *
163  */
164
165 #define MAX_VAR_LEN    48
166 #define MAX_VAL_LEN  1024
167
168 #define DEF_NUM_COLS    6
169 e_prefs *
170 read_prefs(char **pf_path_return) {
171   enum { START, IN_VAR, PRE_VAL, IN_VAL, IN_SKIP };
172   FILE     *pf;
173   gchar     cur_var[MAX_VAR_LEN], cur_val[MAX_VAL_LEN];
174   int       got_c, state = START, i;
175   gint      var_len = 0, val_len = 0, fline = 1, pline = 1;
176   gboolean  got_val = FALSE;
177   fmt_data *cfmt;
178   gchar    *col_fmt[] = {"No.",      "%m", "Time",        "%t",
179                          "Source",   "%s", "Destination", "%d",
180                          "Protocol", "%p", "Info",        "%i"};
181
182   
183   /* Initialize preferences.  With any luck, these values will be
184      overwritten below. */
185   if (init_prefs) {
186     init_prefs       = 0;
187     prefs.pr_format  = PR_FMT_TEXT;
188     prefs.pr_dest    = PR_DEST_CMD;
189     prefs.pr_file    = g_strdup("ethereal.out");
190     prefs.pr_cmd     = g_strdup("lpr");
191     prefs.col_list = NULL;
192     for (i = 0; i < DEF_NUM_COLS; i++) {
193       cfmt = (fmt_data *) g_malloc(sizeof(fmt_data));
194       cfmt->title = g_strdup(col_fmt[i * 2]);
195       cfmt->fmt   = g_strdup(col_fmt[(i * 2) + 1]);
196       prefs.col_list = g_list_append(prefs.col_list, cfmt);
197     }
198     prefs.num_cols  = DEF_NUM_COLS;
199     prefs.st_client_fg.pixel =     0;
200     prefs.st_client_fg.red   = 32767;
201     prefs.st_client_fg.green =     0;
202     prefs.st_client_fg.blue  =     0;
203     prefs.st_client_bg.pixel = 65535;
204     prefs.st_client_bg.red   = 65535;
205     prefs.st_client_bg.green = 65535;
206     prefs.st_client_bg.blue  = 65535;
207     prefs.st_server_fg.pixel =     0;
208     prefs.st_server_fg.red   =     0;
209     prefs.st_server_fg.green =     0;
210     prefs.st_server_fg.blue  = 32767;
211     prefs.st_server_bg.pixel = 65535;
212     prefs.st_server_bg.red   = 65535;
213     prefs.st_server_bg.green = 65535;
214     prefs.st_server_bg.blue  = 65535;
215     prefs.gui_scrollbar_on_right = TRUE;
216     prefs.gui_plist_sel_browse = FALSE;
217     prefs.gui_ptree_sel_browse = FALSE;
218     prefs.gui_ptree_line_style = 0;
219     prefs.gui_ptree_expander_style = 1;
220   }
221
222   if (! pf_path) {
223     pf_path = (gchar *) g_malloc(strlen(getenv("HOME")) + strlen(PF_DIR) +
224       strlen(PF_NAME) + 4);
225     sprintf(pf_path, "%s/%s/%s", getenv("HOME"), PF_DIR, PF_NAME);
226   }
227     
228   *pf_path_return = NULL;
229   if ((pf = fopen(pf_path, "r")) == NULL) {
230     if (errno != ENOENT)
231       *pf_path_return = pf_path;
232     return &prefs;
233   }
234     
235   while ((got_c = getc(pf)) != EOF) {
236     if (got_c == '\n') {
237       state = START;
238       fline++;
239       continue;
240     }
241     if (var_len >= MAX_VAR_LEN) {
242       g_warning ("%s line %d: Variable too long", pf_path, fline);
243       state = IN_SKIP;
244       var_len = 0;
245       continue;
246     }
247     if (val_len >= MAX_VAL_LEN) {
248       g_warning ("%s line %d: Value too long", pf_path, fline);
249       state = IN_SKIP;
250       var_len = 0;
251       continue;
252     }
253     
254     switch (state) {
255       case START:
256         if (isalnum(got_c)) {
257           if (var_len > 0) {
258             if (got_val) {
259               cur_var[var_len] = '\0';
260               cur_val[val_len] = '\0';
261               if (! set_pref(cur_var, cur_val))
262                 g_warning ("%s line %d: Bogus preference", pf_path, pline);
263             } else {
264               g_warning ("%s line %d: Incomplete preference", pf_path, pline);
265             }
266           }
267           state      = IN_VAR;
268           got_val    = FALSE;
269           cur_var[0] = got_c;
270           var_len    = 1;
271           pline = fline;
272         } else if (isspace(got_c) && var_len > 0 && got_val) {
273           state = PRE_VAL;
274         } else if (got_c == '#') {
275           state = IN_SKIP;
276         } else {
277           g_warning ("%s line %d: Malformed line", pf_path, fline);
278         }
279         break;
280       case IN_VAR:
281         if (got_c != ':') {
282           cur_var[var_len] = got_c;
283           var_len++;
284         } else {
285           state   = PRE_VAL;
286           val_len = 0;
287           got_val = TRUE;
288         }
289         break;
290       case PRE_VAL:
291         if (!isspace(got_c)) {
292           state = IN_VAL;
293           cur_val[val_len] = got_c;
294           val_len++;
295         }
296         break;
297       case IN_VAL:
298         if (got_c != '#')  {
299           cur_val[val_len] = got_c;
300           val_len++;
301         } else {
302           while (isspace(cur_val[val_len]) && val_len > 0)
303             val_len--;
304           state = IN_SKIP;
305         }
306         break;
307     }
308   }
309   if (var_len > 0) {
310     if (got_val) {
311       cur_var[var_len] = '\0';
312       cur_val[val_len] = '\0';
313       if (! set_pref(cur_var, cur_val))
314         g_warning ("%s line %d: Bogus preference", pf_path, pline);
315     } else {
316       g_warning ("%s line %d: Incomplete preference", pf_path, pline);
317     }
318   }
319   fclose(pf);
320   
321   return &prefs;
322 }
323
324 #define PRS_PRINT_FMT    "print.format"
325 #define PRS_PRINT_DEST   "print.destination"
326 #define PRS_PRINT_FILE   "print.file"
327 #define PRS_PRINT_CMD    "print.command"
328 #define PRS_COL_FMT      "column.format"
329 #define PRS_STREAM_CL_FG "stream.client.fg"
330 #define PRS_STREAM_CL_BG "stream.client.bg"
331 #define PRS_STREAM_SR_FG "stream.server.fg"
332 #define PRS_STREAM_SR_BG "stream.server.bg"
333 #define PRS_GUI_SCROLLBAR_ON_RIGHT "gui.scrollbar_on_right"
334 #define PRS_GUI_PLIST_SEL_BROWSE "gui.packet_list_sel_browse"
335 #define PRS_GUI_PTREE_SEL_BROWSE "gui.protocol_tree_sel_browse"
336 #define PRS_GUI_PTREE_LINE_STYLE "gui.protocol_tree_line_style"
337 #define PRS_GUI_PTREE_EXPANDER_STYLE "gui.protocol_tree_expander_style"
338
339 #define RED_COMPONENT(x)   ((((x) >> 16) & 0xff) * 65535 / 255)
340 #define GREEN_COMPONENT(x) ((((x) >>  8) & 0xff) * 65535 / 255)
341 #define BLUE_COMPONENT(x)   (((x)        & 0xff) * 65535 / 255)
342
343 static gchar *pr_formats[] = { "text", "postscript" };
344 static gchar *pr_dests[]   = { "command", "file" };
345
346 int
347 set_pref(gchar *pref, gchar *value) {
348   GList    *col_l;
349   gint      llen;
350   fmt_data *cfmt;
351   unsigned long int cval;
352
353   if (strcmp(pref, PRS_PRINT_FMT) == 0) {
354     if (strcmp(value, pr_formats[PR_FMT_TEXT]) == 0) {
355       prefs.pr_format = PR_FMT_TEXT;
356     } else if (strcmp(value, pr_formats[PR_FMT_PS]) == 0) {
357       prefs.pr_format = PR_FMT_PS;
358     } else {
359       return 0;
360     }
361   } else if (strcmp(pref, PRS_PRINT_DEST) == 0) {
362     if (strcmp(value, pr_dests[PR_DEST_CMD]) == 0) {
363       prefs.pr_dest = PR_DEST_CMD;
364     } else if (strcmp(value, pr_dests[PR_DEST_FILE]) == 0) {
365       prefs.pr_dest = PR_DEST_FILE;
366     } else {
367       return 0;
368     }
369   } else if (strcmp(pref, PRS_PRINT_FILE) == 0) {
370     if (prefs.pr_file) g_free(prefs.pr_file);
371     prefs.pr_file = g_strdup(value);
372   } else if (strcmp(pref, PRS_PRINT_CMD) == 0) {
373     if (prefs.pr_cmd) g_free(prefs.pr_cmd);
374     prefs.pr_cmd = g_strdup(value);
375   } else if (strcmp(pref, PRS_COL_FMT) == 0) {
376     if ((col_l = get_string_list(value)) && (g_list_length(col_l) % 2) == 0) {
377       while (prefs.col_list) {
378         cfmt = prefs.col_list->data;
379         g_free(cfmt->title);
380         g_free(cfmt->fmt);
381         g_free(cfmt);
382         prefs.col_list = g_list_remove_link(prefs.col_list, prefs.col_list);
383       }
384       llen             = g_list_length(col_l);
385       prefs.num_cols   = llen / 2;
386       col_l = g_list_first(col_l);
387       while(col_l) {
388         cfmt = (fmt_data *) g_malloc(sizeof(fmt_data));
389         cfmt->title    = g_strdup(col_l->data);
390         col_l          = col_l->next;
391         cfmt->fmt      = g_strdup(col_l->data);
392         col_l          = col_l->next;
393         prefs.col_list = g_list_append(prefs.col_list, cfmt);
394       }
395       /* To do: else print some sort of error? */
396     }
397     clear_string_list(col_l);
398   } else if (strcmp(pref, PRS_STREAM_CL_FG) == 0) {
399     cval = strtoul(value, NULL, 16);
400     prefs.st_client_fg.pixel = 0;
401     prefs.st_client_fg.red   = RED_COMPONENT(cval);
402     prefs.st_client_fg.green = GREEN_COMPONENT(cval);
403     prefs.st_client_fg.blue  = BLUE_COMPONENT(cval);
404   } else if (strcmp(pref, PRS_STREAM_CL_BG) == 0) {
405     cval = strtoul(value, NULL, 16);
406     prefs.st_client_bg.pixel = 0;
407     prefs.st_client_bg.red   = RED_COMPONENT(cval);
408     prefs.st_client_bg.green = GREEN_COMPONENT(cval);
409     prefs.st_client_bg.blue  = BLUE_COMPONENT(cval);
410   } else if (strcmp(pref, PRS_STREAM_SR_FG) == 0) {
411     cval = strtoul(value, NULL, 16);
412     prefs.st_server_fg.pixel = 0;
413     prefs.st_server_fg.red   = RED_COMPONENT(cval);
414     prefs.st_server_fg.green = GREEN_COMPONENT(cval);
415     prefs.st_server_fg.blue  = BLUE_COMPONENT(cval);
416   } else if (strcmp(pref, PRS_STREAM_SR_BG) == 0) {
417     cval = strtoul(value, NULL, 16);
418     prefs.st_server_bg.pixel = 0;
419     prefs.st_server_bg.red   = RED_COMPONENT(cval);
420     prefs.st_server_bg.green = GREEN_COMPONENT(cval);
421     prefs.st_server_bg.blue  = BLUE_COMPONENT(cval);
422   } else if (strcmp(pref, PRS_GUI_SCROLLBAR_ON_RIGHT) == 0) {
423     if (strcmp(value, "TRUE") == 0) {
424             prefs.gui_scrollbar_on_right = TRUE;
425     }
426     else {
427             prefs.gui_scrollbar_on_right = FALSE;
428     }
429   } else if (strcmp(pref, PRS_GUI_PLIST_SEL_BROWSE) == 0) {
430     if (strcmp(value, "TRUE") == 0) {
431             prefs.gui_plist_sel_browse = TRUE;
432     }
433     else {
434             prefs.gui_plist_sel_browse = FALSE;
435     }
436   } else if (strcmp(pref, PRS_GUI_PTREE_SEL_BROWSE) == 0) {
437     if (strcmp(value, "TRUE") == 0) {
438             prefs.gui_ptree_sel_browse = TRUE;
439     }
440     else {
441             prefs.gui_ptree_sel_browse = FALSE;
442     }
443   } else if (strcmp(pref, PRS_GUI_PTREE_LINE_STYLE) == 0) {
444           prefs.gui_ptree_line_style =
445                   find_index_from_string_array(value, gui_ptree_line_style_text, 0);
446   } else if (strcmp(pref, PRS_GUI_PTREE_EXPANDER_STYLE) == 0) {
447           prefs.gui_ptree_expander_style =
448                   find_index_from_string_array(value, gui_ptree_expander_style_text, 1);
449   } else {
450     return 0;
451   }
452   
453   return 1;
454 }
455
456 int
457 write_prefs(char **pf_path_return) {
458   FILE        *pf;
459   struct stat  s_buf;
460   
461   /* To do:
462    * - Split output lines longer than MAX_VAL_LEN
463    * - Create a function for the preference directory check/creation
464    *   so that duplication can be avoided with filter.c
465    */
466
467   if (! pf_path) {
468     pf_path = (gchar *) g_malloc(strlen(getenv("HOME")) + strlen(PF_DIR) +
469       strlen(PF_NAME) + 4);
470   }
471
472   sprintf(pf_path, "%s/%s", getenv("HOME"), PF_DIR);
473   if (stat(pf_path, &s_buf) != 0)
474 #ifdef WIN32
475     mkdir(pf_path);
476 #else
477     mkdir(pf_path, 0755);
478 #endif
479
480   sprintf(pf_path, "%s/%s/%s", getenv("HOME"), PF_DIR, PF_NAME);
481   if ((pf = fopen(pf_path, "w")) == NULL) {
482     *pf_path_return = pf_path;
483     return errno;
484   }
485     
486   fputs("# Configuration file for Ethereal " VERSION ".\n"
487     "#\n"
488     "# This file is regenerated each time preferences are saved within\n"
489     "# Ethereal.  Making manual changes should be safe, however.\n"
490     "\n"
491     "######## Printing ########\n"
492     "\n", pf);
493
494   fprintf (pf, "# Can be one of \"text\" or \"postscript\".\n"
495     "print.format: %s\n\n", pr_formats[prefs.pr_format]);
496
497   fprintf (pf, "# Can be one of \"command\" or \"file\".\n"
498     "print.destination: %s\n\n", pr_dests[prefs.pr_dest]);
499
500   fprintf (pf, "# This is the file that gets written to when the "
501     "destination is set to \"file\"\n"
502     "%s: %s\n\n", PRS_PRINT_FILE, prefs.pr_file);
503
504   fprintf (pf, "# Output gets piped to this command when the destination "
505     "is set to \"command\"\n"
506     "%s: %s\n\n", PRS_PRINT_CMD, prefs.pr_cmd);
507
508   fprintf (pf, "# Packet list column format.  Each pair of strings consists "
509     "of a column title \n# and its format.\n"
510     "%s: %s\n\n", PRS_COL_FMT, col_format_to_pref_str());
511
512   fprintf (pf, "# TCP stream window color preferences.  Each value is a six "
513     "digit hexadecimal value in the form rrggbb.\n");
514   fprintf (pf, "%s: %02x%02x%02x\n", PRS_STREAM_CL_FG,
515     (prefs.st_client_fg.red * 255 / 65535),
516     (prefs.st_client_fg.green * 255 / 65535),
517     (prefs.st_client_fg.blue * 255 / 65535));
518   fprintf (pf, "%s: %02x%02x%02x\n", PRS_STREAM_CL_BG,
519     (prefs.st_client_bg.red * 255 / 65535),
520     (prefs.st_client_bg.green * 255 / 65535),
521     (prefs.st_client_bg.blue * 255 / 65535));
522   fprintf (pf, "%s: %02x%02x%02x\n", PRS_STREAM_SR_FG,
523     (prefs.st_server_fg.red * 255 / 65535),
524     (prefs.st_server_fg.green * 255 / 65535),
525     (prefs.st_server_fg.blue * 255 / 65535));
526   fprintf (pf, "%s: %02x%02x%02x\n", PRS_STREAM_SR_BG,
527     (prefs.st_server_bg.red * 255 / 65535),
528     (prefs.st_server_bg.green * 255 / 65535),
529     (prefs.st_server_bg.blue * 255 / 65535));
530
531   fprintf(pf, "\n# Vertical scrollbars should be on right side? TRUE/FALSE\n");
532   fprintf(pf, PRS_GUI_SCROLLBAR_ON_RIGHT ": %s\n",
533                   prefs.gui_scrollbar_on_right == TRUE ? "TRUE" : "FALSE");
534
535   fprintf(pf, "\n# Packet-list selection bar can be used to browse w/o selecting? TRUE/FALSE\n");
536   fprintf(pf, PRS_GUI_PLIST_SEL_BROWSE ": %s\n",
537                   prefs.gui_plist_sel_browse == TRUE ? "TRUE" : "FALSE");
538
539   fprintf(pf, "\n# Protocol-tree selection bar can be used to browse w/o selecting? TRUE/FALSE\n");
540   fprintf(pf, PRS_GUI_PTREE_SEL_BROWSE ": %s\n",
541                   prefs.gui_ptree_sel_browse == TRUE ? "TRUE" : "FALSE");
542
543   fprintf(pf, "\n# Protocol-tree line style. One of: NONE, SOLID, DOTTED, TABBED\n");
544   fprintf(pf, PRS_GUI_PTREE_LINE_STYLE ": %s\n",
545                   gui_ptree_line_style_text[prefs.gui_ptree_line_style]);
546
547   fprintf(pf, "\n# Protocol-tree expander style. One of: NONE, SQUARE, TRIANGLE, CIRCULAR\n");
548   fprintf(pf, PRS_GUI_PTREE_EXPANDER_STYLE ": %s\n",
549                   gui_ptree_expander_style_text[prefs.gui_ptree_expander_style]);
550
551   fclose(pf);
552
553   /* XXX - catch I/O errors (e.g. "ran out of disk space") and return
554      an error indication, or maybe write to a new preferences file and
555      rename that file on top of the old one only if there are not I/O
556      errors. */
557   return 0;
558 }