text: /*
   * %CopyrightBegin%
   * 
   * Copyright Ericsson AB 1999-2011. 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;
  
      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;
  }
  
  