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/trace_file_drv.c | |
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/trace_file_drv.c')
-rw-r--r-- | lib/runtime_tools/c_src/trace_file_drv.c | 638 |
1 files changed, 638 insertions, 0 deletions
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; +} + |