/*
* %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;
}