Cleanup getenv(3) by GLib compatible functions.
[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 <glib/gstrfuncs.h>
27 #include <glib/gutils.h>
28 #include <stdio.h>
29 #include <errno.h>
30 #include <ctype.h>
31 #include <string.h>
32 #include <stdlib.h>
33
34 #include "configuration.h"      /* self */
35 #include "pathname.h"
36 #include "network.h"
37 #include "main.h"
38
39
40 /* OK, FIXME: GConf would be nice.
41  * Unfortunately the fully-static build does not support GConf process spawn at
42  * all.
43  */
44
45
46 /* Config: */
47 #define LOCATION_LINK "/proc/self/exe"  /* for Linux kernel */
48
49
50 static G_CONST_RETURN gchar *configuration_pathname(void)
51 {
52 static const gchar *static_pathname;
53
54         return pathname_find(&static_pathname,
55                         G_STRINGIFY(SYSCONFDIR) "/sysconfig",PACKAGE,
56                         G_STRINGIFY(SYSCONFDIR) "/default",PACKAGE,
57                         G_STRINGIFY(SYSCONFDIR) "",PACKAGE,
58                         /* g_get_home_dir() may return NULL and terminate the list prematurely. */
59                         g_get_home_dir(),"." PACKAGE "rc",
60                         NULL);
61 }
62
63 static GHashTable *configuration_hash_new(void)
64 {
65         return g_hash_table_new_full(g_str_hash,g_str_equal,
66                         (GDestroyNotify)g_free, /* key_destroy_func; of g_strdup() strings */
67                         (GDestroyNotify)g_free);        /* value_destroy_func; of g_strdup() strings */
68 }
69
70 static GHashTable *configuration_comments_hash_new(void)
71 {
72 static GHashTable *hash;
73
74         if (!hash) {
75                 hash=g_hash_table_new(g_str_hash,g_str_equal);
76                 g_hash_table_insert(hash,"PORT",_("Local UDP port"));
77                 g_hash_table_insert(hash,"LOCATION",_("Binary location pathname"));
78                 }
79         return hash;
80 }
81
82 static gboolean configuration_file_write(const gchar *file_content)
83 {
84 FILE *f;
85 const gchar *pathname;
86
87         g_return_val_if_fail(file_content!=NULL,FALSE);
88
89         if (!(pathname=configuration_pathname()))
90                 return FALSE;
91         if (!(f=fopen(pathname,"w"))) {
92                 g_warning(_("Error write opening configuration file \"%s\": %m"),pathname);
93                 return FALSE;
94                 }
95         if (fputs(file_content,f)<0) {
96                 g_warning(_("Error writing configuration file \"%s\": %m"),pathname);
97                 fclose(f);      /* errors ignored */
98                 return FALSE;
99                 }
100         if (fclose(f))
101                 g_warning(_("Error closing configuration file \"%s\": %m"),pathname);
102         return TRUE;
103 }
104
105 struct configuration_hash_readwrite_hash_flush_foreach_param {
106         GString *gstring;
107         GHashTable *hash_flushed;
108         gboolean modified;
109         };
110 static void configuration_hash_readwrite_hash_flush_foreach
111                 (const gchar *key,const gchar *value,struct configuration_hash_readwrite_hash_flush_foreach_param *param)
112 {
113 const gchar *comment;
114
115         g_return_if_fail(key!=NULL);
116         g_return_if_fail(value!=NULL);
117         g_return_if_fail(param!=NULL);
118         g_return_if_fail(param->gstring!=NULL);
119         g_return_if_fail(param->hash_flushed!=NULL);
120
121         /* Already written out? */
122         if (g_hash_table_lookup(param->hash_flushed,key))
123                 return;
124         if (!(comment=g_hash_table_lookup(configuration_comments_hash_new(),key)))
125                 comment=_("*** UNDOCUMENTED ***");
126         g_string_append(param->gstring,udpgate_printf_alloca("# %s: %s\n%s=%s\n",key,comment,key,value));
127         param->modified=TRUE;
128         if (optarg_verbose)
129                 g_message(_("Appended configuration variable name \"%s\", value \"%s\""),key,value);
130 }
131
132 /* hash_flush==NULL => read info and return new (GHashTable *)
133  * hash_flush!=NULL => update configuration file by this (GHashTable *)
134  */
135 /* FIXME: File locking! */
136 static GHashTable *configuration_hash_readwrite(GHashTable *hash_flush)
137 {
138 FILE *f;
139 char line[LINE_MAX];
140 int lineno;
141 GHashTable *hash_fill;
142 /* Which 'keys' of 'hash_flush' were already written. 'key' must be g_free()able.
143  * Any 'value' is ignored. 'value' must be !=NULL. 'value' must be g_free()able.
144  */
145 GHashTable *hash_flushed;
146 GString *gstring;
147 gboolean modified=FALSE;        /* 'gstring' contains modified value */
148 gboolean already_written=FALSE;
149 const gchar *pathname;
150
151         if (!(pathname=configuration_pathname()))
152                 return NULL;
153 open_retry:
154         if (!(f=fopen(pathname,(!hash_flush ? "r" : "rw")))) {
155                 if (errno!=ENOENT)
156                         g_warning(_("Error r/o opening configuration file \"%s\": %m"),pathname);
157                 if (!hash_flush || already_written)
158                         return NULL;
159                 else {
160                         already_written=TRUE;
161                         if (!configuration_file_write(_(
162 "# Configuration file for UDPGate - see its man page udpgate(1).\n"
163 "\n"
164                                         )))
165                                 return NULL;
166                         goto open_retry;
167                         }
168                 }
169         if (!hash_flush) {
170                 hash_fill=configuration_hash_new();
171                 hash_flushed=NULL;
172                 gstring=NULL;
173                 }
174         else {
175                 hash_fill=NULL;
176                 hash_flushed=configuration_hash_new();
177                 gstring=g_string_new(NULL);
178                 }
179         lineno=0;
180         while (errno=0,fgets(line,sizeof(line),f)) {
181 char *s;
182 char *varname_start,*varname_stop,varname_stop_orig;
183 char *varcontent_start,*varcontent_stop,varcontent_stop_orig=0 /* shut up GCC */;
184 const gchar *value;
185
186                 lineno++;
187                 s=line;
188                 varname_stop=NULL;
189                 varcontent_stop=NULL;
190                 /* Parse: ^\s*([[:alnum:]])\s*=\s*(\S*)\s*$
191                  */
192                 while (*s && isspace(*s)) s++;
193                 if (!*s || *s=='#') {
194 err_append:
195                         if (gstring)
196                                 g_string_append(gstring,line);
197                         continue;
198                         }
199                 varname_start=s;
200                 while (*s && isalnum(*s)) s++;
201                 varname_stop=s;
202                 varname_stop_orig=*varname_stop;
203                 while (*s && isspace(*s)) s++;
204                 if (*s++!='=') {
205 err_line:
206                         if (varname_stop)
207                                 *varname_stop=varname_stop_orig;
208                         if (varcontent_stop)
209                                 *varcontent_stop=varcontent_stop_orig;
210                         g_warning(_("Error parsing line %d of the configuration file \"%s\": %s"),lineno,pathname,line);
211                         goto err_append;
212                         }
213                 while (*s && isspace(*s)) s++;
214                 varcontent_start=s;
215                 if (!*s)
216                         goto err_line;
217                 while (*s && !isspace(*s)) s++;
218                 varcontent_stop=s;
219                 varcontent_stop_orig=*varcontent_stop;
220                 while (*s && isspace(*s)) s++;
221                 if (*s && *s!='#')
222                         goto err_line;
223
224                 if (!*varname_start || !*varcontent_start)
225                         goto err_line;
226
227                 *varname_stop='\0';
228                 *varcontent_stop='\0';
229                 if (optarg_verbose)
230                         g_message(_("Parsed configuration variable name \"%s\", value \"%s\""),varname_start,varcontent_start);
231                 if (hash_fill)
232                         g_hash_table_insert(
233                                         hash_fill,      /* hash_table */
234                                         g_strdup(varname_start),        /* key */
235                                         g_strdup(varcontent_start));    /* value */
236                 if (hash_flush && (value=g_hash_table_lookup(hash_flush,varname_start))) {
237 const char *line_new=udpgate_printf_alloca("%s=%s\n",varname_start,value);
238
239                         *varname_stop=varname_stop_orig;
240                         *varcontent_stop=varcontent_stop_orig;
241                         if (strcmp(line,line_new))
242                                 modified=TRUE;
243                         *varname_stop=0;
244                         *varcontent_stop=0;
245                         g_string_append(gstring,line_new);
246                         g_hash_table_insert(hash_flushed,g_strdup(varname_start),g_strdup(varcontent_start));
247                         }
248                 else
249                         goto err_append;
250                 }
251         if (errno) {
252                 g_warning(_("Error reading line from the configuration file \"%s\": %s"),pathname,strerror(errno));
253                 if (hash_fill)
254                         g_hash_table_destroy(hash_fill);
255                 fclose(f);      /* errors ignored */
256                 return NULL;
257                 }
258         if (fclose(f))
259                 g_warning(_("Error closing configuration file \"%s\": %m"),pathname);
260         if (hash_flushed) {
261 struct configuration_hash_readwrite_hash_flush_foreach_param param;
262
263                 /* Append variable names not yet present in the file */
264                 param.gstring=gstring;
265                 param.hash_flushed=hash_flushed;
266                 param.modified=modified;
267                 g_hash_table_foreach(hash_flush,(GHFunc)configuration_hash_readwrite_hash_flush_foreach,&param);
268                 g_hash_table_destroy(hash_flushed);
269                 hash_flushed=NULL;
270                 modified=param.modified;
271                 }
272         if (gstring) {
273                 if (modified)
274                         configuration_file_write(gstring->str); /* errors ignored */
275                 g_string_free(gstring,
276                                 TRUE);  /* free_segment */
277                 gstring=NULL;
278                 }
279         return hash_fill;
280 }
281
282 static void configuration_read_hash_foreach(const gchar *key,const gchar *value,gpointer user_data)
283 {
284         g_return_if_fail(key!=NULL);
285         g_return_if_fail(value!=NULL);
286
287         /**/ if (!strcmp(key,"PORT"))
288                 optarg_port_set_string(value);  
289         else if (!strcmp(key,"LOCATION"))
290                 /* nop */;
291         else
292                 g_warning(_("Unknown configuration key \"%s\" with value \"%s\" found in the file \"%s\""),
293                                 key,value,configuration_pathname());
294 }
295
296 gboolean configuration_read(void)
297 {
298 GHashTable *hash;
299
300         if (!(hash=configuration_hash_readwrite(NULL)))
301                 return FALSE;
302         g_hash_table_foreach(hash,(GHFunc)configuration_read_hash_foreach,NULL);
303         g_hash_table_destroy(hash);
304         return TRUE;
305 }
306
307 static void location_insert(GHashTable *hash)
308 {
309 gchar buf[LINE_MAX];
310 int got;
311
312         g_return_if_fail(hash!=NULL);
313
314         /* FIXME: Support also argv[0] as a fallback. */
315         got=readlink(LOCATION_LINK,buf,sizeof(buf)-1);
316         if (got<=0 || got>=(int)(sizeof(buf)-1))
317                 return;
318         buf[got]='\0';
319         g_hash_table_insert(hash,"LOCATION",g_strdup(buf));
320 }
321
322 gboolean configuration_write(void)
323 {
324 GHashTable *hash;
325
326         hash=g_hash_table_new_full(g_str_hash,g_str_equal,
327                         (GDestroyNotify)NULL,   /* key_destroy_func */
328                         (GDestroyNotify)g_free);        /* value_destroy_func; of g_strdup() strings */
329         g_hash_table_insert(hash,"PORT",g_strdup_printf("%d",(int)optarg_port));
330         location_insert(hash);
331         configuration_hash_readwrite(hash);     /* FIXME: errors ignored */
332         g_hash_table_destroy(hash);
333         return TRUE;
334 }