+ }
+ break;
+ case 'r':
+ serve->flags |= F_READONLY;
+ break;
+ case 'm':
+ serve->flags |= F_MULTIFILE;
+ break;
+ case 'o':
+ do_output = TRUE;
+ section_header = g_strdup(optarg);
+ break;
+ case 'p':
+ strncpy(pidftemplate, optarg, 256);
+ break;
+ case 'c':
+ serve->flags |=F_COPYONWRITE;
+ break;
+ case 'C':
+ g_free(config_file_pos);
+ config_file_pos=g_strdup(optarg);
+ break;
+ case 'l':
+ g_free(serve->authname);
+ serve->authname=g_strdup(optarg);
+ break;
+ default:
+ usage();
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+ /* What's left: the port to export, the name of the to be exported
+ * file, and, optionally, the size of the file, in that order. */
+ if(nonspecial<2) {
+ g_free(serve);
+ serve=NULL;
+ }
+ if(do_output) {
+ if(!serve) {
+ g_critical("Need a complete configuration on the command line to output a config file section!");
+ exit(EXIT_FAILURE);
+ }
+ dump_section(serve, section_header);
+ }
+ 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_VALUE_UNSUPPORTED,/**< A value is not supported in this build */
+ CFILE_PROGERR, /**< Programmer error */
+ CFILE_NO_EXPORTS /**< A config file was specified that does not
+ define any exports */
+} 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 Array of SERVER* pointers, 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
+ **/
+GArray* parse_cfile(gchar* f, GError** e) {
+ const char* DEFAULT_ERROR = "Could not parse %s in group %s: %s";
+ const char* MISSING_REQUIRED_ERROR = "Could not find required value %s in group %s: %s";
+ SERVER s;
+ gchar *virtstyle=NULL;
+ PARAM lp[] = {
+ { "exportname", TRUE, PARAM_STRING, NULL, 0 },
+ { "port", TRUE, PARAM_INT, NULL, 0 },
+ { "authfile", FALSE, PARAM_STRING, NULL, 0 },
+ { "filesize", FALSE, PARAM_INT, NULL, 0 },
+ { "virtstyle", FALSE, PARAM_STRING, NULL, 0 },
+ { "prerun", FALSE, PARAM_STRING, NULL, 0 },
+ { "postrun", FALSE, PARAM_STRING, NULL, 0 },
+ { "readonly", FALSE, PARAM_BOOL, NULL, F_READONLY },
+ { "multifile", FALSE, PARAM_BOOL, NULL, F_MULTIFILE },
+ { "copyonwrite", FALSE, PARAM_BOOL, NULL, F_COPYONWRITE },
+ { "sparse_cow", FALSE, PARAM_BOOL, NULL, F_SPARSE },
+ { "sdp", FALSE, PARAM_BOOL, NULL, F_SDP },
+ { "sync", FALSE, PARAM_BOOL, NULL, F_SYNC },
+ { "listenaddr", FALSE, PARAM_STRING, NULL, 0 },
+ };
+ const int lp_size=sizeof(lp)/sizeof(PARAM);
+ PARAM gp[] = {
+ { "user", FALSE, PARAM_STRING, &runuser, 0 },
+ { "group", FALSE, PARAM_STRING, &rungroup, 0 },
+ };
+ PARAM* p=gp;
+ int p_size=sizeof(gp)/sizeof(PARAM);
+ GKeyFile *cfile;
+ GError *err = NULL;
+ const char *err_msg=NULL;
+ GQuark errdomain;
+ GArray *retval=NULL;
+ gchar **groups;
+ gboolean value;
+ gchar* startgroup;
+ gint i;
+ gint j;
+
+ errdomain = g_quark_from_string("parse_cfile");
+ cfile = g_key_file_new();
+ retval = g_array_new(FALSE, TRUE, sizeof(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.", f);
+ g_key_file_free(cfile);
+ return retval;
+ }
+ startgroup = g_key_file_get_start_group(cfile);
+ if(!startgroup || strcmp(startgroup, "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++) {
+ memset(&s, '\0', sizeof(SERVER));
+ lp[0].target=&(s.exportname);
+ lp[1].target=&(s.port);
+ lp[2].target=&(s.authname);
+ lp[3].target=&(s.expected_size);
+ lp[4].target=&(virtstyle);
+ lp[5].target=&(s.prerun);
+ lp[6].target=&(s.postrun);
+ lp[7].target=lp[8].target=lp[9].target=
+ lp[10].target=lp[11].target=
+ lp[12].target=&(s.flags);
+ lp[13].target=&(s.listenaddr);
+
+ /* After the [generic] group, start parsing exports */
+ if(i==1) {
+ p=lp;
+ p_size=lp_size;
+ }
+ for(j=0;j<p_size;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) {
+ if(value) {
+ *((gint*)p[j].target) |= p[j].flagval;
+ } else {
+ *((gint*)p[j].target) &= ~(p[j].flagval);
+ }
+ }
+ break;
+ }
+ if(err) {
+ if(err->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
+ if(!p[j].required) {
+ /* Ignore not-found error for optional values */
+ g_clear_error(&err);
+ continue;
+ } else {
+ err_msg = MISSING_REQUIRED_ERROR;
+ }