[mw-devel] [Git][arthur/mw][master] Add a configuration system to the server

Andrew Price welshbyte at sucs.org
Sun Oct 18 23:58:12 BST 2015


Andrew Price pushed to branch master at Justin Mitchell / mw


Commits:
83256d1c by Andrew Price at 2015-10-18T23:46:24Z
Add a configuration system to the server

Until now configuring mwserv has been limited to command line options
and build configuration. This config system should make things a bit
more flexible by adding the ability to load bits and pieces from a
json config file (or many json config files).

This is how it works:
1. A json object 'cfg' is built containing the build-time defaults for
   each config option recognised by mwserv. This will be used to
   validate the options in config files later.
2. A json config file in the user's home directory is loaded if it
   exists. It does not have to be complete: its contents will be merged
   into the defaults. Unrecognised options are ignored.
3. Otherwise, a config file in /etc is loaded and used similarly.
4. Command line options are parsed and the cfg object is updated with
   cfg_set_* in the order in which the options are found.
   4a. If the [--config|-c <file>] option is encountered, the file is
   loaded and merged into the cfg object as before.
5. Once all of the config information is gathered and merged into the
   cfg object, if the [--print-config|-P] option was given, the final
   merged config is printed in json format (mwserv then exits).
6. Config options are then accessed using the cfg_get_* type-specific
   functions.

This should allow the build system to be simplified in future, e.g.
'make test' could go away in favour of using ~/.mwserv.conf to store
the test environment configuration.

For now, the place to add new config options and their default values is
in cfg_init_defaults() in servercfg.c.

- - - - -


3 changed files:

- src/server/mwserv.c
- + src/server/servercfg.c
- + src/server/servercfg.h


Changes:

=====================================
src/server/mwserv.c
=====================================
--- a/src/server/mwserv.c
+++ b/src/server/mwserv.c
@@ -10,6 +10,7 @@
 #include <time.h>
 #include <socket.h>
 #include "servsock.h"
+#include "servercfg.h"
 
 /* unused, but necessary to link other util functions */
 int idle = 0;
@@ -20,35 +21,56 @@ int userposn = 0;
 
 time_t uptime = 0;
 
-struct servopts {
-	uint16_t port;
-	int foreground;
-	int help;
-};
-
 static void usage(char *name)
 {
-	printf("Usage:\n  %s [--help|-h][--port|-p <port>][--foreground|-f]\n", name);
+	printf("Usage:\n  %s "
+	       "[--config|-c <file>]"
+	       "[--help|-h]"
+	       "[--foreground|-f]"
+	       "[--port|-p <port>]"
+	       "[--print-config|-P]"
+	       "\n", name);
 }
 
-static int getopts(int argc, char **argv, struct servopts *opts)
+static int getconfig(int argc, char **argv)
 {
 	int c;
+	int ret;
 	int optidx = 0;
+	int printcfg = 0;
 	long num;
 	static struct option loptspec[] = {
-		{"port", required_argument, 0, 'p'},
-		{"help", no_argument, 0, 'h'},
-		{"foreground", no_argument, 0, 'f'},
+		{"config",       required_argument, 0, 'c'},
+		{"foreground",   no_argument,       0, 'f'},
+		{"help",         no_argument,       0, 'h'},
+		{"print-config", no_argument,       0, 'P'},
+		{"port",         required_argument, 0, 'p'},
 		{0, 0, 0, 0}
 	};
 
+	ret = cfg_init_defaults();
+	if (ret)
+		return ret;
+
+	ret = cfg_load(NULL);
+	if (ret != 0)
+		return 1;
+
 	while (1) {
-		c = getopt_long(argc, argv, "p:hf", loptspec, &optidx);
+		c = getopt_long(argc, argv, "c:fhPp:", loptspec, &optidx);
 		if (c == -1)
 			break;
 
 		switch (c) {
+			case 'c':
+				ret = cfg_load(optarg);
+				if (ret != 0)
+					return ret;
+				break;
+			case 'P':
+				/* Do this once all the config sources have been merged */
+				printcfg = 1;
+				break;
 			case 'p':
 				errno = 0;
 				num = strtol(optarg, NULL, 0);
@@ -56,16 +78,17 @@ static int getopts(int argc, char **argv, struct servopts *opts)
 					fprintf(stderr, "Bad port number\n");
 					return 1;
 				}
-				opts->port = (uint16_t)num;
+				cfg_set_int("port", num);
 				break;
 			case 'f':
-				opts->foreground = 1;
+				cfg_set_bool("foreground", 1);
 				break;
 			case 'h':
-				opts->help = 1;
-				return 0;
+				usage(argv[0]);
+				exit(0);
 			case '?':
 			default:
+				usage(argv[0]);
 				return 1;
 		}
 	}
@@ -77,23 +100,26 @@ static int getopts(int argc, char **argv, struct servopts *opts)
 		fprintf(stderr, "\n");
 		return 1;
 	}
