//  This may look like C code, but it is really -*- C++ -*-

//  ------------------------------------------------------------------
//  The Goldware Library
//  Copyright (C) 2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License as
//  published by the Free Software Foundation; either version 2 of the
//  License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  Based on freeware sources from Digital Dynamics
//  ------------------------------------------------------------------
//  Synchronet message base
//  ------------------------------------------------------------------

#include <cerrno>
#include <gmemdbg.h>
#include <gmosmb.h>


//  ------------------------------------------------------------------
//  Open a message base
//  If retry_time is 0, fast open method (no compatibility/validity check)
//  Opens files for READing messages or updating message indices only

int SMBArea::smb_open(int retry_time)
{
  int file;
  smbhdr_t hdr;

  data->shd_fp = data->sdt_fp = data->sid_fp = NULL;
  if ((data->shd_fp = fsopen(AddPath(path(), ".shd"), "rb+", WideSharemode)) == NULL)
    return (2);
  file = fileno(data->shd_fp);
  if (retry_time && filelength(file) >= sizeof(smbhdr_t)) {
    setvbuf(data->shd_fp, data->shd_buf, _IONBF, SHD_BLOCK_LEN);
    if (smb_locksmbhdr(retry_time)) {
      smb_close();
      return (-1);
    }
    memset(&hdr, 0, sizeof(smbhdr_t));
    fread(&hdr, sizeof(smbhdr_t), 1, data->shd_fp);
    if (memcmp(hdr.id, "SMB\x1a", 4)) {
      smb_close();
      return (-2);
    }
    if (hdr.version < 0x110) { // Compatibility check
      smb_close();
      return (-3);
    }
    smb_unlocksmbhdr();
    rewind(data->shd_fp);
  }
  if ((data->sdt_fp = fsopen(AddPath(path(), ".sdt"), "rb+", WideSharemode)) == NULL) {
    smb_close();
    return (1);
  }
  if ((data->sid_fp = fsopen(AddPath(path(), ".sid"), "rb+", WideSharemode)) == NULL) {
    smb_close();
    return (3);
  }
  return (0);
}


//  ------------------------------------------------------------------
//  Closes the currently open message base

void SMBArea::smb_close(void)
{
  if (data->shd_fp != NULL) {
    // In case it's been locked
    smb_unlocksmbhdr();
    fclose(data->shd_fp);
  }
  if (data->sid_fp != NULL)
    fclose(data->sid_fp);
  if (data->sdt_fp != NULL)
    fclose(data->sdt_fp);
  data->sid_fp = data->shd_fp = data->sdt_fp = NULL;
}


//  ------------------------------------------------------------------
//  Opens the data block allocation table message base 'smb_file'
//  Retrys for retry_time number of seconds
//  Return 0 on success, non-zero otherwise

int SMBArea::smb_open_da(int retry_time)
{
  data->sda_fp = smb_openexlusively(AddPath(path(), ".sda"), retry_time);
  return data->sda_fp == NULL ? 1 : 0;
}


//  ------------------------------------------------------------------
//  Opens the header block allocation table for message base 'smb_file'
//  Retrys for retry_time number of seconds
//  Return 0 on success, non-zero otherwise

int SMBArea::smb_open_ha(int retry_time)
{
  data->sha_fp = smb_openexlusively(AddPath(path(), ".sha"), retry_time);
  return data->sha_fp == NULL ? 1 : 0;
}


//  ------------------------------------------------------------------
//  Truncates header file
//  Retrys for retry_time number of seconds
//  Return 0 on success, non-zero otherwise

int SMBArea::smb_trunchdr(int retry_time)
{
  long start;

  start = time(NULL);
  rewind(data->shd_fp);
  while (1) {
    if (not chsize(fileno(data->shd_fp), 0L))
      break;
    if (errno != EACCES)
      return (-1);
    if (time(NULL) - start >= retry_time) // Time-out
      return (-2);
  }
  return (0);
}


//  ------------------------------------------------------------------
//  Message Base Header Functions

//  ------------------------------------------------------------------
//  Attempts for retry_time number of seconds to lock the message base hdr

