[mw-devel] [Git][arthur/mw][master] 7 commits: Consolidate update_user functions

Andrew Price welshbyte at sucs.org
Wed Oct 21 13:31:03 BST 2015


Andrew Price pushed to branch master at Justin Mitchell / mw


Commits:
f9b15ca6 by Andrew Price at 2015-10-21T12:54:05Z
Consolidate update_user functions

Move it from src/{web,}client/user.[ch] into src/user.c and simplify
with pwrite(2).

- - - - -
665110bd by Andrew Price at 2015-10-21T12:54:32Z
Consolidate is_old functions

Dedupe it into src/user.c where it fits and rewrite it in terms of a new
user_find_name function which will be used more later.

- - - - -
e40b5341 by Andrew Price at 2015-10-21T12:54:49Z
Don't use FOLDERFILE outside of folders.c

- - - - -
f57942ba by Andrew Price at 2015-10-21T12:55:02Z
Don't use USERFILE outside of user.c

- - - - -
62d5bc94 by Andrew Price at 2015-10-21T12:55:12Z
Make the config system completely generic

Move the bits which specified the default server options and config file
paths out of the config system. Add some bits to simplify constructing an
array of default config options.

- - - - -
79e884d5 by Andrew Price at 2015-10-21T12:55:24Z
Move the config system to src/

Now the client can use it too if necessary.

- - - - -
bfb79d90 by Andrew Price at 2015-10-21T12:55:35Z
Add the built files in src/utils/ to .gitignore

- - - - -


18 changed files:

- .gitignore
- src/client/mod.c
- src/client/user.c
- src/client/user.h
- src/folders.c
- src/folders.h
- src/server/servercfg.c → src/mwcfg.c
- + src/mwcfg.h
- src/server/mwserv.c
- − src/server/servercfg.h
- src/user.c
- src/user.h
- src/utils/clean_users.c
- src/utils/del_user.c
- src/utils/fixuser.c
- src/utils/listuser.c
- src/webclient/import.c
- src/webclient/import.h


Changes:

=====================================
.gitignore
=====================================
--- a/.gitignore
+++ b/.gitignore
@@ -15,6 +15,10 @@ src/mwserv
 src/mwtest
 src/nonce.h
 src/server/mwserv
+src/utils/del_user
+src/utils/fixuser
+src/utils/listuser
+src/utils/sizes
 src/webclient/mwpoll
 mozjs/build
 mozjs/installroot


=====================================
src/client/mod.c
=====================================
--- a/src/client/mod.c
+++ b/src/client/mod.c
@@ -98,13 +98,16 @@ void moderate(void)
 	int f_file;
 	int tmpindex,tmptext;
 
-	if ((f_file=open(FOLDERFILE,O_RDWR))<0)
-		{perror(FOLDERFILE);exit(-1);}
+	f_file=openfolderfile(O_RDWR);
+	if (f_file < 0)
+		exit(-1);
+
 	while (get_folder_entry(f_file,&fold))
 	{
-	sprintf(oldpath,"%s/%s%s%s",STATEDIR,fold.name,INDEX_END,MOD_END);
-	if (!access(oldpath,00))
-	{
+		sprintf(oldpath,"%s/%s%s%s",STATEDIR,fold.name,INDEX_END,MOD_END);
+		if (access(oldpath,00))
+			continue;
+
 		printf(_("Changing to folder %s\n"),fold.name);
 		/* move the unmoderated messages to a temporary file */
 		sprintf(fullpath,"%s/%s%s%s",STATEDIR,fold.name,INDEX_END,".tmp");
@@ -165,7 +168,6 @@ void moderate(void)
 		sprintf(fullpath,"%s/%s%s%s",STATEDIR,fold.name,TEXT_END,".tmp");
 		if (unlink(fullpath)) {perror(fullpath);exit(-1);}
 	}
-	}
 	close(f_file);
 }
 


