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