/etc/sysconfig/udpgate configuration file read/write support.
[udpgate.git] / src / configuration.c
1 /* $Id$
2  * UDP Gateway persistent configuration
3  * Copyright (C) 2004 Jan Kratochvil <project-udpgate@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #include <glib/gmessages.h>
23 #include <glib/ghash.h>
24 #include <glib/gstring.h>
25 #include <glib/gmem.h>
26 #include <stdio.h>
27 #include <errno.h>
28 #include <ctype.h>
29 #include <glib/gstrfuncs.h>
30 #include <string.h>
31
32 #include "configuration.h"      /* self */
33 #include "network.h"
34 #include "main.h"
35
36
37 /* OK, GConf would be nice.
38  * Unfortunately the fully-static build does not support GConf process spawn at
39  * all. Also /etc/sysconfig directory is standard for the daemon services.
40  */
41
42
43 /* Config: */
44 #define CONFIGURATION_FILE "/etc/sysconfig/udpgate"
45
46
47 static GHashTable *configuration_hash_new(void)
48 {
49         return g_hash_table_new_full(g_str_hash,g_str_equal,
50                         (GDestroyNotify)g_free, /* key_equal_func; of g_strdup() strings */
51                         (GDestroyNotify)g_free);        /* value_equal_func; of g_strdup() strings */
52 }
53
54 static GHashTable *configuration_comments_hash_new(void)
55 {
56 static GHashTable *hash;
57
58         if (!hash) {
59                 hash=g_hash_table_new(g_str_hash,g_str_equal);
60                 g_hash_table_insert(hash,"PORT",_("Local UDP port"));
61                 }
62         return hash;
63 }
64
65 static gboolean configuration_file_write(const gchar *file_content)
66 {
67 FILE *f;
68
69         g_return_val_if_fail(file_content!=NULL,FALSE);
70
71         if (!(f=fopen(CONFIGURATION_FILE,("w")))) {
72                 g_warning(_("Error write opening configuration file \"%s\": %m"),CONFIGURATION_FILE);
73                 return FALSE;
74                 }
75         if (fputs(file_content,f)<0) {
76                 g_warning(_("Error writing configuration file \"%s\": %m"),CONFIGURATION_FILE);
77                 fclose(f);      /* errors ignored */
78                 return FALSE;
79                 }
80         if (fclose(f))
81                 g_warning(_("Error closing configuration file \"%s\": %m"),CONFIGURATION_FILE);
82         return TRUE;
83 }
84
85 struct configuration_hash_readwrite_hash_flush_foreach_param {
86         GString *gstring;
87         GHashTable *hash_flushed;
88         gboolean modified;
89         };
90 static void configuration_hash_readwrite_hash_flush_foreach
91                 (const gchar *key,const gchar *value,struct configuration_hash_readwrite_hash_flush_foreach_param *param)
92 {
93 const gchar *comment;
94
95         g_return_if_fail(key!=NULL);
96         g_return_if_fail(value!=NULL);
97         g_return_if_fail(param!=NULL);
98         g_return_if_fail(param->gstring!=NULL);
99         g_return_if_fail(param->hash_flushed!=NULL);
100
101         /* Already written out? */
102         if (g_hash_table_lookup(param->hash_flushed,key))
103                 return;
104         if (!(comment=g_hash_table_lookup(configuration_comments_hash_new(),key)))
105                 comment=_("*** UNDOCUMENTED ***");
106         g_string_append(param->gstring,udpgate_printf_alloca("# %s: %s\n%s=%s\n",key,comment,key,value));
107         param->modified=TRUE;
108         if (optarg_verbose)
109                 g_message(_("Appended configuration variable name \"%s\", value \"%s\""),key,value);
110 }
111
112 /* hash_flush==NULL => read info and return new (GHashTable *)
113  * hash_flush!=NULL => update configuration file by this (GHashTable *)
114  */
115 /* FIXME: File locking! */
116 static GHashTable *configuration_hash_readwrite(GHashTable *hash_flush)
117 {
118 FILE *f;
119 char line[LINE_MAX];
120 int lineno;
121 GHashTable *hash_fill;
122 /* Which 'keys' of 'hash_flush' were already written. 'key' must be g_free()able.
123  * Any 'value' is ignored. 'value' must be !=NULL. 'value' must be g_free()able.
124  */
125 GHashTable *hash_flushed;
126 GString *gstring;
127 gboolean modified=FALSE;        /* 'gstring' contains modified value */
128 gboolean already_written=FALSE;
129
130 open_retry:
131         if (!(f=fopen(CONFIGURATION_FILE,(!hash_flush ? "r" : "rw")))) {
132                 if (errno!=ENOENT)
133                         g_warning(_("Error r/o opening configuration file \"%s\": %m"),CONFIGURATION_FILE);
134                 if (!hash_flush || already_written)
135                         return NULL;
136                 else {
137                         already_written=TRUE;
138                         if (!configuration_file_write(_(
139 "# Configuration file for UDPGate - see its man page udpgate(1).\n"
140 "\n"
141                                         )))
142                                 return NULL;
143                         goto open_retry;
144                         }
145                 }
146         if (!hash_flush) {
147                 hash_fill=configuration_hash_new();
148                 hash_flushed=NULL;
149                 gstring=NULL;
150                 }
151         else {
152                 hash_fill=NULL;
153                 hash_flushed=configuration_hash_new();
154                 gstring=g_string_new(NULL);
155                 }
156         lineno=0;
157         while (errno=0,fgets(line,sizeof(line),f)) {
158 char *s;
159 char *varname_start,*varname_stop,varname_stop_orig;
160 char *varcontent_start,*varcontent_stop,varcontent_stop_orig;
161 const gchar *value;
162
163                 lineno++;
164                 s=line;
165                 varname_stop=NULL;
166                 varcontent_stop=NULL;
167                 /* Parse: ^\s*([[:alnum:]])\s*=\s*(\S*)\s*$
168                  */
169                 while (*s && isspace(*s)) s++;
170                 if (!*s || *s=='#') {
171 err_append:
172                         if (gstring)
173                                 g_string_append(gstring,line);
174                         continue;
175                         }
176                 varname_start=s;
177                 while (*s && isalnum(*s)) s++;
178                 varname_stop=s;
179                 varname_stop_orig=*varname_stop;
180                 while (*s && isspace(*s)) s++;
181                 if (*s++!='=') {
182 err_line:
183                         if (varname_stop)
184                                 *varname_stop=varname_stop_orig;
185                         if (varcontent_stop)
186                                 *varcontent_stop=varcontent_stop_orig;
187                         g_warning(_("Error parsing line %d of the configuration file \"%s\": %s"),lineno,CONFIGURATION_FILE,line);
188                         goto err_append;
189                         }
190                 while (*s && isspace(*s)) s++;
191                 varcontent_start=s;
192                 if (!*s)
193                         goto err_line;
194                 while (*s && !isspace(*s)) s++;
195                 varcontent_stop=s;
196                 varcontent_stop_orig=*varcontent_stop;
197                 while (*s && isspace(*s)) s++;
198                 if (*s && *s!='#')
199                         goto err_line;
200
201                 if (!*varname_start || !*varcontent_start)
202                         goto err_line;
203
204                 *varname_stop='\0';
205                 *varcontent_stop='\0';
206                 if (optarg_verbose)
207                         g_message(_("Parsed configuration variable name \"%s\", value \"%s\""),varname_start,varcontent_start);
208                 if (hash_fill)
209                         g_hash_table_insert(
210                                         hash_fill,      /* hash_table */
211                                         g_strdup(varname_start),        /* key */
212                                         g_strdup(varcontent_start));    /* value */
213                 if (hash_flush && (value=g_hash_table_lookup(hash_flush,varname_start))) {
214 const char *line_new=udpgate_printf_alloca("%s=%s\n",varname_start,value);
215
216                         *varname_stop=varname_stop_orig;
217                         *varcontent_stop=varcontent_stop_orig;
218                         if (strcmp(line,line_new))
219                                 modified=TRUE;
220                         *varname_stop=0;
221                         *varcontent_stop=0;
222                         g_string_append(gstring,line_new);
223                         g_hash_table_insert(hash_flushed,g_strdup(varname_start),g_strdup(varcontent_start));
224                         }
225                 else
226                         goto err_append;
227                 }
228         if (errno) {
229                 g_warning(_("Error reading line from the configuration file \"%s\": %s"),CONFIGURATION_FILE,strerror(errno));
230                 if (hash_fill)
231                         g_hash_table_destroy(hash_fill);
232                 fclose(f);      /* errors ignored */
233                 return NULL;
234                 }
235         if (fclose(f))
236                 g_warning(_("Error closing configuration file \"%s\": %m"),CONFIGURATION_FILE);
237         if (hash_flushed) {
238 struct configuration_hash_readwrite_hash_flush_foreach_param param;
239
240                 /* Append variable names not yet present in the file */
241                 param.gstring=gstring;
242                 param.hash_flushed=hash_flushed;
243                 param.modified=modified;
244                 g_hash_table_foreach(hash_flush,(GHFunc)configuration_hash_readwrite_hash_flush_foreach,&param);
245                 g_hash_table_destroy(hash_flushed);
246                 hash_flushed=NULL;
247                 modified=param.modified;
248                 }
249         if (gstring) {
250                 if (modified)
251                         configuration_file_write(gstring->str); /* errors ignored */
252                 g_string_free(gstring,
253                                 TRUE);  /* free_segment */
254                 gstring=NULL;
255                 }
256         return hash_fill;
257 }
258
259 static void configuration_read_hash_foreach(const gchar *key,const gchar *value,gpointer user_data)
260 {
261         g_return_if_fail(key!=NULL);
262         g_return_if_fail(value!=NULL);
263
264         if (!strcmp(key,"PORT"))
265                 optarg_port_set_string(value);  
266         else
267                 g_warning(_("Unknown configuration key \"%s\" with value \"%s\" found in the file \"%s\""),
268                                 key,value,CONFIGURATION_FILE);
269 }
270
271 gboolean configuration_read(void)
272 {
273 GHashTable *hash;
274
275         if (!(hash=configuration_hash_readwrite(NULL)))
276                 return FALSE;
277         g_hash_table_foreach(hash,(GHFunc)configuration_read_hash_foreach,NULL);
278         g_hash_table_destroy(hash);
279         return TRUE;
280 }
281
282 gboolean configuration_write(void)
283 {
284 GHashTable *hash;
285
286         hash=g_hash_table_new(g_str_hash,g_str_equal);
287         g_hash_table_insert(hash,"PORT",(/* de-const */ gpointer)udpgate_printf_alloca("%d",(int)optarg_port));
288         configuration_hash_readwrite(hash);     /* FIXME: errors ignored */
289         g_hash_table_destroy(hash);
290         return TRUE;
291 }