/*      
 * iroffer by David Johnson (PMG) 
 * Copyright (C) 1998-2005 David Johnson 
 * 
 * By using this file, you agree to the terms and conditions set
 * forth in the GNU General Public License.  More information is    
 * available in the README file.
 * 
 * If you received this file without documentation, it can be
 * downloaded from http://iroffer.org/
 * 
 * @(#) iroffer_statefile.c 1.23@(#)
 * pmg@wellington.i202.centerclick.org|src/iroffer_statefile.c|20050313183435|62320
 * 
 */

/* include the headers */
#include "iroffer_config.h"
#include "iroffer_defines.h"
#include "iroffer_headers.h"
#include "iroffer_globals.h"


/*
 *      ******  FILE FORMAT  ******
 *
 * Word size:     4 Bytes
 * Byte order:    Network Order
 *
 * == OVERALL ==
 * Word 0:        Magic
 * Word 1:        Version
 * Word 2..n-4:   TLV Data
 * Word n-3..n:   md5sum
 *
 * == TLV Format ==
 * Word 0:        Tag
 * Word 1:        Length in Bytes (Tag+Length+Value, but not Padding)
 * Word 2..n-1:   Value
 * Word n:        Value + Zero Padding
 *
 * Some tags are compound, some are not.  Compound tags contain TLV
 * data of their own.
 *
 */

typedef enum
{
  STATEFILE_TAG_IROFFER_VERSION      =  1 << 8,
  STATEFILE_TAG_TIMESTAMP,
  
  STATEFILE_TAG_XFR_RECORD           =  2 << 8,
  STATEFILE_TAG_SENT_RECORD,
  STATEFILE_TAG_TOTAL_SENT,
  STATEFILE_TAG_TOTAL_UPTIME,
  STATEFILE_TAG_LAST_LOGROTATE,
  
  STATEFILE_TAG_IGNORE               = 10 << 8, /* compound */
  STATEFILE_TAG_IGNORE_FLAGS,
  STATEFILE_TAG_IGNORE_BUCKET,
  STATEFILE_TAG_IGNORE_LASTCONTACT,
  STATEFILE_TAG_IGNORE_HOSTMASK,
  
  STATEFILE_TAG_MSGLOG               = 11 << 8, /* compound */
  STATEFILE_TAG_MSGLOG_WHEN,
  STATEFILE_TAG_MSGLOG_HOSTMASK,
  STATEFILE_TAG_MSGLOG_MESSAGE,
  
  STATEFILE_TAG_XDCCS                = 12 << 8, /* compound */
  STATEFILE_TAG_XDCCS_FILE,
  STATEFILE_TAG_XDCCS_DESC,
  STATEFILE_TAG_XDCCS_NOTE,
  STATEFILE_TAG_XDCCS_GETS,
  STATEFILE_TAG_XDCCS_MINSPEED,
  STATEFILE_TAG_XDCCS_MAXSPEED,
  STATEFILE_TAG_XDCCS_MD5SUM_INFO,
  
  STATEFILE_TAG_TLIMIT_DAILY_USED    = 13 << 8,
  STATEFILE_TAG_TLIMIT_DAILY_ENDS,
  STATEFILE_TAG_TLIMIT_WEEKLY_USED,
  STATEFILE_TAG_TLIMIT_WEEKLY_ENDS,
  STATEFILE_TAG_TLIMIT_MONTHLY_USED,
  STATEFILE_TAG_TLIMIT_MONTHLY_ENDS,
  
} statefile_tag_t;

typedef struct
{
  statefile_tag_t tag;
  ir_int32 length; /* includes header */
} statefile_hdr_t;

#define STATEFILE_MAGIC (('I' << 24) | ('R' << 16) | ('F' << 8) | 'R')
#define STATEFILE_VERSION 1

typedef struct
{
  ir_uint32 upper;
  ir_uint32 lower;
} statefile_uint64_t;

typedef struct
{
  statefile_hdr_t hdr;
  time_t g_time;
} statefile_item_generic_time_t;

typedef struct
{
  statefile_hdr_t hdr;
  ir_int32 g_int;
} statefile_item_generic_int_t;

typedef struct
{
  statefile_hdr_t hdr;
  ir_uint32 g_uint;
} statefile_item_generic_uint_t;

typedef struct
{
  statefile_hdr_t hdr;
  statefile_uint64_t g_ullint;
} statefile_item_generic_ullint_t;

typedef struct
{
  statefile_hdr_t hdr;
  float g_float;
} statefile_item_generic_float_t;

typedef struct
{
  statefile_hdr_t hdr;
  statefile_uint64_t st_size;
  statefile_uint64_t st_dev;
  statefile_uint64_t st_ino;
  time_t mtime;
  MD5Digest md5sum;
} statefile_item_md5sum_info_t;


static int write_statefile_item(ir_boutput_t *bout, void *item)
{
  int callval;
  statefile_hdr_t *hdr = (statefile_hdr_t*)item;
  ir_int32 length = hdr->length;
  unsigned char dummy[4] = {};
  
  hdr->tag = htonl(hdr->tag);
  hdr->length = htonl(hdr->length);
  
  callval = ir_boutput_write(bout, item, length);
  
  if (callval != length)
    {
      outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Write To State File (%d != %d) %s",
               callval, length, strerror(errno));
      return -1;
    }
  
  if (length & 3)
    {
      length = 4 - (length & 3);
      callval = ir_boutput_write(bout, dummy, length);
      
      if (callval != length)
        {
          outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Write To State File (%d != %d) %s",
                   callval, length, strerror(errno));
          return -1;
        }
    }
  
  return 0;
}


