+ return serve;
+}
+
+/**
+ * Error codes for config file parsing
+ **/
+typedef enum {
+ CFILE_NOTFOUND, /**< The configuration file is not found */
+ CFILE_MISSING_GENERIC, /**< The (required) group "generic" is missing */
+ CFILE_KEY_MISSING, /**< A (required) key is missing */
+ CFILE_VALUE_INVALID, /**< A value is syntactically invalid */
+ CFILE_PROGERR /**< Programmer error */
+} CFILE_ERRORS;
+
+/**
+ * Remove a SERVER from memory. Used from the hash table
+ **/
+void remove_server(gpointer s) {
+ SERVER *server;
+
+ server=(SERVER*)s;
+ g_free(server->exportname);
+ if(server->authname)
+ g_free(server->authname);
+ g_free(server);
+}
+
+/**
+ * Parse the config file.
+ *
+ * @param f the name of the config file
+ * @param e a GError. @see CFILE_ERRORS for what error values this function can
+ * return.
+ * @return a GHashTable of SERVER* pointers, with the port number as the hash
+ * key. If the config file is empty or does not exist, returns an empty
+ * GHashTable; if the config file contains an error, returns NULL, and
+ * e is set appropriately
+ **/
+GHashTable* parse_cfile(gchar* f, GError** e) {
+ SERVER *s;
+ PARAM p[] = {
+ { "exportname", TRUE, PARAM_STRING, NULL, 0 },
+ { "port", TRUE, PARAM_INT, NULL, 0 },
+ { "authfile", FALSE, PARAM_STRING, NULL, 0 },
+ { "timeout", FALSE, PARAM_INT, NULL, 0 },
+ { "filesize", FALSE, PARAM_INT, NULL, 0 },
+ { "readonly", FALSE, PARAM_BOOL, NULL, F_READONLY },
+ { "multifile", FALSE, PARAM_BOOL, NULL, F_MULTIFILE },
+ { "copyonwrite", FALSE, PARAM_BOOL, NULL, F_COPYONWRITE },
+ };
+ GKeyFile *cfile;
+ GError *err = NULL;
+ GQuark errdomain;
+ GHashTable *retval;
+ gchar **groups;
+ gboolean value;
+ gint i,j;
+
+ errdomain = g_quark_from_string("parse_cfile");
+ cfile = g_key_file_new();
+ retval = g_hash_table_new_full(g_int_hash, g_int_equal, NULL, remove_server);
+ if(!g_key_file_load_from_file(cfile, f, G_KEY_FILE_KEEP_COMMENTS |
+ G_KEY_FILE_KEEP_TRANSLATIONS, &err)) {
+ g_set_error(e, errdomain, CFILE_NOTFOUND, "Could not open config file.");
+ g_key_file_free(cfile);
+ return retval;
+ }
+ if(strcmp(g_key_file_get_start_group(cfile), "generic")) {
+ g_set_error(e, errdomain, CFILE_MISSING_GENERIC, "Config file does not contain the [generic] group!");
+ g_key_file_free(cfile);
+ return NULL;
+ }
+ groups = g_key_file_get_groups(cfile, NULL);
+ for(i=0;groups[i];i++) {
+ s=g_new0(SERVER, 1);
+ p[0].target=&(s->exportname);
+ p[1].target=&(s->port);
+ p[2].target=&(s->authname);
+ p[3].target=&(s->timeout);
+ p[4].target=&(s->expected_size);
+ p[5].target=p[6].target=p[7].target=p[8].target=&(s->flags);
+ for(j=0;j<9;j++) {
+ g_assert(p[j].target != NULL);
+ g_assert(p[j].ptype==PARAM_INT||p[j].ptype==PARAM_STRING||p[j].ptype==PARAM_BOOL);
+ switch(p[j].ptype) {
+ case PARAM_INT:
+ *((gint*)p[j].target) = g_key_file_get_integer(cfile, groups[i], p[j].paramname, &err);
+ break;
+ case PARAM_STRING:
+ *((gchar**)p[j].target) = g_key_file_get_string(cfile, groups[i], p[j].paramname, &err);
+ break;
+ case PARAM_BOOL:
+ value = g_key_file_get_boolean(cfile, groups[i], p[j].paramname, &err);
+ if(!err) {
+ *((gint*)p[j].target) |= value;
+ }
+ break;
+ }
+ if(err) {
+ if(err->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
+ if(p[j].required) {
+ g_set_error(e, errdomain, CFILE_KEY_MISSING, "Could not find required value %s in group %s: %s", p[j].paramname, groups[i], err->message);
+ g_hash_table_destroy(retval);
+ g_error_free(err);
+ g_key_file_free(cfile);
+ g_free(s);
+ return NULL;
+ } else {
+ g_error_free(err);
+ continue;
+ }
+ g_set_error(e, errdomain, CFILE_VALUE_INVALID, "Could not parse %s in group %s: %s", p[j].paramname, groups[i], err->message);
+ g_hash_table_destroy(retval);
+ g_error_free(err);
+ g_key_file_free(cfile);
+ g_free(s);
+ return NULL;
+ }
+ }