=====================================
src/client/user.c
=====================================
--- a/src/client/user.c
+++ b/src/client/user.c
@@ -47,18 +47,6 @@ char *getmylogin(void)
 		return(pw->pw_name);
 }
 
-void update_user(struct person *record, int32_t userposn)
-{
-	int outfile;
-
-	outfile=userdb_open(O_RDWR|O_CREAT);
-	/*Lock_File(outfile); */
-	lseek(outfile,userposn,0);
-	write(outfile,record,sizeof(*record));
-	/*Unlock_File(outfile); */
-	close(outfile);
-}
-
 static int old_usr(struct person *usr)
 {
 	char salt[3],passwd[PASSWDSIZE];
@@ -335,21 +323,6 @@ static int new_usr(struct person *usr, char *name, int32_t *userposn)
 	else return(false);
 }
 
-int is_old(struct person *usr, const char *name, int32_t *userposn)
-{
-	int file,found=false;
-
-	if (access(USERFILE,00)) return(false);
-	file=userdb_open(O_RDONLY);
-	while (!found && get_person(file,usr))
-		if (stringcmp(usr->name,name,-1) && !u_del(usr->status))
-			found=true;
-	if (found)
-		*userposn=lseek(file,0,1)-sizeof(struct person);
-	close(file);
-	return(found);
-}
-
 void login_ok(struct person *usr, int32_t *userposn, int *autochat)
 {
 	/* main function */


=====================================
src/client/user.h
=====================================
--- a/src/client/user.h
+++ b/src/client/user.h
@@ -8,13 +8,11 @@ struct person * user_get(const char * name);
 char *getmylogin(void);
 void get_login(char *name, int autochat);
 int get_person(int file, struct person *tmp);
-void update_user(struct person *record, int32_t userposn);
 void list_users(int newonly);
 void list_users_since(long date);
 void login_ok(struct person *usr, int32_t *userposn, int *autochat);
 void strip_name(char *string);
 void pick_salt(char *salt);
 void search(const char *args, const char *ptr);
-int is_old(struct person *usr, const char *name, int32_t *userposn);
 
 #endif


=====================================
src/folders.c
=====================================
--- a/src/folders.c
+++ b/src/folders.c
@@ -8,6 +8,8 @@
 #include "folders.h"
 #include "files.h"
 
+#define FOLDERFILE  STATEDIR"/folders.bb"
+
 int openfolderfile(int mode)
 {
 	int x;


=====================================
src/folders.h
=====================================
--- a/src/folders.h
+++ b/src/folders.h
@@ -3,7 +3,6 @@
 
 #include <stdint.h>
 
-#define FOLDERFILE  STATEDIR"/folders.bb"
 #define FOLNAMESIZE 	10	/* length of folder names */
 #define TOPICSIZE 	30	/* length of the topic of the folder */
 


=====================================
src/server/servercfg.c → src/mwcfg.c
=====================================
--- a/src/server/servercfg.c
+++ b/src/mwcfg.c
@@ -9,13 +9,39 @@
 #include <unistd.h>
 #include <jansson.h>
 
-#include <util.h>
-#include "servercfg.h"
+#include "mwcfg.h"
 
 json_t *cfg;
 
-int cfg_init_defaults(void)
+static int cfg_new_opt_int(const struct cfg_default_opt *o)
 {
+	json_t *val = json_integer(o->o_val.integer);
+	return json_object_set_new(cfg, o->o_name, val);
+}
+
+static int cfg_new_opt_bool(const struct cfg_default_opt *o)
+{
+	json_t *val = o->o_val.boolean ? json_true() : json_false();
+	return json_object_set_new(cfg, o->o_name, val);
+}
+
+static int cfg_new_opt_str(const struct cfg_default_opt *o)
+{
+	json_t *val = json_string(o->o_val.string);
+	return json_object_set_new(cfg, o->o_name, val);
+}
+
+typedef int (*cfg_new_opt_fn)(const struct cfg_default_opt *);
+
+static const cfg_new_opt_fn cfg_new_opt_fns[] = {
+	[CFG_OPT_INT] = cfg_new_opt_int,
+	[CFG_OPT_BOOL] = cfg_new_opt_bool,
+	[CFG_OPT_STR] = cfg_new_opt_str
+};
+
+int cfg_init_defaults(const struct cfg_default_opt *dft_opts)
+{
+	const struct cfg_default_opt *dopt;
 	int ret;
 
 	cfg = json_object();
@@ -23,18 +49,24 @@ int cfg_init_defaults(void)
 		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));
+	for (dopt = dft_opts; dopt && dopt->o_type != CFG_OPT_NULL; dopt++) {
+		cfg_new_opt_fn fn;
 
-	if (ret != 0) {
-		fprintf(stderr, "Failed to build default config items\n");
-		json_decref(cfg);
-		return 1;
+		fn = cfg_new_opt_fns[dopt->o_type];
+		if (fn == NULL) {
+			fprintf(stderr, "Invalid config option type: %d\n", dopt->o_type);
+			goto err_decref;
+		}
+		ret = fn(dopt);
+		if (ret != 0) {
+			fprintf(stderr, "Failed to build default config items\n");
+			goto err_decref;
+		}
 	}
 	return 0;
+err_decref:
+	json_decref(cfg);
+	return 1;
 }
 
 static const char *jsontype_to_str[] = {
@@ -99,7 +131,13 @@ static int cfg_obj_merge(json_t *obj)
 	return ret;
 }
 
-static int cfg_load_file(const char *path, int skip_if_missing)
+/**
+ * Returns:
+ * >0 if the path is not found or is not accessible
+ * <0 if another error occurs
+ *  0 if the path was successfully loaded
+ **/
+int cfg_load(const char *path)
 {
 	json_error_t err;
 	json_t *obj;
@@ -109,7 +147,7 @@ static int cfg_load_file(const char *path, int skip_if_missing)
 
 	fd = open(path, O_RDONLY);
 	if (fd < 0) {
-		if ((errno == EACCES || errno == ENOENT) && skip_if_missing)
+		if ((errno == EACCES || errno == ENOENT))
 			return 1;
 		fprintf(stderr, "Failed to open config file '%s': %s\n", path, strerror(errno));
 		return -1;
@@ -130,7 +168,7 @@ static int cfg_load_file(const char *path, int skip_if_missing)
 	ret = cfg_obj_validate(obj);
 	if (ret != 0)
 		return -1;
-
+	printf("Loading configuration from '%s'\n", path);
 	ret = cfg_obj_merge(obj);
 	json_decref(obj);
 	if (ret != 0)
@@ -138,37 +176,6 @@ static int cfg_load_file(const char *path, int skip_if_missing)
 	return 0;
 }
 
-static int cfg_load_default_path(void)
-{
-	AUTOFREE_BUFFER homeconf = NULL;
-	const char *homedir;
-	const char *etcconf;
-	int ret;
-
-	homedir = 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)))