int SMBArea::smb_locksmbhdr(int retry_time)
{
  dword start;

  if(WideCanLock) {
    start = time(NULL);
    while (1) {
      if (not ::lock(fileno(data->shd_fp), 0L, sizeof(smbhdr_t) + sizeof(smbstatus_t)))
        return (0);
      if (time(NULL) - start >= retry_time)
        break; // Incase we've already locked it
      ::unlock(fileno(data->shd_fp), 0L, sizeof(smbhdr_t) + sizeof(smbstatus_t));
    }
    return (-1);
  }
  return (0);
}


//  ------------------------------------------------------------------
//  Read the SMB header from the header file and place into "status"

int SMBArea::smb_getstatus(smbstatus_t *status)
{
  int i;

  clearerr(data->shd_fp);
  fseek(data->shd_fp, sizeof(smbhdr_t), SEEK_SET);
  i = fread(status, 1, sizeof(smbstatus_t), data->shd_fp);
  if (i == sizeof(smbstatus_t))
    return (0);
  return (1);
}


//  ------------------------------------------------------------------
//  Writes message base header

int SMBArea::smb_putstatus(smbstatus_t status)
{
  int i;

  clearerr(data->shd_fp);
  fseek(data->shd_fp, sizeof(smbhdr_t), SEEK_SET);
  i = fwrite(&status, 1, sizeof(smbstatus_t), data->shd_fp);
  fflush(data->shd_fp);
  if (i == sizeof(smbstatus_t))
    return (0);
  return (1);
}


//  ------------------------------------------------------------------
//  Unlocks previously locks message base header

int SMBArea::smb_unlocksmbhdr()
{
  if(not WideCanLock) return 0;
  return (::unlock(fileno(data->shd_fp), 0L, sizeof(smbhdr_t) + sizeof(smbstatus_t)));
}


//  ------------------------------------------------------------------
//  Individual Message Functions

//  ------------------------------------------------------------------
//  Attempts for retry_time number of seconds to lock the header for 'msg'

int SMBArea::smb_lockmsghdr(smbmsg_t msg, int retry_time)
{
  dword start;

  if(WideCanLock) {
    start = time(NULL);
    while (1) {
      if (not ::lock(fileno(data->shd_fp), msg.idx.offset, sizeof(msghdr_t)))
        return (0);
      if (time(NULL) - start >= retry_time)
        break;
      ::unlock(fileno(data->shd_fp), msg.idx.offset, sizeof(msghdr_t));
    }
    return (-1);
  }
  return 0;
}


//  ------------------------------------------------------------------
//  Fills msg->idx with message index based on msg->hdr.number
//  OR if msg->hdr.number is 0, based on msg->offset (record offset).
//  if msg.hdr.number does not equal 0, then msg->offset is filled too.
//  Either msg->hdr.number or msg->offset must be initialized before
//  calling this function
//  Returns 1 if message number wasn't found, 0 if it was

int SMBArea::smb_getmsgidx(smbmsg_t *msg)
{
  idxrec_t idx;
  dword l, length, total, bot, top;

  clearerr(data->sid_fp);
  if (not msg->hdr.number) {
    fseek(data->sid_fp, msg->offset * sizeof(idxrec_t), SEEK_SET);
    if (not fread(&msg->idx, sizeof(idxrec_t), 1, data->sid_fp))
      return (1);
    return (0);
  }
  length = filelength(fileno(data->sid_fp));
  if (not length)
    return (1);
  total = length / sizeof(idxrec_t);
  if (not total)
    return (1);

  bot = 0;
  top = total;
  l = total / 2; // Start at middle index
  while (1) {
    fseek(data->sid_fp, l * sizeof(idxrec_t), SEEK_SET);
    if (not fread(&idx, sizeof(idxrec_t), 1, data->sid_fp))
      return (1);
    if (bot == top - 1 and idx.number != msg->hdr.number)
      return (1);
    if (idx.number > msg->hdr.number) {
      top = l;
      l = bot + ((top - bot) / 2);
      continue;
    }
    if (idx.number < msg->hdr.number) {
      bot = l;
      l = top - ((top - bot) / 2);
      continue;
    }
    break;
  }
  msg->idx = idx;
  msg->offset = l;
  return (0);
}


