diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/runtime_tools/c_src | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/runtime_tools/c_src')
-rw-r--r-- | lib/runtime_tools/c_src/Makefile | 5 | ||||
-rw-r--r-- | lib/runtime_tools/c_src/Makefile.in | 151 | ||||
-rw-r--r-- | lib/runtime_tools/c_src/trace_file_drv.c | 638 | ||||
-rw-r--r-- | lib/runtime_tools/c_src/trace_ip_drv.c | 938 |
4 files changed, 1732 insertions, 0 deletions
diff --git a/lib/runtime_tools/c_src/Makefile b/lib/runtime_tools/c_src/Makefile new file mode 100644 index 0000000000..19184f2e8d --- /dev/null +++ b/lib/runtime_tools/c_src/Makefile @@ -0,0 +1,5 @@ +# +# Invoke with GNU make or clearmake -C gnu. +# +include $(ERL_TOP)/make/run_make.mk + diff --git a/lib/runtime_tools/c_src/Makefile.in b/lib/runtime_tools/c_src/Makefile.in new file mode 100644 index 0000000000..05b5598e51 --- /dev/null +++ b/lib/runtime_tools/c_src/Makefile.in @@ -0,0 +1,151 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 1999-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 + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk +VSN=$(RUNTIME_TOOLS_VSN) + +# ---------------------------------------------------- +# The following variables differ on different systems, we set +# reasonable defaults, if something different is needed it should +# be set for that system only. +# ---------------------------------------------------- +CC = @CC@ +CFLAGS = @DED_CFLAGS@ +LD = @DED_LD@ +SHELL = /bin/sh +LIBS = @LIBS@ +LDFLAGS += @DED_LDFLAGS@ +ERLANG_OSTYPE = @ERLANG_OSTYPE@ + +SYSINCLUDE = -I$(ERL_TOP)/erts/emulator/beam \ + -I$(ERL_TOP)/erts/emulator/sys/$(ERLANG_OSTYPE) \ + -I$(ERL_TOP)/erts/include/internal \ + -I$(ERL_TOP)/erts/include/internal/$(ERLANG_OSTYPE) \ + -I$(ERL_TOP)/erts/include \ + -I$(ERL_TOP)/erts/include/$(ERLANG_OSTYPE) +ifeq ($(findstring vxworks,$(TARGET)),vxworks) + SYSINCLUDE += -I$(ERL_TOP)/erts/etc/vxworks +endif + +TRACE_DRV_INCLUDES = $(SYSINCLUDE) + +ALL_CFLAGS = $(CFLAGS) @DEFS@ $(TYPE_FLAGS) $(TRACE_DRV_INCLUDES) + + +ifeq ($(TYPE),debug) +TYPEMARKER = .debug +TYPE_FLAGS = -g -DDEBUG @DEBUG_FLAGS@ +else +TYPEMARKER = +TYPE_FLAGS = -O2 +endif + +ROOTDIR = $(ERL_TOP)/lib +PRIVDIR = ../priv +LIBDIR = $(PRIVDIR)/lib/$(TARGET) +OBJDIR = $(PRIVDIR)/obj/$(TARGET) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/runtime_tools-$(VSN) + +# ---------------------------------------------------- +# Misc Macros +# ---------------------------------------------------- +TRACE_IP_DRV_OBJS = \ + $(OBJDIR)/trace_ip_drv.o + +TRACE_FILE_DRV_OBJS = \ + $(OBJDIR)/trace_file_drv.o + +ifeq ($(findstring win32,$(TARGET)), win32) +SOLIBS = $(LIBDIR)/trace_ip_drv.dll $(LIBDIR)/trace_file_drv.dll +LN=cp +else +ifeq ($(findstring vxworks,$(TARGET)),vxworks) +SOLIBS = $(LIBDIR)/trace_ip_drv.eld $(LIBDIR)/trace_file_drv.eld +else +SOLIBS = $(LIBDIR)/trace_ip_drv.so $(LIBDIR)/trace_file_drv.so +endif +endif +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(OBJDIR) $(BINDIR) $(SOLIBS) + +$(OBJDIR): + -@mkdir -p $(OBJDIR) + +$(BINDIR): + -@mkdir -p $(BINDIR) + +$(OBJDIR)/%.o: %.c + $(INSTALL_DIR) $(OBJDIR) + $(CC) -c -o $@ $(ALL_CFLAGS) $< + +$(LIBDIR)/trace_ip_drv.so: $(TRACE_IP_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ -lc $(LIBS) + +$(LIBDIR)/trace_file_drv.so: $(TRACE_FILE_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ -lc $(LIBS) + +$(LIBDIR)/trace_ip_drv.dll: $(TRACE_IP_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ $(LIBS) +$(LIBDIR)/trace_file_drv.dll: $(TRACE_FILE_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ $(LIBS) +# +# VxWorks is simply to different from Unix in this sense. +# Here are the inference rules for VxWorks +# +$(LIBDIR)/trace_ip_drv.eld: $(TRACE_IP_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ + +$(LIBDIR)/trace_file_drv.eld: $(TRACE_FILE_DRV_OBJS) + $(INSTALL_DIR) $(LIBDIR) + $(LD) $(LDFLAGS) -o $@ $^ + +clean: + rm -f $(SOLIBS) $(TRACE_IP_DRV_OBJS) $(TRACE_FILE_DRV_OBJS) + rm -f core *~ + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/priv/lib + $(INSTALL_PROGRAM) $(SOLIBS) $(RELSYSDIR)/priv/lib + +release_docs_spec: + diff --git a/lib/runtime_tools/c_src/trace_file_drv.c b/lib/runtime_tools/c_src/trace_file_drv.c new file mode 100644 index 0000000000..482fcc0288 --- /dev/null +++ b/lib/runtime_tools/c_src/trace_file_drv.c @@ -0,0 +1,638 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1999-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% + */ + +/* + * Purpose: Send trace messages to a file. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef __WIN32__ +# include <io.h> +# define write _write +# define open _open +# define close _close +# define unlink _unlink +#else +# include <unistd.h> +#endif +#include <errno.h> +#include <sys/types.h> +#include <fcntl.h> +#ifdef VXWORKS +# include "reclaim.h" +#endif + + + +/* + * Deduce MAXPATHLEN, which is the one to use in this file, + * from any available definition. + */ +#ifndef MAXPATHLEN +# ifdef PATH_MAX /* Posix */ +# define MAXPATHLEN PATH_MAX +# else +# ifdef _POSIX_PATH_MAX /* Posix */ +# define MAXPATHLEN _POSIX_PATH_MAX +# else +# ifdef MAXPATH +# define MAXPATHLEN MAXPATH +# else +# ifdef MAX_PATH +# define MAXPATHLEN MAX_PATH +# else +# ifdef _MAX_PATH +# define MAXPATHLEN _MAX_PATH +# else +# error Could not define MAXPATHLEN +# endif +# endif +# endif +# endif +# endif +#endif + + + +#ifdef DEBUG +#ifndef __WIN32__ +#define ASSERT(X) do {if (!(X)) {erl_exit(1,"%s",#X);} } while(0) +#else +#include <assert.h> +#define ASSERT(X) assert(X) +#endif +#else +#define ASSERT(X) +#endif + + + +#include "erl_driver.h" + + + +/* +** Protocol from driver: +** '\0' -> ok +** '\1' ++ String -> {error, Atom} +** +** Protocol when opening (arguments to start): +** ["w <WrapSize> <WrapCnt> <TailIndex> "] "n <Filename>" +** Where... +** <Filename>, a string ('\0' terminated): +** The filename where the trace output is to be written. +** "w ...", if present orders a size limited wrapping log. +** <WrapSize>, an unsigned integer: +** The size limit of each log file. +** <WrapCnt>, an unsigned integer: +** The number of log files. +** <TailIndex>, an unsigned integer: +** The (zero based) index of where to insert the filename +** sequence count "a".."z","aa".."az","ba".."zz","aaa"... +** +** Port control messages handled: +** 'f' -> '\0' (ok) | '\1' ++ String (error) : Flush file. +** +** The package written to the file looks like this: +** +--+--------+-----------------------------------+ +** |Op|Size NBO|Term in external format or empty | +** +--+--------+-----------------------------------+ +** Op, a char, for conformance with the IP driver: +** 0 = binary, 1 = drop +** If Op is 1, then Size reflects the number of dropped messages. The +** op 1 is never used in this driver. +** Size, a 32 bit interger in network byte order: +** Either the size of the binary term, or the number of packet's dropped. +** Term, an array of bytes: +** An erlang term in the external format or simply empty if Op == 1, the +** term is Size long. +*/ + +typedef int FILETYPE; + +#define BUFFER_SIZE (BUFSIZ*8) + +#define OP_BINARY 0 +#define OP_DROP 1 + +/* +** State structures +*/ + +typedef struct trace_file_name { + char name[MAXPATHLEN+1]; /* Incl. space for terminating '\0' */ + unsigned suffix; /* Index of suffix start */ + unsigned tail; /* Index of tail start */ + unsigned len; /* Total length (strlen) */ + unsigned cnt; /* Current file count 0 <= cnt <= n */ + unsigned n; /* Number of files */ +} TraceFileName; + +typedef struct trace_file_wrap_data { + TraceFileName cur; /* Current trace file */ + TraceFileName del; /* Next file to delete when wrapping */ + unsigned size; /* File max size */ + int cnt; /* How many remains before starting to wrap */ + unsigned long time; /* Time to pass until starting to delete old files */ + unsigned len; /* Current file len */ +} TraceFileWrapData; + +typedef struct trace_file_data { + FILETYPE fd; + ErlDrvPort port; + struct trace_file_data *next, *prev; + TraceFileWrapData *wrap; /* == NULL => no wrap */ + int buff_siz; + int buff_pos; + unsigned char buff[1]; /* You guessed it, will be longer... */ +} TraceFileData; + +static TraceFileData *first_data; + +/* +** Interface routines +*/ +static ErlDrvData trace_file_start(ErlDrvPort port, char *buff); +static void trace_file_stop(ErlDrvData handle); +static void trace_file_output(ErlDrvData handle, char *buff, int bufflen); +static void trace_file_finish(void); +static int trace_file_control(ErlDrvData handle, unsigned int command, + char* buff, int count, + char** res, int res_size); +static void trace_file_timeout(ErlDrvData handle); + +/* +** Internal routines +*/ +static unsigned digits(unsigned n); +static void next_name(TraceFileName *tfn); +static void *my_alloc(size_t size); +static int my_write(TraceFileData *data, unsigned char *buff, int siz); +static int my_flush(TraceFileData *data); +static void put_be(unsigned n, unsigned char *s); +static void close_unlink_port(TraceFileData *data); +static int wrap_file(TraceFileData *data); + +/* +** The driver struct +*/ +ErlDrvEntry trace_file_driver_entry = { + NULL, /* F_PTR init, N/A */ + trace_file_start, /* L_PTR start, called when port is opened */ + trace_file_stop, /* F_PTR stop, called when port is closed */ + trace_file_output, /* F_PTR output, called when erlang has sent */ + NULL, /* F_PTR ready_input, called when input descriptor + ready */ + NULL, /* F_PTR ready_output, called when output + descriptor ready */ + "trace_file_drv", /* char *driver_name, the argument to open_port */ + trace_file_finish, /* F_PTR finish, called when unloaded */ + NULL, /* void * that is not used (BC) */ + trace_file_control, /* F_PTR control, port_control callback */ + trace_file_timeout, /* F_PTR timeout, driver_set_timer callback */ + NULL /* F_PTR outputv, reserved */ +}; + +/* +** Driver initialization routine +*/ +DRIVER_INIT(trace_file_drv) +{ + first_data = NULL; + return &trace_file_driver_entry; +} + +/* +** Driver interface routines +*/ + +/* +** Open a port +*/ +static ErlDrvData trace_file_start(ErlDrvPort port, char *buff) +{ + unsigned size, cnt, time, tail, len; + char *p; + TraceFileData *data; + TraceFileWrapData *wrap; + FILETYPE fd; + int n, w; + static const char name[] = "trace_file_drv"; + +#ifdef HARDDEBUG + fprintf(stderr,"hello (%s)\r\n", buff); +#endif + w = 0; /* Index of where sscanf gave up */ + size = 0; /* Warning elimination */ + cnt = 0; /* -""- */ + time = 0; /* -""- */ + tail = 0; /* -""- */ + n = sscanf(buff, "trace_file_drv %n w %u %u %u %u %n", + &w, &size, &cnt, &time, &tail, &w); + + if (w < sizeof(name) || (n != 0 && n != 4)) + return ERL_DRV_ERROR_BADARG; + + /* Search for "n <Filename>" in the rest of the string */ + p = buff + w; + for (p = buff + w; *p == ' '; p++); /* Skip space (necessary?) */ + if (*p++ != 'n') + return ERL_DRV_ERROR_BADARG; + if (*p++ != ' ') + return ERL_DRV_ERROR_BADARG; + /* Here we are at the start of the filename; p */ + len = strlen(p); + if (tail >= len) + /* Tail must start within filename */ + return ERL_DRV_ERROR_BADARG; + + data = my_alloc(sizeof(TraceFileData) - 1 + BUFFER_SIZE); + + /* We have to check the length in case we are running on + * VxWorks since too long pathnames may cause bus errors + * instead of error return from file operations. + */ + if (n == 4) { + /* Size limited wrapping log */ + unsigned d = digits(cnt); /* Nof digits in filename counter */ + if (len+d >= MAXPATHLEN) { + errno = ENAMETOOLONG; + return ERL_DRV_ERROR_ERRNO; + } + wrap = my_alloc(sizeof(TraceFileWrapData)); + wrap->size = size; + wrap->cnt = cnt; + wrap->time = time; + wrap->len = 0; + strcpy(wrap->cur.name, p); + wrap->cur.suffix = tail; + wrap->cur.tail = tail; + wrap->cur.len = len; + wrap->cur.cnt = cnt; + wrap->cur.n = cnt; + next_name(&wrap->cur); /* Incr to suffix "0" */ + wrap->del = wrap->cur; /* Struct copy! */ + p = wrap->cur.name; /* Use new name for open */ + } else { + /* Regular log */ + if (len >= MAXPATHLEN) { + errno = ENAMETOOLONG; + return ERL_DRV_ERROR_ERRNO; + } + wrap = NULL; + } + + if ((fd = open(p, O_WRONLY | O_TRUNC | O_CREAT +#ifdef O_BINARY + | O_BINARY +#endif + , 0777)) < 0) { + if (wrap) + driver_free(wrap); + driver_free(data); + return ERL_DRV_ERROR_ERRNO; + } + + data->fd = fd; + data->port = port; + data->buff_siz = BUFFER_SIZE; + data->buff_pos = 0; + data->wrap = wrap; + + if (first_data) { + data->prev = first_data->prev; + first_data->prev = data; + } else + data->prev = NULL; + data->next = first_data; + first_data = data; + + if (wrap && wrap->time > 0) + driver_set_timer(port, wrap->time); + + return (ErlDrvData) data; +} + + +/* +** Close a port +*/ +static void trace_file_stop(ErlDrvData handle) +{ + close_unlink_port((TraceFileData *) handle); +} + +/* +** Data sent from erlang to port. +*/ +static void trace_file_output(ErlDrvData handle, char *buff, int bufflen) +{ + int heavy = 0; + TraceFileData *data = (TraceFileData *) handle; + unsigned char b[5] = ""; + put_be((unsigned) bufflen, b + 1); + switch (my_write(data, b, sizeof(b))) { + case 1: + heavy = !0; + case 0: + switch (my_write(data, buff, bufflen)) { + case 1: + heavy = !0; + case 0: + break; + case -1: + driver_failure_posix(data->port, errno); /* XXX */ + return; + } + break; + case -1: + driver_failure_posix(data->port, errno); /* XXX */ + return; + } + if (data->wrap) { + TraceFileWrapData *wrap = data->wrap; + /* Size limited wrapping log files */ + wrap->len += sizeof(b) + bufflen; + if (wrap->time == 0 && wrap->len >= wrap->size) { + if (wrap_file(data) < 0) { + driver_failure_posix(data->port, errno); /* XXX */ + return; + } + heavy = !0; + } + } + if (heavy) { + set_port_control_flags(data->port, PORT_CONTROL_FLAG_HEAVY); + } +} + +/* +** Control message from erlang, we handle $f, which is flush. +*/ +static int trace_file_control(ErlDrvData handle, unsigned int command, + char* buff, int count, + char** res, int res_size) +{ + if (command == 'f') { + TraceFileData *data = (TraceFileData *) handle; + if (my_flush(data) < 0) { + driver_failure_posix(data->port, errno); /* XXX */ + } + if (res_size < 1) { + *res = my_alloc(1); + } + **res = '\0'; + return 1; + } + return -1; +} + +/* +** Timeout from driver_set_timer. +*/ +static void trace_file_timeout(ErlDrvData handle) { + TraceFileData *data = (TraceFileData *) handle; + if (data->wrap) { + if (wrap_file(data) < 0) { + driver_failure_posix(data->port, errno); /* XXX */ + return; + } else { + driver_set_timer(data->port, data->wrap->time); + } + } +} + +/* +** Driver unloaded +*/ +static void trace_file_finish(void) +{ + while (first_data != NULL) { + close_unlink_port(first_data); + } +} + +/* +** Internal helpers +*/ + +/* Calculate needed number of digits in filename counter. +**/ +static unsigned digits(unsigned n) { + unsigned m, i; + for (m = 10, i = 1; n >= m; i++, m *= 10) ; + return i; +} + +/* +** Increment filename. +** +** The filename counter counts "0"-"9","10"-"19"..."[n->n]","0"..., +** but also "","0" which is used for initialization. +*/ +static void next_name(TraceFileName *n) { + if (n->cnt >= n->n) { + n->cnt = 0; + /* Circular count from "[n->n]" to "0", or from "" to "0" */ + memmove(&n->name[n->suffix+1], + &n->name[n->tail], + n->len+1 - n->tail); /* Including '\0' */ + n->name[n->suffix] = '0'; + n->len -= n->tail - n->suffix - 1; + n->tail = n->suffix + 1; + } else { + int i = n->tail; + n->cnt++; + do { + i--; + /* Increment from the end, + * '0'..'1', carry propagate forward */ + if (n->name[i] < '9') { + n->name[i]++; + return; + } else + n->name[i] = '0'; + } while (i > n->suffix); + /* Wrapped around from "99..99" to "00..00", + * need one more character */ + memmove(&n->name[n->tail+1], + &n->name[n->tail], + n->len+1 - n->tail); /* Incl '\0' */ + n->name[n->tail++] = '0'; + n->name[n->suffix] = '1'; + n->len++; + } +} + +/* +** Yet another malloc wrapper +*/ +static void *my_alloc(size_t size) +{ + void *ret; + if ((ret = (void *) driver_alloc(size)) == NULL) { + /* May or may not work... */ + fprintf(stderr, "Could not allocate %d bytes of memory in %s.", + (int) size, __FILE__); + exit(1); + } + return ret; +} + +/* +** A write wrapper that regards it as an error if not all data was written. +*/ +static int do_write(FILETYPE fd, unsigned char *buff, int siz) { + int w = write(fd, buff, siz); + if (w != siz) { + if (w >= 0) { + errno = ENOSPC; + } + return -1; + } + return siz; +} + +/* +** Returns 0 if write to cache, 1 i write to file, and -1 if write failed. +*/ +static int my_write(TraceFileData *data, unsigned char *buff, int siz) +{ + int wrote, w; + + if (data->buff_siz - data->buff_pos >= siz) { + memcpy(data->buff + data->buff_pos, buff, siz); + data->buff_pos += siz; + return 0; + } + + wrote = data->buff_siz - data->buff_pos; + memcpy(data->buff + data->buff_pos, buff, wrote); + if (do_write(data->fd, data->buff, data->buff_siz) < 0) { + return -1; + } + data->buff_pos = 0; + if (siz - wrote >= data->buff_siz) { + /* Write directly, no need to buffer... */ + if (do_write(data->fd, buff + wrote, siz - wrote) < 0) { + return -1; + } + return 1; + } + memcpy(data->buff, buff + wrote, siz - wrote); + data->buff_pos = siz - wrote; + set_port_control_flags(data->port, PORT_CONTROL_FLAG_HEAVY); + return 1; +} + +/* +** Returns negative if it failed to write. + */ +static int my_flush(TraceFileData *data) +{ + if (do_write(data->fd, data->buff, data->buff_pos) < 0) { + return -1; + } + data->buff_pos = 0; + return 0; +} + +/* +** Write unsigned to buffer in big endian +*/ +static void put_be(unsigned n, unsigned char *s) +{ + s[0] = n >> 24; + s[1] = (n >> 16) & 0xFFFFU; + s[2] = (n >> 8) & 0xFFFFU; + s[3] = n & 0xFFFFU; +} + +/* +** Wrapper that only closes non-negative filehandles +*/ +static void do_close(FILETYPE fd) { + if (fd != -1) { + close(fd); + } +} + +/* +** Close the whole port and clean up +*/ +static void close_unlink_port(TraceFileData *data) +{ + my_flush(data); + do_close(data->fd); + + if (data->next) + data->next->prev = data->prev; + if (data->prev) + data->prev->next = data->next; + else + first_data = data->next; + + if (data->wrap) + driver_free(data->wrap); + driver_free(data); +} + +/* +** Wrap to new file - close the current, open a new and +** perhaps delete a too old one +** +** Returns negative if something failed. +*/ +static int wrap_file(TraceFileData *data) { + if (my_flush(data) < 0) { + close(data->fd); + data->fd = -1; + return -1; + } + close(data->fd); + data->fd = -1; + data->buff_pos = 0; + data->wrap->len = 0; + /* Count down before starting to remove old files */ + if (data->wrap->cnt > 0) + data->wrap->cnt--; + if (data->wrap->cnt == 0) { + /* Remove an old file */ + unlink(data->wrap->del.name); + next_name(&data->wrap->del); + } + next_name(&data->wrap->cur); + data->fd = open(data->wrap->cur.name, O_WRONLY | O_TRUNC | O_CREAT +#ifdef O_BINARY + | O_BINARY +#endif + , 0777); + if (data->fd < 0) { + data->fd = -1; + return -1; + } + return 0; +} + diff --git a/lib/runtime_tools/c_src/trace_ip_drv.c b/lib/runtime_tools/c_src/trace_ip_drv.c new file mode 100644 index 0000000000..d2ed1a294b --- /dev/null +++ b/lib/runtime_tools/c_src/trace_ip_drv.c @@ -0,0 +1,938 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1999-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 is a trace port, which means it cannot be busy and takes +** no opcodes. +** Send trace messages over the net. +*/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef __WIN32__ +# include <winsock2.h> +# include <windows.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifndef __WIN32__ +# ifdef VXWORKS +# include <sockLib.h> +# include <sys/times.h> +# include <iosLib.h> +# include <taskLib.h> +# include <selectLib.h> +# include <ioLib.h> +# include "reclaim.h" +# endif +# include <unistd.h> +# include <errno.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <netinet/in.h> +# include <fcntl.h> +#endif + +#ifdef DEBUG +# ifndef __WIN32__ + /* erl_exit is not available to dll_drivers on windows. */ + void erl_exit(int, char *, ...); +# define ASSERT(X) \ + do { \ + if (!(X)) { \ + erl_exit(1,"%s",#X); \ + } \ + } while(0) +# else +# include <assert.h> +# define ASSERT(X) assert(X) +# endif +#else +# define ASSERT(X) +#endif + +#define sock2event(s) ((ErlDrvEvent)(long)(s)) +#define event2sock(p) ((SOCKET)(long)(p)) + +#include "erl_driver.h" + +/* +** Protocol from driver: +** '\0' -> ok +** '\1' ++ String -> {error, Atom} +** Protocol when opening (arguments to start): +** <Portno> <Quesize> <Fl> +** Where... +** Portno, ascii string representing a number: +** The TCP port number to listen to. +** Quesize, ascii string representing a number: +** The number of messages to que up before dropping. +** Fl, ascii string representing a flag byte: +** 0x1 -> Drop oldest when que is full (instead of last arrived) +** 0x2 -> Fill the que even if noone is listening. +** +** The package sent over the network looks like this: +** +--+--------+-----------------------------------+ +** |Op|Size NBO|Term in external format or empty | +** +--+--------+-----------------------------------+ +** Op, a char: +** 0 = binary, 1 = drop +** If Op is 1, then Size reflects the number of dropped messages. +** Size, a 32 bit interger in network byte order: +** Either the size of the binary term, or the number of packet's dropped. +** Term, an array of bytes: +** An erlang term in the external format or simply empty if Op == 1, the +** term is Size long. +*/ + +/* +** SO, most of the differences between WinDoze and Posixish OS'es +** is handeled here, but the multiplexing (driver_select) is also quite +** interesting, see my_driver_select further down in the file... +*/ + +#ifndef __WIN32__ +# define FLAG_READ ERL_DRV_READ +# define FLAG_WRITE ERL_DRV_WRITE + typedef int SOCKET; +# define INVALID_SOCKET -1 +# define IS_INVALID_SOCKET(S) ((S) < 0) +# define closesocket(Sock) close(Sock) +# define ERRNO errno +# ifdef EWOULDBLOCK +# define ERRNO_BLOCK EWOULDBLOCK +# else +# ifdef EAGAIN +# define ERRNO_BLOCK EAGAIN +# else +# error "No EWOULDBLOCK found" +# endif +# endif +#else /* Win32 */ +# define FLAG_READ (FD_READ | FD_CONNECT | FD_ACCEPT) +# define FLAG_WRITE FD_WRITE +# define ERRNO WSAGetLastError() +# define ERRNO_BLOCK WSAEWOULDBLOCK +# define IS_INVALID_SOCKET(S) ((S) == INVALID_SOCKET) +#endif + + +/* +** Option flags +*/ +#define FLAG_DROP_OLDEST 1 +#define FLAG_FILL_ALWAYS 2 +#define FLAG_LISTEN_PORT 4 + +/* +** Op's in messages +*/ +#define OP_BINARY 0 +#define OP_DROP 1 + +/* +** State structure +*/ + +typedef struct trace_ip_message { + int siz; /* the size of the "binary data" */ + int written; /* if only a part was written, when == siz, all is written */ + unsigned char bin[1]; /* The opcode, the Size and optionally the binary. */ +} TraceIpMessage; + + +typedef struct trace_ip_data { + unsigned flags; + int listen_portno; + SOCKET listenfd; + SOCKET fd; +#ifdef __WIN32__ + unsigned listen_event_mask; + HANDLE listen_event; + unsigned event_mask; + HANDLE event; +#endif + ErlDrvPort port; + struct trace_ip_data *next; + int quesiz; + int questart; + int questop; + TraceIpMessage *que[1]; /* You guessed it, will be longer... */ +} TraceIpData; + +static TraceIpData *first_data; + +/* +** Interface routines +*/ +static ErlDrvData trace_ip_start(ErlDrvPort port, char *buff); +static void trace_ip_stop(ErlDrvData handle); +static void trace_ip_output(ErlDrvData handle, char *buff, int bufflen); +#ifdef __WIN32__ +static void trace_ip_event(ErlDrvData handle, ErlDrvEvent event); +#endif +static void trace_ip_ready_input(ErlDrvData handle, ErlDrvEvent fd); +static void trace_ip_ready_output(ErlDrvData handle, ErlDrvEvent fd); +static void trace_ip_finish(void); /* No arguments, despite what might be stated + in any documentation */ +static int trace_ip_control(ErlDrvData handle, unsigned int command, + char* buff, int count, + char** res, int res_size); + +/* +** Internal routines +*/ +static void *my_alloc(size_t size); +static ErlDrvBinary *my_alloc_binary(int size); +static int write_until_done(SOCKET s, char *buff, int bufflen); +static unsigned get_be(unsigned char *s); +static void put_be32(unsigned n, unsigned char *s); +static void put_be16(unsigned n, unsigned char *s); +static TraceIpData *lookup_data_by_port(int portno); +static int set_nonblocking(SOCKET sock); +static TraceIpMessage *make_buffer(int datasiz, unsigned char op, + unsigned number); +static void enque_message(TraceIpData *data, unsigned char *buff, int bufflen, + int byteswritten); +static void clean_que(TraceIpData *data); +static void close_client(TraceIpData *data); +static int trywrite(TraceIpData *data, unsigned char *buff, int bufflen); +static SOCKET my_accept(SOCKET sock); +static void close_unlink_port(TraceIpData *data); +enum MySelectOp { SELECT_ON, SELECT_OFF, SELECT_CLOSE }; +static int my_driver_select(TraceIpData *desc, SOCKET fd, int flags, enum MySelectOp); +static void stop_select(ErlDrvEvent event, void*); + +/* +** The driver struct +*/ +ErlDrvEntry trace_ip_driver_entry = { + NULL, /* F_PTR init, N/A */ + trace_ip_start, /* L_PTR start, called when port is opened */ + trace_ip_stop, /* F_PTR stop, called when port is closed */ + trace_ip_output, /* F_PTR output, called when erlang has sent */ +#ifdef __WIN32__ + trace_ip_event, /* Anything happens on an associated event */ + NULL, /* Write selections not supported on win32 */ +#else + trace_ip_ready_input, /* F_PTR ready_input, called when input descriptor + ready */ + trace_ip_ready_output, /* F_PTR ready_output, called when output + descriptor ready */ +#endif + "trace_ip_drv", /* char *driver_name, the argument to open_port */ + trace_ip_finish, /* F_PTR finish, called when unloaded */ + NULL, /* void * that is not used */ + trace_ip_control, /* F_PTR control, port_control callback */ + NULL, /* F_PTR timeout, reserved */ + NULL, /* F_PTR outputv, reserved */ + NULL, /* ready_async */ + NULL, /* flush */ + NULL, /* call */ + NULL, /* event */ + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + 0, /* ERL_DRV_FLAGs */ + NULL, + NULL, /* process_exit */ + stop_select +}; + +/* +** Driver initialization routine +** +** No matter what's stated otherwise, this function shall return a pointer. +*/ + +DRIVER_INIT(trace_ip_drv) +{ + first_data = NULL; + /*trace_ip_driver_entry.handle = handle; ??? What is this, and why? It is no more! */ + return &trace_ip_driver_entry; +} + +/* +** Driver interface routines +*/ + +/* +** Open a port +*/ +static ErlDrvData trace_ip_start(ErlDrvPort port, char *buff) +{ + TraceIpData *ret; + int portno; + int quesiz; + int flags; + SOCKET s; + struct sockaddr_in sin; + int reuse = 1; + +#ifdef HARDDEBUG + fprintf(stderr,"trace_ip_drv/trace_ip_start (%s)\r\n", buff); +#endif + if (sscanf(buff,"trace_ip_drv %d %d %d",&portno, &quesiz, &flags) != 3) + return ERL_DRV_ERROR_GENERAL; + + if (flags > 3 || flags < 0 || portno < 0 || quesiz < 0) + return ERL_DRV_ERROR_GENERAL; + + if (lookup_data_by_port(portno) != NULL) + return ERL_DRV_ERROR_GENERAL; + + if (IS_INVALID_SOCKET(s = socket(AF_INET, SOCK_STREAM, 0))) + return ERL_DRV_ERROR_GENERAL; + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (char *) &reuse, sizeof(reuse)) < 0) { + closesocket(s); + return ERL_DRV_ERROR_GENERAL; + } + + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons((short) portno); + + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) != 0) { + closesocket(s); + return ERL_DRV_ERROR_GENERAL; + } + + if (portno == 0) { +#ifdef HAVE_SOCKLEN_T + socklen_t sinlen = sizeof(sin); +#else + int sinlen = (int) sizeof(sin); +#endif + if (getsockname(s, (struct sockaddr *)&sin, &sinlen) != 0) { + closesocket(s); + return ERL_DRV_ERROR_GENERAL; + } else { + portno = ntohs(sin.sin_port); + } + } + + if (listen(s, 1)) { /* No significant backlog needed */ + closesocket(s); + return ERL_DRV_ERROR_GENERAL; + } + + if (set_nonblocking(s) != 0){ + closesocket(s); + return ERL_DRV_ERROR_GENERAL; + } + + /* OK, the socket is created, lets build the structure. */ + /* Deliberately one more pointer than the quesize specified... */ + ret = my_alloc(sizeof(TraceIpData) + + quesiz * sizeof(TraceIpMessage *)); + + ret->flags = flags | FLAG_LISTEN_PORT; + ret->listen_portno = portno; + ret->listenfd = s; + ret->fd = INVALID_SOCKET; + ret->port = port; + ret->next = first_data; + ret->quesiz = quesiz+1; + ret->questart = ret->questop = 0; + memset(ret->que, 0, ret->quesiz); + + first_data = ret; +#ifdef __WIN32__ + ret->listen_event_mask = 0; + ret->listen_event = 0; + ret->event_mask = 0; + ret->event = 0; +#endif + my_driver_select(ret, s, FLAG_READ, SELECT_ON); + set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY); + + return (ErlDrvData) ret; +} + +/* +** Close a port +*/ +static void trace_ip_stop(ErlDrvData handle) +{ + close_unlink_port((TraceIpData *) handle); +} + +/* +** Data sent from erlang to port. +*/ +static void trace_ip_output(ErlDrvData handle, char *buff, int bufflen) +{ + TraceIpData *data = (TraceIpData *) handle; + if (data->flags & FLAG_LISTEN_PORT) { + if (data->flags & FLAG_FILL_ALWAYS) { + enque_message(data, buff, bufflen, 0); + } + return; + } + if (data->que[data->questart] != NULL) { + trace_ip_ready_output(handle, sock2event(data->fd)); + } + if (data->que[data->questart] == NULL) { + int written = trywrite(data, buff, bufflen); + if (written >= 0 && written < bufflen + 5) { + enque_message(data, buff, bufflen, written); + my_driver_select(data, data->fd, FLAG_WRITE, SELECT_ON); + } + return; + } + enque_message(data, buff, bufflen, 0); + return; +} + +/* +** We have something to read from a file descriptor +*/ +static void trace_ip_ready_input(ErlDrvData handle, ErlDrvEvent fd) +{ + TraceIpData *data = (TraceIpData *) handle; + int client; + + if (!(data->flags & FLAG_LISTEN_PORT) && event2sock(fd) == data->listenfd) { + /* + ** Someone tries to connect to already connected port, + ** just accept and close. + */ + if (!IS_INVALID_SOCKET((client = my_accept(data->listenfd)))) { + closesocket(client); + } + return; + } + + if (event2sock(fd) == data->listenfd) { + /* + ** Maybe accept, we are a listen port... + */ + if (!IS_INVALID_SOCKET((client = my_accept(data->listenfd)))) { + data->fd = client; + set_nonblocking(client); + if (data->que[data->questart] != NULL) { + my_driver_select(data, data->fd, FLAG_WRITE | FLAG_READ, SELECT_ON); + } else { + my_driver_select(data, data->fd, FLAG_READ, SELECT_ON); + } + data->flags &= ~(FLAG_LISTEN_PORT); + } + return; + } + + /* + * It is a probably EOF because the other end closed the socket, + * but better make sure. + */ + + if ((SOCKET)(long)fd == data->fd) { +#ifdef __WIN32__ + close_client(data); +#else + int res; + char sbuf[128]; + + if ((res = read(data->fd, sbuf, sizeof sbuf)) == 0) { + close_client(data); + } + + /* + * Something else. Just ignore it. + * + * When /dev/poll is used on Solaris, this callback can + * be called even if there is nothing to read. An attempt + * to read will result in an EAGAIN error. + */ +#ifdef DEBUG + if (res < 0) { + fprintf(stderr, "Read on fd %d failed with errno=%d\r\n", + data->fd, errno); + } +#endif +#endif + return; + } + + ASSERT(0); +} + +#ifdef __WIN32__ +static void trace_ip_event(ErlDrvData handle, ErlDrvEvent event) +{ + TraceIpData *data = (TraceIpData *) handle; + if ((HANDLE)event == data->event) { + WSANETWORKEVENTS netEv; + if (WSAEnumNetworkEvents(data->fd, data->event, &netEv) != 0) { + return; + } + if (netEv.lNetworkEvents & FLAG_WRITE) { + trace_ip_ready_output(handle, (ErlDrvEvent)data->fd); + } + if (netEv.lNetworkEvents & FLAG_READ) { + trace_ip_ready_input(handle, (ErlDrvEvent)data->fd); + } + } else if ((HANDLE)event == data->listen_event) { + trace_ip_ready_input(handle, (ErlDrvEvent)data->listenfd); + } +} +#endif /* ifdef __WIN32__ */ + +/* +** We can write to a file descriptor +*/ +static void trace_ip_ready_output(ErlDrvData handle, ErlDrvEvent fd) +{ + TraceIpData *data = (TraceIpData *) handle; + int res; + int towrite; + TraceIpMessage *tim; + + ASSERT(!(data->flags & FLAG_LISTEN_PORT) && + data->que[data->questart] != NULL && + (SOCKET)(long)fd == data->fd); + + tim = data->que[data->questart]; + towrite = tim->siz - tim->written; + while((res = write_until_done(data->fd, + tim->bin + tim->written, towrite)) + == towrite) { + driver_free(tim); + data->que[data->questart] = NULL; + if (data->questart == data->questop) { + /* + ** We wrote the last message in the que, dont + ** step forward, just 'deselect' + */ + my_driver_select(data, data->fd, FLAG_WRITE, SELECT_OFF); + /* + ** We are really done... + */ + return; + } + if (++(data->questart) == data->quesiz) + data->questart = 0; + tim = data->que[data->questart]; + ASSERT(tim != NULL); + towrite = tim->siz - tim->written; + } + if (res < 0) { + close_client(data); + return; + } + + tim->written += res; +} + +/* +** Control message from erlang, we handle $p, which is get_listen_port. +*/ +static int trace_ip_control(ErlDrvData handle, unsigned int command, + char* buff, int count, + char** res, int res_size) +{ + register void *void_ptr; /* Soft type cast */ + + if (command == 'p') { + TraceIpData *data = (TraceIpData *) handle; + ErlDrvBinary *b = my_alloc_binary(3); + b->orig_bytes[0] = '\0'; /* OK */ + put_be16(data->listen_portno, &(b->orig_bytes[1])); + *res = void_ptr = b; + return 0; + } + return -1; +} + +/* +** Driver unloaded +*/ +static void trace_ip_finish(void) +{ + while (first_data != NULL) { + close_unlink_port(first_data); + } +} + +/* +** Internal helpers +*/ + +/* +** Yet another malloc wrapper +*/ +static void *my_alloc(size_t size) +{ + void *ret; + if ((ret = driver_alloc(size)) == NULL) { + /* May or may not work... */ + fprintf(stderr, "Could not allocate %d bytes of memory in %s.", + (int) size, __FILE__); + exit(1); + } + return ret; +} + +/* +** Yet another malloc wrapper +*/ +static ErlDrvBinary *my_alloc_binary(int size) +{ + ErlDrvBinary *ret; + if ((ret = driver_alloc_binary(size)) == NULL) { + /* May or may not work... */ + fprintf(stderr, "Could not allocate a binary of %d bytes in %s.", + (int) size, __FILE__); + exit(1); + } + return ret; +} + +/* +** Write to a nonblocking descriptor until it states EWOULDBLOCK +*/ +static int write_until_done(SOCKET s, char *buff, int bufflen) +{ + int ret = 0; + int res = 0; + +#ifdef HARDDEBUG + fprintf(stderr, "Writing %d characters.\r\n", bufflen); +#endif + + while(ret < bufflen && (res = send(s, buff + ret, bufflen - ret, 0)) > 0) { + ret += res; + } + if (ret < bufflen) { + if (res == 0) { + fprintf(stderr, "internal error in trace_ip_drv, " + "write to nonblocking " + "returned 0!"); + exit(1); + } else if (ERRNO != ERRNO_BLOCK) { + return -1; + } + } + return ret; +} + +/* +** Translate big endian integer in buffer to unsigned +*/ +static unsigned get_be(unsigned char *s) +{ + return s[3] | (s[2] << 8) | (s[1] << 16) | s[0] << 24; +} + +/* +** Write unsigned to buffer in big endian +*/ + +static void put_be32(unsigned n, unsigned char *s) +{ + s[0] = n >> 24U; + s[1] = n >> 16U; + s[2] = n >> 8U; + s[3] = n; +} + +static void put_be16(unsigned n, unsigned char *s) +{ + s[0] = n >> 8U; + s[1] = n; +} + +/* +** Lookup a port's data structure by the *TCP/IP port number* +*/ +static TraceIpData *lookup_data_by_port(int portno) +{ + TraceIpData *tmp = first_data; + while (tmp != NULL && tmp->listen_portno != portno) + tmp = tmp->next; + return tmp; +} + +/* +** Create a TraceIpMessage buffer (the binary data NOT filled in) +*/ +static TraceIpMessage *make_buffer(int datasiz, unsigned char op, + unsigned number) +{ + TraceIpMessage *ret = my_alloc(sizeof(TraceIpMessage) + + (datasiz + 4)); + ret->siz = datasiz + 5; + ret->written = 0; + ret->bin[0] = op; + put_be32(number, ret->bin + 1); + return ret; +} + +/* +** Add message to que, discarding in a politically correct way... +** The FLAG_DROP_OLDEST is currently ingored... +*/ +static void enque_message(TraceIpData *data, unsigned char *buff, int bufflen, + int byteswritten) +{ + int diff = data->questop - data->questart; + TraceIpMessage *tim; + + if (diff == -1 || diff == data->quesiz - 1) { + put_be32(get_be((data->que[data->questop])->bin + 1) + 1, + (data->que[data->questop])->bin + 1); + } else if (diff == -2 || diff == data->quesiz - 2) { + ASSERT(byteswritten == 0); + if (++(data->questop) == data->quesiz) { + data->questop = 0; + } + data->que[data->questop] = make_buffer(0, OP_DROP, 1); + } else { + if (data->que[data->questop] != NULL && + ++(data->questop) == data->quesiz) { + data->questop = 0; + } + tim = make_buffer(bufflen, OP_BINARY, bufflen); + tim->written = byteswritten; + memcpy(tim->bin + 5, buff, bufflen); + data->que[data->questop] = tim; + } +} + +/* +** Clean a que +*/ +static void clean_que(TraceIpData *data) +{ + int b = data->questart; + int e = data->questop; + + while (b != e) { + if (data->que[b] != NULL) { + driver_free(data->que[b]); + data->que[b] = NULL; + } + if (++b >= data->quesiz) { + b = 0; + } + } + if (data->que[b] != NULL) { + driver_free(data->que[b]); + data->que[b] = NULL; + } + data->questart = data->questop = 0; +} + +/* +** Cleanup closed client (or close the client and cleanup) +*/ +static void close_client(TraceIpData *data) +{ + my_driver_select(data, data->fd, FLAG_WRITE | FLAG_READ, SELECT_CLOSE); + data->flags |= FLAG_LISTEN_PORT; + if (!(data->flags & FLAG_FILL_ALWAYS)) { + clean_que(data); + } +} + +/* +** Try to write a message from erlang directly (only called when que is empty +** and client is connected) +*/ +static int trywrite(TraceIpData *data, unsigned char *buff, int bufflen) +{ + unsigned char op[5]; + int res; + + op[0] = OP_BINARY; + put_be32(bufflen, op + 1); + + if ((res = write_until_done(data->fd, op, 5)) < 0) { + close_client(data); + return -1; + } + if (res < 5) { + return res; + } + + if ((res = write_until_done(data->fd, buff, bufflen)) < 0) { + close_client(data); + return -1; + } + + return res + 5; +} + +/* +** accept wrapper +*/ +static SOCKET my_accept(SOCKET sock) +{ + struct sockaddr_in sin; + int sin_size = sizeof(sin); + + return accept(sock, (struct sockaddr *) &sin, &sin_size); +} + +/* +** Close the whole port and clean up +*/ +static void close_unlink_port(TraceIpData *data) +{ + TraceIpData **tmp; + + data->flags = 0; + if (!IS_INVALID_SOCKET(data->fd)) { + close_client(data); + } + my_driver_select(data, data->listenfd, FLAG_READ, SELECT_CLOSE); + + for(tmp = &first_data; *tmp != NULL && *tmp != data; + tmp = &((*tmp)->next)) + ; + if (*tmp != NULL) { + *tmp = (*tmp)->next; + } + driver_free(data); +} + + + + +#ifdef __WIN32__ +/* +** Mostly stolen from inet_drv in the emulator. +*/ +static int my_driver_select(TraceIpData *desc, SOCKET fd, + int flags, enum MySelectOp op) +{ + HANDLE *event; + unsigned *event_mask; + int ret = -1; + unsigned save_event_mask; + + if(fd == desc->listenfd) { + event = &(desc->listen_event); + event_mask = &(desc->listen_event_mask); + } else if(fd == desc->fd) { + event = &(desc->event); + event_mask = &(desc->event_mask); + } else { + return -1; + } + + save_event_mask = *event_mask; + + if (op==SELECT_ON) { + *event_mask |= flags; + } else { + *event_mask &= (~flags); + } + if (*event_mask != 0 && *event == 0) { + *event = WSACreateEvent(); + driver_select(desc->port, *event, ERL_DRV_READ|ERL_DRV_USE, 1); + } + + /* The RIGHT WAY (TM) to do this is to make sure: + A) The cancelling of all network events is done with + NULL as the event parameter (bug in NT's winsock), + B) The actual event handle is reset so that it is only + raised if one of the requested network events is active, + C) Avoid race conditions by making sure that the event cannot be set + while we are preparing to set the correct network event mask. + The simplest way to do it is to turn off all events, reset the + event handle and then, if event_mask != 0, turn on the appropriate + events again. */ + if (WSAEventSelect(fd, NULL, 0) != 0) { + *event_mask = save_event_mask; + goto error; + } + if (!ResetEvent(*event)) { + *event_mask = 0; + goto error; + } + if (*event_mask != 0) { + if (WSAEventSelect(fd, + *event, + *event_mask) != 0) { + *event_mask = 0; + goto error; + } + } + ret = 0; +error: + if (*event_mask == 0 && *event != 0 && (op==SELECT_CLOSE || ret!=0)) { + WSAEventSelect(fd, NULL, 0); /* Not necessary? + Well, actually I dont know, and + MS documentation states nothing + about what happens if WSAEventSelect + is called with empty event mask and + then the event is deleted. */ + driver_select(desc->port, *event, ERL_DRV_READ|ERL_DRV_USE, 0); + *event = 0; + if (op == SELECT_CLOSE) { + closesocket(fd); + } + } + return ret; +} + +static void stop_select(ErlDrvEvent event, void* _) +{ + WSACloseEvent((HANDLE)event); +} + +#else /* UNIX/VXWORKS */ + +static int my_driver_select(TraceIpData *desc, SOCKET fd, int flags, enum MySelectOp op) +{ + if (op != SELECT_OFF) { + flags |= ERL_DRV_USE; + } + return driver_select(desc->port, sock2event(fd), flags, (op==SELECT_ON)); +} + +static void stop_select(ErlDrvEvent event, void* _) +{ + closesocket((SOCKET)(long)event); +} + +#endif /* !__WIN32__ */ + +/* +** Set socket nonblocking, keep this at end of file. +*/ +#undef ERRNO_BLOCK +#undef ASSERT +#ifndef WANT_NONBLOCKING +#define WANT_NONBLOCKING +#endif +#include "sys.h" +static int set_nonblocking(SOCKET sock) +{ + SET_NONBLOCKING(sock); + return 0; +} + + |