void write_statefile(void)
{
  char *statefile_tmp, *statefile_bkup;
  int fd;
  int callval;
  statefile_hdr_t *hdr;
  ir_int32 length;
  ir_boutput_t bout;
  
  updatecontext();
  
  if (gdata.statefile == NULL)
    {
      return;
    }
  
  statefile_tmp = mymalloc(strlen(gdata.statefile)+5);
  statefile_bkup = mymalloc(strlen(gdata.statefile)+2);
  
  sprintf(statefile_tmp,  "%s.tmp", gdata.statefile);
  sprintf(statefile_bkup, "%s~",    gdata.statefile);
  
  if (gdata.debug > 0)
    {
      ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
              COLOR_NO_COLOR, "Saving State File... ");
    }
  
  fd = open(statefile_tmp,
            O_WRONLY | O_CREAT | O_TRUNC | ADDED_OPEN_FLAGS,
            CREAT_PERMISSIONS);
  
  if (fd < 0)
    {
      outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Create State File '%s': %s",
               statefile_tmp, strerror(errno));
      goto error_out;
    }
  
  ir_boutput_init(&bout, fd, BOUTPUT_MD5SUM | BOUTPUT_NO_LIMIT);
  
  /*** write ***/
  
  {
    ir_uint32 magic = htonl(STATEFILE_MAGIC);
    callval = ir_boutput_write(&bout, &magic, sizeof(magic));
    if (callval != sizeof(magic))
      {
        outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Write To State File (%d != %d) %s",
                 callval, (int)sizeof(magic), strerror(errno));
      }
  }
  
  {
    ir_uint32 version = htonl(STATEFILE_VERSION);
    callval = ir_boutput_write(&bout, &version, sizeof(version));
    if (callval != sizeof(version))
      {
        outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Write To State File (%d != %d) %s",
                 callval, (int)sizeof(version), strerror(errno));
      }
  }
  
  {
    unsigned char *data;
    unsigned char *next;
    
    length = sizeof(statefile_hdr_t) + maxtextlength;
    
    data = mycalloc(length);
    
    /* header */
    hdr = (statefile_hdr_t*)data;
    hdr->tag = STATEFILE_TAG_IROFFER_VERSION;
    hdr->length = sizeof(statefile_hdr_t);
    next = (unsigned char*)(&hdr[1]);
    
    length = snprintf(next, maxtextlength-1,
                      "iroffer v" VERSIONLONG ", %s", gdata.osstring);
    
    if ((length < 0) || (length >= maxtextlength))
      {
       outerror(OUTERROR_TYPE_WARN_LOUD, "Version too long! %d",
                 length);
      }
    else
      {
        hdr->length += ceiling(length+1, 4);
        write_statefile_item(&bout, data);
      }
    
    mydelete(data);
  }
  
  {
    statefile_item_generic_time_t item_timestamp;
    item_timestamp.hdr.tag = STATEFILE_TAG_TIMESTAMP;
    item_timestamp.hdr.length = sizeof(statefile_item_generic_time_t);
    item_timestamp.g_time = htonl(gdata.curtime);
    write_statefile_item(&bout, &item_timestamp);
  }
  
  {
    statefile_item_generic_float_t item_xfr_record;
    item_xfr_record.hdr.tag = STATEFILE_TAG_XFR_RECORD;
    item_xfr_record.hdr.length = sizeof(statefile_item_generic_float_t);
    item_xfr_record.g_float = gdata.record;
    write_statefile_item(&bout, &item_xfr_record);
  }
  
  {
    statefile_item_generic_float_t item_sent_record;
    item_sent_record.hdr.tag = STATEFILE_TAG_SENT_RECORD;
    item_sent_record.hdr.length = sizeof(statefile_item_generic_float_t);
    item_sent_record.g_float = gdata.sentrecord;
    write_statefile_item(&bout, &item_sent_record);
  }
  
  {
    statefile_item_generic_ullint_t item_total_sent;
    item_total_sent.hdr.tag = STATEFILE_TAG_TOTAL_SENT;
    item_total_sent.hdr.length = sizeof(statefile_item_generic_ullint_t);
    item_total_sent.g_ullint.upper = htonl(gdata.totalsent >> 32);
    item_total_sent.g_ullint.lower = htonl(gdata.totalsent & 0xFFFFFFFF);
    write_statefile_item(&bout, &item_total_sent);
  }
  
  {
    statefile_item_generic_int_t item_total_uptime;
    item_total_uptime.hdr.tag = STATEFILE_TAG_TOTAL_UPTIME;
    item_total_uptime.hdr.length = sizeof(statefile_item_generic_int_t);
    item_total_uptime.g_int = htonl(gdata.totaluptime);
    write_statefile_item(&bout, &item_total_uptime);
  }
  
  {
    statefile_item_generic_time_t item_last_logrotate;
    item_last_logrotate.hdr.tag = STATEFILE_TAG_LAST_LOGROTATE;
    item_last_logrotate.hdr.length = sizeof(statefile_item_generic_int_t);
    item_last_logrotate.g_time = htonl(gdata.last_logrotate);
    write_statefile_item(&bout, &item_last_logrotate);
  }
  
  {
    unsigned char *data;
    unsigned char *next;
    igninfo *ignore;
    statefile_item_generic_int_t *g_int;
    statefile_item_generic_uint_t *g_uint;
    statefile_item_generic_time_t *g_time;
    
    ignore = irlist_get_head(&gdata.ignorelist);
    
    while (ignore)
      {
        if (ignore->flags & IGN_IGNORING)
          {
            /*
             * need room to write:
             *  flags         uint
             *  bucket        int
             *  lastcontact   time_t
             *  hostmask      string
             */
            length = sizeof(statefile_hdr_t) +
              sizeof(statefile_item_generic_uint_t) + 
              sizeof(statefile_item_generic_int_t) + 
              sizeof(statefile_item_generic_time_t) + 
              sizeof(statefile_hdr_t) + ceiling(strlen(ignore->hostmask) + 1, 4);
            
            data = mycalloc(length);
            
            /* outter header */
            hdr = (statefile_hdr_t*)data;
            hdr->tag = STATEFILE_TAG_IGNORE;
            hdr->length = length;
            next = (unsigned char*)(&hdr[1]);
            
            /* flags */
            g_uint = (statefile_item_generic_uint_t*)next;
            g_uint->hdr.tag = htonl(STATEFILE_TAG_IGNORE_FLAGS);
            g_uint->hdr.length = htonl(sizeof(*g_uint));
            g_uint->g_uint = htonl(ignore->flags);
            next = (unsigned char*)(&g_uint[1]);
            
            /* bucket */
            g_int = (statefile_item_generic_int_t*)next;
            g_int->hdr.tag = htonl(STATEFILE_TAG_IGNORE_BUCKET);
            g_int->hdr.length = htonl(sizeof(*g_int));
            g_int->g_int = htonl(ignore->bucket);
            next = (unsigned char*)(&g_int[1]);
            
            /* lastcontact */
            g_time = (statefile_item_generic_time_t*)next;
            g_time->hdr.tag = htonl(STATEFILE_TAG_IGNORE_LASTCONTACT);
            g_time->hdr.length = htonl(sizeof(*g_time));
            g_time->g_time = htonl(ignore->lastcontact);
            next = (unsigned char*)(&g_time[1]);
            
            /* hostmask */
            hdr = (statefile_hdr_t*)next;
            hdr->tag = htonl(STATEFILE_TAG_IGNORE_HOSTMASK);
            hdr->length = htonl(sizeof(statefile_hdr_t) + strlen(ignore->hostmask) + 1);
            next = (unsigned char*)(&hdr[1]);
            strcpy(next, ignore->hostmask);
            next += ceiling(strlen(ignore->hostmask) + 1, 4);
        
            write_statefile_item(&bout, data);
            
            mydelete(data);
          }
        ignore = irlist_get_next(ignore);
      }
  }
  
  {
    unsigned char *data;
    unsigned char *next;
    msglog_t *msglog;
    statefile_item_generic_time_t *g_time;
    
    msglog = irlist_get_head(&gdata.msglog);
    
    while (msglog)
      {
        /*
         * need room to write:
         *  when          time_t
         *  hostmask      string
         *  message       string
         */
        length = sizeof(statefile_hdr_t) +
          sizeof(statefile_item_generic_time_t) + 
          sizeof(statefile_hdr_t) + ceiling(strlen(msglog->hostmask) + 1, 4) +
          sizeof(statefile_hdr_t) + ceiling(strlen(msglog->message) + 1, 4);
        
        data = mycalloc(length);
        
        /* outter header */
        hdr = (statefile_hdr_t*)data;
        hdr->tag = STATEFILE_TAG_MSGLOG;
        hdr->length = length;
        next = (unsigned char*)(&hdr[1]);
        
        /* when */
        g_time = (statefile_item_generic_time_t*)next;
        g_time->hdr.tag = htonl(STATEFILE_TAG_MSGLOG_WHEN);
        g_time->hdr.length = htonl(sizeof(*g_time));
        g_time->g_time = htonl(msglog->when);
        next = (unsigned char*)(&g_time[1]);
        
        /* hostmask */
        hdr = (statefile_hdr_t*)next;
        hdr->tag = htonl(STATEFILE_TAG_MSGLOG_HOSTMASK);
        hdr->length = htonl(sizeof(statefile_hdr_t) + strlen(msglog->hostmask) + 1);
        next = (unsigned char*)(&hdr[1]);
        strcpy(next, msglog->hostmask);
        next += ceiling(strlen(msglog->hostmask) + 1, 4);
        
        /* message */
        hdr = (statefile_hdr_t*)next;
        hdr->tag = htonl(STATEFILE_TAG_MSGLOG_MESSAGE);
        hdr->length = htonl(sizeof(statefile_hdr_t) + strlen(msglog->message) + 1);
        next = (unsigned char*)(&hdr[1]);
        strcpy(next, msglog->message);
        next += ceiling(strlen(msglog->message) + 1, 4);
        
        write_statefile_item(&bout, data);
        
        mydelete(data);
        msglog = irlist_get_next(msglog);
      }
  }
  
  {
    unsigned char *data;
    unsigned char *next;
    xdcc *xd;
    statefile_item_generic_int_t *g_int;
    statefile_item_generic_float_t *g_float;
    statefile_item_md5sum_info_t *md5sum_info;
    
    xd = irlist_get_head(&gdata.xdccs);
    
    while (xd)
      {
        /*
         * need room to write:
         *  file          string
         *  desc          string
         *  note          string
         *  gets          int
         *  minspeed      float
         *  maxspeed      float
         */
        length = sizeof(statefile_hdr_t) +
          sizeof(statefile_hdr_t) + ceiling(strlen(xd->file) + 1, 4) +
          sizeof(statefile_hdr_t) + ceiling(strlen(xd->desc) + 1, 4) +
          sizeof(statefile_hdr_t) + ceiling(strlen(xd->note) + 1, 4) +
          sizeof(statefile_item_generic_int_t) + 
          sizeof(statefile_item_generic_float_t) + 
          sizeof(statefile_item_generic_float_t);
        
        if (xd->has_md5sum)
          {
            length += ceiling(sizeof(statefile_item_md5sum_info_t), 4);
          }
        
        data = mycalloc(length);
        
        /* outter header */
        hdr = (statefile_hdr_t*)data;
        hdr->tag = STATEFILE_TAG_XDCCS;
        hdr->length = length;
        next = (unsigned char*)(&hdr[1]);
        
        /* file */
        hdr = (statefile_hdr_t*)next;
        hdr->tag = htonl(STATEFILE_TAG_XDCCS_FILE);
        hdr->length = htonl(sizeof(statefile_hdr_t) + strlen(xd->file) + 1);
        next = (unsigned char*)(&hdr[1]);
        strcpy(next, xd->file);
        next += ceiling(strlen(xd->file) + 1, 4);
        
        /* desc */
        hdr = (statefile_hdr_t*)next;
        hdr->tag = htonl(STATEFILE_TAG_XDCCS_DESC);
        hdr->length = htonl(sizeof(statefile_hdr_t) + strlen(xd->desc) + 1);
        next = (unsigned char*)(&hdr[1]);
        strcpy(next, xd->desc);
        next += ceiling(strlen(xd->desc) + 1, 4);
        
        /* note */
        hdr = (statefile_hdr_t*)next;
        hdr->tag = htonl(STATEFILE_TAG_XDCCS_NOTE);
        hdr->length = htonl(sizeof(statefile_hdr_t) + strlen(xd->note) + 1);
        next = (unsigned char*)(&hdr[1]);
        strcpy(next, xd->note);
        next += ceiling(strlen(xd->note) + 1, 4);
        
        /* gets */
        g_int = (statefile_item_generic_int_t*)next;
        g_int->hdr.tag = htonl(STATEFILE_TAG_XDCCS_GETS);
        g_int->hdr.length = htonl(sizeof(*g_int));
        g_int->g_int = htonl(xd->gets);
        next = (unsigned char*)(&g_int[1]);
        
        /* minspeed */
        g_float = (statefile_item_generic_float_t*)next;
        g_float->hdr.tag = htonl(STATEFILE_TAG_XDCCS_MINSPEED);
        g_float->hdr.length = htonl(sizeof(*g_float));
        if (gdata.transferminspeed == xd->minspeed)
          {
            g_float->g_float = 0;
          }
        else
          {
            g_float->g_float = xd->minspeed;
          }
        next = (unsigned char*)(&g_float[1]);
        
        /* maxspeed */
        g_float = (statefile_item_generic_float_t*)next;
        g_float->hdr.tag = htonl(STATEFILE_TAG_XDCCS_MAXSPEED);
        g_float->hdr.length = htonl(sizeof(*g_float));
        if (gdata.transfermaxspeed == xd->maxspeed)
          {
            g_float->g_float = 0;
          }
        else
          {
            g_float->g_float = xd->maxspeed;
          }
        next = (unsigned char*)(&g_float[1]);
        
        if (xd->has_md5sum)
          {
            /* md5sum */
            md5sum_info = (statefile_item_md5sum_info_t*)next;
            md5sum_info->hdr.tag = htonl(STATEFILE_TAG_XDCCS_MD5SUM_INFO);
            md5sum_info->hdr.length = htonl(sizeof(*md5sum_info));
            md5sum_info->st_size.upper = htonl(((ir_uint64)xd->st_size) >> 32);
            md5sum_info->st_size.lower = htonl(xd->st_size & 0xFFFFFFFF);
            md5sum_info->st_dev.upper  = htonl(((ir_uint64)xd->st_dev) >> 32);
            md5sum_info->st_dev.lower  = htonl(xd->st_dev & 0xFFFFFFFF);
            md5sum_info->st_ino.upper  = htonl(((ir_uint64)xd->st_ino) >> 32);
            md5sum_info->st_ino.lower  = htonl(xd->st_ino & 0xFFFFFFFF);
            md5sum_info->mtime         = htonl(xd->mtime);
            memcpy(md5sum_info->md5sum, xd->md5sum, sizeof(MD5Digest));
            next = (unsigned char*)(&md5sum_info[1]);
          }
        
        write_statefile_item(&bout, data);
        
        mydelete(data);
        xd = irlist_get_next(xd);
      }
  }
  
  {
    statefile_item_generic_ullint_t item_daily_used;
    item_daily_used.hdr.tag = STATEFILE_TAG_TLIMIT_DAILY_USED;
    item_daily_used.hdr.length = sizeof(statefile_item_generic_ullint_t);
    item_daily_used.g_ullint.upper = htonl(gdata.transferlimits[TRANSFERLIMIT_DAILY].used >> 32);
    item_daily_used.g_ullint.lower = htonl(gdata.transferlimits[TRANSFERLIMIT_DAILY].used & 0xFFFFFFFF);
    write_statefile_item(&bout, &item_daily_used);
  }
  {
    statefile_item_generic_time_t item_daily_ends;
    item_daily_ends.hdr.tag = STATEFILE_TAG_TLIMIT_DAILY_ENDS;
    item_daily_ends.hdr.length = sizeof(statefile_item_generic_time_t);
    item_daily_ends.g_time = htonl(gdata.transferlimits[TRANSFERLIMIT_DAILY].ends);
    write_statefile_item(&bout, &item_daily_ends);
  }
  
  {
    statefile_item_generic_ullint_t item_weekly_used;
    item_weekly_used.hdr.tag = STATEFILE_TAG_TLIMIT_WEEKLY_USED;
    item_weekly_used.hdr.length = sizeof(statefile_item_generic_ullint_t);
    item_weekly_used.g_ullint.upper = htonl(gdata.transferlimits[TRANSFERLIMIT_WEEKLY].used >> 32);
    item_weekly_used.g_ullint.lower = htonl(gdata.transferlimits[TRANSFERLIMIT_WEEKLY].used & 0xFFFFFFFF);
    write_statefile_item(&bout, &item_weekly_used);
  }
  {
    statefile_item_generic_time_t item_weekly_ends;
    item_weekly_ends.hdr.tag = STATEFILE_TAG_TLIMIT_WEEKLY_ENDS;
    item_weekly_ends.hdr.length = sizeof(statefile_item_generic_time_t);
    item_weekly_ends.g_time = htonl(gdata.transferlimits[TRANSFERLIMIT_WEEKLY].ends);
    write_statefile_item(&bout, &item_weekly_ends);
  }
  
  {
    statefile_item_generic_ullint_t item_monthly_used;
    item_monthly_used.hdr.tag = STATEFILE_TAG_TLIMIT_MONTHLY_USED;
    item_monthly_used.hdr.length = sizeof(statefile_item_generic_ullint_t);
    item_monthly_used.g_ullint.upper = htonl(gdata.transferlimits[TRANSFERLIMIT_MONTHLY].used >> 32);
    item_monthly_used.g_ullint.lower = htonl(gdata.transferlimits[TRANSFERLIMIT_MONTHLY].used & 0xFFFFFFFF);
    write_statefile_item(&bout, &item_monthly_used);
  }
  {
    statefile_item_generic_time_t item_monthly_ends;
    item_monthly_ends.hdr.tag = STATEFILE_TAG_TLIMIT_MONTHLY_ENDS;
    item_monthly_ends.hdr.length = sizeof(statefile_item_generic_time_t);
    item_monthly_ends.g_time = htonl(gdata.transferlimits[TRANSFERLIMIT_MONTHLY].ends);
    write_statefile_item(&bout, &item_monthly_ends);
  }
  
  {
    MD5Digest digest = {};
    
    ir_boutput_get_md5sum(&bout, digest);
    
    callval = ir_boutput_write(&bout, &digest, sizeof(digest));
    if (callval != sizeof(digest))
      {
        outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Write md5sum To State File (%d != %d) %s",
                 callval, (int)sizeof(digest), strerror(errno));
      }
  }
  
  /*** end write ***/
  
  ir_boutput_set_flags(&bout, 0);
  
  callval = ir_boutput_attempt_flush(&bout);
  if (callval < 0)
    {
      outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Write to State File: %s",
               strerror(errno));
    }
  
  if (bout.count_dropped || (bout.count_written != bout.count_flushed))
    {
      outerror(OUTERROR_TYPE_WARN_LOUD, "Write failed to State File: %d/%d/%d",
               bout.count_written,
               bout.count_flushed,
               bout.count_dropped);
    }
  
  ir_boutput_delete(&bout);
  close(fd);
  
  /* remove old bkup */
  callval = unlink(statefile_bkup);
  if ((callval < 0) && (errno != ENOENT))
    {
      outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Remove Old State File '%s': %s",
               statefile_bkup, strerror(errno));
      /* ignore, continue */
    }
  
  /* backup old -> bkup */
  callval = link(gdata.statefile, statefile_bkup);
  if ((callval < 0) && (errno != ENOENT))
    {
      outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Backup Old State File '%s' -> '%s': %s",
               gdata.statefile, statefile_bkup, strerror(errno));
      /* ignore, continue */
    }
  
  /* rename new -> current */
  callval = rename(statefile_tmp, gdata.statefile);
  if (callval < 0)
    {
      outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Save New State File '%s': %s",
               gdata.statefile, strerror(errno));
      /* ignore, continue */
    }
  
  if (gdata.debug > 0)
    {
      ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
              COLOR_NO_COLOR, "Done");
    }
  
 error_out:
  
  mydelete(statefile_tmp);
  mydelete(statefile_bkup);
  return;
}