//  ------------------------------------------------------------------
//  Reads the last index record in the open message base

int SMBArea::smb_getlastidx(idxrec_t *idx)
{
  long length;

  clearerr(data->sid_fp);
  length = filelength(fileno(data->sid_fp));
  if (length < sizeof(idxrec_t))
    return (-1);
  fseek(data->sid_fp, length - sizeof(idxrec_t), SEEK_SET);
  if (not fread(idx, sizeof(idxrec_t), 1, data->sid_fp))
    return (-2);
  return (0);
}


//  ------------------------------------------------------------------
//  Figures out the total length of the header record for 'msg'
//  Returns length

uint SMBArea::smb_getmsghdrlen(smbmsg_t msg)
{
  int i;

  // fixed portion
  msg.hdr.length = sizeof(msghdr_t);
  // data fields
  msg.hdr.length += msg.hdr.total_dfields * sizeof(dfield_t);
  // header fields
  for (i = 0; i < msg.total_hfields; i++) {
    msg.hdr.length += sizeof(hfield_t);
    msg.hdr.length += msg.hfield[i].length;
  }
  return (msg.hdr.length);
}


//  ------------------------------------------------------------------
//  Figures out the total length of the data buffer for 'msg'
//  Returns length

dword SMBArea::smb_getmsgdatlen(smbmsg_t msg)
{
  int i;
  dword length = 0L;

  for (i = 0; i < msg.hdr.total_dfields; i++)
    length += msg.dfield[i].length;
  return (length);
}


//  ------------------------------------------------------------------
//  Read header information into 'msg' structure
//  msg->idx.offset must be set before calling this function
//  Must call smb_freemsgmem() to free memory allocated for var len strs
//  Returns 0 on success, non-zero if error

int SMBArea::smb_getmsghdr(smbmsg_t *msg)
{
  word i;
  dword l, offset;
  idxrec_t idx;

  rewind(data->shd_fp);
  fseek(data->shd_fp, msg->idx.offset, SEEK_SET);
  idx = msg->idx;
  offset = msg->offset;
  memset(msg, 0, sizeof(smbmsg_t));
  msg->idx = idx;
  msg->offset = offset;
  if (not fread(&msg->hdr, sizeof(msghdr_t), 1, data->shd_fp))
    return (-1);
  if (memcmp(msg->hdr.id, "SHD\x1a", 4))
    return (-2);
  if (msg->hdr.version < 0x110)
    return (-9);
  l = sizeof(msghdr_t);
  msg->dfield = (dfield_t *)throw_xmalloc(msg->hdr.total_dfields*sizeof(dfield_t));
  i = 0;
  while (i < msg->hdr.total_dfields and l < msg->hdr.length) {
    if (not fread(&msg->dfield[i], sizeof(dfield_t), 1, data->shd_fp)) {
      smb_freemsgmem(*msg);
      return (-4);
    }
    i++;
    l += sizeof(dfield_t);
  }
  if (i < msg->hdr.total_dfields) {
    smb_freemsgmem(*msg);
    return (-8);
  }
  while (l < msg->hdr.length) {
    i = msg->total_hfields++;
    msg->hfield_dat = (void **)throw_xrealloc(msg->hfield_dat, msg->total_hfields*sizeof(void *));
    msg->hfield = (hfield_t *)throw_xrealloc(msg->hfield, msg->total_hfields*sizeof(hfield_t));
    if (not fread(&msg->hfield[i], sizeof(hfield_t), 1, data->shd_fp)) {
      smb_freemsgmem(*msg);
      return (-5);
    }
    l += sizeof(hfield_t);
    msg->hfield_dat[i] = (char *)throw_xmalloc(msg->hfield[i].length + 1);
    memset(msg->hfield_dat[i], 0, msg->hfield[i].length + 1); // init to NULL
    if (msg->hfield[i].length and not fread(msg->hfield_dat[i], msg->hfield[i].length, 1, data->shd_fp)) {
      smb_freemsgmem(*msg);
      return (-6);
    }
    switch (msg->hfield[i].type) { // convenience variables
      case SENDER:
        if (not msg->from) {
          msg->from = (uchar *)msg->hfield_dat[i];
          break;
        }
      case FORWARDED: // fall through
        msg->forwarded = 1;
        break;
      case SENDERAGENT:
        if (not msg->forwarded)
          msg->from_agent = *(word *)msg->hfield_dat[i];
        break;
      case SENDEREXT:
        if (not msg->forwarded)
          msg->from_ext = (uchar *)msg->hfield_dat[i];
        break;
      case SENDERNETTYPE:
        if (not msg->forwarded)
          msg->from_net.type = *(word *)msg->hfield_dat[i];
        break;
      case SENDERNETADDR:
        if (not msg->forwarded)
          msg->from_net.addr = (char *)msg->hfield_dat[i];
        break;
      case REPLYTO:
        msg->replyto = (uchar *)msg->hfield_dat[i];
        break;
      case REPLYTOEXT:
        msg->replyto_ext = (uchar *)msg->hfield_dat[i];
        break;
      case REPLYTOAGENT:
        msg->replyto_agent = *(word *)msg->hfield_dat[i];
        break;
      case REPLYTONETTYPE:
        msg->replyto_net.type = *(word *)msg->hfield_dat[i];
        break;
      case REPLYTONETADDR:
        msg->replyto_net.addr = (char *)msg->hfield_dat[i];
        break;
      case RECIPIENT:
        msg->to = (uchar *)msg->hfield_dat[i];
        break;
      case RECIPIENTEXT:
        msg->to_ext = (uchar *)msg->hfield_dat[i];
        break;
      case RECIPIENTAGENT:
        msg->to_agent = *(word *)msg->hfield_dat[i];
        break;
      case RECIPIENTNETTYPE:
        msg->to_net.type = *(word *)msg->hfield_dat[i];
        break;
      case RECIPIENTNETADDR:
        msg->to_net.addr = (char *)msg->hfield_dat[i];
        break;
      case SUBJECT:
        msg->subj = (uchar *)msg->hfield_dat[i];
        break;
    }
    l += msg->hfield[i].length;
  }

  if (not msg->from or not msg->to or not msg->subj) {
    smb_freemsgmem(*msg);
    return (-7);
  }
  return (0);
}