=====================================
src/mwcfg.h
=====================================
--- /dev/null
+++ b/src/mwcfg.h
@@ -0,0 +1,40 @@
+#ifndef MWCFG_H
+#define MWCFG_H
+
+struct cfg_default_opt {
+#define CFG_OPT_NULL (0)
+#define CFG_OPT_INT  (1)
+#define CFG_OPT_BOOL (2)
+#define CFG_OPT_STR  (3)
+	int o_type;
+	const char *o_name;
+	union {
+#define _OPTTYP_INT integer
+		long integer;
+#define _OPTTYP_BOOL boolean
+		int boolean;
+#define _OPTTYP_STR string
+		const char *string;
+	} o_val;
+};
+
+#define _OPTTYP(t) _OPTTYP_##t
+
+#define CFG_OPT(type, name, value) { \
+	.o_type = CFG_OPT_##type, \
+	.o_name = name, \
+	.o_val._OPTTYP(type) = value }
+#define CFG_END { NULL, NULL, {0} }
+
+extern int cfg_init_defaults(const struct cfg_default_opt *dft_opts);
+extern int cfg_load(const char *cfg_file);
+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 /* MWCFG_H */


=====================================
src/server/mwserv.c
=====================================
--- a/src/server/mwserv.c
+++ b/src/server/mwserv.c
@@ -9,8 +9,10 @@
 #include <string.h>
 #include <time.h>
 #include <socket.h>
