--- /dev/null
+/* $Id$
+ * UDP Gateway persistent configuration
+ * Copyright (C) 2004 Jan Kratochvil <project-udpgate@jankratochvil.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; exactly version 2 of June 1991 is required
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+
+#include "config.h"
+
+#include <glib/gmessages.h>
+#include <glib/ghash.h>
+#include <glib/gstring.h>
+#include <glib/gmem.h>
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <glib/gstrfuncs.h>
+#include <string.h>
+
+#include "configuration.h" /* self */
+#include "network.h"
+#include "main.h"
+
+
+/* OK, GConf would be nice.
+ * Unfortunately the fully-static build does not support GConf process spawn at
+ * all. Also /etc/sysconfig directory is standard for the daemon services.
+ */
+
+
+/* Config: */
+#define CONFIGURATION_FILE "/etc/sysconfig/udpgate"
+
+
+static GHashTable *configuration_hash_new(void)
+{
+ return g_hash_table_new_full(g_str_hash,g_str_equal,
+ (GDestroyNotify)g_free, /* key_equal_func; of g_strdup() strings */
+ (GDestroyNotify)g_free); /* value_equal_func; of g_strdup() strings */
+}
+
+static GHashTable *configuration_comments_hash_new(void)
+{
+static GHashTable *hash;
+
+ if (!hash) {
+ hash=g_hash_table_new(g_str_hash,g_str_equal);
+ g_hash_table_insert(hash,"PORT",_("Local UDP port"));
+ }
+ return hash;
+}
+
+static gboolean configuration_file_write(const gchar *file_content)
+{
+FILE *f;
+
+ g_return_val_if_fail(file_content!=NULL,FALSE);
+
+ if (!(f=fopen(CONFIGURATION_FILE,("w")))) {
+ g_warning(_("Error write opening configuration file \"%s\": %m"),CONFIGURATION_FILE);
+ return FALSE;
+ }
+ if (fputs(file_content,f)<0) {
+ g_warning(_("Error writing configuration file \"%s\": %m"),CONFIGURATION_FILE);
+ fclose(f); /* errors ignored */
+ return FALSE;
+ }
+ if (fclose(f))
+ g_warning(_("Error closing configuration file \"%s\": %m"),CONFIGURATION_FILE);
+ return TRUE;
+}
+
+struct configuration_hash_readwrite_hash_flush_foreach_param {
+ GString *gstring;
+ GHashTable *hash_flushed;
+ gboolean modified;
+ };
+static void configuration_hash_readwrite_hash_flush_foreach
+ (const gchar *key,const gchar *value,struct configuration_hash_readwrite_hash_flush_foreach_param *param)
+{
+const gchar *comment;
+
+ g_return_if_fail(key!=NULL);
+ g_return_if_fail(value!=NULL);
+ g_return_if_fail(param!=NULL);
+ g_return_if_fail(param->gstring!=NULL);
+ g_return_if_fail(param->hash_flushed!=NULL);
+
+ /* Already written out? */
+ if (g_hash_table_lookup(param->hash_flushed,key))
+ return;
+ if (!(comment=g_hash_table_lookup(configuration_comments_hash_new(),key)))
+ comment=_("*** UNDOCUMENTED ***");
+ g_string_append(param->gstring,udpgate_printf_alloca("# %s: %s\n%s=%s\n",key,comment,key,value));
+ param->modified=TRUE;
+ if (optarg_verbose)
+ g_message(_("Appended configuration variable name \"%s\", value \"%s\""),key,value);
+}
+
+/* hash_flush==NULL => read info and return new (GHashTable *)
+ * hash_flush!=NULL => update configuration file by this (GHashTable *)
+ */
+/* FIXME: File locking! */
+static GHashTable *configuration_hash_readwrite(GHashTable *hash_flush)
+{
+FILE *f;
+char line[LINE_MAX];
+int lineno;
+GHashTable *hash_fill;
+/* Which 'keys' of 'hash_flush' were already written. 'key' must be g_free()able.
+ * Any 'value' is ignored. 'value' must be !=NULL. 'value' must be g_free()able.
+ */
+GHashTable *hash_flushed;
+GString *gstring;
+gboolean modified=FALSE; /* 'gstring' contains modified value */
+gboolean already_written=FALSE;
+
+open_retry:
+ if (!(f=fopen(CONFIGURATION_FILE,(!hash_flush ? "r" : "rw")))) {
+ if (errno!=ENOENT)
+ g_warning(_("Error r/o opening configuration file \"%s\": %m"),CONFIGURATION_FILE);
+ if (!hash_flush || already_written)
+ return NULL;
+ else {
+ already_written=TRUE;
+ if (!configuration_file_write(_(
+"# Configuration file for UDPGate - see its man page udpgate(1).\n"
+"\n"
+ )))
+ return NULL;
+ goto open_retry;
+ }
+ }
+ if (!hash_flush) {
+ hash_fill=configuration_hash_new();
+ hash_flushed=NULL;
+ gstring=NULL;
+ }
+ else {
+ hash_fill=NULL;
+ hash_flushed=configuration_hash_new();
+ gstring=g_string_new(NULL);
+ }
+ lineno=0;
+ while (errno=0,fgets(line,sizeof(line),f)) {
+char *s;
+char *varname_start,*varname_stop,varname_stop_orig;
+char *varcontent_start,*varcontent_stop,varcontent_stop_orig;
+const gchar *value;
+
+ lineno++;
+ s=line;
+ varname_stop=NULL;
+ varcontent_stop=NULL;
+ /* Parse: ^\s*([[:alnum:]])\s*=\s*(\S*)\s*$
+ */
+ while (*s && isspace(*s)) s++;
+ if (!*s || *s=='#') {
+err_append:
+ if (gstring)
+ g_string_append(gstring,line);
+ continue;
+ }
+ varname_start=s;
+ while (*s && isalnum(*s)) s++;
+ varname_stop=s;
+ varname_stop_orig=*varname_stop;
+ while (*s && isspace(*s)) s++;
+ if (*s++!='=') {
+err_line:
+ if (varname_stop)
+ *varname_stop=varname_stop_orig;
+ if (varcontent_stop)
+ *varcontent_stop=varcontent_stop_orig;
+ g_warning(_("Error parsing line %d of the configuration file \"%s\": %s"),lineno,CONFIGURATION_FILE,line);
+ goto err_append;
+ }
+ while (*s && isspace(*s)) s++;
+ varcontent_start=s;
+ if (!*s)
+ goto err_line;
+ while (*s && !isspace(*s)) s++;
+ varcontent_stop=s;
+ varcontent_stop_orig=*varcontent_stop;
+ while (*s && isspace(*s)) s++;
+ if (*s && *s!='#')
+ goto err_line;
+
+ if (!*varname_start || !*varcontent_start)
+ goto err_line;
+
+ *varname_stop='\0';
+ *varcontent_stop='\0';
+ if (optarg_verbose)
+ g_message(_("Parsed configuration variable name \"%s\", value \"%s\""),varname_start,varcontent_start);
+ if (hash_fill)
+ g_hash_table_insert(
+ hash_fill, /* hash_table */
+ g_strdup(varname_start), /* key */
+ g_strdup(varcontent_start)); /* value */
+ if (hash_flush && (value=g_hash_table_lookup(hash_flush,varname_start))) {
+const char *line_new=udpgate_printf_alloca("%s=%s\n",varname_start,value);
+
+ *varname_stop=varname_stop_orig;
+ *varcontent_stop=varcontent_stop_orig;
+ if (strcmp(line,line_new))
+ modified=TRUE;
+ *varname_stop=0;
+ *varcontent_stop=0;
+ g_string_append(gstring,line_new);
+ g_hash_table_insert(hash_flushed,g_strdup(varname_start),g_strdup(varcontent_start));
+ }
+ else
+ goto err_append;
+ }
+ if (errno) {
+ g_warning(_("Error reading line from the configuration file \"%s\": %s"),CONFIGURATION_FILE,strerror(errno));
+ if (hash_fill)
+ g_hash_table_destroy(hash_fill);
+ fclose(f); /* errors ignored */
+ return NULL;
+ }
+ if (fclose(f))
+ g_warning(_("Error closing configuration file \"%s\": %m"),CONFIGURATION_FILE);
+ if (hash_flushed) {
+struct configuration_hash_readwrite_hash_flush_foreach_param param;
+
+ /* Append variable names not yet present in the file */
+ param.gstring=gstring;
+ param.hash_flushed=hash_flushed;
+ param.modified=modified;
+ g_hash_table_foreach(hash_flush,(GHFunc)configuration_hash_readwrite_hash_flush_foreach,¶m);
+ g_hash_table_destroy(hash_flushed);
+ hash_flushed=NULL;
+ modified=param.modified;
+ }
+ if (gstring) {
+ if (modified)
+ configuration_file_write(gstring->str); /* errors ignored */
+ g_string_free(gstring,
+ TRUE); /* free_segment */
+ gstring=NULL;
+ }
+ return hash_fill;
+}
+
+static void configuration_read_hash_foreach(const gchar *key,const gchar *value,gpointer user_data)
+{
+ g_return_if_fail(key!=NULL);
+ g_return_if_fail(value!=NULL);
+
+ if (!strcmp(key,"PORT"))
+ optarg_port_set_string(value);
+ else
+ g_warning(_("Unknown configuration key \"%s\" with value \"%s\" found in the file \"%s\""),
+ key,value,CONFIGURATION_FILE);
+}
+
+gboolean configuration_read(void)
+{
+GHashTable *hash;
+
+ if (!(hash=configuration_hash_readwrite(NULL)))
+ return FALSE;
+ g_hash_table_foreach(hash,(GHFunc)configuration_read_hash_foreach,NULL);
+ g_hash_table_destroy(hash);
+ return TRUE;
+}
+
+gboolean configuration_write(void)
+{
+GHashTable *hash;
+
+ hash=g_hash_table_new(g_str_hash,g_str_equal);
+ g_hash_table_insert(hash,"PORT",(/* de-const */ gpointer)udpgate_printf_alloca("%d",(int)optarg_port));
+ configuration_hash_readwrite(hash); /* FIXME: errors ignored */
+ g_hash_table_destroy(hash);
+ return TRUE;
+}