//  ------------------------------------------------------------------
//  Frees memory allocated for 'msg'

void SMBArea::smb_freemsgmem(smbmsg_t msg)
{
  word i;

  throw_xfree(msg.dfield);
  for (i = 0; i < msg.total_hfields; i++)
    throw_xfree(msg.hfield_dat[i]);
  throw_xfree(msg.hfield);
  throw_xfree(msg.hfield_dat);
}


//  ------------------------------------------------------------------
//  Unlocks header for 'msg'

int SMBArea::smb_unlockmsghdr(smbmsg_t msg)
{
  if(not WideCanLock) return 0;
  return (::unlock(fileno(data->shd_fp), msg.idx.offset, sizeof(msghdr_t)));
}


//  ------------------------------------------------------------------
//  Adds a header field to the 'msg' structure (in memory only)

int SMBArea::smb_hfield(smbmsg_t * msg, word type, word length, void *data)
{
  int i;

  i = msg->total_hfields;
  msg->hfield = (hfield_t *)throw_xrealloc(msg->hfield, (i+1)*sizeof(hfield_t));
  msg->hfield_dat = (void **)throw_xrealloc(msg->hfield_dat, (i+1)*sizeof(void *));
  msg->total_hfields++;
  msg->hfield[i].type = type;
  msg->hfield[i].length = length;
  if (length) {
    msg->hfield_dat[i] = (void *)throw_xmalloc(length);
    memcpy(msg->hfield_dat[i], data, length);
  } else
    msg->hfield_dat[i] = NULL;
  return (0);
}


//  ------------------------------------------------------------------
//  Adds a data field to the 'msg' structure (in memory only)
//  Automatically figures out the offset into the data buffer from existing
//  dfield lengths