+
+#include <util.h>
+#include <mwcfg.h>
 #include "servsock.h"
-#include "servercfg.h"
 
 /* unused, but necessary to link other util functions */
 int idle = 0;
@@ -18,6 +20,7 @@ int internet = 0;
 int userposn = 0;
 
 #define MWUSER "mw"
+#define MWSERVCONF "mwserv.conf"
 
 time_t uptime = 0;
 
@@ -32,13 +35,50 @@ static void usage(char *name)
 	       "\n", name);
 }
 
+/**
+ * Config options must be set here so that the config system will recognise
+ * them. It also uses them to validate the types of the overrides specified in
+ * config files. Any options found in config files that are not listed here
+ * will be ignored (non-fatally).
+ **/
+static const struct cfg_default_opt defcfgs[] = {
+	CFG_OPT(BOOL, "foreground", 0),
+	CFG_OPT(INT, "port", 9999),
+	CFG_END
+};
+
+static int config_init(void)
+{
+	AUTOFREE_BUFFER homeconf = NULL;
+	const char *homedir;
+	int ret;
+
+	ret = cfg_init_defaults(defcfgs);
+	if (ret)
+		return ret;
+
+	homedir = getenv("HOME");
+	if (homedir) {
+		ret = asprintf(&homeconf, "%s/."MWSERVCONF, homedir);
+		if (ret > 0) {
+			ret = cfg_load(homeconf);
+			if (ret <= 0)
+				return ret;
+		}
+	}
+	ret = cfg_load("/etc/"MWSERVCONF);
+	if (ret <= 0)
+		return ret;
+	return 0;
+}
+
 static int getconfig(int argc, char **argv)
 {
 	int c;
 	int ret;
+	long num;
 	int optidx = 0;
 	int printcfg = 0;
-	long num;
 	static struct option loptspec[] = {
 		{"config",       required_argument, 0, 'c'},
 		{"foreground",   no_argument,       0, 'f'},
@@ -48,13 +88,9 @@ static int getconfig(int argc, char **argv)
 		{0, 0, 0, 0}
 	};
 
-	ret = cfg_init_defaults();
-	if (ret)
-		return ret;
-
-	ret = cfg_load(NULL);
+	ret = config_init();
 	if (ret != 0)
-		return 1;
+		return ret;
 
 	while (1) {
 		c = getopt_long(argc, argv, "c:fhPp:", loptspec, &optidx);


=====================================
src/server/servercfg.h deleted
=====================================
--- a/src/server/servercfg.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#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 */


=====================================
src/user.c
=====================================
--- a/src/user.c
+++ b/src/user.c
@@ -2,9 +2,13 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include <strings.h>
 
 #include "user.h"
 #include "files.h"
+#include "perms.h"
+
+#define USERFILE STATEDIR"/users.bb"
 
 int userdb_open(int flags)
 {
@@ -28,6 +32,15 @@ void userdb_write(struct person *record, int32_t *userposn)
 	close(outfile);
 }
 
+void update_user(struct person *record, int32_t userposn)
+{
+	int outfile;
+
+	outfile = userdb_open(O_WRONLY|O_CREAT);
+	pwrite(outfile, record, sizeof(*record), userposn);
+	close(outfile);
+}
+
 void fetch_user(struct person *record, int32_t userposn)
 {
         int outfile;
@@ -38,3 +51,35 @@ void fetch_user(struct person *record, int32_t userposn)
         close(outfile);
 }
 
+static int user_find_name(const char *name, struct person *user, int32_t *userposn, int *found)
+{
+	ssize_t bytes;
+	int fd = open(USERFILE, O_RDONLY);
+
+	if (fd < 0)
+		return 1;
+
+	*found = 0;
+	*userposn = 0;
+	while((bytes = read(fd, user, sizeof(*user)))) {
+		if (bytes != sizeof(*user)) {
+			perror(USERFILE);
+			close(fd);
+			return 1;
+		}
+		*userposn += sizeof(*user);
+		if (!strcasecmp(user->name, name) && !u_del(user->status)) {
+			*found = 1;
+			break;
+		}
+	}
+	close(fd);
+	return 0;
+}
+
+int is_old(struct person *usr, const char *name, int32_t *userposn)
+{
+        int found;
+
+	return !user_find_name(name, usr, userposn, &found) && found;
+}


=====================================
src/user.h
=====================================
--- a/src/user.h
+++ b/src/user.h
@@ -3,8 +3,6 @@
 
 #include <stdint.h>
 
-#define USERFILE STATEDIR"/users.bb"
-
 #define NAMESIZE 	16	/* username */
 #define PASSWDSIZE 	20	/* password (after encryption) */
 #define REALNAMESIZE	30	/* real name */
@@ -53,6 +51,7 @@ struct person
 extern int userdb_open(int flags);
 extern void userdb_write(struct person *record, int32_t *userposn);
 extern void fetch_user(struct person *record, int32_t userposn);
-
+extern void update_user(struct person *record, int32_t userposn);
+extern int is_old(struct person *usr, const char *name, int32_t *userposn);
 
 #endif


=====================================
src/utils/clean_users.c
=====================================
--- a/src/utils/clean_users.c
+++ b/src/utils/clean_users.c
@@ -4,26 +4,30 @@
 int internet=0;
 struct person user;
 
-main()
+int main(int argc, char **argv)
 {
-	char oldpath[1024];
-	char newpath[1024];
+	const char *oldpath;
+	char *newpath;
 	int newfile,oldfile;
 	char buff[20];
 	long tt=0l;
 
-	sprintf(oldpath,"%s/%s",HOMEPATH,USERFILE);
-	sprintf(newpath,"%s.new",oldpath);
+	if (argc != 2) {
+		fprintf(stderr, "Usage: %s </path/to/user.bb>\n", argv[0]);
+		exit(1);
+	}
+	oldpath = argv[1];
+	asprintf(&newpath, "%s.new", oldpath);
 
 	if ((oldfile=open(oldpath,O_RDONLY))<0)
 	{
 		perror(oldpath);
-		exit(0);
+		exit(1);
 	}
 	if ((newfile=open(newpath,O_WRONLY|O_CREAT,0600))<0)
 	{
 		perror(newpath);
-		exit(0);
+		exit(1);
 	}
 
 	tt=time(0);
@@ -44,4 +48,6 @@ main()
 	}
 	close(newfile);
 	close(oldfile);
+	free(newpath);
+	return 0;
 }


=====================================
src/utils/del_user.c
=====================================
--- a/src/utils/del_user.c
+++ b/src/utils/del_user.c
@@ -11,15 +11,23 @@
 int internet=0;
 struct person user;
 
-int main(void)
+int main(int argc, char **argv)
 {
-	const char *newpath = USERFILE ".new";
+	const char *origpath;
+	char *newpath;
 	int newfile,oldfile;
 	long tt=0l;
 
-	if ((oldfile=open(USERFILE,O_RDONLY))<0)
+	if (argc != 2) {
+		fprintf(stderr, "Usage: %s </path/to/users.bb>\n", argv[0]);
+		exit(1);
+	}
+	origpath = argv[1];
+	asprintf(&newpath, "%s.new", origpath);
+
+	if ((oldfile=open(origpath,O_RDONLY))<0)
 	{
-		perror(USERFILE);
+		perror(origpath);
 		exit(1);
 	}
 	if ((newfile=open(newpath,O_WRONLY|O_CREAT,0600))<0)
@@ -55,4 +63,6 @@ int main(void)
 	}
 	close(newfile);
 	close(oldfile);
+	free(newpath);
+	return 0;
 }


