/* $Id$ * UDP Gateway persistent configuration * Copyright (C) 2004 Jan Kratochvil * * 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 #include #include #include #include #include #include #include #include #include #include #include "configuration.h" /* self */ #include "pathname.h" #include "network.h" #include "main.h" /* OK, FIXME: GConf would be nice. * Unfortunately the fully-static build does not support GConf process spawn at * all. */ /* Config: */ #define LOCATION_LINK "/proc/self/exe" /* for Linux kernel */ static G_CONST_RETURN gchar *configuration_pathname(void) { static const gchar *static_pathname; return pathname_find(&static_pathname, G_STRINGIFY(SYSCONFDIR) "/sysconfig",PACKAGE, G_STRINGIFY(SYSCONFDIR) "/default",PACKAGE, G_STRINGIFY(SYSCONFDIR) "",PACKAGE, /* g_get_home_dir() may return NULL and terminate the list prematurely. */ g_get_home_dir(),"." PACKAGE "rc", NULL); } static GHashTable *configuration_hash_new(void) { return g_hash_table_new_full(g_str_hash,g_str_equal, (GDestroyNotify)g_free, /* key_destroy_func; of g_strdup() strings */ (GDestroyNotify)g_free); /* value_destroy_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")); g_hash_table_insert(hash,"LOCATION",_("Binary location pathname")); } return hash; } static gboolean configuration_file_write(const gchar *file_content) { FILE *f; const gchar *pathname; g_return_val_if_fail(file_content!=NULL,FALSE); if (!(pathname=configuration_pathname())) return FALSE; if (!(f=fopen(pathname,"w"))) { g_warning(_("Error write opening configuration file \"%s\": %m"),pathname); return FALSE; } if (fputs(file_content,f)<0) { g_warning(_("Error writing configuration file \"%s\": %m"),pathname); fclose(f); /* errors ignored */ return FALSE; } if (fclose(f)) g_warning(_("Error closing configuration file \"%s\": %m"),pathname); 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; const gchar *pathname; if (!(pathname=configuration_pathname())) return NULL; open_retry: if (!(f=fopen(pathname,(!hash_flush ? "r" : "rw")))) { if (errno!=ENOENT) g_warning(_("Error r/o opening configuration file \"%s\": %m"),pathname); 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=0 /* shut up GCC */; 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,pathname,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"),pathname,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"),pathname); 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 if (!strcmp(key,"LOCATION")) /* nop */; else g_warning(_("Unknown configuration key \"%s\" with value \"%s\" found in the file \"%s\""), key,value,configuration_pathname()); } 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; } static void location_insert(GHashTable *hash) { gchar buf[LINE_MAX]; int got; g_return_if_fail(hash!=NULL); /* FIXME: Support also argv[0] as a fallback. */ got=readlink(LOCATION_LINK,buf,sizeof(buf)-1); if (got<=0 || got>=(int)(sizeof(buf)-1)) return; buf[got]='\0'; g_hash_table_insert(hash,"LOCATION",g_strdup(buf)); } gboolean configuration_write(void) { GHashTable *hash; hash=g_hash_table_new_full(g_str_hash,g_str_equal, (GDestroyNotify)NULL, /* key_destroy_func */ (GDestroyNotify)g_free); /* value_destroy_func; of g_strdup() strings */ g_hash_table_insert(hash,"PORT",g_strdup_printf("%d",(int)optarg_port)); location_insert(hash); configuration_hash_readwrite(hash); /* FIXME: errors ignored */ g_hash_table_destroy(hash); return TRUE; }