int SMBArea::smb_dfield(smbmsg_t * msg, word type, dword length)
{
  int i, j;

  i = msg->hdr.total_dfields;
  msg->dfield = (dfield_t *)throw_xrealloc(msg->dfield, (i+1)*sizeof(dfield_t));
  msg->hdr.total_dfields++;
  msg->dfield[i].type = type;
  msg->dfield[i].length = length;
  for (j = msg->dfield[i].offset = 0; j < i; j++)
    msg->dfield[i].offset += msg->dfield[j].length;
  return (0);
}


//  ------------------------------------------------------------------
//  Checks CRC history file for duplicate crc. If found, returns 1.
//  If no dupe, adds to CRC history and returns 0, or negative if error.

int SMBArea::smb_addcrc(dword max_crcs, dword crc, int retry_time)
{
  int file;
  long length;
  dword l, *buf;

  if (not max_crcs)
    return (0);
  file = smb_openexlusively2(AddPath(path(), ".sch"), retry_time);
  length = filelength(file);
  if (length < 0L) {
    ::close(file);
    return (-4);
  }
  buf = (dword *)throw_xmalloc(max_crcs * sizeof(dword));
  if (length >= max_crcs * 4) { // Reached or exceeds max crcs
    read(file, buf, max_crcs * 4);
    for (l = 0; l < max_crcs; l++)
      if (crc == buf[l])
        break;
    if (l < max_crcs) { // Dupe CRC found
      ::close(file);
      throw_xfree(buf);
      return (1);
    }
    chsize(file, 0L); // truncate it
    lseek(file, 0L, SEEK_SET);
    write(file, buf + 4, (max_crcs - 1) * 4);
  } else if (length / 4) { // Less than max crcs
    read(file, buf, length);
    for (l = 0; l < length / 4; l++)
      if (crc == buf[l])
        break;
    if (l < length / 4) { // Dupe CRC found
      ::close(file);
      throw_xfree(buf);
      return (1);
    }
  }
  lseek(file, 0L, SEEK_END);
  write(file, &crc, 4); // Write to the end
  throw_xfree(buf);
  ::close(file);
  return (0);
}


//  ------------------------------------------------------------------
//  Creates a new message header record in the header file.
//  If storage is SMB_SELFPACK, self-packing conservative allocation is used
//  If storage is SMB_FASTALLOC, fast allocation is used
//  If storage is SMB_HYPERALLOC, no allocation tables are used (fastest)

int SMBArea::smb_addmsghdr(smbmsg_t * msg, smbstatus_t * status, int storage, int retry_time)
{
  int i;
  long l;

  if (smb_locksmbhdr(retry_time))
    return (1);
  if (smb_getstatus(status))
    return (2);

  if (storage != SMB_HYPERALLOC and (i = smb_open_ha(retry_time)) != 0)
    return (i);

  msg->hdr.length = smb_getmsghdrlen(*msg);
  if (storage == SMB_HYPERALLOC)
    l = smb_hallochdr(status->header_offset);
  else if (storage == SMB_FASTALLOC)
    l = smb_fallochdr(msg->hdr.length);
  else
    l = smb_allochdr(msg->hdr.length);
  if (l == -1L) {
    smb_unlocksmbhdr();
    fclose(data->sha_fp);
    return (-1);
  }
  status->last_msg++;
  msg->idx.number = msg->hdr.number = status->last_msg;
  msg->idx.offset = status->header_offset + l;
  msg->idx.time = msg->hdr.when_imported.time;
  msg->idx.attr = msg->hdr.attr;
  msg->offset = status->total_msgs;
  status->total_msgs++;
  smb_putstatus(*status);

  if (storage != SMB_HYPERALLOC)
    fclose(data->sha_fp);
  i = smb_putmsg(*msg);
  smb_unlocksmbhdr();
  return (i);
}


//  ------------------------------------------------------------------
//  Writes both header and index information for msg 'msg'

int SMBArea::smb_putmsg(smbmsg_t msg)
{
  int i;

  i = smb_putmsghdr(msg);
  if (i)
    return (i);
  return (smb_putmsgidx(msg));
}


//  ------------------------------------------------------------------
//  Writes index information for 'msg'
//  msg.idx and msg.offset must be set prior to calling to this function
//  Returns 0 if everything ok