static statefile_hdr_t* read_statefile_item(ir_uint32 **buffer, int *buffer_len)
{
  statefile_hdr_t *all;
  
  if (*buffer_len < sizeof(statefile_hdr_t))
    {
      return NULL;
    }
  
  all = (statefile_hdr_t*)*buffer;
  
  all->tag = ntohl(all->tag);
  all->length = ntohl(all->length);
  
  if (*buffer_len < all->length)
    {
      outerror(OUTERROR_TYPE_WARN_LOUD, "Bad Header From State File (%d < %d)",
               *buffer_len, all->length);
      return NULL;
    }
  
  *buffer += ceiling(all->length, 4) / sizeof(ir_uint32);
  *buffer_len -= ceiling(all->length, 4);
  
  return all;
}


void read_statefile(void)
{
  int fd;
  ir_uint32 *buffer, *buffer_begin;
  int buffer_len;
  struct MD5Context md5sum;
  MD5Digest digest;
  struct stat st;
  statefile_hdr_t *hdr;
  int callval;
  time_t timestamp = 0;
  
  updatecontext();
  
  if (gdata.statefile == NULL)
    {
      return;
    }
  
  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
          COLOR_NO_COLOR, "Loading State File... ");
  
  fd = open(gdata.statefile,
            O_RDONLY | O_CREAT | ADDED_OPEN_FLAGS,
            CREAT_PERMISSIONS );
  
  if (fd < 0)
    {
      outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Access State File '%s': %s",
               gdata.statefile, strerror(errno));
      return;
    }
  
  if ((fstat(fd, &st) < 0) || (st.st_size < ((sizeof(ir_uint32) * 2) + sizeof(MD5Digest))))
    {
      ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
              COLOR_NO_COLOR, "State File: Too small, Skipping");
      close(fd);
      return;
    }
  
  buffer_len = st.st_size;
  buffer_begin = buffer = mycalloc(buffer_len);
  
  callval = read(fd, buffer, buffer_len);
  close(fd);
  if (callval != buffer_len)
    {
      outerror(OUTERROR_TYPE_WARN_LOUD, "Cant Read State File (%d != %d) %s",
               callval, buffer_len, strerror(errno));
      goto error_out;
    }
  
  /* verify md5sum */
  buffer_len -= sizeof(MD5Digest);
  
  MD5Init(&md5sum);
  MD5Update(&md5sum, (md5byte*)buffer, buffer_len);
  MD5Final(digest, &md5sum);
  
  if (memcmp(digest, buffer+(buffer_len/sizeof(ir_uint32)), sizeof(MD5Digest)))
    {
      outerror(OUTERROR_TYPE_CRASH,
               "\"%s\" Appears corrupt or is not an iroffer state file",
               gdata.statefile);
      goto error_out;
    }
  
  /* read */
  
  if (ntohl(*buffer) != STATEFILE_MAGIC)
    {
      outerror(OUTERROR_TYPE_CRASH,
               "\"%s\" Does not appear to be an iroffer state file",
               gdata.statefile);
      goto error_out;
    }
  buffer++;
  buffer_len -= sizeof(ir_uint32);
  
  if (gdata.debug > 0)
    {
      ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
              COLOR_NO_COLOR, "  [Version %lu State File]", (unsigned long)ntohl(*buffer));
    }
  buffer++;
  buffer_len -= sizeof(ir_uint32);
  
  while ((hdr = read_statefile_item(&buffer, &buffer_len)))
    {
      switch (hdr->tag)
        {
        case STATEFILE_TAG_TIMESTAMP:
          if (hdr->length == sizeof(statefile_item_generic_time_t))
            {
              char *tempstr;
              statefile_item_generic_time_t *g_time = (statefile_item_generic_time_t*)hdr;
              timestamp = ntohl(g_time->g_time);
              
              tempstr = mycalloc(maxtextlength);
              getdatestr(tempstr, timestamp, maxtextlength);
              
              ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                      COLOR_NO_COLOR, "  [Written on %s]", tempstr);
              mydelete(tempstr);
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Timestamp Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_XFR_RECORD:
          if (hdr->length == sizeof(statefile_item_generic_float_t))
            {
              statefile_item_generic_float_t *g_float = (statefile_item_generic_float_t*)hdr;
              gdata.record = g_float->g_float;
              
              if (gdata.debug > 0)
                {
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Record %1.1fKB/s]", gdata.record);
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad xfr Record Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_SENT_RECORD:
          if (hdr->length == sizeof(statefile_item_generic_float_t))
            {
              statefile_item_generic_float_t *g_float = (statefile_item_generic_float_t*)hdr;
              gdata.sentrecord = g_float->g_float;
              
              if (gdata.debug > 0)
                {
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Bandwidth Record %1.1fKB/s]", gdata.sentrecord);
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad sent Record Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_TOTAL_SENT:
          if (hdr->length == sizeof(statefile_item_generic_ullint_t))
            {
              statefile_item_generic_ullint_t *g_ullint = (statefile_item_generic_ullint_t*)hdr;
              gdata.totalsent = (((ir_uint64)ntohl(g_ullint->g_ullint.upper)) << 32) | ((ir_uint64)ntohl(g_ullint->g_ullint.lower));
              
              if (gdata.debug > 0)
                {
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Total Transferred %1.2f %cB]",
                          (gdata.totalsent/1024/1024) > 1024 ? ( (gdata.totalsent/1024/1024/1024) > 1024 ? ((float)gdata.totalsent)/1024/1024/1024/1024
                                                                 : ((float)gdata.totalsent)/1024/1024/1024 ) : ((float)gdata.totalsent)/1024/1024 ,
                          (gdata.totalsent/1024/1024) > 1024 ? ( (gdata.totalsent/1024/1024/1024) > 1024 ? 'T' : 'G' ) : 'M');
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad sent Record Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_TOTAL_UPTIME:
          if (hdr->length == sizeof(statefile_item_generic_int_t))
            {
              char *tempstr;
              statefile_item_generic_int_t *g_int = (statefile_item_generic_int_t*)hdr;
              gdata.totaluptime = ntohl(g_int->g_int);
              
              if (gdata.debug > 0)
                {
                  tempstr = mycalloc(maxtextlength);
                  getuptime(tempstr, 0, gdata.curtime-gdata.totaluptime, maxtextlength);
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Total Runtime %s]",
                          tempstr);
                  mydelete(tempstr);
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad sent Record Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_LAST_LOGROTATE:
          if (hdr->length == sizeof(statefile_item_generic_time_t))
            {
              char *tempstr;
              statefile_item_generic_time_t *g_time = (statefile_item_generic_time_t*)hdr;
              gdata.last_logrotate = ntohl(g_time->g_time);
              
              if (gdata.debug > 0)
                {
                  tempstr = mycalloc(maxtextlength);
                  getdatestr(tempstr, gdata.last_logrotate, maxtextlength);
                  
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Last Log Rotate %s]", tempstr);
                  mydelete(tempstr);
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Last Log Rotate Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_IROFFER_VERSION:
          if (hdr->length > sizeof(statefile_hdr_t))
            {
              char *iroffer_version = (char*)(&hdr[1]);
              iroffer_version[hdr->length-sizeof(statefile_hdr_t)-1] = '\0';
              
              if (gdata.debug > 0)
                {
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Written by %s]", iroffer_version);
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Iroffer Version Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_IGNORE:
          {
            igninfo *ignore;
            char *tempr;
            statefile_hdr_t *ihdr;

            ignore = irlist_add(&gdata.ignorelist, sizeof(igninfo));
            
            hdr->length -= sizeof(*hdr);
            ihdr = &hdr[1];
            
            while (hdr->length >= sizeof(*hdr))
              {
                ihdr->tag = ntohl(ihdr->tag);
                ihdr->length = ntohl(ihdr->length);
                switch (ihdr->tag)
                  {
                  case STATEFILE_TAG_IGNORE_FLAGS:
                    if (ihdr->length == sizeof(statefile_item_generic_uint_t))
                      {
                        statefile_item_generic_uint_t *g_uint = (statefile_item_generic_uint_t*)ihdr;
                        ignore->flags = ntohl(g_uint->g_uint);
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Ignore Flags Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  case STATEFILE_TAG_IGNORE_BUCKET:
                    if (ihdr->length == sizeof(statefile_item_generic_int_t))
                      {
                        statefile_item_generic_int_t *g_int = (statefile_item_generic_int_t*)ihdr;
                        ignore->bucket = ntohl(g_int->g_int);
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Ignore Bucket Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  case STATEFILE_TAG_IGNORE_LASTCONTACT:
                    if (ihdr->length == sizeof(statefile_item_generic_time_t))
                      {
                        statefile_item_generic_time_t *g_time = (statefile_item_generic_time_t*)ihdr;
                        ignore->lastcontact = ntohl(g_time->g_time);
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Ignore Lastcontact Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  case STATEFILE_TAG_IGNORE_HOSTMASK:
                    if (ihdr->length > sizeof(statefile_hdr_t))
                      {
                        char *hostmask = (char*)(&ihdr[1]);
                        hostmask[ihdr->length-sizeof(statefile_hdr_t)-1] = '\0';
                        ignore->hostmask = mymalloc(strlen(hostmask)+1);
                        strcpy(ignore->hostmask,hostmask);
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Ignore Hostmask Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  default:
                    outerror(OUTERROR_TYPE_WARN, "Ignoring Unknown Ignore Tag 0x%X (len=%d)",
                             ihdr->tag, ihdr->length);
                  }
                hdr->length -= ceiling(ihdr->length, 4);
                ihdr = (statefile_hdr_t*)(((char*)ihdr) + ceiling(ihdr->length, 4));
              }
            
            if ((!ignore->lastcontact) || (!ignore->hostmask))
              {
                outerror(OUTERROR_TYPE_WARN, "Ignoring Incomplete Ignore Tag");
                
                mydelete(ignore->hostmask);
                irlist_delete(&gdata.ignorelist, ignore);
              }
            else
              {
                ignore->bucket -= (gdata.curtime - timestamp)/gdata.autoignore_threshold;
                
                ignore->regexp = mycalloc(sizeof(regex_t));
                tempr = hostmasktoregex(ignore->hostmask);
                if (regcomp(ignore->regexp,tempr,REG_ICASE|REG_NOSUB))
                  {
                    ignore->regexp = NULL;
                  }
                mydelete(tempr);
              }
          }
          
          break;
          
        case STATEFILE_TAG_MSGLOG:
          {
            msglog_t *msglog;
            statefile_hdr_t *ihdr;

            msglog = irlist_add(&gdata.msglog, sizeof(msglog_t));
            
            hdr->length -= sizeof(*hdr);
            ihdr = &hdr[1];
            
            while (hdr->length >= sizeof(*hdr))
              {
                ihdr->tag = ntohl(ihdr->tag);
                ihdr->length = ntohl(ihdr->length);
                switch (ihdr->tag)
                  {
                  case STATEFILE_TAG_MSGLOG_WHEN:
                    if (ihdr->length == sizeof(statefile_item_generic_time_t))
                      {
                        statefile_item_generic_time_t *g_time = (statefile_item_generic_time_t*)ihdr;
                        msglog->when = ntohl(g_time->g_time);
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Msglog When Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  case STATEFILE_TAG_MSGLOG_HOSTMASK:
                    if (ihdr->length > sizeof(statefile_hdr_t))
                      {
                        char *hostmask = (char*)(&ihdr[1]);
                        hostmask[ihdr->length-sizeof(statefile_hdr_t)-1] = '\0';
                        msglog->hostmask = mymalloc(strlen(hostmask)+1);
                        strcpy(msglog->hostmask,hostmask);
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Msglog Hostmask Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  case STATEFILE_TAG_MSGLOG_MESSAGE:
                    if (ihdr->length > sizeof(statefile_hdr_t))
                      {
                        char *message = (char*)(&ihdr[1]);
                        message[ihdr->length-sizeof(statefile_hdr_t)-1] = '\0';
                        msglog->message = mymalloc(strlen(message)+1);
                        strcpy(msglog->message,message);
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Msglog Message Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  default:
                    outerror(OUTERROR_TYPE_WARN, "Ignoring Unknown Msglog Tag 0x%X (len=%d)",
                             ihdr->tag, ihdr->length);
                  }
                hdr->length -= ceiling(ihdr->length, 4);
                ihdr = (statefile_hdr_t*)(((char*)ihdr) + ceiling(ihdr->length, 4));
              }
            
            if ((!msglog->when) || (!msglog->hostmask) || (!msglog->message))
              {
                outerror(OUTERROR_TYPE_WARN, "Ignoring Incomplete Msglog Tag");
                
                mydelete(msglog->hostmask);
                mydelete(msglog->message);
                irlist_delete(&gdata.msglog, msglog);
              }
          }
          
          break;
          
        case STATEFILE_TAG_XDCCS:
          {
            xdcc *xd;
            statefile_hdr_t *ihdr;
            
            xd = irlist_add(&gdata.xdccs, sizeof(xdcc));
            
            xd->minspeed = gdata.transferminspeed;
            xd->maxspeed = gdata.transfermaxspeed;
            
            hdr->length -= sizeof(*hdr);
            ihdr = &hdr[1];
            
            while (hdr->length >= sizeof(*hdr))
              {
                ihdr->tag = ntohl(ihdr->tag);
                ihdr->length = ntohl(ihdr->length);
                switch (ihdr->tag)
                  {
                  case STATEFILE_TAG_XDCCS_FILE:
                    if (ihdr->length > sizeof(statefile_hdr_t))
                      {
                        char *file = (char*)(&ihdr[1]);
                        file[ihdr->length-sizeof(statefile_hdr_t)-1] = '\0';
                        xd->file = mymalloc(strlen(file)+1);
                        strcpy(xd->file,file);
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad XDCC File Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  case STATEFILE_TAG_XDCCS_DESC:
                    if (ihdr->length > sizeof(statefile_hdr_t))
                      {
                        char *desc = (char*)(&ihdr[1]);
                        desc[ihdr->length-sizeof(statefile_hdr_t)-1] = '\0';
                        xd->desc = mymalloc(strlen(desc)+1);
                        strcpy(xd->desc,desc);
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad XDCC Desc Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  case STATEFILE_TAG_XDCCS_NOTE:
                    if (ihdr->length > sizeof(statefile_hdr_t))
                      {
                        char *note = (char*)(&ihdr[1]);
                        note[ihdr->length-sizeof(statefile_hdr_t)-1] = '\0';
                        xd->note = mymalloc(strlen(note)+1);
                        strcpy(xd->note,note);
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad XDCC Note Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  case STATEFILE_TAG_XDCCS_GETS:
                    if (ihdr->length == sizeof(statefile_item_generic_int_t))
                      {
                        statefile_item_generic_int_t *g_int = (statefile_item_generic_int_t*)ihdr;
                        xd->gets = ntohl(g_int->g_int);
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad XDCC Gets Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  case STATEFILE_TAG_XDCCS_MINSPEED:
                    if (ihdr->length == sizeof(statefile_item_generic_float_t))
                      {
                        statefile_item_generic_float_t *g_float = (statefile_item_generic_float_t*)ihdr;
                        if (g_float->g_float)
                          {
                            xd->minspeed = g_float->g_float;
                          }
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad XDCC Minspeed Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  case STATEFILE_TAG_XDCCS_MAXSPEED:
                    if (ihdr->length == sizeof(statefile_item_generic_float_t))
                      {
                        statefile_item_generic_float_t *g_float = (statefile_item_generic_float_t*)ihdr;
                        if (g_float->g_float)
                          {
                            xd->maxspeed = g_float->g_float;
                          }
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad XDCC Maxspeed Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  case STATEFILE_TAG_XDCCS_MD5SUM_INFO:
                    if (ihdr->length == sizeof(statefile_item_md5sum_info_t))
                      {
                        statefile_item_md5sum_info_t *md5sum_info = (statefile_item_md5sum_info_t*)ihdr;
                        xd->has_md5sum = 1;

                        xd->st_size = (off_t)((((ir_uint64)ntohl(md5sum_info->st_size.upper)) << 32) | ((ir_uint64)ntohl(md5sum_info->st_size.lower)));
                        xd->st_dev  = (dev_t)((((ir_uint64)ntohl(md5sum_info->st_dev.upper)) << 32) | ((ir_uint64)ntohl(md5sum_info->st_dev.lower)));
                        xd->st_ino  = (ino_t)((((ir_uint64)ntohl(md5sum_info->st_ino.upper)) << 32) | ((ir_uint64)ntohl(md5sum_info->st_ino.lower)));
                        xd->mtime   = ntohl(md5sum_info->mtime);
                        memcpy(xd->md5sum, md5sum_info->md5sum, sizeof(MD5Digest));
                      }
                    else
                      {
                        outerror(OUTERROR_TYPE_WARN, "Ignoring Bad XDCC md5sum Tag (len = %d)",
                                 ihdr->length);
                      }
                    break;
                    
                  default:
                    outerror(OUTERROR_TYPE_WARN, "Ignoring Unknown XDCC Tag 0x%X (len=%d)",
                             ihdr->tag, ihdr->length);
                  }
                hdr->length -= ceiling(ihdr->length, 4);
                ihdr = (statefile_hdr_t*)(((char*)ihdr) + ceiling(ihdr->length, 4));
              }
            
            if ((!xd->file) || (!xd->desc) || (!xd->note))
              {
                outerror(OUTERROR_TYPE_WARN, "Ignoring Incomplete XDCC Tag");
                
                mydelete(xd->file);
                mydelete(xd->desc);
                mydelete(xd->note);
                irlist_delete(&gdata.xdccs, xd);
              }
            else
              {
                int xfd;
                xfd = open(xd->file, O_RDONLY | ADDED_OPEN_FLAGS);
                if (xfd < 0)
                  {
                    outerror(OUTERROR_TYPE_WARN,"Cant Access Offered File '%s': %s",
                             xd->file, strerror(errno));
                    memset(&st,0,sizeof(st));
                  }
                else if (fstat(xfd, &st) < 0)
                  {
                    outerror(OUTERROR_TYPE_WARN,"Cant Access Offered File Details '%s': %s",
                             xd->file, strerror(errno));
                    memset(&st, 0, sizeof(st));
                  }
                
                if (!xd->has_md5sum ||
                    (xd->st_dev   != st.st_dev) ||
                    (xd->st_ino   != st.st_ino) ||
                    (xd->mtime    != st.st_mtime) ||
                    (xd->st_size  != st.st_size))
                  {
                    xd->st_size     = st.st_size;
                    xd->st_dev      = st.st_dev;
                    xd->st_ino      = st.st_ino;
                    xd->mtime       = st.st_mtime;
                    xd->has_md5sum  = 0;
                    memset(xd->md5sum, 0, sizeof(MD5Digest));
                  }
                
                if (xd->st_size == 0)
                  {
                    outerror(OUTERROR_TYPE_WARN,"The file \"%s\" has size of 0 bytes!",
                             xd->file);
                  }
                
                if (xd->st_size > gdata.max_file_size)
                  {
                    outerror(OUTERROR_TYPE_CRASH,"The file \"%s\" is too large!",
                             xd->file);
                  }
                
                if (xfd >= 0)
                  {
                    close(xfd);
                  }
              }
          }
          
          break;
          
        case STATEFILE_TAG_TLIMIT_DAILY_USED:
          if (hdr->length == sizeof(statefile_item_generic_ullint_t))
            {
              statefile_item_generic_ullint_t *g_ullint = (statefile_item_generic_ullint_t*)hdr;
              gdata.transferlimits[TRANSFERLIMIT_DAILY].used = (((ir_uint64)ntohl(g_ullint->g_ullint.upper)) << 32) | ((ir_uint64)ntohl(g_ullint->g_ullint.lower));
              
              if (gdata.debug > 0)
                {
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Daily Transfer Limit Used %" LLPRINTFMT "uMB]",
                          gdata.transferlimits[TRANSFERLIMIT_DAILY].used / 1024 / 1024);
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Daily Transfer Limit Used Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_TLIMIT_DAILY_ENDS:
          if (hdr->length == sizeof(statefile_item_generic_time_t))
            {
              char *tempstr;
              statefile_item_generic_time_t *g_time = (statefile_item_generic_time_t*)hdr;
              gdata.transferlimits[TRANSFERLIMIT_DAILY].ends = ntohl(g_time->g_time);
              
              if (gdata.debug > 0)
                {
                  tempstr = mycalloc(maxtextlength);
                  getdatestr(tempstr, gdata.transferlimits[TRANSFERLIMIT_DAILY].ends, maxtextlength);
                  
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Daily Transfer Limit Ends %s]", tempstr);
                  mydelete(tempstr);
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Daily Transfer Limit Ends Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_TLIMIT_WEEKLY_USED:
          if (hdr->length == sizeof(statefile_item_generic_ullint_t))
            {
              statefile_item_generic_ullint_t *g_ullint = (statefile_item_generic_ullint_t*)hdr;
              gdata.transferlimits[TRANSFERLIMIT_WEEKLY].used = (((ir_uint64)ntohl(g_ullint->g_ullint.upper)) << 32) | ((ir_uint64)ntohl(g_ullint->g_ullint.lower));
              
              if (gdata.debug > 0)
                {
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Weekly Transfer Limit Used %" LLPRINTFMT "uMB]",
                          gdata.transferlimits[TRANSFERLIMIT_WEEKLY].used / 1024 / 1024);
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Weekly Transfer Limit Used Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_TLIMIT_WEEKLY_ENDS:
          if (hdr->length == sizeof(statefile_item_generic_time_t))
            {
              char *tempstr;
              statefile_item_generic_time_t *g_time = (statefile_item_generic_time_t*)hdr;
              gdata.transferlimits[TRANSFERLIMIT_WEEKLY].ends = ntohl(g_time->g_time);
              
              if (gdata.debug > 0)
                {
                  tempstr = mycalloc(maxtextlength);
                  getdatestr(tempstr, gdata.transferlimits[TRANSFERLIMIT_WEEKLY].ends, maxtextlength);
                  
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Weekly Transfer Limit Ends %s]", tempstr);
                  mydelete(tempstr);
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Weekly Transfer Limit Ends Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_TLIMIT_MONTHLY_USED:
          if (hdr->length == sizeof(statefile_item_generic_ullint_t))
            {
              statefile_item_generic_ullint_t *g_ullint = (statefile_item_generic_ullint_t*)hdr;
              gdata.transferlimits[TRANSFERLIMIT_MONTHLY].used = (((ir_uint64)ntohl(g_ullint->g_ullint.upper)) << 32) | ((ir_uint64)ntohl(g_ullint->g_ullint.lower));
              
              if (gdata.debug > 0)
                {
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Monthly Transfer Limit Used %" LLPRINTFMT "uMB]",
                          gdata.transferlimits[TRANSFERLIMIT_MONTHLY].used / 1024 / 1024);
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Monthly Transfer Limit Used Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        case STATEFILE_TAG_TLIMIT_MONTHLY_ENDS:
          if (hdr->length == sizeof(statefile_item_generic_time_t))
            {
              char *tempstr;
              statefile_item_generic_time_t *g_time = (statefile_item_generic_time_t*)hdr;
              gdata.transferlimits[TRANSFERLIMIT_MONTHLY].ends = ntohl(g_time->g_time);
              
              if (gdata.debug > 0)
                {
                  tempstr = mycalloc(maxtextlength);
                  getdatestr(tempstr, gdata.transferlimits[TRANSFERLIMIT_MONTHLY].ends, maxtextlength);
                  
                  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
                          COLOR_NO_COLOR, "  [Monthly Transfer Limit Ends %s]", tempstr);
                  mydelete(tempstr);
                }
            }
          else
            {
              outerror(OUTERROR_TYPE_WARN, "Ignoring Bad Monthly Transfer Limit Ends Tag (len = %d)",
                       hdr->length);
            }
          break;
          
        default:
          outerror(OUTERROR_TYPE_WARN, "Ignoring Unknown Tag 0x%X (len=%d)",
                 hdr->tag, hdr->length);
          break;
        }
    }
  
  if (buffer_len)
    {
      outerror(OUTERROR_TYPE_WARN, "Extra data at end of state file!? %d left",
               buffer_len);
    }
  
  /* end read */
  
  if ((gdata.debug > 0) || irlist_size(&gdata.ignorelist))
    {
      ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
              COLOR_NO_COLOR, "  [Found %d Ignore%s]",
              irlist_size(&gdata.ignorelist),
              (irlist_size(&gdata.ignorelist) == 1) ? "" : "s");
    }
  
  if ((gdata.debug > 0) || irlist_size(&gdata.msglog))
    {
      ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
              COLOR_NO_COLOR, "  [Found %d Message%s]",
              irlist_size(&gdata.msglog),
              (irlist_size(&gdata.msglog) == 1) ? "" : "s");
    }
  
  if ((gdata.debug > 0) || irlist_size(&gdata.xdccs))
    {
      ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
              COLOR_NO_COLOR, "  [Found %d Pack%s]",
              irlist_size(&gdata.xdccs),
              (irlist_size(&gdata.xdccs) == 1) ? "" : "s");
    }
  
  ioutput(CALLTYPE_NORMAL, OUT_S|OUT_L|OUT_D,
          COLOR_NO_COLOR, "  [Done]");
  
 error_out:
  
  mydelete(buffer_begin);
  
  return;
}


/* End of File */