=====================================
src/utils/fixuser.c
=====================================
--- a/src/utils/fixuser.c
+++ b/src/utils/fixuser.c
@@ -12,7 +12,7 @@
 int main(void)
 {
 	struct person user;
-	const char *path = USERFILE;
+	const char *path = "users.bb";
 	int count=0;
 	int ff, ff2;
 


=====================================
src/utils/listuser.c
=====================================
--- a/src/utils/listuser.c
+++ b/src/utils/listuser.c
@@ -5,14 +5,12 @@
 #include <string.h>
 #include <unistd.h>
 
-#include <bb.h>
 #include <user.h>
-#include <files.h>
 
 int main(int argc, char **argv)
 {
 	struct person user;
-	const char *path = USERFILE;
+	const char *path = "users.bb";
 	int count=0;
 	int ff;
 


=====================================
src/webclient/import.c
=====================================
--- a/src/webclient/import.c
+++ b/src/webclient/import.c
@@ -36,18 +36,6 @@ int get_person(int file, struct person *tmp)
         return(true);
 }
 
-void update_user(struct person *record, int32_t userposn)
-{
-        int outfile;
-
-        outfile=userdb_open(O_RDWR|O_CREAT);
-        /*Lock_File(outfile); */
-        lseek(outfile,userposn,0);
-        write(outfile,record,sizeof(*record));
-        /*Unlock_File(outfile); */
-        close(outfile);
-}
-
 /* chatmode flags, as in currently active modes */
 unsigned long cm_flags(unsigned long cm, unsigned long flags, int mode)
 {
@@ -137,21 +125,6 @@ char *remove_first_word(char *args)
         } else return(NULL);
 }
 
