Commits (22)
"It's easier to ask forgiveness than it is to get permission."
-- Grace Hopper
"It worked!"
-- J. Robert Oppenheimer
The Hitchhacker's Guide to Milliways Development
(To be read in the voice of Peter Jones)
This is the best and worst guide to Milliways testing and development ever
written. It assumes that you are already familiar with git and know where to
clone the mw repository from. Whether you use your own public clone and send a
pull request, or you have permission to push to the main git tree, or use git
format-patch and git send-email is largely academic as no single git workflow
has been chosen (nor, more importantly, rejected). It also assumes that you're
familiar with running and using mw and have a general sense of how it works, or
at least a willingness to learn how it works for yourself.
This guide should be kept updated to reflect changes in workflows and the build
system, which is currently a strange tangle of context-sensitive GNU Make rules
grown organically over time to support some unconventional requirements. If you
find any problems with, or ways to improve this document, report a bug or
send/push a patch.
The Condensed Version
1. git clone .../mw.git
2. cd mw/src
3. make test
4. workworkwork...
5. make test
6. ./mwserv -f &
7. ./mw
8. testtesttest... !qq
9. if fail, goto 4
10. git add changed files...
11. git commit
The Version Nobody Reads
The most difficult part of contributing to any open source project can be
finding something to work on. Therefore, the most useful thing to do when
starting out with a project (assuming you want to work on the code rather than
the docs or other supporting material) is to increase your chances of
triggering bugs. One way to do this with mw is to run your own test version, as
your user, from the source directory, so that you can throw all sorts of crazy
combinations of inputs at it without stepping on anyone's toes. It also means
you can quickly test any changes in a tight develop-test cycle.
To get started, first clone the git tree and change into the src subdirectory:
$ git clone .../mw.git
$ cd mw/src
Then run the 'test' make rule, which first creates a directory (named mwtest by
default) into which an appropriate directory structure and mw's static files
(docs, banner, etc.) are installed. It then builds mw and mwserv, setting the
appropriate hardcoded paths to the new directory by passing them as -D options
to the compiler. [NB it would be useful and more flexible to add configuration
options and/or command line options to provide the paths in future, to simplify
or obsolete the separate 'test' rule.]
$ make test
As of the time of writing, the Javascript library that mw uses is bundled in
with the source tree and will be built the first time you run this command.
This can take a while on a slow machine but it will only need to be done once
unless the mozjs directory is cleaned.
In order to build mw you will need the build dependencies installed on your
system. The best way to find out what these currently are is to look at the
build requirements listed in the RPM spec file 'mw.spec' at the top of the
source tree, or the debian-template/control file. You will also need basic
toolchain packages like gcc, g++ and GNU Make installed.
Now that you've completed your first mw build, you can run the 'mwserv' server
and the 'mw' client programs.
$ ./mwserv
A note on mwserv daemonization: by default, mwserv quietly 'daemonizes' itself
by forking off and ending the parent process so that the child is not attached
to your terminal. This means that you will have to kill it with 'pkill mwserv'
or similar in order to shut it down. Alternatively, and better for testing,
mwserv has a foreground mode which causes it to stay running in your terminal
and also turns on useful debugging messages. This can be enabled using the
--foreground or -f command line option, but if you intend to run mwserv often,
you might prefer to create a ~/.mwserv.conf file and set the "foreground"
option to true. To generate an initial .mwserv.conf file containing mwserv's
default configuration, run ./mwserv -P which will print a JSON-formatted config
similar to:
$ ./mwserv -P
"foreground": false,
"port": 9999,
"user": "mw"
You can use this as the base for your ~/.mwserv.conf. You may also wish to
change the port number, particularly if you are testing on a shared development
machine, although this will mean you have to run the client (./mw) with the
-server option. The "user" option is really only useful when mw has been
deployed and is launched as the root user which will allow it to setuid() as
the specified user. Once you've created this file, you can verify that mwserv
will pick up your changes by running './mwserv -P' again. With mwserv
configured to run in the foreground, run it with
$ ./mwserv &
to send it into the background, or just run it in a separate terminal in the
foreground, so that you can then run
$ ./mw
and begin your testing.
A note on mw's client/server status: mw started off as a peer-to-peer system
which meant that all mw processes had to be run with permission to read and
write the data files containing the BBS folder, messages, logs and user data.
As the transition to client/server is still ongoing this is largely still the
case, although much of the actual chat-related communication is now sent via a
custom protocol over TCP/IP sockets. Details of the protocol can be found in
the src/server/PROTOCOL file. After making changes and rebuilding with 'make
test' you will need to make sure your previously running mwserv process has
been killed before testing again.
To remove all of the built files in the src directory and below you can run
'make cleanall' in the src directory. This will leave your mwtest directory in
place. To remove that too, run 'make testclean'.
As a rule, the code that lives in the src directory is meant to be library code
which is common to the client and the server. It gets compiled into a static
libmw.a archive which makes it easier for the client and server to link against
it. The code in the src/client/ and src/server/ directories are specific to the
client and server respectively.
// TODO: What else would be useful to include here?
......@@ -4,13 +4,19 @@ include Makefile.common
$(MAKE) -C mozjs
$(MAKE) -C src $@
$(MAKE) -C po $@
ifeq ($(GITVER),)
# These rules can only be called from inside an exported tree
$(MAKE) -C po
# The non source files that should get installed
INSTALLFILES = colour help login.banner scripthelp talkhelp wizhelp COPYING INSTALL LICENSE README
install -d $(DESTDIR)$(libdir)/mw
cp -a $(INSTALLFILES) $(DESTDIR)$(libdir)/mw/
ifeq ($(GITVER),)
# These rules can only be called from inside an exported tree
install: install-home
install -d $(DESTDIR)$(initddir)
install mwserv.init $(DESTDIR)$(initddir)/mwserv
$(MAKE) -C src $@
......@@ -54,13 +60,10 @@ endif
$(MAKE) -C src $@
$(MAKE) -C src cleanall
$(MAKE) -C po $@
$(MAKE) -C src/webclient $@
$(MAKE) -C src/server $@
$(MAKE) -C src/client $@
@echo $(VERSION)
.PHONY: build clean rpm deb tarball export install version
.PHONY: build clean rpm deb tarball export install-home install version
......@@ -10,6 +10,9 @@ endif
# Build with `make RELEASE_BUILD=1` to disable debugging features and enable optimisation
prefix ?= /usr
libdir ?= $(prefix)/lib
bindir ?= $(prefix)/bin
......@@ -17,9 +20,6 @@ datadir ?= $(prefix)/share
localstatedir ?= /var
initddir ?= /etc/init.d
# The non source files that should get installed
INSTALLFILES = colour help login.banner scripthelp talkhelp wizhelp COPYING INSTALL LICENSE README
LOGDIR := $(localstatedir)/log/mw
MSGDIR := $(localstatedir)/run/mw
STATEDIR := $(localstatedir)/lib/mw
......@@ -29,21 +29,34 @@ GCCMAJOR := $(echo __GNUC__ | $compiler -E -xc - | tail -n 1)
GCCMINOR := $(echo __GNUC_MINOR__ | $compiler -E -xc - | tail -n 1)
GCCVER := $(shell printf "%02d%02d" $(GCCMAJOR) $(GCCMINOR))
JSFLAGS=-include $(JSDIR)/usr/include/js-17.0/js/RequiredDefines.h -I/usr/include/nspr -I$(JSDIR)/usr/include/js-17.0
# cflags for standard 'cc' compiler
CFLAGS+= -Wall -Wshadow -Wmissing-prototypes -Wpointer-arith -Wwrite-strings -Wcast-align -Wbad-function-cast -Wmissing-format-attribute -Wformat=2 -Wformat-security -Wformat-nonliteral -Wno-long-long -Wno-strict-aliasing -pedantic -std=gnu99 -D_GNU_SOURCE $(JSFLAGS) -DJSSCRIPTTYPE=$(JSSCRIPTTYPE)
JSDIR = $(DEPTH)mozjs/installroot
JSOBJ = $(JSDIR)/usr/lib/libmozjs-17.0.a
-include $(JSDIR)/usr/include/js-17.0/js/RequiredDefines.h \
-I/usr/include/nspr \
-Wall \
-Wshadow \
-Wmissing-prototypes \
-Wpointer-arith \
-Wwrite-strings \
-Wcast-align \
-Wbad-function-cast \
-Wmissing-format-attribute \
-Wformat=2 \
-Wformat-security \
-Wformat-nonliteral \
-Wtype-limits \
-Wno-long-long \
-Wno-strict-aliasing \
# until gcc catches up (4.7.x is good)
CFLAGS += $(shell if [ $(GCCVER) -lt 0406 ] ; then echo "-Wno-multichar"; fi )
WARNINGS += $(shell if [ $(GCCVER) -lt 0406 ] ; then echo "-Wno-multichar"; fi )
CFLAGS += -fpie
LDFLAGS += -pie
CCSEC = -fpie -fstack-protector-all
LDSEC = -pie -Wl,-z,relro,-z,now
# info strings, do not edit.
DEFS:= -DBUILD_DATE=\"$(shell date +%Y%m%d)\"
......@@ -54,27 +67,45 @@ DEFS+= -DLOGDIR=\"$(LOGDIR)\"
### uncomment for gdb debugging
LDFLAGS+= -ggdb -g
CFLAGS+= -ggdb -g -D__NO_STRING_INLINE -fstack-protector-all
### Optimisation - uncomment for release & extra testing
CFLAGS+=-O0 -Werror
# Set debugging and optimisation features depending on the build type
ifneq ($(RELEASE_BUILD),0)
MWCFLAGS = -std=gnu99 -pedantic -g $(DEFS) $(JSFLAGS) $(CCSEC) $(WARNINGS)
ifneq ($(RELEASE_BUILD),0)
# This requires optimisation so add it here instead of CCSEC
MWCFLAGS += -O0 -Werror
# Let the user add some flags at the end
### The magic which lets us autogenerate dependencies
CODE=$(wildcard *.c)
HDRS=$(wildcard *.h)
all: build
# Using -MMD -MF <filename> outputs make rules for the required .o into
# <filename> with automatically generated dependencies on the .c file and the
# local headers included by the .c file
%.o: %.c
$(CC) $(CFLAGS) -c -o $@ $<
$(CC) $(CFLAGS) -MMD -MF .$(@:.o=.d) -c -o $@ $<
-include $(CODE:.c=.d)
# The generated rule for the requested .o file is then included, dependencies
# are checked and the rule is executed without the -M* options.
-include $(CODE:%.c=.%.d)
$(MAKE) -C $(SRCROOT)/mozjs libdir=/usr/lib
Milliways III
Copyright (c) 1992-2006 Justin Mitchell <>
Copyright (c) 1992-2016 Justin Mitchell <>
Justin "Arthur Dent" Mitchell (Alpha Spod)
Justin "Arthur" Mitchell (Alpha Spod)
Alan "anarchy" Cox (Linux/Internet patches, command parser)
Chris "Fry" Fry (many features including MUD rooms)
Finn "finnw" Wilcox (I/O multiplexing, hash tables)
Steve "FireFury" Hill (debugging wrappers)
Steve "FireFury" Hill (debugging wrappers, comms)
Chris "cmckenna" McKenna (MUD rooms, gags)
Denis "Dez" Walker (manpage style help, testing)
Peter "pwb" Berry (UTF-8 compatibility, rpm maintenance)
Andy "welshbyte" Price (refactoring and general code tidying)
Thanks also to:
Jonathan "Arashi" Care (ideas)
Telsa "hobbit" Gwynne (spelling)
#!/usr/bin/make -f
#export DH_VERBOSE=1
dh $@
......@@ -55,7 +55,7 @@ $MILESTONE_FILE = "$TOPSRCDIR/config/milestone.txt";
my $milestone = Moz::Milestone::getOfficialMilestone($MILESTONE_FILE);
if (defined(@TEMPLATE_FILE)) {
my $TFILE;
......@@ -22,7 +22,7 @@ architecture, intended to be used over telnet or similar.
%setup -q
make libdir="%{_libdir}" bindir="%{_bindir}"
make libdir="%{_libdir}" bindir="%{_bindir}" RELEASE_BUILD=1
make DESTDIR=$RPM_BUILD_ROOT prefix=/usr libdir="%{_libdir}" install
......@@ -44,10 +44,13 @@ do_start()
# check if the mw folder in /var/run exists, if not then make it
if [ ! -d /var/run/mw ]; then
mkdir /var/run/mw
chown mw:mw /var/run/mw
mkdir /var/run/mw
# Occasionally /var/run/mw ends up with the wrong permissions
# Running this unconditionally should ensure that we always have ownership
chown mw:mw /var/run/mw
# Return
# 0 if daemon has been started
# 1 if daemon was already running
......@@ -27,7 +27,7 @@ install: all
install -d $(DESTDIR)$(STATEDIR)
-rm -f *.o *.d nonce-def.h mw mwserv libmw.a
-rm -f *.o *.d .*.d nonce-def.h mw mwserv libmw.a
cleanall: clean
$(MAKE) -C client clean
......@@ -37,14 +37,11 @@ cleanall: clean
ifndef TESTDIR
test testclean:
$(MAKE) TESTDIR=$(CURDIR)/mwtest $@
$(MAKE) TESTDIR="$(CURDIR)/mwtest" $@
mkdir -p "$(TESTDIR)"
$(MAKE) -C $(SRCROOT) libdir="$(TESTDIR)" localstatedir="$(TESTDIR)" install-home
cd "$(TESTDIR)" && mkdir -p mw run/mw log/mw lib/mw
for d in $(INSTALLFILES); do \
cp -a ../$$d $(TESTDIR)/mw/ ; \
$(MAKE) libdir="$(TESTDIR)" localstatedir="$(TESTDIR)" all
$(MAKE) -C utils libdir="$(TESTDIR)" localstatedir="$(TESTDIR)" all
......@@ -3,7 +3,6 @@ DEPTH=../../
include ../../Makefile.common
LDLIBS+= -lreadline -ltermcap -lcrypt -lsqlite3 -lcurl -lpthread -lcrypto -ljansson -lz -lm
build: mw
......@@ -15,7 +14,7 @@ install: mw
install -D mw $(DESTDIR)$(bindir)/mw
-rm -f *.o *.d mw
-rm -f *.o .*.d *.d mw
ifndef TESTDIR
......@@ -83,5 +83,5 @@ CommandList table[]={
{"wiz", 0x002, 1, "Usage: wiz <message>", "Send message to all wizchat users", c_wiz, 1},
{"wizchat", 0x002, 1, "Usage: wizchat on|off", "Toggle wizchat", c_wizchat, 1},
{"write", 0x000, 0, "Usage: write", "Post a message to the current folder", c_write, 1},
{NULL, 0x000, 0, NULL, NULL, 0}
{NULL, 0x000, 0, NULL, NULL, NULL, 0}
......@@ -66,6 +66,9 @@ void hash_free(int field)
static union fieldrec *frec;
/* make sure its actually alloced first */
if (field == -1) return;
/* Make sure all variables are removed */
......@@ -474,6 +474,21 @@ static void display_uptime(ipc_message_t *msg)
/** Server sent us an error, usually pretty fatal */
static void handle_ipc_error(ipc_message_t *msg)
json_t * j = json_init(msg);
const char * type = json_getstring(j, "error");
if (strcasecmp(type, "NONCE")==0) {
printf("Incompatible server version. Quitting.\n");
} else {
printf("Undefined server error '%s'\n", type);
close_down(0, NULL, NULL);
static void display_error(ipc_message_t *msg)
json_t * j = json_init(msg);
......@@ -754,6 +769,9 @@ static void accept_pipe_cmd(ipc_message_t *msg, struct user *mesg_user)
devel_msg("incoming_mesg", "unknown message type %d.\007", state);
......@@ -25,20 +25,27 @@
extern struct user * const user;
/* drop and restore user level privs */
static int private_myid = -1;
static int private_myuid = -1;
static int private_mygid = -1;
int perms_drop(void)
if (seteuid(getuid()) == -1) return -1;
return private_myid;
if (setegid(getgid()) == -1) return -1;
return private_myuid;
void perms_restore(void)
if (private_myid != -1) {
private_myid = -1;
if (private_myuid != -1) {
private_myuid = -1;
if (private_mygid != -1) {
private_mygid = -1;
......@@ -14,11 +14,9 @@
/* Ugly but necessary (this is how gjs shuts up the warnings,
only gcc 4.6 has a nicer (push/pop) way to do it */
#pragma GCC diagnostic ignored "-Wstrict-prototypes"
#pragma GCC diagnostic ignored "-Winvalid-offsetof"
#pragma GCC diagnostic ignored "-Wbad-function-cast"
#include <jsdbgapi.h>
#pragma GCC diagnostic warning "-Wbad-function-cast"
#pragma GCC diagnostic warning "-Winvalid-offsetof"
#pragma GCC diagnostic warning "-Wstrict-prototypes"
#include <sqlite.h>
......@@ -1127,11 +1125,7 @@ js_error_handler(JSContext __attribute__((unused)) *cx, const char *msg, JSError
int load_jsfile(FILE *f, const char *filename)
JSBool success;
#error "JSSCRIPTTYPE not defined"
JSScript *script;
jsval retval;
/* Compile the js file specified */
......@@ -1208,8 +1202,8 @@ int js_isrunning(void)
// cleans up the javascript environment
int stop_js(void)
if (jscx != NULL) JS_DestroyContext(jscx);
if (jsrt != NULL) JS_DestroyRuntime(jsrt);
return 0;
......@@ -17,7 +17,6 @@
extern struct user * const user;
static void *file_url(void * data);
static void *file_tag(void * data);
* Check whats said for URLs and log them
......@@ -27,7 +26,7 @@ struct uripatt {
const char *regex;
int regflags; /* compile flags */
regex_t *patt; /* compiled version */
enum { END=0, URL, FLAG, IGNORE, NOLOG, TAG } type;
enum { END=0, URL, FLAG, IGNORE, NOLOG } type;
uint32_t flags;
......@@ -54,15 +53,6 @@ struct uripatt urilist[] = {
{"^-log", REG_ICASE, NULL, NOLOG, 0},
{"^(-log)$", REG_ICASE, NULL, NOLOG, 0},
{"^(nolog)$", REG_ICASE, NULL, NOLOG, 0},
/* section for hashtags, exclude C preprocessor directives */
{"^#include$", REG_ICASE, NULL, IGNORE, 0},
{"^#define$", REG_ICASE, NULL, IGNORE, 0},
{"^#pragma$", REG_ICASE, NULL, IGNORE, 0},
{"^#if$", REG_ICASE, NULL, IGNORE, 0},
{"^#ifdef$", REG_ICASE, NULL, IGNORE, 0},
{"^#ifndef$", REG_ICASE, NULL, IGNORE, 0},
{"^#endif$", REG_ICASE, NULL, IGNORE, 0},
{"^#[[:alnum:]]+$", REG_ICASE|REG_EXTENDED, NULL, TAG, 0},
{ NULL, 0, NULL, END, 0 }
......@@ -71,11 +61,6 @@ struct urihit {
uint32_t flags;
struct taghit {
char *tag;
char *line;
void catchuri(const char *what)
char *text = strip_colours(what);
......@@ -83,8 +68,6 @@ void catchuri(const char *what)
/* kludge, find at most 20 URLs */
char *foundurl[20];
int nfoundurl=0;
char *foundtag[20];
int nfoundtag=0;
uint32_t flags = 0;
/* split the line into words on whitespace */
......@@ -128,9 +111,6 @@ void catchuri(const char *what)
if (u->type == TAG) {
if (nfoundtag < 20) foundtag[nfoundtag++] = token;
......@@ -156,24 +136,6 @@ void catchuri(const char *what)
/* we also found some tags */
if (nfoundtag>0) {
int i;
pthread_attr_t ptattr;
pthread_attr_setdetachstate(&ptattr, PTHREAD_CREATE_DETACHED);
for (i=0;i<nfoundtag;i++) {
struct taghit *tag;
pthread_t pt;
tag = malloc(sizeof(struct taghit));
tag->tag = strdup(foundtag[i]);
tag->line = strdup(what);
pthread_create(&pt, &ptattr, file_tag, tag);
......@@ -321,32 +283,6 @@ static void *file_url(void * data)
return NULL;
static void *file_tag(void * data)
struct taghit *tag = data;
struct db_result *res;
char *query = sqlite3_mprintf("INSERT INTO mwtag (user, tag, added, line) "
"VALUES (%Q,%Q,datetime('now'),%Q)",
user->, tag->tag, tag->line);
res = db_query(MWURI_DB, query, 1);
if (res == NULL) {
res = db_query(MWURI_DB, "CREATE TABLE mwtag "
"user TEXT, tag TEXT, added TEXT, line TEXT)", 0);
if (res != NULL) {
res = db_query(MWURI_DB, query, 0);
return NULL;
/* store the doing/status string in the db */
void catchdoing(const char *what)
......@@ -20,7 +20,7 @@ extern struct user * const user;
void inform_of_mail(char *to)
if (ipc_send_to_username(to, IPC_NEWMAIL, NULL) < 0)
if (ipc_send_to_username(to, IPC_NEWMAIL, NULL) != 0)
printf(_("Cannot inform %s of new mail.\n"),to);
......@@ -932,6 +932,7 @@ static void credits(void)
printf("Dez (man page style help, ideas, testing),\n");
printf("Pwb (RPM packaging, UTF-8 compatibility hacks),\n");
printf("Psycodom (Fixups for IPC, JS, locale conversions, buffer overflows)\n");
printf("welshbyte (Code refactoring, tidying and general cleanups)\n");
......@@ -34,7 +34,7 @@ extern int current_rights;
struct function *function_list=NULL;
extern const char *autoexec_arg;
var_list_t var_list;
var_list_t var_list = -1;
var_list_t *local_vars=NULL;
CompStack *comparison_stack=NULL;
......@@ -260,14 +260,6 @@ void t_runaway(CommandList *cm, int argc, const char **argv, char *args)
if (num < 0)
printf("Invalid Runaway. Must be positive (0 disables)\n");
run_away = num;
if (run_away == 0) printf("Runaway disabled\n");
else printf("Runaway set to value: %lu\n", run_away);