int SMBArea::smb_putmsgidx(smbmsg_t msg)
{
  clearerr(data->sid_fp);
  fseek(data->sid_fp, msg.offset * sizeof(idxrec_t), SEEK_SET);
  if (not fwrite(&msg.idx, sizeof(idxrec_t), 1, data->sid_fp))
    return (1);
  fflush(data->sid_fp);
  return (0);
}


//  ------------------------------------------------------------------
//  Writes header information for 'msg'
//  msg.hdr.length
//  msg.idx.offset
//  and msg.offset must be set prior to calling to this function
//  Returns 0 if everything ok

int SMBArea::smb_putmsghdr(smbmsg_t msg)
{
  word i;
  dword l;

  clearerr(data->shd_fp);
  if (fseek(data->shd_fp, msg.idx.offset, SEEK_SET))
    return (-1);

  // Write the fixed portion of the header record
  if (not fwrite(&msg.hdr, sizeof(msghdr_t), 1, data->shd_fp))
    return (-2);

  // Write the data fields (each is fixed length)
  for (i = 0; i < msg.hdr.total_dfields; i++)
    if (not fwrite(&msg.dfield[i], sizeof(dfield_t), 1, data->shd_fp))
      return (-3);

  // Write the variable length header fields
  for (i = 0; i < msg.total_hfields; i++) {
    if (not fwrite(&msg.hfield[i], sizeof(hfield_t), 1, data->shd_fp))
      return (-4);
    if (msg.hfield[i].length and not fwrite(msg.hfield_dat[i], msg.hfield[i].length, 1, data->shd_fp))
      return (-5);
  }

  l = smb_getmsghdrlen(msg);
  while (l % SHD_BLOCK_LEN) {
    if (fputc(0, data->shd_fp) == EOF) // pad block with NULL
      return (-6);
    l++;
  }
  fflush(data->shd_fp);
  return (0);
}


//  ------------------------------------------------------------------
//  Creates a sub-board's initial header file
//  Truncates and deletes other associated SMB files

int SMBArea::smb_create(dword max_crcs, dword max_msgs, word max_age, word attr, int retry_time)
{
  smbhdr_t hdr;
  smbstatus_t status;

  if (filelength(fileno(data->shd_fp)) >= sizeof(smbhdr_t) + sizeof(smbstatus_t)
    and smb_locksmbhdr(retry_time)) // header exists, so lock it
    return (1);
  memset(&hdr, 0, sizeof(smbhdr_t));
  memset(&status, 0, sizeof(smbstatus_t));
  memcpy(hdr.id, "SMB\x1a", 4);
  hdr.version = SMB_VERSION;
  hdr.length = sizeof(smbhdr_t) + sizeof(smbstatus_t);
  status.last_msg = status.total_msgs = 0;
  status.header_offset = sizeof(smbhdr_t) + sizeof(smbstatus_t);
  status.max_crcs = max_crcs;
  status.max_msgs = max_msgs;
  status.max_age = max_age;
  status.attr = attr;
  rewind(data->shd_fp);
  fwrite(&hdr, 1, sizeof(smbhdr_t), data->shd_fp);
  fwrite(&status, 1, sizeof(smbstatus_t), data->shd_fp);
  rewind(data->shd_fp);
  chsize(fileno(data->shd_fp), sizeof(smbhdr_t) + sizeof(smbstatus_t));
  fflush(data->shd_fp);

  rewind(data->sdt_fp);
  chsize(fileno(data->sdt_fp), 0L);
  rewind(data->sid_fp);
  chsize(fileno(data->sid_fp), 0L);

  remove(AddPath(path(), ".sda")); // if it exists, delete it
  remove(AddPath(path(), ".sha")); // if it exists, delete it
  remove(AddPath(path(), ".sch"));
  smb_unlocksmbhdr();
  return (0);
}


//  ------------------------------------------------------------------
//  Returns number of data blocks required to store "length" amount of data

dword SMBArea::smb_datblocks(dword length)
{
  dword blocks;

  blocks = length / SDT_BLOCK_LEN;
  if (length % SDT_BLOCK_LEN)
    blocks++;
  return (blocks);
}


//  ------------------------------------------------------------------
//  Returns number of header blocks required to store "length" size header

