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