-int is_old(struct person *usr, const char *name, int32_t *userposn)
-{
-        int file,found=false;
-
-        if (access(USERFILE,00)) return(false);
-        file=userdb_open(O_RDONLY);
-        while (!found && get_person(file,usr))
-                if (!strcasecmp(usr->name,name) && !u_del(usr->status))
-                        found=true;
-        if (found)
-                *userposn=lseek(file,0,1)-sizeof(struct person);
-        close(file);
-        return(found);
-}
-
 extern int userposn;
 
 void talk_send_to_room(const char * text, int channel, const char * type, int plural) {


=====================================
src/webclient/import.h
=====================================
--- a/src/webclient/import.h
+++ b/src/webclient/import.h
@@ -4,7 +4,6 @@
 #include <user.h>
 
 int get_person(int file, struct person *tmp);
-void update_user(struct person *record, int32_t userposn);
 unsigned long cm_flags(unsigned long cm, unsigned long flags, int mode);
 char *quotetext(const char *a);
 void broadcast_onoffcode(int code, int method, const char *sourceuser, const char *reason);
@@ -12,6 +11,5 @@ void show_chatmodes(unsigned long cm, char *tmp, int flag);
 void show_chatprivs(unsigned long cp, char *tmp, int flag);
 char *remove_first_word(char *args) ;
 void talk_send_to_room(const char *text, int channel, const char *type, int plural);
-int is_old(struct person *usr, const char *name, int32_t *userposn);
 
 #endif /* IMPORT_H */



View it on GitLab: https://projects.sucs.org/arthur/mw/compare/32d6e80e843389d695b5568eb6a40a02b1b4822b...bfb79d904743ef25cefa37ca59687eca790c9a2d
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.sucs.org/pipermail/mw-devel/attachments/20151021/2c14af3e/attachment-0001.html>


More information about the mw-devel mailing list