+
+	if (printcfg) {
+		cfg_dump(stdout);
+		exit(0);
+	}
 	return 0;
 }
 
 int main(int argc, char **argv)
 {
-	struct servopts opts = {.port = IPCPORT_DEFAULT, .foreground = 0};
 	int mainsock = -1;
-	int err = getopts(argc, argv, &opts);
+	int err;
 
-	if (err || opts.help) {
-		usage(argv[0]);
+	err = getconfig(argc, argv);
+	if (err)
 		return err;
-	}
 
 	init_server();
 
-	mainsock = open_mainsock(opts.port);
+	mainsock = open_mainsock(cfg_get_int("port"));
 
 	if (mainsock < 0) {
 		fprintf(stderr, "Failed.\n");
@@ -116,7 +142,7 @@ int main(int argc, char **argv)
 		}
 	}
 
-	if (!opts.foreground) daemon(0,0);
+	if (!cfg_get_bool("foreground")) daemon(0, 0);
 
 	uptime = time(0);
 	watch_mainsock(mainsock);


=====================================
src/server/servercfg.c
=====================================
--- /dev/null
+++ b/src/server/servercfg.c
@@ -0,0 +1,265 @@
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <jansson.h>
+
+#include <util.h>
+#include "servercfg.h"
+
+json_t *cfg;
+
+int cfg_init_defaults(void)
+{
+	int ret;
+
+	cfg = json_object();
+	if (cfg == NULL) {
+		perror("Failed to create default config object");
+		return 1;
+	}
+	/* README: Config options must be set here so that they will be treated
+	   as valid config options on later changes by config files, command
+	   line options, etc. */
+	ret = json_object_set_new(cfg, "foreground", json_false())
+	||    json_object_set_new(cfg, "port", json_integer(9999));
+
+	if (ret != 0) {
+		fprintf(stderr, "Failed to build default config items\n");
+		json_decref(cfg);
+		return 1;
+	}
+	return 0;
+}
+
+static const char *jsontype_to_str[] = {
+	[JSON_OBJECT]  = "Object",
+	[JSON_ARRAY]   = "Array",
+	[JSON_STRING]  = "String",
+	[JSON_INTEGER] = "Integer",
+	[JSON_REAL]    = "Real",
+	[JSON_TRUE]    = "Boolean",
+	[JSON_FALSE]   = "Boolean",
+	[JSON_NULL]    = "Null"
+};
+
+static int cfg_obj_validate(json_t *obj)
+{
+	const char *key;
+	json_t *cfg_val;
+	int ret = 0;
+	json_t *val;
+
+	json_object_foreach(obj, key, val) {
+		int usr_type, cfg_type;
+
+		cfg_val = json_object_get(cfg, key);
+		if (cfg_val == NULL)
+			continue;
+		usr_type = json_typeof(val);
+		cfg_type = json_typeof(cfg_val);
+
+		if (usr_type == JSON_FALSE) usr_type = JSON_TRUE;
+		if (cfg_type == JSON_FALSE) cfg_type = JSON_TRUE;
+
+		if (usr_type != cfg_type) {
+			fprintf(stderr, "Wrong type for config option '%s'. ", key);
+			fprintf(stderr, "Expected %s, got %s.\n",
+			        jsontype_to_str[cfg_type], jsontype_to_str[usr_type]);
+			ret = 1;
+		}
+	}
+	return ret;
+}
+
+static int cfg_obj_merge(json_t *obj)
+{
+	const char *key;
+	json_t *cfg_val;
+	int ret = 0;
+	json_t *val;
+
+	json_object_foreach(obj, key, val) {
+		cfg_val = json_object_get(cfg, key);
+		if (cfg_val == NULL) {
+			fprintf(stderr, "Unrecognised config option '%s'. Ignoring.\n", key);
+			continue;
+		}
+		ret = json_object_set(cfg, key, val);
+		if (ret != 0) {
+			fprintf(stderr, "Failed to update config option '%s'. Giving up. ", key);
+			break;
+		}
+	}
+	return ret;
+}
+
+static int cfg_load_file(const char *path, int skip_if_missing)
+{
+	json_error_t err;
+	json_t *obj;
+	FILE *fp;
+	int ret;
+	int fd;
+
+	fd = open(path, O_RDONLY);
+	if (fd < 0) {
+		if ((errno == EACCES || errno == ENOENT) && skip_if_missing)
+			return 1;
+		fprintf(stderr, "Failed to open config file '%s': %s\n", path, strerror(errno));
+		return -1;
+	}
+	fp = fdopen(fd, "r");
+	if (fp == NULL) {
+		fprintf(stderr, "Failed to create file stream for '%s': %s\n", path, strerror(errno));
+		close(fd);
+		return -1;
+	}
+	obj = json_loadf(fp, 0, &err);
+	if (obj == NULL) {
+		fprintf(stderr, "Failed to read config file '%s': %s\n", path, err.text);
+		fclose(fp);
+		return -1;
+	}
+	fclose(fp);
+	ret = cfg_obj_validate(obj);
+	if (ret != 0)
+		return -1;
+
+	ret = cfg_obj_merge(obj);
+	json_decref(obj);
+	if (ret != 0)
+		return -1;
+	return 0;
+}
+
+static int cfg_load_default_path(void)
+{
+	AUTOFREE_BUFFER homeconf = NULL;
+	const char *homedir;
+	const char *etcconf;
+	int ret;
+
+	homedir = secure_getenv("HOME");
+	if (homedir) {
+		ret = asprintf(&homeconf, "%s/.mwserv.conf", homedir);
+		if (ret > 0) {
+			ret = cfg_load_file(homeconf, 1);
+			if (ret <= 0)
+				return ret;
+		}
+	}
+	etcconf = "/etc/mwserv.conf";
+	ret = cfg_load_file(etcconf, 1);
+	if (ret <= 0)
+		return ret;
+	return 0;
+}
+
+int cfg_load(const char *cfg_file)
+{
+	if (cfg_file == NULL)
+		return cfg_load_default_path();
+
+	return cfg_load_file(cfg_file, 0);
+}
+
+void cfg_dump(FILE *f)
+{
+	if (json_dumpf(cfg, f, JSON_PRESERVE_ORDER|JSON_INDENT(1)))
+		perror("Error encountered while printing config");
+}
+
+static json_t *cfg_get(const char *key)
+{
+	json_t *j = json_object_get(cfg, key);
+	if (j == NULL)
+		fprintf(stderr, "Invalid config option: %s\n", key);
+	return j;
+}
+
+int cfg_get_bool(const char *key)
+{
+	json_t *j = cfg_get(key);
+	if (j != NULL && !json_is_boolean(j)) {
+		fprintf(stderr, "Config option is not a boolean: %s\n", key);
+		return 0;
+	}
+	return j == json_true();
+}
+
+long cfg_get_int(const char *key)
+{
+	json_t *j = cfg_get(key);
+	if (j != NULL && !json_is_integer(j)) {
+		fprintf(stderr, "Config option is not an integer: %s\n", key);
+		return 0;
+	}
+	return json_integer_value(j);
+}
+
+const char *cfg_get_string(const char *key)
+{
+	json_t *j = cfg_get(key);
+	if (j != NULL && !json_is_string(j)) {
+		fprintf(stderr, "Config option is not a string: %s\n", key);
+		return 0;
+	}
+	return json_string_value(j);
+}
+
+int cfg_set_bool(const char *key, int bool_val)
+{
+	json_t *j = cfg_get(key);
+	if (j == NULL)
+		return 1;
+	if (!json_is_boolean(j)) {
+		fprintf(stderr, "Config option %s is not a boolean\n", key);
+		return 1;
+	}
+	if (json_object_set(cfg, key, bool_val ? json_true() : json_false())) {
+		fprintf(stderr, "Failed to set boolean config option %s to %s\n",
+		        key, bool_val ? "true" : "false");
+		return 1;
+	}
+	return 0;
+}
+
+int cfg_set_int(const char *key, long int_val)
+{
+	json_t *j = cfg_get(key);
+	if (j == NULL)
+		return 1;
+	if (!json_is_integer(j)) {
+		fprintf(stderr, "Config option %s is not an integer\n", key);
+		return 1;
+	}
+	if (json_integer_set(j, int_val)) {
+		fprintf(stderr, "Failed to set integer config option %s to %ld\n",
+		        key, int_val);
+		return 1;
+	}
+	return 0;
+}
+
+int cfg_set_string(const char *key, const char *str_val)
+{
+	json_t *j = cfg_get(key);
+	if (j == NULL)
+		return 1;
+	if (!json_is_string(j)) {
+		fprintf(stderr, "Config option %s is not a string\n", key);
+		return 1;
+	}
+	if (json_string_set(j, str_val)) {
+		fprintf(stderr, "Failed to set string config option %s to %s\n",
+		        key, str_val);
+		return 1;
+	}
+	return 0;
+}


=====================================
src/server/servercfg.h
=====================================
--- /dev/null
+++ b/src/server/servercfg.h
@@ -0,0 +1,16 @@
+#ifndef SERVERCFG_H
+#define SERVERCFG_H
+
+extern int cfg_load(const char *cfg_file);
+extern int cfg_init_defaults(void);
+extern void cfg_dump(FILE *f);
+
+extern int cfg_set_int(const char *keyname, long int_val);
+extern int cfg_set_bool(const char *keyname, int bool_val);
+extern int cfg_set_string(const char *keyname, const char *str_val);
+
+extern int cfg_get_bool(const char *keyname);
+extern long cfg_get_int(const char *keyname);
+extern const char *cfg_get_string(const char *keyname);
+
+#endif /* SERVERCFG_H */



View it on GitLab: https://projects.sucs.org/arthur/mw/commit/83256d1cfa210f8436c88deb59bef52387721ce1
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.sucs.org/pipermail/mw-devel/attachments/20151018/d6a4d45a/attachment.html>


More information about the mw-devel mailing list