aboutsummaryrefslogtreecommitdiffstats
path: root/erts/epmd
diff options
context:
space:
mode:
Diffstat (limited to 'erts/epmd')
-rw-r--r--erts/epmd/Makefile32
-rw-r--r--erts/epmd/doc/.gitignore0
-rw-r--r--erts/epmd/epmd.mk70
-rw-r--r--erts/epmd/src/Makefile22
-rw-r--r--erts/epmd/src/Makefile.in123
-rw-r--r--erts/epmd/src/epmd.c629
-rw-r--r--erts/epmd/src/epmd.h37
-rw-r--r--erts/epmd/src/epmd_cli.c127
-rw-r--r--erts/epmd/src/epmd_int.h346
-rw-r--r--erts/epmd/src/epmd_srv.c1254
-rw-r--r--erts/epmd/test/Makefile80
-rw-r--r--erts/epmd/test/epmd.spec1
-rw-r--r--erts/epmd/test/epmd.spec.vxworks2
-rw-r--r--erts/epmd/test/epmd_SUITE.erl835
14 files changed, 3558 insertions, 0 deletions
diff --git a/erts/epmd/Makefile b/erts/epmd/Makefile
new file mode 100644
index 0000000000..4c1af393ac
--- /dev/null
+++ b/erts/epmd/Makefile
@@ -0,0 +1,32 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1998-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Common Macros
+# ----------------------------------------------------
+SUB_DIRECTORIES = src
+
+SPECIAL_TARGETS =
+
+# ----------------------------------------------------
+# Default Subdir Targets
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_subdir.mk
diff --git a/erts/epmd/doc/.gitignore b/erts/epmd/doc/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/erts/epmd/doc/.gitignore
diff --git a/erts/epmd/epmd.mk b/erts/epmd/epmd.mk
new file mode 100644
index 0000000000..a73f4bc077
--- /dev/null
+++ b/erts/epmd/epmd.mk
@@ -0,0 +1,70 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1998-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+# ------------------------------------------------
+# Server defines
+#
+
+# EPMD port number
+# 4365 - Version 4.2 TCP
+# 4366 - Version 4.3 TCP
+# 4368 - Version 4.4.0 - 4.6.2 TCP
+# 4369 - Version 4.6.3 - 4.7.4 TCP/UDP
+EPMD_PORT_NO=4369
+
+
+# ------------------------------------------------
+# Client defines
+#
+
+# Node type:
+# 72 = R3 hidden node
+# 77 = R3 erlang node
+# 104 = R4 hidden node
+# 109 = R4 erlang node
+# (110 = R6 nodes (explicit flags for differences between nodes))
+#
+# What epmd has been told, differs very much between versions, both
+# 111 and 110 seems to have been used to tell epmd, while
+# the actual nodetypes has still been 104 and 109.
+# EPMD does not care about this, why we move back to using
+# the correct tag (an 'n') for all nodes.
+#
+EPMD_NODE_TYPE=110
+
+# Lowest/Highest supported version of the distribution protocol:
+# 0 = R3
+# 1 = R4
+# 2 = R5 ?????
+# 3 = R5C
+# 4 = R6 (development)
+# 5 = R6
+# There was no protocol change in release R5, so we didn't need to raise
+# the version number. But now that R5A is released, it's best to keep it
+# this way.
+# The number was inadvertently raised for R5C, so we increase it again
+# for R6.
+# Distribution version 4 means a) distributed monitor and b) larger references
+# in the distribution format.
+# In format 5, nodes can explicitly tell each other which of the above
+# mentioned capabilities they can handle.
+# Distribution format 5 contains the new md5 based handshake.
+
+EPMD_DIST_LOW=5
+EPMD_DIST_HIGH=5
+
diff --git a/erts/epmd/src/Makefile b/erts/epmd/src/Makefile
new file mode 100644
index 0000000000..7d586c7438
--- /dev/null
+++ b/erts/epmd/src/Makefile
@@ -0,0 +1,22 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1998-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+# Invoke with GNU make or clearmake -C gnu.
+#
+
+include $(ERL_TOP)/make/run_make.mk
diff --git a/erts/epmd/src/Makefile.in b/erts/epmd/src/Makefile.in
new file mode 100644
index 0000000000..498756b468
--- /dev/null
+++ b/erts/epmd/src/Makefile.in
@@ -0,0 +1,123 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1998-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+include $(ERL_TOP)/make/target.mk
+
+
+ifeq ($(TYPE),debug)
+PURIFY =
+TYPEMARKER = .debug
+TYPE_FLAGS = -DDEBUG @DEBUG_FLAGS@
+else
+ifeq ($(TYPE),purify)
+PURIFY = purify
+TYPEMARKER =
+ifeq ($(findstring ose,$(TARGET)),ose)
+ TYPE_FLAGS = -DPURIFY
+else
+ TYPE_FLAGS = -O2 -DPURIFY
+endif
+else
+PURIFY =
+TYPEMARKER =
+ifeq ($(findstring ose,$(TARGET)),ose)
+ TYPE_FLAGS =
+else
+ TYPE_FLAGS = -O2
+endif
+endif
+endif
+
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+include ../../vsn.mk
+include ../epmd.mk
+
+BINDIR = $(ERL_TOP)/bin/$(TARGET)
+OBJDIR = $(ERL_TOP)/erts/obj$(TYPEMARKER)/$(TARGET)
+
+CC = @CC@
+WFLAGS = @WFLAGS@
+CFLAGS = @CFLAGS@ @DEFS@ $(TYPE_FLAGS) $(WFLAGS)
+LD = @LD@
+LIBS = @LIBS@
+LDFLAGS = @LDFLAGS@
+
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+
+# The targets
+ifeq ($(findstring win32,$(TARGET)),win32)
+EPMD = epmd.exe
+else
+EPMD = epmd
+endif
+
+INSTALL_PROGS = $(BINDIR)/$(EPMD)
+
+#---------------------------------
+# Options
+#---------------------------------
+
+EPMD_FLAGS = -DEPMD_PORT_NO=$(EPMD_PORT_NO)
+
+#---------------------------------
+# source and object file information
+#---------------------------------
+
+EPMD_OBJS = $(OBJDIR)/epmd.o \
+ $(OBJDIR)/epmd_cli.o \
+ $(OBJDIR)/epmd_srv.o
+
+#---------------------------------
+# Build targets
+#---------------------------------
+
+
+all: $(BINDIR)/$(EPMD)
+
+docs:
+
+clean:
+ rm -f $(BINDIR)/$(EPMD)
+ rm -f $(ERL_TOP)/erts/obj/$(TARGET)/epmd.o
+ rm -f $(ERL_TOP)/erts/obj/$(TARGET)/epmd_cli.o
+ rm -f $(ERL_TOP)/erts/obj/$(TARGET)/epmd_srv.o
+ rm -f *.o
+ rm -f *~ core
+
+#
+# Objects & executables
+#
+
+$(BINDIR)/$(EPMD): $(EPMD_OBJS)
+ $(PURIFY) $(LD) $(LDFLAGS) -o $@ $(EPMD_OBJS) $(LIBS)
+
+$(OBJDIR)/%.o: %.c
+ $(CC) $(CFLAGS) $(EPMD_FLAGS) -o $@ -c $<
+
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: all
+ $(INSTALL_DIR) $(RELEASE_PATH)/erts-$(VSN)/bin
+ $(INSTALL_PROGRAM) $(INSTALL_PROGS) $(RELEASE_PATH)/erts-$(VSN)/bin
+
+
+release_docs_spec:
+
diff --git a/erts/epmd/src/epmd.c b/erts/epmd/src/epmd.c
new file mode 100644
index 0000000000..23ac421446
--- /dev/null
+++ b/erts/epmd/src/epmd.c
@@ -0,0 +1,629 @@
+/* -*- c-indent-level: 2; c-continued-statement-offset: 2 -*- */
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1998-2009. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Erlang Public License,
+ * Version 1.1, (the "License"); you may not use this file except in
+ * compliance with the License. You should have received a copy of the
+ * Erlang Public License along with this software. If not, it can be
+ * retrieved online at http://www.erlang.org/.
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include "epmd.h" /* Renamed from 'epmd_r4.h' */
+#include "epmd_int.h"
+
+#ifdef _OSE_
+# include "ose.h"
+# include "efs.h"
+#endif
+
+#ifdef HAVE_STDLIB_H
+# include <stdlib.h>
+#endif
+
+/* forward declarations */
+
+static void usage(EpmdVars *);
+static void run_daemon(EpmdVars*);
+static int get_port_no(void);
+#ifdef __WIN32__
+static int has_console(void);
+#endif
+
+#ifdef DONT_USE_MAIN
+
+static int epmd_main(int, char **, int);
+
+/* VxWorks fill 10 stack words with zero when a function is called
+ from the shell. So it is safe to have argv and argc as parameters
+ even if they are not given in the call. */
+
+#define MAX_DEBUG 10
+
+int epmd_dbg(int level,int port) /* Utility to debug epmd... */
+{
+ char* argv[MAX_DEBUG+2];
+ char ibuff[100];
+ int argc = 0;
+
+ argv[argc++] = "epmd";
+ if(level > MAX_DEBUG)
+ level = MAX_DEBUG;
+ for(;level;--level)
+ argv[argc++] = "-d";
+ if(port)
+ {
+ argv[argc++] = "-port";
+ sprintf(ibuff,"%d",port);
+ argv[argc++] = ibuff;
+ }
+ argv[argc] = NULL;
+
+ return epmd(argc,argv);
+
+}
+
+static char *mystrdup(char *s)
+{
+ char *r = malloc(strlen(s)+1);
+ strcpy(r,s);
+ return r;
+}
+
+#ifdef _OSE_
+
+struct args_sig {
+ SIGSELECT sig_no;
+ int argc ;
+ char argv[20][20];
+};
+
+union SIGNAL {
+ SIGSELECT sig_no;
+ struct args_sig args;
+};
+
+/* Start function. It may be called from the start script as well as from
+ the OSE shell directly (using late start hooks). It spawns epmd as an
+ OSE process which calls the epmd main function. */
+int start_ose_epmd(int argc, char **argv) {
+ union SIGNAL *sig;
+ PROCESS epmd_;
+ OSENTRYPOINT ose_epmd;
+ int i;
+
+ if(hunt("epmd", 0, &epmd_, NULL)) {
+ fprintf(stderr, "Warning! EPMD already exists (%u).\n", epmd_);
+ return 0;
+ }
+ else {
+ /* copy start args to signal */
+ sig = alloc(sizeof(struct args_sig), 0);
+ sig->args.argc = argc;
+ for(i=0; i<argc; i++) {
+ strcpy((sig->args.argv)[i], argv[i]);
+ }
+ /* start epmd and send signal */
+ epmd_ = create_process(OS_BG_PROC, /* processtype */
+ "epmd", /* name */
+ ose_epmd, /* entrypoint */
+ 16383, /* stacksize */
+ 20, /* priority */
+ 0, /* timeslice */
+ 0, /* block */
+ NULL,0,0); /* not used */
+ efs_clone(epmd_);
+ start(epmd_);
+ send(&sig, epmd_);
+#ifdef DEBUG
+ printf("EPMD ID: %li\n", epmd_);
+#endif
+ }
+ return 0;
+}
+
+OS_PROCESS(ose_epmd) {
+ union SIGNAL *sig;
+ static const SIGSELECT rec_any_sig[] = { 0 };
+ int i, argc;
+ char **argv;
+
+ sig = receive((SIGSELECT*)rec_any_sig);
+
+ argc = sig->args.argc;
+ argv = (char **)malloc((argc+1)*sizeof(char *));
+ for(i=0; i<argc; i++) {
+ argv[i] = (char *)malloc(strlen((sig->args.argv)[i])+1);
+ strcpy(argv[i], (sig->args.argv)[i]);
+ }
+ argv[argc] = NULL;
+ free_buf(&sig);
+
+ epmd(argc, argv);
+
+ for(i=0; i<argc; i++) {
+ free(argv[i]);
+ }
+ free(argv);
+}
+
+#else /* ifdef _OSE_ */
+
+/* VxWorks start function */
+int start_epmd(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9)
+char *a0, *a1, *a2, *a3, *a4, *a5, *a6, *a7, *a8, *a9;
+{
+ char* argarr[] = {a0,a1,a2,a3,a4,a5,a6,a7,a8,a9};
+ int i;
+ char** argv = malloc(sizeof(char *)*10);
+ int argvsiz = 10;
+ int argc = 1;
+ char* tmp = malloc(100);
+ int tmpsiz = 100;
+ char* pplast;
+ char* token;
+
+ argv[0] = mystrdup("epmd");
+ argv[1] = NULL;
+
+ for(i=0;i<10;++i)
+ {
+ if(argarr[i] == NULL || *argarr[i] == '\0')
+ continue;
+ if(strlen(argarr[i]) >= tmpsiz)
+ tmp = realloc(tmp, tmpsiz = (strlen(argarr[i])+1));
+ strcpy(tmp,argarr[i]);
+ for(token = strtok_r(tmp," ",&pplast);
+ token != NULL;
+ token = strtok_r(NULL," ",&pplast))
+ {
+ if(argc >= argvsiz - 1)
+ argv = realloc(argv,sizeof(char *) * (argvsiz += 10));
+ argv[argc++] = mystrdup(token);
+ argv[argc] = NULL;
+ }
+ }
+ free(tmp);
+ return taskSpawn("epmd",100,VX_FP_TASK,20000,epmd_main,
+ argc,(int) argv,1,
+ 0,0,0,0,0,0,0);
+}
+
+#endif /* _OSE_ */
+
+
+
+
+int epmd(int argc, char **argv)
+{
+ return epmd_main(argc,argv,0);
+}
+
+static int epmd_main(int argc, char** argv, int free_argv)
+#else
+int main(int argc, char** argv)
+#endif /* DONT_USE_MAIN */
+{
+ EpmdVars g_empd_vars;
+ EpmdVars *g = &g_empd_vars;
+#ifdef __WIN32__
+ WORD wVersionRequested;
+ WSADATA wsaData;
+ int err;
+
+ wVersionRequested = MAKEWORD(1, 1);
+
+ err = WSAStartup(wVersionRequested, &wsaData);
+ if (err != 0)
+ epmd_cleanup_exit(g,1);
+
+ if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion ) != 1) {
+ WSACleanup();
+ epmd_cleanup_exit(g,1);
+ }
+#endif
+#ifdef DONT_USE_MAIN
+ if(free_argv)
+ g->argv = argv;
+ else
+ g->argv = NULL;
+#else
+ g->argv = NULL;
+#endif
+
+ g->port = get_port_no();
+ g->debug = 0;
+
+ g->silent = 0;
+ g->is_daemon = 0;
+ g->packet_timeout = CLOSE_TIMEOUT; /* Default timeout */
+ g->delay_accept = 0;
+ g->delay_write = 0;
+ g->progname = argv[0];
+ g->listenfd = -1;
+ g->conn = NULL;
+ g->nodes.reg = g->nodes.unreg = g->nodes.unreg_tail = NULL;
+ g->nodes.unreg_count = 0;
+ g->active_conn = 0;
+
+ argc--;
+ argv++;
+ while (argc > 0) {
+ if ((strcmp(argv[0], "-debug")==0) ||
+ (strcmp(argv[0], "-d")==0)) {
+ g->debug += 1;
+ argv++; argc--;
+ } else if (strcmp(argv[0], "-packet_timeout") == 0) {
+ if ((argc == 1) ||
+ ((g->packet_timeout = atoi(argv[1])) == 0))
+ usage(g);
+ argv += 2; argc -= 2;
+ } else if (strcmp(argv[0], "-delay_accept") == 0) {
+ if ((argc == 1) ||
+ ((g->delay_accept = atoi(argv[1])) == 0))
+ usage(g);
+ argv += 2; argc -= 2;
+ } else if (strcmp(argv[0], "-delay_write") == 0) {
+ if ((argc == 1) ||
+ ((g->delay_write = atoi(argv[1])) == 0))
+ usage(g);
+ argv += 2; argc -= 2;
+ } else if (strcmp(argv[0], "-daemon") == 0) {
+ g->is_daemon = 1;
+ argv++; argc--;
+ } else if (strcmp(argv[0], "-kill") == 0) {
+ if (argc == 1)
+ kill_epmd(g);
+ else
+ usage(g);
+ epmd_cleanup_exit(g,0);
+ } else if (strcmp(argv[0], "-port") == 0) {
+ if ((argc == 1) ||
+ ((g->port = atoi(argv[1])) == 0))
+ usage(g);
+ argv += 2; argc -= 2;
+ } else if (strcmp(argv[0], "-names") == 0) {
+ if (argc == 1)
+ epmd_call(g, EPMD_NAMES_REQ);
+ else
+ usage(g);
+ epmd_cleanup_exit(g,0);
+ } else if (strcmp(argv[0], "-started") == 0) {
+ g->silent = 1;
+ if (argc == 1)
+ epmd_call(g, EPMD_NAMES_REQ);
+ else
+ usage(g);
+ epmd_cleanup_exit(g,0);
+ } else if (strcmp(argv[0], "-dump") == 0) {
+ if (argc == 1)
+ epmd_call(g, EPMD_DUMP_REQ);
+ else
+ usage(g);
+ epmd_cleanup_exit(g,0);
+ }
+ else
+ usage(g);
+ }
+ dbg_printf(g,0,"epmd running - daemon = %d",g->is_daemon);
+
+#ifndef NO_SYSCONF
+ if ((g->max_conn = sysconf(_SC_OPEN_MAX)) <= 0)
+#endif
+ g->max_conn = MAX_FILES;
+
+ /*
+ * max_conn must not be greater than FD_SETSIZE.
+ * (at least QNX crashes)
+ *
+ * More correctly, it must be FD_SETSIZE - 1, beacuse the
+ * listen FD is stored outside the connection array.
+ */
+
+ if (g->max_conn > FD_SETSIZE) {
+ g->max_conn = FD_SETSIZE - 1;
+ }
+
+ if (g->is_daemon) {
+ run_daemon(g);
+ } else {
+ run(g);
+ }
+ return 0;
+}
+
+#ifndef NO_DAEMON
+static void run_daemon(EpmdVars *g)
+{
+ register int child_pid, fd;
+
+ dbg_tty_printf(g,2,"fork a daemon");
+
+ /* fork to make sure first child is not a process group leader */
+ if (( child_pid = fork()) < 0)
+ {
+#ifndef NO_SYSLOG
+ syslog(LOG_ERR,"erlang mapper daemon cant fork %m");
+#endif
+ epmd_cleanup_exit(g,1);
+ }
+ else if (child_pid > 0)
+ {
+ dbg_tty_printf(g,2,"daemon child is %d",child_pid);
+ epmd_cleanup_exit(g,0); /*parent */
+ }
+
+ if (setsid() < 0)
+ {
+ dbg_perror(g,"epmd: Cant setsid()");
+ epmd_cleanup_exit(g,1);
+ }
+
+ /* ???? */
+
+
+ signal(SIGHUP, SIG_IGN);
+
+ /* We don't want to be session leader so we fork again */
+
+ if ((child_pid = fork()) < 0)
+ {
+#ifndef NO_SYSLOG
+ syslog(LOG_ERR,"erlang mapper daemon cant fork 2'nd time %m");
+#endif
+ epmd_cleanup_exit(g,1);
+ }
+ else if (child_pid > 0)
+ {
+ dbg_tty_printf(g,2,"daemon 2'nd child is %d",child_pid);
+ epmd_cleanup_exit(g,0); /*parent */
+ }
+
+ /* move cwd to root to make sure we are not on a mounted filesystem */
+ chdir("/");
+
+ umask(0);
+
+ for (fd = 0; fd < g->max_conn ; fd++) /* close all files ... */
+ close(fd);
+ /* Syslog on linux will try to write to whatever if we dont
+ inform it of that the log is closed. */
+ closelog();
+
+ /* These chouldn't be needed but for safety... */
+
+ open("/dev/null", O_RDONLY); /* Order is important! */
+ open("/dev/null", O_WRONLY);
+ open("/dev/null", O_WRONLY);
+
+ errno = 0; /* if set by open */
+
+ run(g);
+}
+
+#endif /* NO_DAEMON */
+
+#ifdef __WIN32__
+static int has_console(void)
+{
+ HANDLE handle = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ return 0;
+ } else {
+ CloseHandle(handle);
+ return 1;
+ }
+}
+
+static void run_daemon(EpmdVars *g)
+{
+ if (has_console()) {
+ if (spawnvp(_P_DETACH, __argv[0], __argv) == -1) {
+ fprintf(stderr, "Failed to spawn detached epmd\n");
+ exit(1);
+ }
+ exit(0);
+ }
+
+ close(0);
+ close(1);
+ close(2);
+
+ /* These chouldn't be needed but for safety... */
+
+ open("nul", O_RDONLY);
+ open("nul", O_WRONLY);
+ open("nul", O_WRONLY);
+
+ run(g);
+}
+#endif
+
+#if (defined(VXWORKS) || defined(_OSE_))
+static void run_daemon(EpmdVars *g)
+{
+ run(g);
+}
+#endif
+
+
+/***************************************************************************
+ * Misc support routines
+ *
+ */
+
+static void usage(EpmdVars *g)
+{
+ fprintf(stderr, "usage: epmd [-d|-debug] [DbgExtra...] [-port No] [-daemon]\n");
+ fprintf(stderr, " [-d|-debug] [-port No] [-names|-kill]\n\n");
+ fprintf(stderr, "See the Erlang epmd manual page for info about the usage.\n");
+ fprintf(stderr, "The -port and DbgExtra options are\n\n");
+ fprintf(stderr, " -port No\n");
+ fprintf(stderr, " Let epmd listen to another port than default %d\n",
+ EPMD_PORT_NO);
+ fprintf(stderr, " -d\n");
+ fprintf(stderr, " -debug\n");
+ fprintf(stderr, " Enable debugging. This will give a log to\n");
+ fprintf(stderr, " the standard error stream. It will shorten\n");
+ fprintf(stderr, " the number of saved used node names to 5.\n\n");
+ fprintf(stderr, " If you give more than one debug flag you may\n");
+ fprintf(stderr, " get more debugging information.\n\n");
+ fprintf(stderr, " -packet_timout Seconds\n");
+ fprintf(stderr, " Set the number of seconds a connection can be\n");
+ fprintf(stderr, " inactive before epmd times out and closes the\n");
+ fprintf(stderr, " connection (default 60).\n\n");
+ fprintf(stderr, " -delay_accept Seconds\n");
+ fprintf(stderr, " To simulate a busy server you can insert a\n");
+ fprintf(stderr, " delay between epmd gets notified about that\n");
+ fprintf(stderr, " a new connection is requested and when the\n");
+ fprintf(stderr, " connections gets accepted.\n\n");
+ fprintf(stderr, " -delay_write Seconds\n");
+ fprintf(stderr, " Also a simulation of a busy server. Inserts\n");
+ fprintf(stderr, " a delay before a reply is sent.\n");
+ epmd_cleanup_exit(g,1);
+}
+
+/***************************************************************************
+ * Error reporting - dbg_printf() & dbg_tty_printf & dbg_perror()
+ *
+ * The first form will print out on tty or syslog depending on
+ * if it runs as deamon or not. The second form will never print
+ * out on syslog.
+ *
+ * The arguments are
+ *
+ * g Epmd variables
+ * from_level From what debug level we print. 0 means always.
+ * (This argument is missing from dbg_perror() )
+ * format Format string
+ * args... Arguments to print out according to the format
+ *
+ */
+
+static void dbg_gen_printf(int onsyslog,int perr,int from_level,
+ EpmdVars *g,const char *format, va_list args)
+{
+ time_t now;
+ char *timestr;
+ char buf[2048];
+
+ if (g->is_daemon)
+ {
+#ifndef NO_SYSLOG
+ if (onsyslog)
+ {
+ vsprintf(buf, format, args);
+ syslog(LOG_ERR,"epmd: %s",buf);
+ }
+#endif
+ }
+ else
+ {
+ int len;
+
+ time(&now);
+ timestr = (char *)ctime(&now);
+ sprintf(buf, "epmd: %.*s: ", (int) strlen(timestr)-1, timestr);
+ len = strlen(buf);
+ vsprintf(buf + len, format, args);
+ if (perr == 1)
+ perror(buf);
+ else
+ fprintf(stderr,"%s\r\n",buf);
+ }
+}
+
+
+void dbg_perror(EpmdVars *g,const char *format,...)
+{
+ va_list args;
+ va_start(args, format);
+ dbg_gen_printf(1,1,0,g,format,args);
+ va_end(args);
+}
+
+
+void dbg_tty_printf(EpmdVars *g,int from_level,const char *format,...)
+{
+ if (g->debug >= from_level) {
+ va_list args;
+ va_start(args, format);
+ dbg_gen_printf(0,0,from_level,g,format,args);
+ va_end(args);
+ }
+}
+
+void dbg_printf(EpmdVars *g,int from_level,const char *format,...)
+{
+ if (g->debug >= from_level) {
+ va_list args;
+ va_start(args, format);
+ dbg_gen_printf(1,0,from_level,g,format,args);
+ va_end(args);
+ }
+}
+
+
+/***************************************************************************
+ *
+ * This function is to clean up all filedescriptors and free up memory on
+ * VxWorks.
+ * This function exits, there is nothing else to do when all here is run.
+ *
+ */
+
+static void free_all_nodes(EpmdVars *g)
+{
+ Node *tmp;
+ for(tmp=g->nodes.reg; tmp != NULL; tmp = g->nodes.reg){
+ g->nodes.reg = tmp->next;
+ free(tmp);
+ }
+ for(tmp=g->nodes.unreg; tmp != NULL; tmp = g->nodes.unreg){
+ g->nodes.unreg = tmp->next;
+ free(tmp);
+ }
+}
+void epmd_cleanup_exit(EpmdVars *g, int exitval)
+{
+ int i;
+
+ if(g->conn){
+ for (i = 0; i < g->max_conn; i++)
+ if (g->conn[i].open == EPMD_TRUE)
+ epmd_conn_close(g,&g->conn[i]);
+ free(g->conn);
+ }
+ if(g->listenfd >= 0)
+ close(g->listenfd);
+ free_all_nodes(g);
+ if(g->argv){
+ for(i=0; g->argv[i] != NULL; ++i)
+ free(g->argv[i]);
+ free(g->argv);
+ }
+
+
+ exit(exitval);
+}
+
+static int get_port_no(void)
+{
+ char* port_str = getenv("ERL_EPMD_PORT");
+ return (port_str != NULL) ? atoi(port_str) : EPMD_PORT_NO;
+}
+
diff --git a/erts/epmd/src/epmd.h b/erts/epmd/src/epmd.h
new file mode 100644
index 0000000000..9e939ee38e
--- /dev/null
+++ b/erts/epmd/src/epmd.h
@@ -0,0 +1,37 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1998-2009. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Erlang Public License,
+ * Version 1.1, (the "License"); you may not use this file except in
+ * compliance with the License. You should have received a copy of the
+ * Erlang Public License along with this software. If not, it can be
+ * retrieved online at http://www.erlang.org/.
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+/* The port number is now defined in a makefile */
+
+/* Definitions of message codes */
+
+#define EPMD_ALIVE_REQ 'a'
+#define EPMD_ALIVE_OK_RESP 'Y'
+#define EPMD_PORT_REQ 'p'
+#define EPMD_NAMES_REQ 'n'
+#define EPMD_DUMP_REQ 'd'
+#define EPMD_KILL_REQ 'k'
+#define EPMD_STOP_REQ 's'
+
+/* New epmd messages */
+
+#define EPMD_ALIVE2_REQ 'x' /* 120 */
+#define EPMD_PORT2_REQ 'z' /* 122 */
+#define EPMD_ALIVE2_RESP 'y' /* 121 */
+#define EPMD_PORT2_RESP 'w' /* 119 */
diff --git a/erts/epmd/src/epmd_cli.c b/erts/epmd/src/epmd_cli.c
new file mode 100644
index 0000000000..c12f711bc5
--- /dev/null
+++ b/erts/epmd/src/epmd_cli.c
@@ -0,0 +1,127 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1998-2009. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Erlang Public License,
+ * Version 1.1, (the "License"); you may not use this file except in
+ * compliance with the License. You should have received a copy of the
+ * Erlang Public License along with this software. If not, it can be
+ * retrieved online at http://www.erlang.org/.
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include "epmd.h" /* Renamed from 'epmd_r4.h' */
+#include "epmd_int.h"
+
+/* forward declarations */
+
+static int conn_to_epmd(EpmdVars*);
+static int read_fill(int,char*,int);
+
+
+void kill_epmd(EpmdVars *g)
+{
+ char buf[5];
+ int fd, rval;
+
+ fd = conn_to_epmd(g);
+ put_int16(1,buf);
+ buf[2] = EPMD_KILL_REQ;
+ if (write(fd, buf, 3) != 3) {
+ printf("epmd: Can't write to epmd\n");
+ epmd_cleanup_exit(g,1);
+ }
+ if ((rval = read_fill(fd,buf,2)) == 2) {
+ printf("Killed\n");
+ epmd_cleanup_exit(g,0);
+ } else if (rval < 0) {
+ printf("epmd: failed to read answer from local epmd\n");
+ epmd_cleanup_exit(g,1);
+ } else { /* rval is now 0 or 1 */
+ buf[rval] = '\0';
+ printf("epmd: local epmd responded with <%s>\n", buf);
+ epmd_cleanup_exit(g,1);
+ }
+}
+
+/* what == EPMD_NAMES_REQ || EPMD_DUMP_REQ */
+
+void epmd_call(EpmdVars *g,int what)
+{
+ char buf[OUTBUF_SIZE];
+ int rval,fd,i,j;
+
+ fd = conn_to_epmd(g);
+ put_int16(1,buf);
+ buf[2] = what;
+ write(fd,buf,3);
+ if (read(fd,(char *)&i,4) != 4) {
+ if (!g->silent)
+ printf("epmd: no response from local epmd\n");
+ epmd_cleanup_exit(g,1);
+ }
+ j = ntohl(i);
+ if (!g->silent)
+ printf("epmd: up and running on port %d with data:\n", j);
+ while(1) {
+ if ((rval = read(fd,buf,1)) <= 0) {
+ close(fd);
+ epmd_cleanup_exit(g,0);
+ }
+ buf[rval] = '\0';
+ if (!g->silent)
+ printf("%s",buf);
+ }
+}
+
+
+
+static int conn_to_epmd(EpmdVars *g)
+{
+ struct EPMD_SOCKADDR_IN address;
+ int connect_sock;
+
+ connect_sock = socket(FAMILY, SOCK_STREAM, 0);
+ if (connect_sock<0)
+ goto error;
+
+ { /* store port number in unsigned short */
+ unsigned short sport = g->port;
+ SET_ADDR_LOOPBACK(address, FAMILY, sport);
+ }
+
+ if (connect(connect_sock, (struct sockaddr*)&address, sizeof address) < 0)
+ goto error;
+ return connect_sock;
+
+ error:
+ if (!g->silent) {
+ fprintf(stderr, "epmd: Cannot connect to local epmd\n");
+ }
+ epmd_cleanup_exit(g,1);
+ return -1;
+}
+
+/* Fill buffer, return buffer length, 0 for EOF, < 0 for error. */
+static int read_fill(int fd,char *buf,int len)
+{
+ int i;
+ int got = 0;
+
+ do {
+ if ((i = read(fd, buf+got, len-got)) <= 0)
+ return (i);
+ got += i;
+ } while (got < len);
+ return (len);
+}
diff --git a/erts/epmd/src/epmd_int.h b/erts/epmd/src/epmd_int.h
new file mode 100644
index 0000000000..b120b44579
--- /dev/null
+++ b/erts/epmd/src/epmd_int.h
@@ -0,0 +1,346 @@
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1998-2009. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Erlang Public License,
+ * Version 1.1, (the "License"); you may not use this file except in
+ * compliance with the License. You should have received a copy of the
+ * Erlang Public License along with this software. If not, it can be
+ * retrieved online at http://www.erlang.org/.
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * %CopyrightEnd%
+ */
+/*
+ * This file is for internal use within epmd.
+ */
+
+/* This file don't depend on "sys.h" so we have to do some target
+ definitions ourselves */
+
+#ifdef __WIN32__
+#define NO_SYSLOG
+#define NO_SYSCONF
+#define NO_DAEMON
+#endif
+
+#ifdef VXWORKS
+#define NO_SYSLOG
+#define NO_SYSCONF
+#define NO_DAEMON
+#define NO_FCNTL
+#define DONT_USE_MAIN
+#endif
+
+#ifdef _OSE_
+#define NO_SYSLOG
+#define NO_SYSCONF
+#define NO_DAEMON
+#define DONT_USE_MAIN
+#ifndef HAVE_SYS_TIME_H
+#define HAVE_SYS_TIME_H
+#endif
+#ifndef HAVE_UNISTD_H
+#define HAVE_UNISTD_H
+#endif
+#endif
+
+/* ************************************************************************ */
+/* Standard includes */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef __WIN32__
+# ifndef WINDOWS_H_INCLUDES_WINSOCK2_H
+# include <winsock2.h>
+# endif
+# include <windows.h>
+# include <process.h>
+#endif
+
+#include <sys/types.h>
+#include <fcntl.h>
+
+#ifdef VXWORKS
+# include <sys/times.h>
+# include <time.h>
+# include <selectLib.h>
+# include <sysLib.h>
+# include <sockLib.h>
+# include <ioLib.h>
+# include <taskLib.h>
+# include <rpc/rpc.h>
+#else /* ! VXWORKS */
+#ifndef __WIN32__
+# ifdef TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+# else
+# ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+# else
+# include <time.h>
+# endif
+# endif
+#endif
+#endif /* ! VXWORKS */
+
+#if (!defined(__WIN32__) && !defined(_OSE_))
+# include <netinet/in.h>
+# include <sys/socket.h>
+# include <sys/stat.h>
+
+# ifdef DEF_INADDR_LOOPBACK_IN_RPC_TYPES_H
+# include <rpc/types.h>
+# endif
+
+# include <arpa/inet.h>
+# include <netinet/tcp.h>
+#endif /* ! WIN32 */
+
+#ifndef _OSE_
+#include <ctype.h>
+#include <signal.h>
+#endif
+
+#include <errno.h>
+
+#ifndef NO_SYSLOG
+# include <syslog.h>
+#endif
+
+#ifdef SYS_SELECT_H
+# include <sys/select.h>
+#endif
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdarg.h>
+
+#ifdef _OSE_
+# include "ose.h"
+# include "inet.h"
+# include "sys/stat.h"
+#endif
+
+
+/* ************************************************************************ */
+/* Replace some functions by others by making the function name a macro */
+
+#ifdef __WIN32__
+# define close(s) closesocket((s))
+# define write(a,b,c) send((a),(b),(c),0)
+# define read(a,b,c) recv((a),(char *)(b),(c),0)
+# define sleep(s) Sleep((s) * 1000)
+# define ioctl(s,r,o) ioctlsocket((s),(r),(o))
+#endif /* WIN32 */
+
+#ifdef VXWORKS
+#define sleep(n) taskDelay((n) * sysClkRateGet())
+#endif /* VXWORKS */
+
+#ifdef _OSE_
+#define sleep(n) delay((n))
+#endif
+
+#ifdef USE_BCOPY
+# define memcpy(a, b, c) bcopy((b), (a), (c))
+# define memcmp(a, b, c) bcmp((a), (b), (c))
+# define memzero(buf, len) bzero((buf), (len))
+#else
+# define memzero(buf, len) memset((buf), '\0', (len))
+#endif
+
+/* ************************************************************************ */
+/* Try to find replacement values for undefined system parameters */
+
+#if defined(__WIN32__) && !defined(EADDRINUSE)
+# define EADDRINUSE WSAEADDRINUSE
+#endif
+
+#ifndef SOMAXCONN
+# define SOMAXCONN 128
+#endif
+
+/*
+ * How to get max no of file descriptors? We used to use NOFILE from
+ * <sys/param.h>, but that tends to have little relation to reality.
+ * Best is to use sysconf() (POSIX), but we'll just punt if that isn't
+ * available. Start out with a high value because it will also be
+ * used as the number of file descriptors given to select() (it's is
+ * a terrible bug not to have all file descriptors included in the select()).
+ * The value will be adjusted down if FD_SETSIZE is smaller.
+ */
+
+#define MAX_FILES 2048 /* if sysconf() isn't available, or fails */
+
+/* ************************************************************************ */
+/* Macros that let us use IPv6 */
+
+#if defined(HAVE_IN6) && defined(AF_INET6) && defined(EPMD6)
+
+#define EPMD_SOCKADDR_IN sockaddr_in6
+#define FAMILY AF_INET6
+
+#define SET_ADDR_LOOPBACK(addr, af, port) do { \
+ static u_int32_t __addr[4] = IN6ADDR_LOOPBACK_INIT; \
+ memset((char*)&(addr), 0, sizeof(addr)); \
+ (addr).sin6_family = (af); \
+ (addr).sin6_flowinfo = 0; \
+ (addr).sin6_addr.s6_addr32[0] = __addr[0]; \
+ (addr).sin6_addr.s6_addr32[1] = __addr[1]; \
+ (addr).sin6_addr.s6_addr32[2] = __addr[2]; \
+ (addr).sin6_addr.s6_addr32[3] = __addr[3]; \
+ (addr).sin6_port = htons(port); \
+ } while(0)
+
+#define SET_ADDR_ANY(addr, af, port) do { \
+ static u_int32_t __addr[4] = IN6ADDR_ANY_INIT; \
+ memset((char*)&(addr), 0, sizeof(addr)); \
+ (addr).sin6_family = (af); \
+ (addr).sin6_flowinfo = 0; \
+ (addr).sin6_addr.s6_addr32[0] = __addr[0]; \
+ (addr).sin6_addr.s6_addr32[1] = __addr[1]; \
+ (addr).sin6_addr.s6_addr32[2] = __addr[2]; \
+ (addr).sin6_addr.s6_addr32[3] = __addr[3]; \
+ (addr).sin6_port = htons(port); \
+ } while(0)
+
+#else /* Not IP v6 */
+
+#define EPMD_SOCKADDR_IN sockaddr_in
+#define FAMILY AF_INET
+
+#define SET_ADDR_LOOPBACK(addr, af, port) do { \
+ memset((char*)&(addr), 0, sizeof(addr)); \
+ (addr).sin_family = (af); \
+ (addr).sin_addr.s_addr = htonl(INADDR_LOOPBACK); \
+ (addr).sin_port = htons(port); \
+ } while(0)
+
+#define SET_ADDR_ANY(addr, af, port) do { \
+ memset((char*)&(addr), 0, sizeof(addr)); \
+ (addr).sin_family = (af); \
+ (addr).sin_addr.s_addr = htonl(INADDR_ANY); \
+ (addr).sin_port = htons(port); \
+ } while(0)
+
+#endif /* Not IP v6 */
+
+/* ************************************************************************ */
+/* Our own definitions */
+
+#define EPMD_FALSE 0
+#define EPMD_TRUE 1
+
+/* If no activity we let select() return every IDLE_TIMEOUT second
+ A file descriptor that are idle for CLOSE_TIMEOUT seconds and
+ isn't a ALIVE socket is probably hanging and we close it */
+
+#define IDLE_TIMEOUT 5
+#define CLOSE_TIMEOUT 60
+
+/* We save the name of nodes that are unregistered. If a new
+ node register the name we want to increment the "creation",
+ a constant 1..3. But we put an limit to this saving to keep
+ the lookup fast and not to leak memory. */
+
+#define MAX_UNREG_COUNT 1000
+#define DEBUG_MAX_UNREG_COUNT 5
+
+/* Maximum length of a node name == atom name */
+#define MAXSYMLEN 255
+
+#define INBUF_SIZE 1024
+#define OUTBUF_SIZE 1024
+
+#define get_int16(s) ((((unsigned char*) (s))[0] << 8) | \
+ (((unsigned char*) (s))[1]))
+
+#define put_int16(i, s) {((unsigned char*)(s))[0] = ((i) >> 8) & 0xff; \
+ ((unsigned char*)(s))[1] = (i) & 0xff;}
+
+/* ************************************************************************ */
+
+/* Stuctures used by server */
+
+typedef struct {
+ int fd; /* File descriptor */
+ unsigned open:1; /* TRUE if open */
+ unsigned keep:1; /* Don't close when sent reply */
+ unsigned got; /* # of bytes we have got */
+ unsigned want; /* Number of bytes we want */
+ char *buf; /* The remaining buffer */
+
+ time_t mod_time; /* Last activity on this socket */
+} Connection;
+
+struct enode {
+ struct enode *next;
+ int fd; /* The socket in use */
+ unsigned short port; /* Port number of Erlang node */
+ char symname[MAXSYMLEN+1]; /* Name of the Erlang node */
+ short creation; /* Started as a random number 1..3 */
+ char nodetype; /* 77 = normal erlang node 72 = hidden (c-node */
+ char protocol; /* 0 = tcp/ipv4 */
+ unsigned short highvsn; /* 0 = OTP-R3 erts-4.6.x, 1 = OTP-R4 erts-4.7.x*/
+ unsigned short lowvsn;
+ char extra[MAXSYMLEN+1];
+};
+
+typedef struct enode Node;
+
+typedef struct {
+ Node *reg;
+ Node *unreg;
+ Node *unreg_tail;
+ int unreg_count;
+} Nodes;
+
+
+/* This is the structure with all variables needed to pass on
+ to all functions. This makes this program reentrant */
+
+typedef struct {
+ int port;
+ int debug;
+ int silent;
+ int is_daemon;
+ unsigned packet_timeout;
+ unsigned delay_accept;
+ unsigned delay_write;
+ int max_conn;
+ int active_conn;
+ char *progname;
+ Connection *conn;
+ Nodes nodes;
+ fd_set orig_read_mask;
+ int listenfd;
+ char **argv;
+} EpmdVars;
+
+void dbg_printf(EpmdVars*,int,const char*,...);
+void dbg_tty_printf(EpmdVars*,int,const char*,...);
+void dbg_perror(EpmdVars*,const char*,...);
+void kill_epmd(EpmdVars*);
+void epmd_call(EpmdVars*,int);
+void run(EpmdVars*);
+void epmd_cleanup_exit(EpmdVars*, int);
+int epmd_conn_close(EpmdVars*,Connection*);
+
+#ifdef DONT_USE_MAIN
+int start_epmd(char *,char *,char *,char *,char *,char *,char *,char *,char *,char *);
+int epmd(int,char **);
+int epmd_dbg(int,int);
+#endif
+
+
diff --git a/erts/epmd/src/epmd_srv.c b/erts/epmd/src/epmd_srv.c
new file mode 100644
index 0000000000..b71e27cffd
--- /dev/null
+++ b/erts/epmd/src/epmd_srv.c
@@ -0,0 +1,1254 @@
+/* -*- c-indent-level: 2; c-continued-statement-offset: 2 -*- */
+/*
+ * %CopyrightBegin%
+ *
+ * Copyright Ericsson AB 1998-2009. All Rights Reserved.
+ *
+ * The contents of this file are subject to the Erlang Public License,
+ * Version 1.1, (the "License"); you may not use this file except in
+ * compliance with the License. You should have received a copy of the
+ * Erlang Public License along with this software. If not, it can be
+ * retrieved online at http://www.erlang.org/.
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * %CopyrightEnd%
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+#include "epmd.h" /* Renamed from 'epmd_r4.h' */
+#include "epmd_int.h"
+
+/*
+ *
+ * This server is a local name server for Erlang nodes. Erlang nodes can
+ * ask this server for the listening port of other Erlang nodes on the
+ * machine EPMD is running on. New distributed nodes that are started
+ * register their names with this server.
+ *
+ * To be accessible to all Erlang nodes this server listens to a well
+ * known port EPMD_PORT_NO (curently port 4369) where requests
+ * for connections can be sent.
+ *
+ * To keep track of when registered Erlang nodes are terminated this
+ * server keeps the socket open where the request for registration was
+ * made.
+ *
+ * The protocol is briefly documented in "erl_ext_dist.txt". All requests
+ * to this server are done with a packet
+ *
+ * 2 n
+ * +--------+---------+
+ * | Length | Request |
+ * +--------+---------+
+ *
+ * In all but one case there is only one request for each connection made
+ * to this server so we can safely close the socket after sending the
+ * reply. The exception is ALIVE_REQ where we keep the connection
+ * open without sending any data. When we receive a "close" this is
+ * an indication that the Erlang node was terminated. The termination
+ * may have been "normal" or caused by a crash. The operating system
+ * ensure that the connection is closed either way.
+ *
+ * Reading is done non-blocking, i.e. we call a "read" only if we are
+ * told by the "select" function that there are data to read.
+ *
+ * Two databases are used: One node database where the registered names
+ * of the nodes are stored, and one connection database where the state
+ * of sockets and the data buffers is stored.
+ *
+ * Incomplete packets are thrown away after a timout. The Erlang node
+ * doing the request is responsible for completing in it in a reasonable time.
+ *
+ * Note that if the server gets busy it may not have time to
+ * process all requests for connection. The "accept()" function
+ * will on most operating systems silently refuse to accept more
+ * than 5 outstanding requests. It is the client's responsibility
+ * to retry the request a number of times with random time interval.
+ * The "-debug" flag will insert a delay so you can test this
+ * behaviour.
+ *
+ * FIXME: In this code we assume that the packets we send on each
+ * socket is so small that a "write()" never block
+ *
+ * FIXME: We never restarts a read or write that was terminated
+ * by an interrupt. Do we need to?
+ *
+ */
+
+/* We use separate data structures for node names and connections
+ so that a request will not use a slot with a name that we
+ want to resuse later incrementing the "creation" */
+
+
+/* forward declarations */
+
+static void do_request(EpmdVars*,int,Connection*,char*,int);
+static int do_accept(EpmdVars*,int);
+static void do_read(EpmdVars*,Connection*);
+static time_t current_time(EpmdVars*);
+
+static Connection *conn_init(EpmdVars*);
+static int conn_open(EpmdVars*,int);
+static int conn_close_fd(EpmdVars*,int);
+
+static void node_init(EpmdVars*);
+static Node *node_reg(EpmdVars*,char*,int,int);
+static Node *node_reg2(EpmdVars*,char*, int, int, unsigned char, unsigned char, int, int, char*);
+static int node_unreg(EpmdVars*,char*);
+static int node_unreg_sock(EpmdVars*,int);
+
+static int reply(EpmdVars*,int,char *,int);
+static void dbg_print_buf(EpmdVars*,char *,int);
+static void print_names(EpmdVars*);
+
+
+void run(EpmdVars *g)
+{
+ int listensock;
+ int i;
+ int opt;
+ struct EPMD_SOCKADDR_IN iserv_addr;
+
+ node_init(g);
+ g->conn = conn_init(g);
+
+ dbg_printf(g,2,"try to initiate listening port %d", g->port);
+
+ if ((listensock = socket(FAMILY,SOCK_STREAM,0)) < 0) {
+ dbg_perror(g,"error opening stream socket");
+ epmd_cleanup_exit(g,1);
+ }
+ g->listenfd = listensock;
+
+ /*
+ * Initialize number of active file descriptors.
+ * Stdin, stdout, and stderr are still open.
+ * One for the listen socket.
+ */
+ g->active_conn = 3+1;
+
+ /*
+ * Note that we must not enable the SO_REUSEADDR on Windows,
+ * because addresses will be reused even if they are still in use.
+ */
+
+#if (!defined(__WIN32__) && !defined(_OSE_))
+ /* We ignore the SIGPIPE signal that is raised when we call write
+ twice on a socket closed by the other end. */
+ signal(SIGPIPE, SIG_IGN);
+
+ opt = 1; /* Set this option */
+ if (setsockopt(listensock,SOL_SOCKET,SO_REUSEADDR,(char* ) &opt,
+ sizeof(opt)) <0) {
+ dbg_perror(g,"can't set sockopt");
+ epmd_cleanup_exit(g,1);
+ }
+#endif
+
+ /* In rare cases select returns because there is someone
+ to accept but the request is withdrawn before the
+ accept function is called. We set the listen socket
+ to be non blocking to prevent us from being hanging
+ in accept() waiting for the next request. */
+#ifdef _OSE_
+ opt = 1;
+ if (ioctl(listensock, FIONBIO, (char*)&opt) != 0)
+#else
+#if (defined(__WIN32__) || defined(NO_FCNTL))
+ opt = 1;
+ if (ioctl(listensock, FIONBIO, &opt) != 0) /* Gives warning in VxWorks */
+#else
+ opt = fcntl(listensock, F_GETFL, 0);
+ if (fcntl(listensock, F_SETFL, opt | O_NONBLOCK) == -1)
+#endif /* __WIN32__ || VXWORKS */
+#endif /* _OSE_ */
+ dbg_perror(g,"failed to set non-blocking mode of listening socket %d",
+ listensock);
+
+ { /* store port number in unsigned short */
+ unsigned short sport = g->port;
+ SET_ADDR_ANY(iserv_addr, FAMILY, sport);
+ }
+
+#ifdef _OSE_
+ {
+ int optlen = sizeof(opt);
+ opt = 1;
+ if(getsockopt(listensock, SOL_SOCKET, SO_REUSEADDR,
+ (void*)&opt, &optlen) < 0)
+ fprintf(stderr, "\n\nGETSOCKOPT FAILS! %d\n\n", errno);
+ else if(opt == 1)
+ fprintf(stderr, "SO_REUSEADDR is set!\n");
+ }
+#endif
+
+ if(bind(listensock,(struct sockaddr*) &iserv_addr, sizeof(iserv_addr)) < 0 )
+ {
+ if (errno == EADDRINUSE)
+ {
+ dbg_tty_printf(g,1,"there is already a epmd running at port %d",
+ g->port);
+ epmd_cleanup_exit(g,0);
+ }
+ else
+ {
+ dbg_perror(g,"failed to bind socket");
+ epmd_cleanup_exit(g,1);
+ }
+ }
+
+ dbg_printf(g,2,"starting");
+
+ listen(listensock, SOMAXCONN);
+
+
+ FD_ZERO(&g->orig_read_mask);
+ FD_SET(listensock,&g->orig_read_mask);
+
+ dbg_tty_printf(g,2,"entering the main select() loop");
+
+ select_again:
+ while(1)
+ {
+ fd_set read_mask = g->orig_read_mask;
+ struct timeval timeout;
+ int ret;
+
+ /* If we are idle we time out now and then to enable the code
+ below to close connections that are old and probably
+ hanging. Make sure that select will return often enough. */
+
+ timeout.tv_sec = (g->packet_timeout < IDLE_TIMEOUT) ? 1 : IDLE_TIMEOUT;
+ timeout.tv_usec = 0;
+
+ if ((ret = select(g->max_conn,&read_mask,(fd_set *)0,(fd_set *)0,&timeout)) < 0)
+ dbg_perror(g,"error in select ");
+ else {
+ time_t now;
+ if (ret == 0) {
+ FD_ZERO(&read_mask);
+ }
+ if (g->delay_accept) { /* Test of busy server */
+ sleep(g->delay_accept);
+ }
+
+ if (FD_ISSET(listensock,&read_mask)) {
+ if (do_accept(g, listensock) && g->active_conn < g->max_conn) {
+ /*
+ * The accept() succeeded, and we have at least one file
+ * descriptor still free, which means that another accept()
+ * could succeed. Go do do another select(), in case there
+ * are more incoming connections waiting to be accepted.
+ */
+ goto select_again;
+ }
+ }
+
+ /* Check all open streams marked by select for data or a
+ close. We also close all open sockets except ALIVE
+ with no activity for a long period */
+
+ now = current_time(g);
+ for (i = 0; i < g->max_conn; i++) {
+ if (g->conn[i].open == EPMD_TRUE) {
+ if (FD_ISSET(g->conn[i].fd,&read_mask))
+ do_read(g,&g->conn[i]);
+ else if ((g->conn[i].keep == EPMD_FALSE) &&
+ ((g->conn[i].mod_time + g->packet_timeout) < now)) {
+ dbg_tty_printf(g,1,"closing because timed out on receive");
+ epmd_conn_close(g,&g->conn[i]);
+ }
+ }
+ }
+ }
+ }
+}
+
+/*
+ * This routine read as much of the packet as possible and
+ * if completed calls "do_request()" to fullfill the request.
+ *
+ */
+
+static void do_read(EpmdVars *g,Connection *s)
+{
+ int val, pack_size;
+
+ if (s->open == EPMD_FALSE)
+ {
+ dbg_printf(g,0,"read on unknown socket");
+ return;
+ }
+
+ /* Check if we already got the whole packet but we keep the
+ connection alive to find out when a node is terminated. We then
+ want to check for a close */
+
+ if (s->keep == EPMD_TRUE)
+ {
+ val = read(s->fd, s->buf, INBUF_SIZE);
+
+ if (val == 0)
+ {
+ node_unreg_sock(g,s->fd);
+ epmd_conn_close(g,s);
+ }
+ else if (val < 0)
+ {
+ dbg_tty_printf(g,1,"error on ALIVE socket %d (%d; errno=0x%x)",
+ s->fd, val, errno);
+ node_unreg_sock(g,s->fd);
+ epmd_conn_close(g,s);
+ }
+ else
+ {
+ dbg_tty_printf(g,1,"got more than expected on ALIVE socket %d (%d)",
+ s->fd,val);
+ dbg_print_buf(g,s->buf,val);
+
+ /* FIXME: Shouldn't be needed to close down.... */
+ node_unreg_sock(g,s->fd);
+ epmd_conn_close(g,s);
+ }
+ /* FIXME: We always close, probably the right thing to do */
+ return;
+ }
+
+ /* If unknown size we request the whole buffer - what we got - 1
+ We subtract 1 because we will add a "\0" in "do_request()".
+ This is not needed for R3A or higher versions of Erlang,
+ because the '\0' is included in the request,
+ but is kept for backwards compatibility to allow R2D to use
+ this epmd. */
+
+ pack_size = s->want ? s->want : INBUF_SIZE - 1;
+ val = read(s->fd, s->buf + s->got, pack_size - s->got);
+
+ if (val == 0)
+ {
+ /* A close when we haven't got all data */
+ dbg_printf(g,0,"got partial packet only on file descriptor %d (%d)",
+ s->fd,s->got);
+ epmd_conn_close(g,s);
+ return;
+ }
+
+ if (val < 0)
+ {
+ dbg_perror(g,"error in read");
+ epmd_conn_close(g,s);
+ return;
+ }
+
+ dbg_print_buf(g,s->buf,val);
+
+ s->got += val;
+
+ if ((s->want == 0) && (s->got >= 2))
+ {
+ /* The two byte header that specify the length of the packet
+ doesn't count the header as part of the packet so we add 2
+ to "s->want" to make us talk about all bytes we get. */
+
+ s->want = get_int16(s->buf) + 2;
+
+ if ((s->want < 3) || (s->want >= INBUF_SIZE))
+ {
+ dbg_printf(g,0,"invalid packet size (%d)",s->want - 2);
+ epmd_conn_close(g,s);
+ return;
+ }
+
+ if (s->got > s->want)
+ {
+ dbg_printf(g,0,"got %d bytes in packet, expected %d",
+ s->got - 2, s->want - 2);
+ epmd_conn_close(g,s);
+ return;
+ }
+ }
+
+ s->mod_time = current_time(g); /* Note activity */
+
+ if (s->want == s->got)
+ {
+ /* Do action and close up */
+ /* Skip header bytes */
+
+ do_request(g, s->fd, s, s->buf + 2, s->got - 2);
+
+ if (!s->keep)
+ epmd_conn_close(g,s); /* Normal close */
+ }
+}
+
+static int do_accept(EpmdVars *g,int listensock)
+{
+ int msgsock;
+ struct EPMD_SOCKADDR_IN icli_addr; /* workaround for QNX bug - cannot */
+ int icli_addr_len; /* handle NULL pointers to accept. */
+
+ icli_addr_len = sizeof(icli_addr);
+
+ msgsock = accept(listensock,(struct sockaddr*) &icli_addr,
+ (unsigned int*) &icli_addr_len);
+
+ if (msgsock < 0) {
+ dbg_perror(g,"error in accept");
+ return EPMD_FALSE;
+ }
+
+ return conn_open(g,msgsock);
+}
+
+static void do_request(g, fd, s, buf, bsize)
+ EpmdVars *g;
+ int fd;
+ Connection *s;
+ char *buf;
+ int bsize;
+{
+ char wbuf[OUTBUF_SIZE]; /* Buffer for writing */
+ int i;
+
+ /*
+ * Terminate packet as a C string. Needed for requests received from Erlang
+ * nodes with lower version than R3A.
+ */
+
+ buf[bsize] = '\0';
+
+ switch (*buf)
+ {
+ case EPMD_ALIVE_REQ:
+ dbg_printf(g,1,"** got ALIVE_REQ");
+
+ /* The packet has the format "axxyyyyyy" where xx is port, given
+ in network byte order, and yyyyyy is symname, possibly null
+ terminated. */
+
+ if (buf[bsize - 1] == '\000') /* Skip null termination */
+ bsize--;
+
+ if (bsize <= 3)
+ {
+ dbg_printf(g,0,"packet to small for request ALIVE_REQ (%d)", bsize);
+ return;
+ }
+
+ for (i = 3; i < bsize; i++)
+ if (buf[i] == '\000')
+ {
+ dbg_printf(g,0,"node name contains ascii 0 in ALIVE_REQ");
+ return;
+ }
+
+ {
+ Node *node;
+ int eport;
+ char *name = &buf[3]; /* points to node name */
+
+ eport = get_int16(&buf[1]);
+
+ if ((node = node_reg(g, name, fd, eport)) == NULL)
+ return;
+
+ wbuf[0] = EPMD_ALIVE_OK_RESP;
+ put_int16(node->creation, wbuf+1);
+
+ if (g->delay_write) /* Test of busy server */
+ sleep(g->delay_write);
+
+ if (reply(g, fd, wbuf, 3) != 3)
+ {
+ dbg_tty_printf(g,1,"failed to send ALIVE_OK_RESP for \"%s\"",name);
+ return;
+ }
+
+ dbg_tty_printf(g,1,"** sent ALIVE_OK_RESP for \"%s\"",name);
+ s->keep = EPMD_TRUE; /* Don't close on inactivity */
+ }
+ break;
+
+ case EPMD_PORT_REQ:
+ dbg_printf(g,1,"** got PORT_REQ");
+
+ if (buf[bsize - 1] == '\000') /* Skip null termination */
+ bsize--;
+
+ if (bsize <= 1)
+ {
+ dbg_printf(g,0,"packet to small for request PORT_REQ (%d)", bsize);
+ return;
+ }
+
+ for (i = 1; i < bsize; i++)
+ if (buf[i] == '\000')
+ {
+ dbg_printf(g,0,"node name contains ascii 0 in PORT_REQ");
+ return;
+ }
+
+ {
+ char *name = &buf[1]; /* Points to node name */
+ Node *node;
+
+ for (node = g->nodes.reg; node; node = node->next)
+ {
+ if (strcmp(node->symname, name) == 0)
+ {
+ put_int16(node->port,wbuf);
+ if (reply(g, fd, wbuf, 2) != 2)
+ {
+ dbg_tty_printf(g,1,"failed to send PORT_RESP for %s: %d",
+ name,node->port);
+ return;
+ }
+ dbg_tty_printf(g,1,"** sent PORT_RESP for %s: %d",
+ name,node->port);
+ return;
+ }
+ }
+ dbg_tty_printf(g,1,"Closed on PORT_REQ for %s",name);
+ }
+ /* FIXME: How about an answer if no port? Is a close enough? */
+ break;
+
+ case EPMD_ALIVE2_REQ:
+ dbg_printf(g,1,"** got ALIVE2_REQ");
+
+ /* The packet has the format "axxyyyyyy" where xx is port, given
+ in network byte order, and yyyyyy is symname, possibly null
+ terminated. */
+
+ if (bsize <= 13)
+ {
+ dbg_printf(g,0,"packet to small for request ALIVE2_REQ (%d)",bsize);
+ return;
+ }
+
+ {
+ Node *node;
+ int eport;
+ unsigned char nodetype;
+ unsigned char protocol;
+ unsigned short highvsn;
+ unsigned short lowvsn;
+ int namelen;
+ int extralen;
+ char *name;
+ char *extra;
+ eport = get_int16(&buf[1]);
+ nodetype = buf[3];
+ protocol = buf[4];
+ highvsn = get_int16(&buf[5]);
+ lowvsn = get_int16(&buf[7]);
+ namelen = get_int16(&buf[9]);
+ extralen = get_int16(&buf[11+namelen]);
+ for (i = 11 ; i < 11 + namelen; i ++)
+ if (buf[i] == '\000') {
+ dbg_printf(g,0,"node name contains ascii 0 in ALIVE2_REQ");
+ return;
+ }
+ name = &buf[11];
+ name[namelen]='\000';
+ extra = &buf[11+namelen+1];
+ extra[extralen]='\000';
+ wbuf[0] = EPMD_ALIVE2_RESP;
+ if ((node = node_reg2(g, name, fd, eport, nodetype, protocol,
+ highvsn, lowvsn, extra)) == NULL) {
+ wbuf[1] = 1; /* error */
+ put_int16(99, wbuf+2);
+ } else {
+ wbuf[1] = 0; /* ok */
+ put_int16(node->creation, wbuf+2);
+ }
+
+ if (g->delay_write) /* Test of busy server */
+ sleep(g->delay_write);
+
+ if (reply(g, fd, wbuf, 4) != 4)
+ {
+ dbg_tty_printf(g,1,"** failed to send ALIVE2_RESP for \"%s\"",
+ name);
+ return;
+ }
+
+ dbg_tty_printf(g,1,"** sent ALIVE2_RESP for \"%s\"",name);
+ s->keep = EPMD_TRUE; /* Don't close on inactivity */
+ }
+ break;
+
+ case EPMD_PORT2_REQ:
+ dbg_printf(g,1,"** got PORT2_REQ");
+
+ if (buf[bsize - 1] == '\000') /* Skip null termination */
+ bsize--;
+
+ if (bsize <= 1)
+ {
+ dbg_printf(g,0,"packet to small for request PORT2_REQ (%d)", bsize);
+ return;
+ }
+
+ for (i = 1; i < bsize; i++)
+ if (buf[i] == '\000')
+ {
+ dbg_printf(g,0,"node name contains ascii 0 in PORT2_REQ");
+ return;
+ }
+
+ {
+ char *name = &buf[1]; /* Points to node name */
+ Node *node;
+
+ wbuf[0] = EPMD_PORT2_RESP;
+ for (node = g->nodes.reg; node; node = node->next) {
+ int offset;
+ if (strcmp(node->symname, name) == 0) {
+ wbuf[1] = 0; /* ok */
+ put_int16(node->port,wbuf+2);
+ wbuf[4] = node->nodetype;
+ wbuf[5] = node->protocol;
+ put_int16(node->highvsn,wbuf+6);
+ put_int16(node->lowvsn,wbuf+8);
+ put_int16(strlen(node->symname),wbuf+10);
+ offset = 12;
+ strcpy(wbuf + offset,node->symname);
+ offset += strlen(node->symname);
+ put_int16(strlen(node->extra),wbuf + offset);
+ offset += 2;
+ strcpy(wbuf + offset,node->extra);
+ offset += (strlen(node->extra)-1);
+ if (reply(g, fd, wbuf, offset) != offset)
+ {
+ dbg_tty_printf(g,1,"** failed to send PORT2_RESP (ok) for \"%s\"",name);
+ return;
+ }
+ dbg_tty_printf(g,1,"** sent PORT2_RESP (ok) for \"%s\"",name);
+ return;
+ }
+ }
+ wbuf[1] = 1; /* error */
+ if (reply(g, fd, wbuf, 2) != 2)
+ {
+ dbg_tty_printf(g,1,"** failed to send PORT2_RESP (error) for \"%s\"",name);
+ return;
+ }
+ dbg_tty_printf(g,1,"** sent PORT2_RESP (error) for \"%s\"",name);
+ return;
+ }
+ break;
+
+ case EPMD_NAMES_REQ:
+ dbg_printf(g,1,"** got NAMES_REQ");
+ {
+ Node *node;
+
+ i = htonl(g->port);
+ memcpy(wbuf,&i,4);
+
+ if (reply(g, fd,wbuf,4) != 4)
+ {
+ dbg_tty_printf(g,1,"failed to send NAMES_RESP");
+ return;
+ }
+
+ for (node = g->nodes.reg; node; node = node->next)
+ {
+ int len;
+
+ /* CAREFUL!!! These are parsed by "erl_epmd.erl" so a slight
+ change in syntax will break < OTP R3A */
+
+ sprintf(wbuf,"name %s at port %d\n",node->symname, node->port);
+ len = strlen(wbuf);
+ if (reply(g, fd, wbuf, len) != len)
+ {
+ dbg_tty_printf(g,1,"failed to send NAMES_RESP");
+ return;
+ }
+ }
+ }
+ dbg_tty_printf(g,1,"** sent NAMES_RESP");
+ break;
+
+ case EPMD_DUMP_REQ:
+ dbg_printf(g,1,"** got DUMP_REQ");
+ {
+ Node *node;
+
+ i = htonl(g->port);
+ memcpy(wbuf,&i,4);
+ if (reply(g, fd,wbuf,4) != 4)
+ {
+ dbg_tty_printf(g,1,"failed to send DUMP_RESP");
+ return;
+ }
+
+ for (node = g->nodes.reg; node; node = node->next)
+ {
+ int len;
+
+ /* CAREFUL!!! These are parsed by "erl_epmd.erl" so a slight
+ change in syntax will break < OTP R3A */
+
+ sprintf(wbuf,"active name <%s> at port %d, fd = %d\n",
+ node->symname, node->port, node->fd);
+ len = strlen(wbuf) + 1;
+ if (reply(g, fd,wbuf,len) != len)
+ {
+ dbg_tty_printf(g,1,"failed to send DUMP_RESP");
+ return;
+ }
+ }
+
+ for (node = g->nodes.unreg; node; node = node->next)
+ {
+ int len;
+
+ /* CAREFUL!!! These are parsed by "erl_epmd.erl" so a slight
+ change in syntax will break < OTP R3A */
+
+ sprintf(wbuf,"old/unused name <%s>, port = %d, fd = %d \n",
+ node->symname,node->port, node->fd);
+ len = strlen(wbuf) + 1;
+ if (reply(g, fd,wbuf,len) != len)
+ {
+ dbg_tty_printf(g,1,"failed to send DUMP_RESP");
+ return;
+ }
+ }
+ }
+ dbg_tty_printf(g,1,"** sent DUMP_RESP");
+ break;
+
+ case EPMD_KILL_REQ:
+ dbg_printf(g,1,"** got KILL_REQ");
+ if (reply(g, fd,"OK",2) != 2)
+ dbg_printf(g,0,"failed to send reply to KILL_REQ");
+ dbg_tty_printf(g,1,"epmd killed");
+ conn_close_fd(g,fd); /* We never return to caller so close here */
+ dbg_printf(g,0,"got KILL_REQ - terminates normal");
+ epmd_cleanup_exit(g,0); /* Normal exit */
+
+ case EPMD_STOP_REQ:
+ dbg_printf(g,1,"** got STOP_REQ");
+ if (bsize <= 1 )
+ {
+ dbg_printf(g,0,"packet to small for request STOP_REQ (%d)",bsize);
+ return;
+ }
+
+ {
+ char *name = &buf[1]; /* Points to node name */
+ int node_fd;
+
+ if ((node_fd = node_unreg(g,name)) < 0)
+ {
+ if (reply(g, fd,"NOEXIST",7) != 7)
+ {
+ dbg_tty_printf(g,1,"failed to send STOP_RESP NOEXIST");
+ return;
+ }
+ dbg_tty_printf(g,1,"** sent STOP_RESP NOEXIST");
+ }
+
+ conn_close_fd(g,node_fd);
+ dbg_tty_printf(g,1,"epmd connection stopped");
+
+ if (reply(g, fd,"STOPPED",7) != 7)
+ {
+ dbg_tty_printf(g,1,"failed to send STOP_RESP STOPPED");
+ return;
+ }
+ dbg_tty_printf(g,1,"** sent STOP_RESP STOPPED");
+ }
+ break;
+
+ default:
+ dbg_printf(g,0,"got garbage ");
+ }
+}
+
+
+/****************************************************************************
+ *
+ * Handle database with data for each socket to read
+ *
+ ****************************************************************************/
+
+static Connection *conn_init(EpmdVars *g)
+{
+ int nbytes = g->max_conn * sizeof(Connection);
+ Connection *connections = (Connection *)malloc(nbytes);
+
+ if (connections == NULL)
+ {
+ dbg_printf(g,0,"epmd: Insufficient memory");
+#ifdef DONT_USE_MAIN
+ free(g->argv);
+#endif
+ exit(1);
+ }
+
+ memzero(connections, nbytes);
+
+ return connections;
+}
+
+static int conn_open(EpmdVars *g,int fd)
+{
+ int i;
+ Connection *s;
+
+#ifdef VXWORKS
+ /*
+ * Since file descriptors are global on VxWorks, we might get an fd that
+ * does not fit in the FD_SET.
+ *
+ * Note: This test would be harmless on Unix, but would fail on Windows
+ * because socket are numbered differently and FD_SETs are implemented
+ * differently.
+ */
+ if (fd >= FD_SETSIZE) {
+ dbg_tty_printf(g,0,"file descriptor %d: too high for FD_SETSIZE=%d",
+ fd,FD_SETSIZE);
+ close(fd);
+ return EPMD_FALSE;
+ }
+#endif
+
+ for (i = 0; i < g->max_conn; i++) {
+ if (g->conn[i].open == EPMD_FALSE) {
+ g->active_conn++;
+ s = &g->conn[i];
+
+ /* From now on we want to know if there are data to be read */
+ FD_SET(fd, &g->orig_read_mask);
+
+ s->fd = fd;
+ s->open = EPMD_TRUE;
+ s->keep = EPMD_FALSE;
+ s->want = 0; /* Currently unknown */
+ s->got = 0;
+ s->mod_time = current_time(g); /* Note activity */
+
+ s->buf = (char *)malloc(INBUF_SIZE);
+
+ if (s->buf == NULL) {
+ dbg_printf(g,0,"epmd: Insufficient memory");
+ close(fd);
+ return EPMD_FALSE;
+ }
+
+ dbg_tty_printf(g,2,"opening connection on file descriptor %d",fd);
+ return EPMD_TRUE;
+ }
+ }
+
+ dbg_tty_printf(g,0,"failed opening connection on file descriptor %d",fd);
+ close(fd);
+ return EPMD_FALSE;
+}
+
+static int conn_close_fd(EpmdVars *g,int fd)
+{
+ int i;
+
+ for (i = 0; i < g->max_conn; i++)
+ if (g->conn[i].fd == fd)
+ {
+ epmd_conn_close(g,&g->conn[i]);
+ return EPMD_TRUE;
+ }
+
+ return EPMD_FALSE;
+}
+
+
+int epmd_conn_close(EpmdVars *g,Connection *s)
+{
+ dbg_tty_printf(g,2,"closing connection on file descriptor %d",s->fd);
+
+ FD_CLR(s->fd,&g->orig_read_mask);
+ close(s->fd); /* Sometimes already closed but close anyway */
+ s->open = EPMD_FALSE;
+ if (s->buf != NULL) { /* Should never be NULL but test anyway */
+ free(s->buf);
+ }
+ g->active_conn--;
+ return EPMD_TRUE;
+}
+
+/****************************************************************************
+ *
+ * Handle database with data for each registered node
+ *
+ ****************************************************************************/
+
+
+static void node_init(EpmdVars *g)
+{
+ g->nodes.reg = NULL;
+ g->nodes.unreg = NULL;
+ g->nodes.unreg_tail = NULL;
+ g->nodes.unreg_count = 0;
+}
+
+
+/* We have got a close on a connection and it may be a
+ EPMD_ALIVE_CLOSE_REQ. Note that this call shouild be called
+ *before* calling conn_close() */
+
+static int node_unreg(EpmdVars *g,char *name)
+{
+ Node **prev = &g->nodes.reg; /* Point to cell pointing to... */
+ Node *node = g->nodes.reg; /* Point to first node */
+
+ for (; node; prev = &node->next, node = node->next)
+ if (strcmp(node->symname, name) == 0)
+ {
+ dbg_tty_printf(g,1,"unregistering '%s:%d', port %d",
+ node->symname, node->creation, node->port);
+
+ *prev = node->next; /* Link out from "reg" list */
+
+ if (g->nodes.unreg == NULL) /* Link into "unreg" list */
+ g->nodes.unreg = g->nodes.unreg_tail = node;
+ else
+ {
+ g->nodes.unreg_tail->next = node;
+ g->nodes.unreg_tail = node;
+ }
+
+ g->nodes.unreg_count++;
+
+ node->next = NULL; /* Last in list == first in FIFO queue */
+
+ print_names(g);
+
+ return node->fd;
+ }
+
+ dbg_tty_printf(g,1,"trying to unregister node with unknown name %s", name);
+ return -1;
+}
+
+
+static int node_unreg_sock(EpmdVars *g,int fd)
+{
+ Node **prev = &g->nodes.reg; /* Point to cell pointing to... */
+ Node *node = g->nodes.reg; /* Point to first node */
+
+ for (; node; prev = &node->next, node = node->next)
+ if (node->fd == fd)
+ {
+ dbg_tty_printf(g,1,"unregistering '%s:%d', port %d",
+ node->symname, node->creation, node->port);
+
+ *prev = node->next; /* Link out from "reg" list */
+
+ if (g->nodes.unreg == NULL) /* Link into "unreg" list */
+ g->nodes.unreg = g->nodes.unreg_tail = node;
+ else
+ {
+ g->nodes.unreg_tail->next = node;
+ g->nodes.unreg_tail = node;
+ }
+
+ g->nodes.unreg_count++;
+
+ node->next = NULL; /* Last in list == first in FIFO queue */
+
+ print_names(g);
+
+ return node->fd;
+ }
+
+ dbg_tty_printf(g,1,
+ "trying to unregister node with unknown file descriptor %d",
+ fd);
+ return -1;
+}
+
+/*
+ * Finding a node slot and a (name,creation) name is a bit tricky.
+ * We try in order
+ *
+ * 1. If the name was used before and we can reuse that slot but use
+ * a new "creation" digit in the range 1..3.
+ *
+ * 2. We try to find a new unused slot.
+ *
+ * 3. We try to use an used slot this isn't used any longer.
+ * FIXME: The criteria for *what* slot to steal should be improved.
+ * Perhaps use the oldest or something.
+ */
+
+static Node *node_reg(EpmdVars *g,char *name,int fd, int port)
+{
+ return node_reg2(g, name, fd, port, 0, 0, 0, 0, NULL);
+}
+
+static Node *node_reg2(EpmdVars *g,
+ char* name,
+ int fd,
+ int port,
+ unsigned char nodetype,
+ unsigned char protocol,
+ int highvsn,
+ int lowvsn,
+ char* extra)
+{
+ Node *prev; /* Point to previous node or NULL */
+ Node *node; /* Point to first node */
+
+ /* Can be NULL; means old style */
+ if (extra == NULL)
+ extra = "";
+
+ /* Fail if node name is too long */
+
+ if (strlen(name) > MAXSYMLEN)
+ {
+ dbg_printf(g,0,"node name is too long (%d) %s", strlen(name), name);
+ return NULL;
+ }
+
+ /* Fail if it is already registered */
+
+ for (node = g->nodes.reg; node; node = node->next)
+ if (strcmp(node->symname, name) == 0)
+ {
+ dbg_printf(g,0,"node name already occupied %s", name);
+ return NULL;
+ }
+
+ /* Try to find the name in the used queue so that we
+ can change "creation" number 1..3 */
+
+ prev = NULL;
+
+ for (node = g->nodes.unreg; node; prev = node, node = node->next)
+ if (strcmp(node->symname, name) == 0)
+ {
+ dbg_tty_printf(g,1,"reusing slot with same name '%s'", node->symname);
+
+ if (prev == NULL) /* First in list matched */
+ {
+ if (node->next == NULL) /* Only one element */
+ g->nodes.unreg = g->nodes.unreg_tail = NULL;
+ else
+ g->nodes.unreg = node->next;
+ }
+ else
+ {
+ if (node->next == NULL) /* Last in list */
+ {
+ g->nodes.unreg_tail = prev; /* Point to new last */
+ prev->next = NULL; /* New last has no next */
+ }
+ else
+ prev->next = node->next; /* Simple link out from list */
+ }
+
+ g->nodes.unreg_count--;
+
+ /* When reusing we change the "creation" number 1..3 */
+
+ node->creation = node->creation % 3 + 1;
+
+ break;
+ }
+
+ if (node == NULL)
+ {
+ /* A new name. If the "unreg" list is too long we steal the
+ oldest node structure and use it for the new node, else
+ we allocate a new node structure */
+
+ if ((g->nodes.unreg_count > MAX_UNREG_COUNT) ||
+ (g->debug && (g->nodes.unreg_count > DEBUG_MAX_UNREG_COUNT)))
+ {
+ /* MAX_UNREG_COUNT > 1 so no need to check unreg_tail */
+ node = g->nodes.unreg; /* Take first == oldest */
+ g->nodes.unreg = node->next; /* Link out */
+ g->nodes.unreg_count--;
+ }
+ else
+ {
+ if ((node = (Node *)malloc(sizeof(Node))) == NULL)
+ {
+ dbg_printf(g,0,"epmd: Insufficient memory");
+ exit(1);
+ }
+
+ node->creation = (current_time(g) % 3) + 1; /* "random" 1-3 */
+ }
+ }
+
+ node->next = g->nodes.reg; /* Link into "reg" queue */
+ g->nodes.reg = node;
+
+ node->fd = fd;
+ node->port = port;
+ node->nodetype = nodetype;
+ node->protocol = protocol;
+ node->highvsn = highvsn;
+ node->lowvsn = lowvsn;
+ strcpy(node->extra,extra);
+ strcpy(node->symname,name);
+ FD_SET(fd,&g->orig_read_mask);
+
+ if (highvsn == 0) {
+ dbg_tty_printf(g,1,"registering '%s:%d', port %d",
+ node->symname, node->creation, node->port);
+ } else {
+ dbg_tty_printf(g,1,"registering '%s:%d', port %d",
+ node->symname, node->creation, node->port);
+ dbg_tty_printf(g,1,"type %d proto %d highvsn %d lowvsn %d",
+ nodetype, protocol, highvsn, lowvsn);
+ }
+
+ print_names(g);
+
+ return node;
+}
+
+
+static time_t current_time(EpmdVars *g)
+{
+ time_t t = time((time_t *)0);
+ dbg_printf(g,3,"time in seconds: %d",t);
+ return t;
+}
+
+
+static int reply(EpmdVars *g,int fd,char *buf,int len)
+{
+ int val;
+
+ if (len < 0)
+ {
+ dbg_printf(g,0,"Invalid length in write %d",len);
+ return -1;
+ }
+
+ if (g->delay_write) /* Test of busy server */
+ sleep(g->delay_write);
+
+ val = write(fd,buf,len);
+ if (val < 0)
+ dbg_perror(g,"error in write");
+ else if (val != len)
+ dbg_printf(g,0,"could only send %d bytes out of %d to fd %d",val,len,fd);
+
+ dbg_print_buf(g,buf,len);
+
+ return val;
+}
+
+
+#define LINEBYTECOUNT 16
+
+static void print_buf_hex(unsigned char *buf,int len,char *prefix)
+{
+ int rows, row;
+
+ rows = len / LINEBYTECOUNT; /* Number of rows */
+ if (len % LINEBYTECOUNT)
+ rows++; /* If leftovers, add a line for them */
+
+ for (row = 0; row < rows; row++)
+ {
+ int rowstart = row * LINEBYTECOUNT;
+ int rowend = rowstart + LINEBYTECOUNT;
+ int i;
+
+ fprintf(stderr,"%s%.8x",prefix,rowstart);
+
+ for (i = rowstart; i < rowend; i++)
+ {
+ if ((i % (LINEBYTECOUNT/2)) == 0)
+ fprintf(stderr," ");
+
+ if (i < len)
+ fprintf(stderr," %.2x",buf[i]);
+ else
+ fprintf(stderr," ");
+ }
+
+ fprintf(stderr," |");
+
+ for (i = rowstart; (i < rowend) && (i < len); i++)
+ {
+ int c = buf[i];
+
+ /* Used to be isprint(c) but we want ascii only */
+
+ if ((c >= 32) && (c <= 126))
+ fprintf(stderr,"%c",c);
+ else
+ fprintf(stderr,".");
+ }
+
+ fprintf(stderr,"|\r\n");
+ }
+}
+
+static void dbg_print_buf(EpmdVars *g,char *buf,int len)
+{
+ int plen;
+
+ if ((g->is_daemon) || /* Don't want to write to stderr if daemon */
+ (g->debug < 2)) /* or debug level too low */
+ return;
+
+ dbg_tty_printf(g,1,"got %d bytes",len);
+
+ plen = len > 1024 ? 1024 : len; /* Limit the number of chars to print */
+
+ print_buf_hex((unsigned char*)buf,plen,"***** ");
+
+ if (len != plen)
+ fprintf(stderr,"***** ......and more\r\n");
+}
+
+static void print_names(EpmdVars *g)
+{
+ int count = 0;
+ Node *node;
+
+ if ((g->is_daemon) || /* Don't want to write to stderr if daemon */
+ (g->debug < 3)) /* or debug level too low */
+ return;
+
+ for (node = g->nodes.reg; node; node = node->next)
+ {
+ fprintf(stderr,"***** active name \"%s#%d\" at port %d, fd = %d\r\n",
+ node->symname, node->creation, node->port, node->fd);
+ count ++;
+ }
+
+ fprintf(stderr, "***** reg calculated count : %d\r\n", count);
+
+ count = 0;
+
+ for (node = g->nodes.unreg; node; node = node->next)
+ {
+ fprintf(stderr,"***** old/unused name \"%s#%d\"\r\n",
+ node->symname, node->creation);
+ count ++;
+ }
+
+ fprintf(stderr, "***** unreg counter : %d\r\n",
+ g->nodes.unreg_count);
+ fprintf(stderr, "***** unreg calculated count: %d\r\n", count);
+}
diff --git a/erts/epmd/test/Makefile b/erts/epmd/test/Makefile
new file mode 100644
index 0000000000..c1d62f0f93
--- /dev/null
+++ b/erts/epmd/test/Makefile
@@ -0,0 +1,80 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1998-2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+include $(ERL_TOP)/make/target.mk
+
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+include ../epmd.mk
+
+EBIN = .
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+
+MODULES= epmd_SUITE
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELEPMDDIR = $(RELEASE_PATH)/epmd_test
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+
+ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/test_server/include \
+ -I$(ERL_TOP)/lib/kernel/src/ \
+ $(EPMD_FLAGS)
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+tests debug opt: $(TARGET_FILES)
+
+clean:
+ rm -f $(TARGET_FILES)
+ rm -f core *~
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec:
+
+release_tests_spec: opt
+ $(INSTALL_DIR) $(RELEPMDDIR)
+ $(INSTALL_DATA) epmd.spec epmd.spec.vxworks $(ERL_FILES) \
+ $(TARGET_FILES) $(RELEPMDDIR)
+ chmod -f -R u+w $(RELEPMDDIR)
+
+release_docs_spec:
+
+
+
+
+
diff --git a/erts/epmd/test/epmd.spec b/erts/epmd/test/epmd.spec
new file mode 100644
index 0000000000..0e2496bc72
--- /dev/null
+++ b/erts/epmd/test/epmd.spec
@@ -0,0 +1 @@
+{topcase, {dir, "../epmd_test"}}.
diff --git a/erts/epmd/test/epmd.spec.vxworks b/erts/epmd/test/epmd.spec.vxworks
new file mode 100644
index 0000000000..476308b481
--- /dev/null
+++ b/erts/epmd/test/epmd.spec.vxworks
@@ -0,0 +1,2 @@
+{topcase, {dir, "../epmd_test"}}.
+{skip,{epmd_rx_SUITE,"EPMD RX does simply not work on VxWorks"}}.
diff --git a/erts/epmd/test/epmd_SUITE.erl b/erts/epmd/test/epmd_SUITE.erl
new file mode 100644
index 0000000000..513c87a13e
--- /dev/null
+++ b/erts/epmd/test/epmd_SUITE.erl
@@ -0,0 +1,835 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(epmd_SUITE).
+-include("test_server.hrl").
+-include_lib("kernel/include/file.hrl").
+
+
+% Timeout for test cases (rather long to work on slow machines)
+-define(SHORT_TEST_TIMEOUT, ?t:seconds(30)). % Default
+-define(MEDIUM_TEST_TIMEOUT, ?t:minutes(3)).
+-define(LONG_TEST_TIMEOUT, ?t:minutes(10)).
+
+% Delay inserted into code
+-define(SHORT_PAUSE, 100).
+-define(MEDIUM_PAUSE, ?t:seconds(1)).
+-define(LONG_PAUSE, ?t:seconds(5)).
+
+% Test server specific exports
+-export([all/1, init_per_testcase/2, fin_per_testcase/2]).
+
+-export(
+ [
+ register_name/1,
+ register_names_1/1,
+ register_names_2/1,
+ register_duplicate_name/1,
+ get_port_nr/1,
+ slow_get_port_nr/1,
+ unregister_others_name_1/1,
+ unregister_others_name_2/1,
+ register_overflow/1,
+ name_with_null_inside/1,
+ name_null_terminated/1,
+ stupid_names_req/1,
+
+ no_data/1,
+ one_byte/1,
+ two_bytes/1,
+ partial_packet/1,
+ zero_length/1,
+ too_large/1,
+ alive_req_too_small_1/1,
+ alive_req_too_small_2/1,
+ alive_req_too_large/1
+ ]).
+
+
+% Port we use for testing
+-define(PORT,2243).
+-define(EPMDARGS,"-packet_timeout 1").
+
+-define(DUMMY_PORT, 1000). % Port number to register
+ % not in real use.
+
+% Timeouts etc inside test cases. Time is in milliseconds.
+-define(CONN_RETRY, 4). % Times to retry connecting
+-define(CONN_SLEEP, 500).
+-define(CONN_TIMEOUT, 100).
+-define(RECV_TIMEOUT, 2000).
+-define(REG_REPEAT_LIM,1000).
+
+% Message codes in epmd protocol
+-define(EPMD_ALIVE_REQ, $a).
+-define(EPMD_ALIVE_OK_RESP, $Y).
+-define(EPMD_PORT_REQ, $p).
+-define(EPMD_NAMES_REQ, $n).
+-define(EPMD_DUMP_REQ, $d).
+-define(EPMD_KILL_REQ, $k).
+-define(EPMD_STOP_REQ, $s).
+
+%%
+%% all/1
+%%
+
+all(suite) ->
+ [
+ register_name,
+ register_names_1,
+ register_names_2,
+ register_duplicate_name,
+ get_port_nr,
+ slow_get_port_nr,
+ unregister_others_name_1,
+ unregister_others_name_2,
+ register_overflow,
+ name_with_null_inside,
+ name_null_terminated,
+ stupid_names_req,
+
+ no_data,
+ one_byte,
+ two_bytes,
+ partial_packet,
+ zero_length,
+ too_large,
+ alive_req_too_small_1,
+ alive_req_too_small_2,
+ alive_req_too_large
+ ].
+
+%%
+%% Run before and after each test case
+%%
+
+init_per_testcase(_Func, Config) ->
+ Dog = test_server:timetrap(?SHORT_TEST_TIMEOUT),
+ cleanup(),
+ [{watchdog, Dog} | Config].
+
+fin_per_testcase(_Func, Config) ->
+ cleanup(),
+ Dog = ?config(watchdog, Config),
+ catch test_server:timetrap_cancel(Dog), % We may have canceled already
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+register_name(doc) ->
+ ["Register a name"];
+register_name(suite) ->
+ [];
+register_name(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = register_node("foobar"),
+ ?line ok = close(Sock), % Unregister
+ ok.
+
+register_names_1(doc) ->
+ ["Register and unregister two nodes"];
+register_names_1(suite) ->
+ [];
+register_names_1(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock1} = register_node("foobar"),
+ ?line {ok,Sock2} = register_node("foozap"),
+ ?line ok = close(Sock1), % Unregister
+ ?line ok = close(Sock2), % Unregister
+ ok.
+
+register_names_2(doc) ->
+ ["Register and unregister two nodes"];
+register_names_2(suite) ->
+ [];
+register_names_2(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock1} = register_node("foobar"),
+ ?line {ok,Sock2} = register_node("foozap"),
+ ?line ok = close(Sock2), % Unregister
+ ?line ok = close(Sock1), % Unregister
+ ok.
+
+register_duplicate_name(doc) ->
+ ["Two nodes with the same name"];
+register_duplicate_name(suite) ->
+ [];
+register_duplicate_name(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = register_node("foobar"),
+ ?line error = register_node("foobar"),
+ ?line ok = close(Sock), % Unregister
+ ok.
+
+% Internal function to register a node name, no close, i.e. unregister
+
+register_node(Name) ->
+ register_node(Name,?DUMMY_PORT).
+
+register_node(Name, Port) ->
+ case connect() of
+ {ok,Sock} ->
+ M = [?EPMD_ALIVE_REQ, put16(Port), Name],
+ case send(Sock, [size16(M), M]) of
+ ok ->
+ case recv(Sock,3) of
+ {ok, [?EPMD_ALIVE_OK_RESP,_D1,_D0]} ->
+ {ok,Sock};
+ Other ->
+ test_server:format("recv on sock ~w: ~p~n",
+ [Sock,Other]),
+ error
+ end;
+ Other ->
+ test_server:format("send on sock ~w: ~w~n",[Sock,Other]),
+ error
+ end;
+ Other ->
+ test_server:format("Connect on port ~w: ~p~n",[Port,Other]),
+ error
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+name_with_null_inside(doc) ->
+ ["Register a name with a null char in it"];
+name_with_null_inside(suite) ->
+ [];
+name_with_null_inside(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line error = register_node("foo\000bar"),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+name_null_terminated(doc) ->
+ ["Register a name with terminating null byte"];
+name_null_terminated(suite) ->
+ [];
+name_null_terminated(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = register_node("foobar\000"),
+ ?line error = register_node("foobar"),
+ ?line ok = close(Sock), % Unregister
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+stupid_names_req(doc) ->
+ ["Read names from epmd in a stupid way"];
+stupid_names_req(suite) ->
+ [];
+stupid_names_req(Config) when list(Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ LongDog = test_server:timetrap(?MEDIUM_TEST_TIMEOUT),
+ ?line ok = epmdrun(),
+ ?line [FirstConn | Conn] = register_many(1, ?REG_REPEAT_LIM, "foo"),
+ ?line unregister_many([FirstConn]),
+ sleep(?MEDIUM_PAUSE),
+ ?line ok = check_names(Conn),
+ ?line ok = unregister_many(Conn),
+ test_server:timetrap_cancel(LongDog),
+ ok.
+
+check_names(Conn) ->
+ ?line {ok,Sock} = connect_active(),
+ ?line {ok,Reply} = do_get_names(Sock),
+ ?line SortConn = lists:sort(Conn),
+ ?line SortReply = lists:sort(Reply),
+ ?line ok = check_names_cmp(SortConn, SortReply),
+ ok.
+
+
+% Compare if the result was the same as was registered
+
+check_names_cmp([], []) ->
+ ok;
+check_names_cmp([{Name,Port,_Sock} | Conn], [{Name,Port} | Reply]) ->
+ check_names_cmp(Conn, Reply).
+
+
+% This code is taken directly from "erl_epmd.erl" in R3A01
+
+-define(int16(X), [(X bsr 8) band 16#ff, X band 16#ff]).
+-define(u32(X1,X2,X3,X4),
+ (((X1) bsl 24) bor ((X2) bsl 16) bor ((X3) bsl 8) bor X4)).
+
+do_get_names(Socket) ->
+ inet_tcp:send(Socket, [?int16(1),?EPMD_NAMES_REQ]),
+ receive
+ {tcp, Socket, [P0,P1,P2,P3 | T]} ->
+ EpmdPort = ?u32(P0,P1,P2,P3),
+ if EpmdPort == ?PORT ->
+ names_loop(Socket, T, []);
+ true ->
+ close(Socket),
+ {error, address}
+ end;
+ {tcp_closed, Socket} ->
+ {ok, []}
+ end.
+
+names_loop(Socket, Acc, Ps) ->
+ receive
+ {tcp, Socket, Bytes} ->
+ {NAcc, NPs} = scan_names(Acc ++ Bytes, Ps),
+ names_loop(Socket, NAcc, NPs);
+ {tcp_closed, Socket} ->
+ {_, NPs} = scan_names(Acc, Ps), % Really needed?
+ {ok, NPs}
+ end.
+
+scan_names(Buf, Ps) ->
+ case scan_line(Buf, []) of
+ {Line, NBuf} ->
+ case parse_line(Line) of
+ {ok, Entry} ->
+ scan_names(NBuf, [Entry | Ps]);
+ error ->
+ scan_names(NBuf, Ps)
+ end;
+ [] -> {Buf, Ps}
+ end.
+
+scan_line([$\n | Buf], Line) -> {lists:reverse(Line), Buf};
+scan_line([C | Buf], Line) -> scan_line(Buf, [C|Line]);
+scan_line([], _) -> [].
+
+parse_line([$n,$a,$m,$e,$ | Buf0]) ->
+ case parse_name(Buf0, []) of
+ {Name, Buf1} ->
+ case Buf1 of
+ [$a,$t,$ ,$p,$o,$r,$t,$ | Buf2] ->
+ case catch list_to_integer(Buf2) of
+ {'EXIT', _} -> error;
+ Port -> {ok, {Name, Port}}
+ end;
+ _ -> error
+ end;
+ error -> error
+ end;
+parse_line(_) -> error.
+
+
+parse_name([$ | Buf], Name) -> {lists:reverse(Name), Buf};
+parse_name([C | Buf], Name) -> parse_name(Buf, [C|Name]);
+parse_name([], _Name) -> error.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_port_nr(doc) ->
+ ["Register a name on a port and ask about port nr"];
+get_port_nr(suite) ->
+ [];
+get_port_nr(Config) when list(Config) ->
+ port_request([?EPMD_PORT_REQ,"foo"]).
+
+slow_get_port_nr(doc) ->
+ ["Register with slow write and ask about port nr"];
+slow_get_port_nr(suite) ->
+ [];
+slow_get_port_nr(Config) when list(Config) ->
+ port_request([?EPMD_PORT_REQ,d,$f,d,$o,d,$o]).
+
+
+% Internal function used above
+
+port_request(M) ->
+ ?line ok = epmdrun(),
+ Port = 1042,
+ ?line {ok,RSock} = register_node("foo", Port),
+ ?line {ok,Sock} = connect(),
+ ?line ok = send(Sock,[size16(M),M]),
+ R = put16(Port),
+ ?line {ok,R} = recv(Sock, length(R)),
+ ?line ok = close(RSock),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+unregister_others_name_1(doc) ->
+ ["Unregister name of other node"];
+unregister_others_name_1(suite) ->
+ [];
+unregister_others_name_1(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,RSock} = register_node("foo"),
+ ?line {ok,Sock} = connect(),
+ M = [?EPMD_STOP_REQ,"foo"],
+ ?line ok = send(Sock,[size16(M),M]),
+ R = "STOPPED",
+ ?line {ok,R} = recv(Sock,length(R)),
+ ?line ok = close(RSock),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+unregister_others_name_2(doc) ->
+ ["Unregister name of other node"];
+unregister_others_name_2(suite) ->
+ [];
+unregister_others_name_2(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = connect(),
+ M = [?EPMD_STOP_REQ,"xxx42"],
+ ?line ok = send(Sock,[size16(M),M]),
+ R = "NOEXIST",
+ ?line {ok,R} = recv(Sock,length(R)),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+register_overflow(doc) ->
+ ["Register too many, clean and redo 10 times"];
+register_overflow(suite) ->
+ [];
+register_overflow(Config) when list(Config) ->
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ LongDog = test_server:timetrap(?LONG_TEST_TIMEOUT),
+ ?line ok = epmdrun(),
+ ?line Conn = register_many(1, ?REG_REPEAT_LIM, "foo"),
+ Count = length(Conn),
+ ?line ok = unregister_many(Conn),
+ sleep(?MEDIUM_PAUSE),
+ test_server:format("Limit was ~w names, now reg/unreg all 10 times~n",
+ [Count]),
+ ?line ok = register_repeat(Count),
+ sleep(?MEDIUM_PAUSE),
+ ?line ok = rregister_repeat(Count),
+ sleep(?MEDIUM_PAUSE),
+ ?line ok = register_repeat(Count),
+ sleep(?MEDIUM_PAUSE),
+ ?line ok = rregister_repeat(Count),
+ sleep(?MEDIUM_PAUSE),
+ ?line ok = register_repeat(Count),
+ sleep(?MEDIUM_PAUSE),
+ ?line ok = rregister_repeat(Count),
+ sleep(?MEDIUM_PAUSE),
+ ?line ok = register_repeat(Count),
+ sleep(?MEDIUM_PAUSE),
+ ?line ok = rregister_repeat(Count),
+ sleep(?MEDIUM_PAUSE),
+ ?line ok = register_repeat(Count),
+ sleep(?MEDIUM_PAUSE),
+ ?line ok = rregister_repeat(Count),
+ test_server:timetrap_cancel(LongDog),
+ ok.
+
+register_repeat(Count) ->
+ Conn = register_many(1, ?REG_REPEAT_LIM, "foo"),
+ ok = unregister_many(Conn),
+ if
+ length(Conn) == Count ->
+ ok;
+ true ->
+ error
+ end.
+
+rregister_repeat(Count) ->
+ Conn = register_many(1, ?REG_REPEAT_LIM, "foo"),
+ ok = unregister_many(lists:reverse(Conn)),
+ if
+ length(Conn) == Count ->
+ ok;
+ true ->
+ error
+ end.
+
+% Return count of successful registrations
+
+register_many(I, N, _Prefix) when I > N ->
+ test_server:format("Done with all ~n", []),
+ [];
+register_many(I, N, Prefix) ->
+ Name = gen_name(Prefix, I),
+ Port = ?DUMMY_PORT + I, % Just make it up
+ case register_node(Name, Port) of
+ {ok,Sock} ->
+ [{Name,Port,Sock} | register_many(I + 1, N, Prefix)];
+ Any ->
+ test_server:format("Can't register: ~w of 1..~w ~w~n",
+ [Name,N,Any]),
+ []
+ end.
+
+unregister_many([]) ->
+ ok;
+unregister_many([{Name,_Port,Sock} | Socks]) ->
+ case close(Sock) of
+ ok ->
+ unregister_many(Socks);
+ Any ->
+ test_server:format("Can't unregister: ~w reason ~w~n", [Name,Any]),
+ error
+ end.
+
+gen_name(Str,Int) ->
+ Str ++ integer_to_list(Int).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+no_data(doc) ->
+ ["Open but send no data"];
+no_data(suite) ->
+ [];
+no_data(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = connect(),
+ sleep(?LONG_PAUSE),
+ ?line closed = recv(Sock,1),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+one_byte(doc) ->
+ ["Send one byte only"];
+one_byte(suite) ->
+ [];
+one_byte(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = connect(),
+ ?line ok = send(Sock,[0]),
+ sleep(?LONG_PAUSE),
+ ?line closed = recv(Sock,1),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+two_bytes(doc) ->
+ ["Send packet size only"];
+two_bytes(suite) ->
+ [];
+two_bytes(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = connect(),
+ ?line ok = send(Sock,[put16(3)]),
+ sleep(?LONG_PAUSE),
+ ?line closed = recv(Sock,1),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+partial_packet(doc) ->
+ ["Got only part of a packet"];
+partial_packet(suite) ->
+ [];
+partial_packet(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = connect(),
+ ?line ok = send(Sock,[put16(100),"only a few bytes"]),
+ sleep(?LONG_PAUSE),
+ ?line closed = recv(Sock,1),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+zero_length(doc) ->
+ ["Invalid zero packet size"];
+zero_length(suite) ->
+ [];
+zero_length(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = connect(),
+ ?line ok = send(Sock,[0,0,0,0,0,0,0,0,0,0]),
+ sleep(?MEDIUM_PAUSE),
+ ?line closed = recv(Sock,1),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+too_large(doc) ->
+ ["Invalid large packet"];
+too_large(suite) ->
+ [];
+too_large(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = connect(),
+ Size = 63000,
+ M = lists:duplicate(Size, $z),
+ ?line ok = send(Sock,[put16(Size),M]),
+ sleep(?MEDIUM_PAUSE),
+ ?line closed = recv(Sock,1),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+alive_req_too_small_1(doc) ->
+ ["Try to register but not enough data"];
+alive_req_too_small_1(suite) ->
+ [];
+alive_req_too_small_1(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = connect(),
+ M = [?EPMD_ALIVE_REQ, 42],
+ ?line ok = send(Sock, [size16(M), M]),
+ sleep(?MEDIUM_PAUSE),
+ ?line closed = recv(Sock,1),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+alive_req_too_small_2(doc) ->
+ ["Try to register but not enough data"];
+alive_req_too_small_2(suite) ->
+ [];
+alive_req_too_small_2(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = connect(),
+ M = [?EPMD_ALIVE_REQ, put16(?DUMMY_PORT)],
+ ?line ok = send(Sock, [size16(M), M]),
+ sleep(?MEDIUM_PAUSE),
+ ?line closed = recv(Sock,1),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+alive_req_too_large(doc) ->
+ ["Try to register but node name too large"];
+alive_req_too_large(suite) ->
+ [];
+alive_req_too_large(Config) when list(Config) ->
+ ?line ok = epmdrun(),
+ ?line {ok,Sock} = connect(),
+ L = [
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ ],
+ M = [?EPMD_ALIVE_REQ, put16(?DUMMY_PORT), L],
+ ?line ok = send(Sock, [size16(M), M]),
+ sleep(?MEDIUM_PAUSE),
+ ?line closed = recv(Sock,1),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Terminate all tests with killing epmd.
+
+cleanup() ->
+ sleep(?MEDIUM_PAUSE),
+ case connect() of
+ {ok,Sock} ->
+ M = [?EPMD_KILL_REQ],
+ send(Sock, [size16(M), M]),
+ recv(Sock,length("OK")),
+ close(Sock),
+ sleep(?MEDIUM_PAUSE);
+ _ ->
+ true
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Normal debug start of epmd
+
+epmdrun() ->
+ case os:find_executable(epmd) of
+ false ->
+ {error, {could_not_find_epmd_in_path}};
+ Path ->
+ epmdrun(Path)
+ end.
+
+epmdrun(Epmd) ->
+ %% test_server:format("epmdrun() => Epmd = ~p",[Epmd]),
+ osrun(Epmd ++ " " ?EPMDARGS " -port " ++ integer_to_list(?PORT)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Start an external process
+
+osrun(Cmd) ->
+ _ = open_port({spawn, Cmd}, []),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% Wrappers of TCP functions
+
+% These two functions is the interface for connect.
+% Passive mode is the default
+
+connect() ->
+ connect(?PORT, passive).
+
+connect_active() ->
+ connect(?PORT, active).
+
+
+% Try a few times before giving up
+
+connect(Port, Mode) ->
+ case connect_repeat(?CONN_RETRY, Port, Mode) of
+ {ok,Sock} ->
+ {ok,Sock};
+ {error,timeout} ->
+ timeout;
+ {error,Reason} ->
+ test_server:format("connect: error: ~w~n",[Reason]),
+ error;
+ Any ->
+ test_server:format("connect: unknown message: ~w~n",[Any]),
+ exit(1)
+ end.
+
+
+% Try a few times before giving up. Pause a small time between
+% each try.
+
+connect_repeat(1, Port, Mode) ->
+ connect_mode(Port, Mode);
+connect_repeat(Retry, Port, Mode) ->
+ case connect_mode(Port, Mode) of
+ {ok,Sock} ->
+ {ok,Sock};
+ {error,Reason} ->
+ test_server:format("connect: error: ~w~n",[Reason]),
+ timer:sleep(?CONN_SLEEP),
+ connect_repeat(Retry - 1, Port, Mode);
+ Any ->
+ test_server:format("connect: unknown message: ~w~n",[Any]),
+ exit(1)
+ end.
+
+connect_mode(Port, active) ->
+ gen_tcp:connect("localhost", Port, [{packet, 0}], ?CONN_TIMEOUT);
+connect_mode(Port, passive) ->
+ gen_tcp:connect("localhost", Port, [{packet, 0}, {active, false}],
+ ?CONN_TIMEOUT).
+
+
+close(Sock) ->
+ case gen_tcp:close(Sock) of
+ {error,_} ->
+ error;
+ ok ->
+ ok;
+ Any ->
+ test_server:format("unknown message: ~w~n",[Any]),
+ exit(1)
+ end.
+
+recv(Sock, Len) ->
+ recv(Sock, Len, ?RECV_TIMEOUT).
+
+recv(Sock, Len, Timeout) ->
+ case gen_tcp:recv(Sock, Len, Timeout) of
+ {ok,[]} -> % Should not be the case
+ recv(Sock, 1, 1); % any longer
+ {ok,Data} ->
+ {ok,Data};
+ {error,timeout} ->
+ timeout;
+ {error,closed} ->
+ closed;
+ {error,_}=Error ->
+ Error;
+ Any ->
+ test_server:format("unknown message: ~w~n",[Any]),
+ exit(1)
+ end.
+
+%% Send data to socket. The list can be non flat and contain
+%% the atom 'd' or tuple {d,Seconds} where this is delay
+%% put in between the sent characters.
+
+send(Sock, SendSpec) ->
+ case send(SendSpec, [], Sock) of
+ {ok,[]} ->
+ ok;
+ {ok,RevBytes} ->
+ send_direct(Sock, lists:reverse(RevBytes));
+ Any ->
+ Any
+ end.
+
+
+% If an error, return immediately
+% Collect real characters in the first argument to form
+% a string to send. Only perform "actions", like a delay,
+% when this argument is empty.
+
+send([], RevBytes, _Sock) ->
+ {ok,RevBytes};
+send([Byte | Spec], RevBytes, Sock) when integer(Byte) ->
+ send(Spec, [Byte | RevBytes], Sock);
+send([List | Spec], RevBytes, Sock) when list(List) ->
+ case send(List, RevBytes, Sock) of
+ {ok,Left} ->
+ send(Spec, Left, Sock);
+ Other ->
+ Other
+ end;
+send([d | Spec], RevBytes, Sock) ->
+ send([{d,1000} | Spec], RevBytes, Sock);
+send([{d,S} | Spec], RevBytes, Sock) ->
+ case send_direct(Sock, lists:reverse(RevBytes)) of
+ ok ->
+ timer:sleep(S),
+ send(Spec, [], Sock);
+ Any ->
+ Any
+ end.
+
+%%%%
+
+send_direct(Sock, Bytes) ->
+ case gen_tcp:send(Sock, Bytes) of
+ ok ->
+ ok;
+ {error, closed} ->
+ closed;
+ {error, _Reason} ->
+ error;
+ Any ->
+ test_server:format("unknown message: ~w~n",[Any]),
+ Any
+ end.
+
+sleep(MilliSeconds) ->
+ timer:sleep(MilliSeconds).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+put16(N) ->
+ [N bsr 8, N band 16#ff].
+
+size16(List) ->
+ N = flat_count(List, 0),
+ [N bsr 8, N band 16#ff].
+
+flat_count([H|T], N) when is_integer(H) ->
+ flat_count(T, N+1);
+flat_count([H|T], N) when is_list(H) ->
+ flat_count(T, flat_count(H, N));
+flat_count([_|T], N) ->
+ flat_count(T, N);
+flat_count([], N) -> N.
+