dword SMBArea::smb_hdrblocks(dword length)
{
  dword blocks;

  blocks = length / SHD_BLOCK_LEN;
  if (length % SHD_BLOCK_LEN)
    blocks++;
  return (blocks);
}


//  ------------------------------------------------------------------
//  Finds unused space in data file based on block allocation table and
//  marks space as used in allocation table.
//  File must be opened read/write DENY ALL
//  Returns offset to beginning of data (in bytes, not blocks)
//  Assumes smb_open_da() has been called
//  fclose(data->sda_fp) should be called after
//  Returns negative on error

long SMBArea::smb_allocdat(dword length, word headers)
{
  word i, j;
  dword l, blocks, offset = 0L;

  blocks = smb_datblocks(length);
  j = 0; //  j is consecutive unused block counter
  fflush(data->sda_fp);
  rewind(data->sda_fp);
  while (not feof(data->sda_fp)) {
    if (not fread(&i, 2, 1, data->sda_fp))
      break;
    offset += SDT_BLOCK_LEN;
    if (not i)
      j++;
    else
      j = 0;
    if (j == blocks) {
      offset -= (blocks * SDT_BLOCK_LEN);
      break;
    }
  }
  clearerr(data->sda_fp);
  fseek(data->sda_fp, (offset / SDT_BLOCK_LEN) * 2L, SEEK_SET);
  for (l = 0; l < blocks; l++)
    if (not fwrite(&headers, 2, 1, data->sda_fp))
      return (-1);
  fflush(data->sda_fp);
  return (offset);
}


//  ------------------------------------------------------------------
//  Allocates space for data, but doesn't search for unused blocks
//  Returns negative on error

long SMBArea::smb_fallocdat(dword length, word headers)
{
  dword l, blocks, offset;

  fflush(data->sda_fp);
  clearerr(data->sda_fp);
  blocks = smb_datblocks(length);
  fseek(data->sda_fp, 0L, SEEK_END);
  offset = (ftell(data->sda_fp) / 2L) * SDT_BLOCK_LEN;
  for (l = 0; l < blocks; l++)
    if (not fwrite(&headers, 2, 1, data->sda_fp))
      break;
  fflush(data->sda_fp);
  if (l < blocks)
    return (-1L);
  return (offset);
}


//  ------------------------------------------------------------------
//  De-allocates space for data
//  Returns non-zero on error

int SMBArea::smb_freemsgdat(dword offset, dword length, word headers)
{
  word i;
  dword l, blocks;

  blocks = smb_datblocks(length);

  clearerr(data->sda_fp);
  for (l = 0; l < blocks; l++) {
    if (fseek(data->sda_fp, ((offset / SDT_BLOCK_LEN) + l) * 2L, SEEK_SET))
      return (1);
    if (not fread(&i, 2, 1, data->sda_fp))
      return (2);
    if (headers > i)
      i = 0; // don't want to go negative
    else
      i -= headers;
    if (fseek(data->sda_fp, -2L, SEEK_CUR))
      return (3);
    if (not fwrite(&i, 2, 1, data->sda_fp))
      return (4);
  }
  fflush(data->sda_fp);
  return (0);
}


//  ------------------------------------------------------------------
//  Adds to data allocation records for blocks starting at 'offset'
//  Returns non-zero on error

int SMBArea::smb_incdat(dword offset, dword length, word headers)
{
  word i;
  dword l, blocks;

  clearerr(data->sda_fp);
  blocks = smb_datblocks(length);
  for (l = 0; l < blocks; l++) {
    fseek(data->sda_fp, ((offset / SDT_BLOCK_LEN) + l) * 2L, SEEK_SET);
    if (not fread(&i, 2, 1, data->sda_fp))
      return (1);
    i += headers;
    fseek(data->sda_fp, -2L, SEEK_CUR);
    if (not fwrite(&i, 2, 1, data->sda_fp))
      return (2);
  }
  fflush(data->sda_fp);
  return (0);
}


//  ------------------------------------------------------------------
//  De-allocates blocks for header record
//  Returns non-zero on error

