[mw-devel] [Git][arthur/mw][master] 4 commits: Add includes required on freebsd

Andrew Price welshbyte at sucs.org
Sun Jul 30 17:49:52 BST 2017


Andrew Price pushed to branch master at Justin Mitchell / mw


Commits:
4a26c344 by Andrew Price at 2017-07-30T11:05:04+01:00
Add includes required on freebsd

On Linux this just makes some implicit includes explicit.

- - - - -
9801780d by Andrew Price at 2017-07-30T11:55:25+01:00
Set appropriate build flags for FreeBSD

And don't try to use strfry() on it.

- - - - -
8538d135 by Andrew Price at 2017-07-30T15:20:35+01:00
server: abstract out the event polling bits

- - - - -
a7dff10a by Andrew Price at 2017-07-30T17:47:14+01:00
Add a kqueue implementation of server/poll.h

MW now works on FreeBSD

- - - - -


17 changed files:

- Makefile.common
- src/Makefile
- src/client/Makefile
- src/client/edit.c
- src/client/main.c
- src/client/mod.c
- src/client/newmain.c
- src/list.h
- src/server/Makefile
- src/server/gags.c
- + src/server/poll-epoll.c
- + src/server/poll-kqueue.c
- + src/server/poll.h
- src/server/servsock.c
- src/socket.c
- src/user.c
- src/webclient/comms.c


Changes:

=====================================
Makefile.common
=====================================
--- a/Makefile.common
+++ b/Makefile.common
@@ -1,3 +1,4 @@
+UNAME = $(shell uname)
 GITVER = $(shell git describe --always --dirty)
 VERSION = $(GITVER)
 
@@ -68,6 +69,10 @@ MWLDFLAGS += -O0
 endif
 
 MWCFLAGS = -std=gnu99 -g $(DEFS) $(CCSEC) $(WARNINGS)
+ifeq ($(UNAME),FreeBSD)
+MWCFLAGS += -I/usr/local/include
+MWLDFLAGS = -L/usr/local/lib
+endif
 ifneq ($(RELEASE_BUILD),0)
 MWCFLAGS += -O2
 # This requires optimisation so add it here instead of CCSEC


=====================================
src/Makefile
=====================================
--- a/src/Makefile
+++ b/src/Makefile
@@ -3,8 +3,8 @@ DEPTH=../
 include ../Makefile.common
 
 build: libmw.a
-	$(MAKE) -C server $@
 	$(MAKE) -C client $@
+	$(MAKE) -C server $@
 	$(MAKE) -C webclient $@
 	$(MAKE) -C utils $@
 	ln -fs server/mwserv client/mw .


=====================================
src/client/Makefile
=====================================
--- a/src/client/Makefile
+++ b/src/client/Makefile
@@ -13,8 +13,12 @@ JSDIR = $(JSDIR_$(JSENGINE))
 JSOBJ = $(JSOBJ_$(JSENGINE))
 JSFLAGS = $(JSFLAGS_$(JSENGINE))
 
-LDLIBS+= -lreadline -ltermcap -lcrypt -lsqlite3 -lcurl -lpthread -lcrypto -ljansson -lz -lm
-CFLAGS+= -I.. $(JSFLAGS)
+LDLIBS += -lreadline -ltermcap -lcrypt -lsqlite3 -lcurl -lpthread -lcrypto -ljansson -lz -lm
+ifeq ($(UNAME),FreeBSD)
+LDLIBS += -lintl
+endif
+
+CFLAGS += -I.. $(JSFLAGS)
 
 # Force build order as we need generated mozjs headers to build mw
 build: $(JSOBJ)


=====================================
src/client/edit.c
=====================================
--- a/src/client/edit.c
+++ b/src/client/edit.c
@@ -15,6 +15,7 @@
 #include <time.h>
 #include <stdbool.h>
 #include <sys/stat.h>
+#include <signal.h>
 
 #include <util.h>
 #include "talker_privs.h"


=====================================
src/client/main.c
=====================================
--- a/src/client/main.c
+++ b/src/client/main.c
@@ -12,6 +12,10 @@
 #include <termcap.h>
 #include <stdbool.h>
 #include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <signal.h>
 
 #include "command.h"
 #include "alarm.h"