int SMBArea::smb_freemsghdr(dword offset, dword length)
{
  uchar c = 0;
  dword l, blocks;

  clearerr(data->sha_fp);
  blocks = smb_hdrblocks(length);
  fseek(data->sha_fp, offset / SHD_BLOCK_LEN, SEEK_SET);
  for (l = 0; l < blocks; l++)
    if (not fwrite(&c, 1, 1, data->sha_fp))
      return (1);
  fflush(data->sha_fp);
  return (0);
}


//  ------------------------------------------------------------------
//  Frees all allocated header and data blocks for 'msg'

int SMBArea::smb_freemsg(smbmsg_t msg, smbstatus_t status)
{
  int i;
  word x;

  if (status.attr & SMB_HYPERALLOC)
    return (0); // Nothing to do

  for (x = 0; x < msg.hdr.total_dfields; x++) {
    if ((i = smb_freemsgdat(msg.hdr.offset + msg.dfield[x].offset, msg.dfield[x].length, 1)) != 0)
      return (i);
  }
  return (smb_freemsghdr(msg.idx.offset - status.header_offset, msg.hdr.length));
}


//  ------------------------------------------------------------------
//  Finds unused space in header file based on block allocation table and
//  marks space as used in allocation table.
//  File must be opened read/write DENY ALL
//  Returns offset to beginning of header (in bytes, not blocks)
//  Assumes smb_open_ha() has been called
//  fclose(data->sha_fp) should be called after
//  Returns -1L on error

long SMBArea::smb_allochdr(dword length)
{
  uchar c;
  word i;
  dword l, blocks, offset = 0;

  blocks = smb_hdrblocks(length);
  i = 0; // i is consecutive unused block counter
  fflush(data->sha_fp);
  rewind(data->sha_fp);
  while (not feof(data->sha_fp)) {
    if (not fread(&c, 1, 1, data->sha_fp))
      break;
    offset += SHD_BLOCK_LEN;
    if (not c)
      i++;
    else
      i = 0;
    if (i == blocks) {
      offset -= (blocks * SHD_BLOCK_LEN);
      break;
    }
  }
  clearerr(data->sha_fp);
  fseek(data->sha_fp, offset / SHD_BLOCK_LEN, SEEK_SET);
  c = 1;
  for (l = 0; l < blocks; l++)
    if (not fwrite(&c, 1, 1, data->sha_fp))
      return (-1L);
  fflush(data->sha_fp);
  return (offset);
}


//  ------------------------------------------------------------------
//  Allocates space for index, but doesn't search for unused blocks
//  Returns -1L on error

long SMBArea::smb_fallochdr(dword length)
{
  uchar c = 1;
  dword l, blocks, offset;

  blocks = smb_hdrblocks(length);
  fflush(data->sha_fp);
  clearerr(data->sha_fp);
  fseek(data->sha_fp, 0L, SEEK_END);
  offset = ftell(data->sha_fp) * SHD_BLOCK_LEN;
  for (l = 0; l < blocks; l++)
    if (not fwrite(&c, 1, 1, data->sha_fp))
      return (-1L);
  fflush(data->sha_fp);
  return (offset);
}


//  ------------------------------------------------------------------
//  Allocate header blocks using Hyper Allocation
//  this function should be most likely not be called from anywhere but
//  smb_addmsghdr()

long SMBArea::smb_hallochdr(dword header_offset)
{
  long l;

  fflush(data->shd_fp);
  fseek(data->shd_fp, 0L, SEEK_END);
  l = ftell(data->shd_fp);
  if (l < header_offset) // Header file truncated?!?
    return (header_offset);
  while ((l - header_offset) % SHD_BLOCK_LEN) // Make sure even block boundry
    l++;
  return (l - header_offset);
}


//  ------------------------------------------------------------------
//  Allocate data blocks using Hyper Allocation
//  smb_locksmbhdr() should be called before this function and not
//  unlocked until all data fields for this message have been written
//  to the SDT file

long SMBArea::smb_hallocdat()
{
  long l;

  fflush(data->sdt_fp);
  fseek(data->sdt_fp, 0L, SEEK_END);
  l = ftell(data->sdt_fp);
  if (l <= 0)
    return (l);
  while (l % SDT_BLOCK_LEN) // Make sure even block boundry
    l++;
  return (l);
}