=====================================
src/client/mod.c
=====================================
--- a/src/client/mod.c
+++ b/src/client/mod.c
@@ -8,6 +8,7 @@
 #include <time.h>
 #include <stdio.h>
 #include <fcntl.h>
+#include <sys/stat.h>
 #include <string.h>
 #include "files.h"
 #include "str_util.h"


=====================================
src/client/newmain.c
=====================================
--- a/src/client/newmain.c
+++ b/src/client/newmain.c
@@ -4,6 +4,7 @@
 #include <string.h>
 #include <sys/wait.h>
 #include <sys/types.h>
+#include <sys/stat.h>
 #include <dirent.h>
 #include <unistd.h>
 #include <time.h>


=====================================
src/list.h
=====================================
--- a/src/list.h
+++ b/src/list.h
@@ -27,6 +27,7 @@ struct hlist_node {
 
 #define LIST_HEAD_INIT(name) { &(name), &(name) }
 
+#undef LIST_HEAD
 #define LIST_HEAD(name) \
 	struct list_head name = LIST_HEAD_INIT(name)
 


=====================================
src/server/Makefile
=====================================
--- a/src/server/Makefile
+++ b/src/server/Makefile
@@ -2,8 +2,16 @@ SRCROOT = $(CURDIR)/../..
 DEPTH=../../
 include $(DEPTH)Makefile.common
 
-CFLAGS+= -I..
-LDLIBS+= -ljansson -lsqlite3
+POLLER = epoll
+CFLAGS += -I..
+ifeq ($(UNAME),FreeBSD)
+CFLAGS += -DSTRFRY_MISSING
+POLLER := kqueue
+endif
+
+CODE := $(filter-out poll-%.c, $(CODE)) poll-$(POLLER).c
+
+LDLIBS += -ljansson -lsqlite3
 
 build: mwserv
 


=====================================
src/server/gags.c
=====================================
--- a/src/server/gags.c
+++ b/src/server/gags.c
@@ -10,9 +10,6 @@
 #include "talker_privs.h"
 #include "gags.h"
 
-/* should be defined by strings.h */
-char *strfry(char *string);
-
 /* local prototypes */
 void gag_normal(char *text);
 void gag_chef(char *text);
@@ -385,6 +382,7 @@ void gag_swab(char *text)
 /* strfry */
 void gag_strfry(char *text)
 {
+#ifndef STRFRY_MISSING
 	char *new;
 	char *old = strdup(text);
 	char str[MAXTEXTLENGTH];
@@ -398,6 +396,7 @@ void gag_strfry(char *text)
 
 	free(new);
 	free(old);
+#endif
 }
 
 static char *duplstr(char *text)


=====================================
src/server/poll-epoll.c
=====================================
--- /dev/null
+++ b/src/server/poll-epoll.c
@@ -0,0 +1,91 @@
+#include <sys/epoll.h>
+#include <strings.h>
+
+#include <socket.h>
+#include "poll.h"
+
+struct epoll_priv {
+	int pollfd;
+};
+
+struct epoll_priv ep = {
+	.pollfd = -1
+};
+
+int poll_addconn(ipc_connection_t *conn)
+{
+	struct epoll_event ev;
+	int ret;
+
+	bzero(&ev, sizeof(ev));
+	ev.events = EPOLLIN | EPOLLERR;
+	ev.data.ptr = conn;
+	ret = epoll_ctl(ep.pollfd, EPOLL_CTL_ADD, conn->fd, &ev);
+	return ret;
+}
+
+int poll_init(void)
+{
+	if (ep.pollfd == -1)
+		ep.pollfd = epoll_create(30);
+	return 0;
+}
+
+void poll_delete(int fd)
+{
+	struct epoll_event ev;
+
+	bzero(&ev, sizeof(ev));
+	epoll_ctl(ep.pollfd, EPOLL_CTL_DEL, fd, &ev);
+}
+
+int poll_with_writes(ipc_connection_t *conn)
+{
+	struct epoll_event ev;
+
+	bzero(&ev, sizeof(ev));
+	ev.events = EPOLLIN | EPOLLOUT | EPOLLERR;
+	ev.data.ptr = conn;
+	return epoll_ctl(ep.pollfd, EPOLL_CTL_MOD, conn->fd, &ev);
+}
+
+int poll_without_writes(ipc_connection_t *conn)
+{
+	struct epoll_event ev;
+
+	bzero(&ev, sizeof(ev));
+	ev.events = EPOLLIN | EPOLLERR;
+	ev.data.ptr = conn;
+	return epoll_ctl(ep.pollfd, EPOLL_CTL_MOD, conn->fd, &ev);
+}
+
+int poll_fd_without_writes(int fd)
+{
+	struct epoll_event ev;
+
+	bzero(&ev, sizeof(ev));
+	ev.events = EPOLLIN | EPOLLERR;
+	ev.data.ptr = NULL;
+	return epoll_ctl(ep.pollfd, EPOLL_CTL_ADD, fd, &ev);
+}
+
+int poll_wait(int timeout_millis, int (*callback)(poll_event_t *, int, void *), void *data)
+{
+#define NEVENTS (20)
+	struct epoll_event evs[NEVENTS];
+	poll_event_t pevs[NEVENTS];
+	int ret;
+
+	ret = epoll_wait(ep.pollfd, evs, NEVENTS, timeout_millis);
+	if (ret < 0)
+		return ret;
+	for (int i = 0; i < ret; i++) {
+		bzero(&pevs[i], sizeof(pevs[i]));
+		pevs[i].data = evs[i].data.ptr;
+		pevs[i].is_error = (evs[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP));
+		pevs[i].is_read = (evs[i].events & (EPOLLIN | EPOLLPRI));
+		pevs[i].is_write = (evs[i].events & (EPOLLOUT));
+	}
+	ret = callback(pevs, ret, data);
+	return ret;
+}


=====================================
src/server/poll-kqueue.c
=====================================
--- /dev/null
+++ b/src/server/poll-kqueue.c
@@ -0,0 +1,102 @@
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <strings.h>
+#include <errno.h>
+
+#include <socket.h>
+#include "poll.h"
+
+struct kq_priv {
+	int kqd;
+};
+
+struct kq_priv kp = {
+	.kqd = -1
+};
+
+int poll_addconn(ipc_connection_t *conn)
+{
+	struct kevent ev;
+
+	EV_SET(&ev, conn->fd, EVFILT_READ, EV_ADD, 0, 0, conn);
+	return kevent(kp.kqd, &ev, 1, NULL, 0, NULL);
+}
+
+int poll_init(void)
+{
+	if (kp.kqd == -1)
+		kp.kqd = kqueue();
+	return 0;
+}
+
+void poll_delete(int fd)
+{
+	struct kevent ev;
+
+	EV_SET(&ev, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
+	kevent(kp.kqd, &ev, 1, NULL, 0, NULL);
+	EV_SET(&ev, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
+	kevent(kp.kqd, &ev, 1, NULL, 0, NULL);
+}
+
+int poll_with_writes(ipc_connection_t *conn)
+{
+	struct kevent ev;
+
+	EV_SET(&ev, conn->fd, EVFILT_WRITE, EV_ADD, 0, 0, conn);
+	return kevent(kp.kqd, &ev, 1, NULL, 0, NULL);
+}
+
+int poll_without_writes(ipc_connection_t *conn)
+{
+	struct kevent ev;
+	int ret;
+
+	EV_SET(&ev, conn->fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
+	ret = kevent(kp.kqd, &ev, 1, NULL, 0, NULL);
+	if (ret != 0 && errno != ENOENT)
+		return ret;
+	return 0;
+}
+
+int poll_fd_without_writes(int fd)
+{
+	struct kevent ev;
+	int ret;
+
+	EV_SET(&ev, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
+	ret = kevent(kp.kqd, &ev, 1, NULL, 0, NULL);
+	if (ret != 0)
+		return ret;
+	EV_SET(&ev, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
+	ret = kevent(kp.kqd, &ev, 1, NULL, 0, NULL);
+	if (ret != 0 && errno != ENOENT)
+		return ret;
+	return 0;
+}
+
+int poll_wait(int timeout_millis, int (*callback)(poll_event_t *, int, void *), void *data)
+{
+#define NEVENTS (20)
+	struct timespec timeout = {
+		.tv_sec = timeout_millis / 1000,
+		.tv_nsec = timeout_millis % 1000 * 1000000
+	};
+	struct kevent evs[NEVENTS];
+	poll_event_t pevs[NEVENTS];
+	int ret;
+
+	ret = kevent(kp.kqd, NULL, 0, evs, NEVENTS, &timeout);
+	if (ret == -1)
+		return ret;
+	for (int i = 0; i < ret; i++) {
+		bzero(&pevs[i], sizeof(pevs[i]));
+		pevs[i].data = evs[i].udata;
+		pevs[i].is_error = (evs[i].flags & (EV_EOF | EV_ERROR));
+		pevs[i].is_read = (evs[i].filter == EVFILT_READ);
+		pevs[i].is_write = (evs[i].filter == EVFILT_WRITE);
+	}
+	ret = callback(pevs, ret, data);
+	return ret;
+}


=====================================
src/server/poll.h
=====================================
--- /dev/null
+++ b/src/server/poll.h
@@ -0,0 +1,19 @@
+#ifndef SERVER_POLL_H
+#define SERVER_POLL_H
+
+typedef struct {
+	int is_read;
+	int is_write;
+	int is_error;
+	void *data;
+} poll_event_t;
+
+extern int poll_addconn(ipc_connection_t *conn);
+extern int poll_init(void);
+extern void poll_delete(int fd);
+extern int poll_with_writes(ipc_connection_t *conn);
+extern int poll_without_writes(ipc_connection_t *conn);
+extern int poll_fd_without_writes(int fd);
+extern int poll_wait(int timeout_millis, int (*callback)(poll_event_t *events, int nmemb, void *data), void *data);
+
+#endif /* SERVER_POLL_H */


=====================================
src/server/servsock.c
=====================================
--- a/src/server/servsock.c
+++ b/src/server/servsock.c
@@ -1,7 +1,6 @@
 #include <stdio.h>
 #include <fcntl.h>
 #include <unistd.h>
-#include <sys/epoll.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
@@ -26,13 +25,13 @@
 #include "replay.h"
 #include "actions.h"
 #include "gags.h"
+#include "poll.h"
 #include <folders.h>
 #include <perms.h>
 #include <special.h>
 
 struct list_head connection_list;
 
-static int pollfd = -1;
 int mainsock_die = 0;
 
 int open_mainsock(uint16_t port)
@@ -83,11 +82,7 @@ ipc_connection_t * add_connection(int fd)
 	list_add_tail(&(new->list), &connection_list);
 
 	/* register interest in read events */
-	struct epoll_event ev;
-	bzero(&ev, sizeof(ev));
-	ev.events = EPOLLIN | EPOLLERR;
-	ev.data.ptr = new;
-	if (epoll_ctl( pollfd, EPOLL_CTL_ADD, new->fd, &ev)) {
+	if (poll_addconn(new)) {
 		fprintf(stderr, "Error adding new conn to poll: %s\n", strerror(errno));
 	}
 
@@ -114,11 +109,8 @@ void accept_connection(int mainsock)
 
 void drop_connection(ipc_connection_t * conn)
 {
-	struct epoll_event ev;
-
 	printf("Drop connection fd=%d\n", conn->fd);
-	bzero(&ev, sizeof(ev));
-	epoll_ctl(pollfd, EPOLL_CTL_DEL, conn->fd, &ev);
+	poll_delete(conn->fd);
 	list_del_init(&conn->list);
 	if (conn->fd != -1) close(conn->fd);
 	conn->fd = -1;
@@ -141,79 +133,78 @@ void drop_connection(ipc_connection_t * conn)
 	ipcmsg_destroy(whoinfo);
 }
 
-void watch_mainsock(int mainsock)
+static int mainsock_event_cb(poll_event_t *events, int nmemb, void *data)
 {
-	struct epoll_event ev;
-
-	/* add the mainsock to the epoll list
-	 * it will only generate read events */
-	bzero(&ev, sizeof(ev));
-	ev.events = EPOLLIN | EPOLLERR;
-	ev.data.ptr = NULL;
-	if (epoll_ctl(pollfd, EPOLL_CTL_ADD, mainsock, &ev)) {
-		fprintf(stderr, "Error adding mainsock: %s\n", strerror(errno));
-		return;
-	}
+	int *mainsock = data;
 
-	struct epoll_event event[20];
-	int ret;
-	struct timeval last;
-	gettimeofday(&last, NULL);
+	for (int i = 0; i < nmemb; i++) {
+		poll_event_t *ev = &events[i];
 
-bodge:
-	while ((ret = epoll_wait(pollfd, event, 20, 1000)) >= 0) {
-		for (int i=0; i<ret; i++) {
-			/* even on mainsock */
-			if (event[i].data.ptr == NULL) {
-				if (event[i].events & (EPOLLERR | EPOLLHUP)) {
-					return;
-				} else
-				if (event[i].events & EPOLLIN) {
-					accept_connection(mainsock);
-				} else {
-					fprintf(stderr, "unexpected event on mainsock.\n");
-				}
+		/* event on mainsock */
+		if (ev->data == NULL) {
+			if (ev->is_error) {
+				return 1;
+			} else
+			if (ev->is_read) {
+				accept_connection(*mainsock);
 			} else {
-				ipc_connection_t *c = event[i].data.ptr;
-				if (event[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) {
-					ipcconn_bad(c);
-				} else
-				if (event[i].events & (EPOLLIN | EPOLLPRI)) {
-					ipc_message_t *msg = read_socket(c, 1);
-					while (msg != NULL) {
-						process_msg(c, msg);
-						msg=read_socket(c,0);
-					}
-				} else
-				if (event[i].events & EPOLLOUT) {
-					write_socket(c);
-				} else {
-					fprintf(stderr, "unexpected event on fd=%d.\n", c->fd);
+				fprintf(stderr, "unexpected event on mainsock.\n");
+			}
+		} else {
+			ipc_connection_t *c = ev->data;
+			if (ev->is_error) {
+				ipcconn_bad(c);
+			} else
+			if (ev->is_read) {
+				ipc_message_t *msg = read_socket(c, 1);
+				while (msg != NULL) {
+					process_msg(c, msg);
+					msg=read_socket(c,0);
 				}
+			} else
+			if (ev->is_write) {
+				write_socket(c);
+			} else {
+				fprintf(stderr, "unexpected event on fd=%d.\n", c->fd);
 			}
+		}
 
-			/* check if we had to drop any connections and clean them away */
-			struct list_head *pos, *q;
-			list_for_each_safe(pos, q, &connection_list) {
-				ipc_connection_t * c = list_entry(pos, ipc_connection_t, list);
-				/* connections that went bad */
-				if (c->fd != -1 && c->state == IPCSTATE_ERROR) {
-					drop_connection(c);
-				}
-				/* connections with a soft close */
-				if (c->state == IPCSTATE_PURGE && list_empty(&(c->outq))) {
-					drop_connection(c);
-				}
+		/* check if we had to drop any connections and clean them away */
+		struct list_head *pos, *q;
+		list_for_each_safe(pos, q, &connection_list) {
+			ipc_connection_t * c = list_entry(pos, ipc_connection_t, list);
+			/* connections that went bad */
+			if (c->fd != -1 && c->state == IPCSTATE_ERROR) {
+				drop_connection(c);
+			}
+			/* connections with a soft close */
+			if (c->state == IPCSTATE_PURGE && list_empty(&(c->outq))) {
+				drop_connection(c);
 			}
 		}
-		/* end of events handling, do periodic stuff here */
 	}
+	return 0;
+}
 
-	/* epoll got interupted, not actually an error, go around again */
-	if (ret == -1 && errno == EINTR) goto bodge;
+void watch_mainsock(int mainsock)
+{
+	int ret;
+	struct timeval last;
+	gettimeofday(&last, NULL);
 
-	/* epoll gave an error */
-	fprintf(stderr, "epoll error: %s\n", strerror(errno));
+	if (poll_fd_without_writes(mainsock)) {
+		fprintf(stderr, "Error adding mainsock: %s\n", strerror(errno));
+		return;
+	}
+
+	do {
+		while ((ret = poll_wait(1000, mainsock_event_cb, &mainsock)) >= 0) {
+			/* end of events handling, do periodic stuff here */
+		}
+		/* poll got interrupted, not actually an error, go around again */
+	} while (ret == -1 && errno == EINTR);
+
+	fprintf(stderr, "poll error: %s\n", strerror(errno));
 }
 
 void write_socket(ipc_connection_t * conn)
@@ -223,13 +214,9 @@ void write_socket(ipc_connection_t * conn)
 
 	if (list_empty(&conn->outq)) {
 		/* tx queue is empty, stop write events */
-		struct epoll_event ev;
-		bzero(&ev, sizeof(ev));
-		ev.events = EPOLLIN | EPOLLERR;
-		ev.data.ptr = conn;
-		if (epoll_ctl(pollfd, EPOLL_CTL_MOD, conn->fd, &ev)) {
-			fprintf(stderr, "Error updating poll fd=%d: %s\n", conn->fd, strerror(errno));
-		}
+		if (poll_without_writes(conn))
+			fprintf(stderr, "Error updating poll fd=%d: %s\n",
+			        conn->fd, strerror(errno));
 		return;
 	}
 	struct list_head * pos = conn->outq.next;
@@ -624,13 +611,8 @@ void msg_attach(ipc_message_t *msg, ipc_connection_t *conn)
 	int wasempty = msg_queue(msg, conn);
 
 	/* nothing was queueed, switch on write notifications */
-	if (wasempty) {
-		struct epoll_event ev;
-		bzero(&ev, sizeof(ev));
-		ev.events = EPOLLIN | EPOLLOUT | EPOLLERR;
-		ev.data.ptr = conn;
-		epoll_ctl(pollfd, EPOLL_CTL_MOD, conn->fd, &ev);
-	}
+	if (wasempty)
+		poll_with_writes(conn);
 }
 
 /* test if the user is gagged and apply the filter
@@ -750,9 +732,7 @@ void migrate_old_folders(void)
 void init_server()
 {
 	INIT_LIST_HEAD(&connection_list);
-	if (pollfd == -1) {
-		pollfd = epoll_create(30);
-	}
+	poll_init();
 }
 
 ipc_message_t * msg_wholist(void)


=====================================
src/socket.c
=====================================
--- a/src/socket.c
+++ b/src/socket.c
@@ -2,8 +2,10 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/types.h>
+#include <sys/uio.h>
 #include <unistd.h>
 #include <sys/socket.h>
+#include <netinet/in.h>
 #include <netdb.h>
 #include <errno.h>
 


=====================================
src/user.c
=====================================
--- a/src/user.c
+++ b/src/user.c
@@ -1,3 +1,5 @@
+#include <sys/types.h>
+#include <sys/stat.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>


=====================================
src/webclient/comms.c
=====================================
--- a/src/webclient/comms.c
+++ b/src/webclient/comms.c
@@ -84,7 +84,7 @@ void open_command_socket()
 	}
 	sa.sun_family = AF_UNIX;
 	snprintf(sa.sun_path, UNIX_PATH_MAX, MSGDIR"/mwpoll.%d", getpid());
-	if (bind(command_sock, &sa, sizeof(sa))) {
+	if (bind(command_sock, (struct sockaddr *)&sa, sizeof(sa))) {
 		fprintf(stderr, "Error binding %s: %s\n", sa.sun_path, strerror(errno));
 		close(command_sock);
 		command_sock = -1;



View it on GitLab: https://projects.sucs.org/arthur/mw/compare/52ffb48b380225c2ab23d73cada72dd693d6e183...a7dff10a26dae1ea180f857f7ea8efe6f9f520b2

---
View it on GitLab: https://projects.sucs.org/arthur/mw/compare/52ffb48b380225c2ab23d73cada72dd693d6e183...a7dff10a26dae1ea180f857f7ea8efe6f9f520b2
You're receiving this email because of your account on projects.sucs.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.sucs.org/pipermail/mw-devel/attachments/20170730/59c40673/attachment-0001.html>


More information about the mw-devel mailing list