/* smblib.c */

/* Synchronet message base (SMB) library routines */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4       (Plain Text/Source Code File Header)            *
 * @format.use-tabs true    (see http://www.synchro.net/ptsc_hdr.html)      *
 *                                                                          *
 * Copyright 2000 Rob Swindell - http://www.synchro.net/copyright.html      *
 *                                                                          *
 * This library is free software; you can redistribute it and/or            *
 * modify it under the terms of the GNU Lesser General Public License       *
 * as published by the Free Software Foundation; either version 2           *
 * of the License, or (at your option) any later version.                   *
 * See the GNU Lesser General Public License for more details: lgpl.txt or  *
 * http://www.fsf.org/copyleft/lesser.html                                  *
 *                                                                          *
 * Anonymous FTP access to the most recent released source is available at  *
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net  *
 *                                                                          *
 * Anonymous CVS access to the development source and modification history  *
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:                  *
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login            *
 *     (just hit return, no password is necessary)                          *
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src     *
 *                                                                          *
 * For Synchronet coding style and modification guidelines, see             *
 * http://www.synchro.net/source.html                                       *
 *                                                                          *
 * You are encouraged to submit any modifications (preferably in Unix diff  *
 * format) via e-mail to mods@synchro.net                                   *
 *                                                                          *
 * Note: If this box doesn't appear square, then you need to fix your tabs. *
 ****************************************************************************/

#if defined __WATCOMC__ || defined __TURBOC__
    #include <mem.h>
#else
    #include <memory.h>
#endif

#ifdef __WATCOMC__
    #include <dos.h>
#elif defined __TURBOC__
    #include <dir.h>
#endif

/* ANSI C Library headers */

#ifdef HAVE_MALLOC_H
    #include <malloc.h>
#endif

#include <time.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

/* SMB-specific headers */
#include "smblib.h"
#include <gtimall.h>
#include <gfilutil.h>

/* Use smb_ver() and smb_lib_ver() to obtain these values */
#define SMBLIB_VERSION      "2.16"      /* SMB library version */
#define SMB_VERSION         0x0121      /* SMB format version */
                                        /* High byte major, low byte minor */

#define U_MODE 777 /* permitions for the new files (real: U_MODE XOR UMASK) */
                   /* This is required for sopen(,,,) */

#ifndef __gtimall_h
time32_t gtime(time32_t *timep)
{
  time32_t temp = (time32_t)time(NULL);
  return timep ? *timep = temp : temp;
}
#endif  //#ifndef __gtimall_h

int SMBCALL smb_ver(void)
{
    return(SMB_VERSION);
}

char* SMBCALL smb_lib_ver(void)
{
    return(SMBLIB_VERSION);
}

/****************************************************************************/
/* Open a message base of name 'smb->file'                                  */
/* Opens files for READing messages or updating message indices only        */
/****************************************************************************/
int SMBCALL smb_open(smb_t* smb)
{
    int file;
    char str[128];
    smbhdr_t hdr;

    /* Set default values, if uninitialized */
    if(!smb->retry_time)
        smb->retry_time=10;     /* seconds */
    if(!smb->retry_delay
        || smb->retry_delay>(smb->retry_time*100))  /* at least ten retries */
        smb->retry_delay=250;   /* milliseconds */
    smb->shd_fp=smb->sdt_fp=smb->sid_fp=NULL;
    smb->last_error[0]=0;
    sprintf(str,"%s.shd",smb->file);
    if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYNO,U_MODE))==-1) {
        sprintf(smb->last_error,"%d opening %s",errno,str);
        return(2);
    }

    if((smb->shd_fp=fdopen(file,"r+b"))==NULL) {
        sprintf(smb->last_error,"%d fdopening %s (%d)",errno,str,file);
        close(file);
        return(4);
    }

    if(filelength(file)>=sizeof(smbhdr_t)) {
        setvbuf(smb->shd_fp,smb->shd_buf,_IONBF,SHD_BLOCK_LEN);
        if(smb_locksmbhdr(smb)!=0) {
            smb_close(smb);
            /* smb_lockmsghdr set last_error */
            return(-1);
        }
        memset(&hdr,0,sizeof(smbhdr_t));
        if(fread(&hdr,sizeof(smbhdr_t),1,smb->shd_fp)!=1) {
            sprintf(smb->last_error,"reading header");
            smb_close(smb);
            return(-10);
        }
        if(memcmp(hdr.id,SMB_HEADER_ID,LEN_HEADER_ID)) {
            sprintf(smb->last_error,"corrupt SMB header ID: %.*s",LEN_HEADER_ID,hdr.id);
            smb_close(smb);
            return(-2);
        }
        if(hdr.version<0x110) {         /* Compatibility check */
            sprintf(smb->last_error,"insufficient header version: %X",hdr.version);
            smb_close(smb);
            return(-3);
        }
        if(fread(&(smb->status),1,sizeof(smbstatus_t),smb->shd_fp)
            !=sizeof(smbstatus_t)) {
            sprintf(smb->last_error,"failed to read status");
            smb_close(smb);
            return(-4);
        }
        smb_unlocksmbhdr(smb);
        rewind(smb->shd_fp);
    }

    setvbuf(smb->shd_fp,smb->shd_buf,_IOFBF,SHD_BLOCK_LEN);

    sprintf(str,"%s.sdt",smb->file);
    if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYNO,U_MODE))==-1) {
        sprintf(smb->last_error,"%d opening %s",errno,str);
        smb_close(smb);
        return(1);
    }

    if((smb->sdt_fp=fdopen(file,"r+b"))==NULL) {
        sprintf(smb->last_error,"%d fdopening %s (%d)",errno,str,file);
        close(file);
        smb_close(smb);
        return(5);
    }

    setvbuf(smb->sdt_fp,NULL,_IOFBF,2*1024);

    sprintf(str,"%s.sid",smb->file);
    if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYNO,U_MODE))==-1) {
        sprintf(smb->last_error,"%d opening %s",errno,str);
        smb_close(smb);
        return(3);
    }

    if((smb->sid_fp=fdopen(file,"r+b"))==NULL) {
        sprintf(smb->last_error,"%d fdopening %s (%d)",errno,str,file);
        close(file);
        smb_close(smb);
        return(6);
    }

    setvbuf(smb->sid_fp,NULL,_IOFBF,2*1024);

    return(0);
}

/****************************************************************************/
/* Closes the currently open message base                                   */
/****************************************************************************/
void SMBCALL smb_close(smb_t* smb)
{
    if(smb->shd_fp!=NULL) {
        smb_unlocksmbhdr(smb);         /* In case it's been locked */
        fclose(smb->shd_fp);
    }
    if(smb->sid_fp!=NULL)
        fclose(smb->sid_fp);
    if(smb->sdt_fp!=NULL)
        fclose(smb->sdt_fp);
    smb->sid_fp=smb->shd_fp=smb->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 SMBCALL smb_open_da(smb_t* smb)
{
    int     file;
    char    str[128];
    time32_t start = 0;

    sprintf(str,"%s.sda",smb->file);
    while(1) {
        if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYRW,U_MODE))!=-1)
            break;
        if(errno!=EACCES && errno!=EAGAIN) {
            sprintf(smb->last_error,"%d opening %s",errno,str);
            return(-1);
        }
        if(!start)
            start = gtime(NULL);
        else
            if(gtime(NULL)-start >= smb->retry_time) {
                sprintf(smb->last_error,"timeout opening %s (retry_time=%d)"
                    ,str,smb->retry_time);
                return(-2);
            }
        usleep(smb->retry_delay);
    }
    if((smb->sda_fp=fdopen(file,"r+b"))==NULL) {
        sprintf(smb->last_error,"%d fdopening %s (%d)",errno,str,file);
        close(file);
        return(-3);
    }
    setvbuf(smb->sda_fp,NULL,_IOFBF,2*1024);
    return(0);
}

void SMBCALL smb_close_da(smb_t* smb)
{
    if(smb->sda_fp!=NULL)
        fclose(smb->sda_fp);
    smb->sda_fp=NULL;
}

/****************************************************************************/
/* Opens the header block allocation table for message base 'smb.file'      */
/* Retrys for smb.retry_time number of seconds                              */
/* Return 0 on success, non-zero otherwise                                  */
/****************************************************************************/
int SMBCALL smb_open_ha(smb_t* smb)
{
    int     file;
    char    str[128];
    time32_t start = 0;

    sprintf(str,"%s.sha",smb->file);
    while(1) {
        if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYRW,U_MODE))!=-1)
            break;
        if(errno!=EACCES && errno!=EAGAIN) {
            sprintf(smb->last_error,"%d opening %s",errno,str);
            return(-1);
        }
        if(!start)
            start = gtime(NULL);
        else
            if(gtime(NULL)-start >= smb->retry_time) {
                sprintf(smb->last_error,"timeout opening %s (retry_time=%d)"
                    ,str,smb->retry_time);
                return(-2);
            }
        usleep(smb->retry_delay);
    }
    if((smb->sha_fp=fdopen(file,"r+b"))==NULL) {
        sprintf(smb->last_error,"%d fdopening %s (%d)",errno,str,file);
        close(file);
        return(-3);
    }
    setvbuf(smb->sha_fp,NULL,_IOFBF,2*1024);
    return(0);
}

void SMBCALL smb_close_ha(smb_t* smb)
{
    if(smb->sha_fp!=NULL)
        fclose(smb->sha_fp);
    smb->sha_fp=NULL;
}

/****************************************************************************/
/* If the parameter 'push' is non-zero, this function stores the currently  */
/* open message base to the "virtual" smb stack. Up to SMB_STACK_LEN        */
/* message bases may be stored (defined in SMBDEFS.H).                      */
/* The parameter 'op' is the operation to perform on the stack. Either      */
/* SMB_STACK_PUSH, SMB_STACK_POP, or SMB_STACK_XCHNG                        */
/* If the operation is SMB_STACK_POP, this function restores a message base */
/* previously saved with a SMB_STACK_PUSH call to this same function.       */
/* If the operation is SMB_STACK_XCHNG, then the current message base is    */
/* exchanged with the message base on the top of the stack (most recently   */
/* pushed.                                                                  */
/* If the current message base is not open, the SMB_STACK_PUSH and          */
/* SMB_STACK_XCHNG operations do nothing                                    */
/* Returns 0 on success, non-zero if stack full.                            */
/* If operation is SMB_STACK_POP or SMB_STACK_XCHNG, it always returns 0.   */
/****************************************************************************/
int SMBCALL smb_stack(smb_t* smb, int op)
{
    static smb_t stack[SMB_STACK_LEN];
    static int  stack_idx;
    smb_t       tmp_smb;

    if(op==SMB_STACK_PUSH) {
        if(stack_idx>=SMB_STACK_LEN) {
            sprintf(smb->last_error,"SMB stack overflow");
            return(1);
        }
        if(smb->shd_fp==NULL || smb->sdt_fp==NULL || smb->sid_fp==NULL)
            return(0);    /* Msg base not open */
        memcpy(&stack[stack_idx],smb,sizeof(smb_t));
        stack_idx++;
        return(0);
    }
    /* pop or xchng */
    if(!stack_idx)  /* Nothing on the stack, so do nothing */
        return(0);
    if(op==SMB_STACK_XCHNG) {
        if(smb->shd_fp==NULL)
            return(0);
        memcpy(&tmp_smb,smb,sizeof(smb_t));
    }

    stack_idx--;
    memcpy(smb,&stack[stack_idx],sizeof(smb_t));
    if(op==SMB_STACK_XCHNG) {
        memcpy(&stack[stack_idx],&tmp_smb,sizeof(smb_t));
        stack_idx++;
    }
    return(0);
}

/****************************************************************************/
/* Truncates header file                                                    */
/* Retrys for smb.retry_time number of seconds                              */
/* Return 0 on success, non-zero otherwise                                  */
/****************************************************************************/
int SMBCALL smb_trunchdr(smb_t* smb)
{
    time32_t start = 0;

    if(smb->shd_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    rewind(smb->shd_fp);
    while(1) {
        if(!chsize(fileno(smb->shd_fp),0L))
            break;
        if(errno!=EACCES && errno!=EAGAIN) {
            sprintf(smb->last_error,"%d changing header file size",errno);
            return(-1);
        }
        if(!start)
            start = gtime(NULL);
        else
            if(gtime(NULL)-start >= smb->retry_time) {  /* Time-out */
                sprintf(smb->last_error,"timeout changing header file size (retry_time=%d)"
                    ,smb->retry_time);
                return(-2);
            }
        usleep(smb->retry_delay);
    }
    return(0);
}

/*********************************/
/* Message Base Header Functions */
/*********************************/

/****************************************************************************/
/* Attempts for smb.retry_time number of seconds to lock the msg base hdr   */
/****************************************************************************/
int SMBCALL smb_locksmbhdr(smb_t* smb)
{
    time32_t start = 0;

    if(smb->shd_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    while(1) {
        if(lock(fileno(smb->shd_fp),0L,sizeof(smbhdr_t)+sizeof(smbstatus_t))==0) {
            smb->locked=1;  /* TRUE */
            return(0);
        }
        if(!start)
            start = gtime(NULL);
        else
            if(gtime(NULL)-start >= smb->retry_time)
                break;
        /* In case we've already locked it */
        if(unlock(fileno(smb->shd_fp),0L,sizeof(smbhdr_t)+sizeof(smbstatus_t))==0)
            smb->locked=0;  /* FALSE */
        usleep(smb->retry_delay);
    }
    sprintf(smb->last_error,"timeout locking header");
    return(-1);
}

/****************************************************************************/
/* Read the SMB header from the header file and place into smb.status       */
/****************************************************************************/
int SMBCALL smb_getstatus(smb_t* smb)
{
    int     i;

    if(smb->shd_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    setvbuf(smb->shd_fp,smb->shd_buf,_IONBF,SHD_BLOCK_LEN);
    clearerr(smb->shd_fp);
    fseek(smb->shd_fp,sizeof(smbhdr_t),SEEK_SET);
    i=fread(&(smb->status),1,sizeof(smbstatus_t),smb->shd_fp);
    setvbuf(smb->shd_fp,smb->shd_buf,_IOFBF,SHD_BLOCK_LEN);
    if(i==sizeof(smbstatus_t))
        return(0);
    sprintf(smb->last_error,"read %d instead of %d",i,(int)sizeof(smbstatus_t));
    return(1);
}

/****************************************************************************/
/* Writes message base header                                               */
/****************************************************************************/
int SMBCALL smb_putstatus(smb_t* smb)
{
    int i;

    if(smb->shd_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    clearerr(smb->shd_fp);
    fseek(smb->shd_fp,sizeof(smbhdr_t),SEEK_SET);
    i=fwrite(&(smb->status),1,sizeof(smbstatus_t),smb->shd_fp);
    fflush(smb->shd_fp);
    if(i==sizeof(smbstatus_t))
        return(0);
    sprintf(smb->last_error,"wrote %d instead of %d",i,(int)sizeof(smbstatus_t));
    return(1);
}

/****************************************************************************/
/* Unlocks previously locks message base header                             */
/****************************************************************************/
int SMBCALL smb_unlocksmbhdr(smb_t* smb)
{
    int result;

    if(smb->shd_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    result = unlock(fileno(smb->shd_fp),0L,sizeof(smbhdr_t)+sizeof(smbstatus_t));
    if(result==0)
        smb->locked=0;  /* FALSE */
    return(result);
}

/********************************/
/* Individual Message Functions */
/********************************/

/****************************************************************************/
/* Attempts for smb.retry_time number of seconds to lock the hdr for 'msg'  */
/****************************************************************************/
int SMBCALL smb_lockmsghdr(smb_t* smb, smbmsg_t* msg)
{
    time32_t start = 0;

    if(smb->shd_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    while(1) {
        if(!lock(fileno(smb->shd_fp),msg->idx.offset,sizeof(msghdr_t)))
            return(0);
        if(!start)
            start = gtime(NULL);
        else
            if(gtime(NULL)-start >= smb->retry_time)
                break;
        /* In case we've already locked it */
        unlock(fileno(smb->shd_fp),msg->idx.offset,sizeof(msghdr_t));
        usleep(smb->retry_delay);
    }
    sprintf(smb->last_error,"timeout locking header");
    return(-1);
}

/****************************************************************************/
/* 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 SMBCALL smb_getmsgidx(smb_t* smb, smbmsg_t* msg)
{
    idxrec_t idx;
    uint32_t     l,length,total,bot,top;

    if(smb->sid_fp==NULL) {
        sprintf(smb->last_error,"index not open");
        return(SMB_ERR_NOT_OPEN);
    }
    clearerr(smb->sid_fp);
    if(!msg->hdr.number) {
        fseek(smb->sid_fp,msg->offset*sizeof(idxrec_t),SEEK_SET);
        if(!fread(&msg->idx,sizeof(idxrec_t),1,smb->sid_fp)) {
            sprintf(smb->last_error,"reading index");
            return(1);
        }
        return(0);
    }

    length=filelength(fileno(smb->sid_fp));
    if(!length) {
        sprintf(smb->last_error,"invalid index file length: %d",length);
        return(1);
    }
    total=length/sizeof(idxrec_t);
    if(!total) {
        sprintf(smb->last_error,"invalid index file length: %d",length);
        return(1);
    }

    bot=0;
    top=total;
    l=total/2; /* Start at middle index */
    while(1) {
        fseek(smb->sid_fp,l*sizeof(idxrec_t),SEEK_SET);
        if(!fread(&idx,sizeof(idxrec_t),1,smb->sid_fp)) {
            sprintf(smb->last_error,"reading index");
            return(1);
        }
        if(bot==top-1 && idx.number!=msg->hdr.number) {
            sprintf(smb->last_error,"msg %d not found",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 first index record in the open message base                    */
/****************************************************************************/
int SMBCALL smb_getfirstidx(smb_t* smb, idxrec_t *idx)
{
    if(smb->sid_fp==NULL) {
        sprintf(smb->last_error,"index not open");
        return(SMB_ERR_NOT_OPEN);
    }
    clearerr(smb->sid_fp);
    fseek(smb->sid_fp,0,SEEK_SET);
    if(!fread(idx,sizeof(idxrec_t),1,smb->sid_fp)) {
        sprintf(smb->last_error,"reading index");
        return(-2);
    }
    return(0);
}

/****************************************************************************/
/* Reads the last index record in the open message base                     */
/****************************************************************************/
int SMBCALL smb_getlastidx(smb_t* smb, idxrec_t *idx)
{
    int32_t length;

    if(smb->sid_fp==NULL) {
        sprintf(smb->last_error,"index not open");
        return(SMB_ERR_NOT_OPEN);
    }
    clearerr(smb->sid_fp);
    length=filelength(fileno(smb->sid_fp));
    if(length<sizeof(idxrec_t)) {
        sprintf(smb->last_error,"invalid index file length: %d",length);
        return(-1);
    }
    fseek(smb->sid_fp,length-sizeof(idxrec_t),SEEK_SET);
    if(!fread(idx,sizeof(idxrec_t),1,smb->sid_fp)) {
        sprintf(smb->last_error,"reading index");
        return(-2);
    }
    return(0);
}

/****************************************************************************/
/* Figures out the total length of the header record for 'msg'              */
/* Returns length                                                           */
/****************************************************************************/
uint SMBCALL 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                                                           */
/****************************************************************************/
uint32_t SMBCALL smb_getmsgdatlen(smbmsg_t* msg)
{
    int i;
    uint32_t 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 SMBCALL smb_getmsghdr(smb_t* smb, smbmsg_t* msg)
{
    hfield_t  *vp;
    void      **vpp;
    uint16_t  i;
    uint32_t  l, offset;
    idxrec_t  idx;

    if(smb->shd_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    rewind(smb->shd_fp);
    fseek(smb->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(!fread(&msg->hdr,sizeof(msghdr_t),1,smb->shd_fp)) {
        sprintf(smb->last_error,"reading msg header");
        return(-1);
    }
    if(memcmp(msg->hdr.id,SHD_HEADER_ID,LEN_HEADER_ID)) {
        sprintf(smb->last_error,"corrupt message header ID: %.*s",LEN_HEADER_ID,msg->hdr.id);
        return(-2);
    }
    if(msg->hdr.version<0x110) {
        sprintf(smb->last_error,"insufficient header version: %X",msg->hdr.version);
        return(-9);
    }
    l=sizeof(msghdr_t);
    if(msg->hdr.total_dfields && (msg->dfield
        =(dfield_t *)MALLOC(sizeof(dfield_t)*msg->hdr.total_dfields))==NULL) {
        smb_freemsgmem(msg);
        sprintf(smb->last_error,"malloc failure of %d bytes for %d data fields"
            ,(int)sizeof(dfield_t)*msg->hdr.total_dfields, msg->hdr.total_dfields);
        return(-3);
    }
    i=0;
    while(i<msg->hdr.total_dfields && l<msg->hdr.length) {
        if(!fread(&msg->dfield[i],sizeof(dfield_t),1,smb->shd_fp)) {
            smb_freemsgmem(msg);
            sprintf(smb->last_error,"reading data field %d",i);
            return(-4);
        }
        i++;
        l+=sizeof(dfield_t);
    }
    if(i<msg->hdr.total_dfields) {
        smb_freemsgmem(msg);
        sprintf(smb->last_error,"insufficient data fields read (%d instead of %d)"
            ,i,msg->hdr.total_dfields);
        return(-8);
    }
    while(l<msg->hdr.length) {
        i=msg->total_hfields;
        if((vpp=(void* *)REALLOC(msg->hfield_dat,sizeof(void* )*(i+1)))==NULL) {
            smb_freemsgmem(msg);
            sprintf(smb->last_error
                ,"realloc failure of %d bytes for header field data"
                ,(int)sizeof(void*)*(i+1));
            return(-3);
        }
        msg->hfield_dat=vpp;
        if((vp=(hfield_t *)REALLOC(msg->hfield,sizeof(hfield_t)*(i+1)))==NULL) {
            smb_freemsgmem(msg);
            sprintf(smb->last_error
                ,"realloc failure of %d bytes for header fields"
                ,(int)sizeof(hfield_t)*(i+1));
            return(-3);
        }
        msg->hfield=vp;
        msg->total_hfields++;
        if(!fread(&msg->hfield[i],sizeof(hfield_t),1,smb->shd_fp)) {
            smb_freemsgmem(msg);
            sprintf(smb->last_error,"reading header field");
            return(-5);
        }
        l+=sizeof(hfield_t);
        if((msg->hfield_dat[i]=(char*)MALLOC(msg->hfield[i].length+1))
            ==NULL) {           /* Allocate 1 extra for NULL terminator */
            sprintf(smb->last_error
                ,"malloc failure of %d bytes for header field %d"
                ,msg->hfield[i].length+1, i);
            smb_freemsgmem(msg);  /* or 0 length field */
            return(-3);
        }
        memset(msg->hfield_dat[i],0,msg->hfield[i].length+1);  /* init to NULL */
        if(msg->hfield[i].length
            && !fread(msg->hfield_dat[i],msg->hfield[i].length,1,smb->shd_fp)) {
            smb_freemsgmem(msg);
            sprintf(smb->last_error,"reading header field data");
            return(-6);
        }
        switch(msg->hfield[i].type) {   /* convenience variables */
            case SENDER:
                if(!msg->from) {
                    msg->from=(char *)msg->hfield_dat[i];
                    break;
                }
            case FORWARDED:     /* fall through */
                msg->forwarded=1;
                break;
            case SENDERAGENT:
                if(!msg->forwarded)
                    msg->from_agent=*(uint16_t *)msg->hfield_dat[i];
                break;
            case SENDEREXT:
                if(!msg->forwarded)
                    msg->from_ext=(char *)msg->hfield_dat[i];
                break;
            case SENDERNETTYPE:
                if(!msg->forwarded)
                    msg->from_net.type=*(uint16_t *)msg->hfield_dat[i];
                break;
            case SENDERNETADDR:
                if(!msg->forwarded)
                    msg->from_net.addr=(char *)msg->hfield_dat[i];
                break;
            case REPLYTO:
                msg->replyto=(char *)msg->hfield_dat[i];
                break;
            case REPLYTOEXT:
                msg->replyto_ext=(char *)msg->hfield_dat[i];
                break;
            case REPLYTOAGENT:
                msg->replyto_agent=*(uint16_t *)msg->hfield_dat[i];
                break;
            case REPLYTONETTYPE:
                msg->replyto_net.type=*(uint16_t *)msg->hfield_dat[i];
                break;
            case REPLYTONETADDR:
                msg->replyto_net.addr=(char *)msg->hfield_dat[i];
                break;
            case RECIPIENT:
                msg->to=(char *)msg->hfield_dat[i];
                break;
            case RECIPIENTEXT:
                msg->to_ext=(char *)msg->hfield_dat[i];
                break;
            case RECIPIENTAGENT:
                msg->to_agent=*(uint16_t *)msg->hfield_dat[i];
                break;
            case RECIPIENTNETTYPE:
                msg->to_net.type=*(uint16_t *)msg->hfield_dat[i];
                break;
            case RECIPIENTNETADDR:
                msg->to_net.addr=(char *)msg->hfield_dat[i];
                break;
            case SUBJECT:
                msg->subj=(char *)msg->hfield_dat[i];
                break;
            case RFC822MSGID:
                msg->id=(char *)msg->hfield_dat[i];
                break;
            case RFC822REPLYID:
                msg->reply_id=(char *)msg->hfield_dat[i];
                break;
            case USENETPATH:
                msg->path=(char *)msg->hfield_dat[i];
                break;
            case USENETNEWSGROUPS:
                msg->newsgroups=(char *)msg->hfield_dat[i];
                break;
            case FIDOMSGID:
                msg->ftn_msgid=(char *)msg->hfield_dat[i];
                break;
            case FIDOREPLYID:
                msg->ftn_reply=(char *)msg->hfield_dat[i];
                break;
            case FIDOAREA:
                msg->ftn_area=(char *)msg->hfield_dat[i];
                break;
            case FIDOPID:
                msg->ftn_pid=(char *)msg->hfield_dat[i];
                break;
            case FIDOFLAGS:
                msg->ftn_flags=(char *)msg->hfield_dat[i];
                break;

        }
        l+=msg->hfield[i].length;
    }

    if(!msg->from || !msg->to || !msg->subj) {
        sprintf(smb->last_error,"missing required header field (from/to/subj)");
        smb_freemsgmem(msg);
        return(-7);
    }
    return(0);
}

/****************************************************************************/
/* Frees memory allocated for 'msg'                                         */
/****************************************************************************/
void SMBCALL smb_freemsgmem(smbmsg_t* msg)
{
    uint16_t  i;

    if(msg->dfield) {
        FREE(msg->dfield);
        msg->dfield=NULL;
    }
    for(i=0;i<msg->total_hfields;i++)
        if(msg->hfield_dat[i]) {
            FREE(msg->hfield_dat[i]);
            msg->hfield_dat[i]=NULL;
        }
    msg->total_hfields=0;
    if(msg->hfield) {
        FREE(msg->hfield);
        msg->hfield=NULL;
    }
    if(msg->hfield_dat) {
        FREE(msg->hfield_dat);
        msg->hfield_dat=NULL;
    }
}

/****************************************************************************/
/* Copies memory allocated for 'srcmsg' to 'msg'                            */
/****************************************************************************/
int SMBCALL smb_copymsgmem(smbmsg_t* msg, smbmsg_t* srcmsg)
{
    int i;

    memcpy(msg,srcmsg,sizeof(smbmsg_t));

    /* data field types/lengths */
    if((msg->dfield=(dfield_t *)MALLOC(msg->hdr.total_dfields*sizeof(dfield_t)))==NULL)
        return(1);
    memcpy(msg->dfield,srcmsg->dfield,msg->hdr.total_dfields*sizeof(dfield_t));

    /* header field types/lengths */
    if((msg->hfield=(hfield_t *)MALLOC(msg->total_hfields*sizeof(hfield_t)))==NULL)
        return(2);
    memcpy(msg->hfield,srcmsg->hfield,msg->total_hfields*sizeof(hfield_t));

    /* header field data */
    if((msg->hfield_dat=(void* *)MALLOC(msg->total_hfields*sizeof(void*)))==NULL)
        return(3);

    for(i=0;i<msg->total_hfields;i++) {
        if((msg->hfield_dat[i]=(char*)MALLOC(msg->hfield[i].length))==NULL)
            return(4);
        memcpy(msg->hfield_dat[i],srcmsg->hfield_dat[i],msg->hfield[i].length);
    }

    return(0);
}

/****************************************************************************/
/* Unlocks header for 'msg'                                                 */
/****************************************************************************/
int SMBCALL smb_unlockmsghdr(smb_t* smb, smbmsg_t* msg)
{
    if(smb->shd_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    return(unlock(fileno(smb->shd_fp),msg->idx.offset,sizeof(msghdr_t)));
}


/****************************************************************************/
/* Adds a header field to the 'msg' structure (in memory only)              */
/****************************************************************************/
int SMBCALL smb_hfield(smbmsg_t* msg, uint16_t type, size_t length, void* data)
{
    hfield_t* vp;
    void* *vpp;
    int i;

    i=msg->total_hfields;
    if((vp=(hfield_t *)REALLOC(msg->hfield,sizeof(hfield_t)*(i+1)))==NULL)
        return(1);

    msg->hfield=vp;
    if((vpp=(void* *)REALLOC(msg->hfield_dat,sizeof(void* )*(i+1)))==NULL)
        return(2);

    msg->hfield_dat=vpp;
    msg->total_hfields++;
    msg->hfield[i].type=type;
    msg->hfield[i].length=length;
    if(length) {
        if((msg->hfield_dat[i]=(void* )MALLOC(length))==NULL)
            return(4);
        memcpy(msg->hfield_dat[i],data,length);
    }
    else
        msg->hfield_dat[i]=NULL;
    return(0);
}

/****************************************************************************/
/* Searches for a specific header field (by type) and returns it            */
/****************************************************************************/
void* SMBCALL smb_get_hfield(smbmsg_t* msg, uint16_t type, hfield_t* hfield)
{
    int i;

    for(i=0;i<msg->total_hfields;i++)
        if(msg->hfield[i].type == type) {
            if(hfield != NULL)
                hfield = &msg->hfield[i];
            return(msg->hfield_dat[i]);
        }

    return(NULL);
}

/****************************************************************************/
/* 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 SMBCALL smb_dfield(smbmsg_t* msg, uint16_t type, uint32_t length)
{
    dfield_t* vp;
    int i,j;

    i=msg->hdr.total_dfields;
    if((vp=(dfield_t *)REALLOC(msg->dfield,sizeof(dfield_t)*(i+1)))==NULL)
        return(1);

    msg->dfield=vp;
    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 SMBCALL smb_addcrc(smb_t* smb, uint32_t crc)
{
    char     str[128];
    int      file;
    int32_t  length;
    uint32_t l, *buf;
    time32_t start = 0;

    if(!smb->status.max_crcs)
        return(0);

    sprintf(str,"%s.sch",smb->file);
    while(1) {
        if((file=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYRW,U_MODE))!=-1)
            break;
        if(errno!=EACCES && errno!=EAGAIN) {
            sprintf(smb->last_error,"%d opening %s", errno, str);
            return(-1);
        }
        if(!start)
            start = gtime(NULL);
        else
            if(gtime(NULL)-start >= smb->retry_time) {
                sprintf(smb->last_error,"timeout opening %s (retry_time=%d)"
                    ,str,smb->retry_time);
                return(-2);
            }
        usleep(smb->retry_delay);
    }

    length=filelength(file);
    if(length<0L) {
        close(file);
        sprintf(smb->last_error,"invalid file length: %d", length);
        return(-4);
    }
    if((buf=(uint32_t*)MALLOC(smb->status.max_crcs*4))==NULL) {
        close(file);
        sprintf(smb->last_error
            ,"malloc failure of %d bytes"
            ,smb->status.max_crcs*4);
        return(-3);
    }
    if((uint32_t)length>=smb->status.max_crcs*4L) { /* Reached or exceeds max crcs */
        read(file,buf,smb->status.max_crcs*4);
        for(l=0;l<smb->status.max_crcs;l++)
            if(crc==buf[l])
                break;
        if(l<smb->status.max_crcs) {                /* Dupe CRC found */
            close(file);
            FREE(buf);
            sprintf(smb->last_error
                ,"duplicate message detected");
            return(1);
        }
        chsize(file,0L);                /* truncate it */
        lseek(file,0L,SEEK_SET);
        write(file,buf+4,(smb->status.max_crcs-1)*4);
    }

    else if(length/4) {                     /* Less than max crcs */
        read(file,buf,length);
        for(l=0;l<(uint32_t)(length/4);l++)
            if(crc==buf[l])
                break;
        if(l<(uint32_t)(length/4L)) {                   /* Dupe CRC found */
            close(file);
            FREE(buf);
            sprintf(smb->last_error
                ,"duplicate message detected");
            return(1);
        }
    }

    lseek(file,0L,SEEK_END);
    write(file,&crc,sizeof(crc));              /* Write to the end */
    FREE(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)    */
/* This function will UN-lock the SMB header                                */
/****************************************************************************/
int SMBCALL smb_addmsghdr(smb_t* smb, smbmsg_t* msg, int storage)
{
    int i;
    int32_t l;

    if(!smb->locked && smb_locksmbhdr(smb))
        return(1);
    if(smb_getstatus(smb)) {
        smb_unlocksmbhdr(smb);
        return(2);
    }

    if(storage!=SMB_HYPERALLOC && (i=smb_open_ha(smb))!=0) {
        smb_unlocksmbhdr(smb);
        return(i);
    }

    msg->hdr.length=smb_getmsghdrlen(msg);
    if(storage==SMB_HYPERALLOC)
        l=smb_hallochdr(smb);
    else if(storage==SMB_FASTALLOC)
        l=smb_fallochdr(smb,msg->hdr.length);
    else
        l=smb_allochdr(smb,msg->hdr.length);
    if(storage!=SMB_HYPERALLOC)
        smb_close_ha(smb);
    if(l==-1L) {
        smb_unlocksmbhdr(smb);
        return(-1);
    }

    msg->idx.number=msg->hdr.number=smb->status.last_msg+1;
    msg->idx.offset=smb->status.header_offset+l;
    msg->idx.time=msg->hdr.when_imported.time;
    msg->idx.attr=msg->hdr.attr;
    msg->offset=smb->status.total_msgs;
    i=smb_putmsg(smb,msg);
    if(i==0) {  /* success */
        smb->status.last_msg++;
        smb->status.total_msgs++;
        smb_putstatus(smb);
    }
    smb_unlocksmbhdr(smb);
    return(i);
}

/****************************************************************************/
/* Writes both header and index information for msg 'msg'                   */
/****************************************************************************/
int SMBCALL smb_putmsg(smb_t* smb, smbmsg_t* msg)
{
    int i;

    i=smb_putmsghdr(smb,msg);
    if(i)
        return(i);
    return(smb_putmsgidx(smb,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 SMBCALL smb_putmsgidx(smb_t* smb, smbmsg_t* msg)
{
    if(smb->sid_fp==NULL) {
        sprintf(smb->last_error,"index not open");
        return(SMB_ERR_NOT_OPEN);
    }
    clearerr(smb->sid_fp);
    fseek(smb->sid_fp,msg->offset*sizeof(idxrec_t),SEEK_SET);
    if(!fwrite(&msg->idx,sizeof(idxrec_t),1,smb->sid_fp)) {
        sprintf(smb->last_error,"writing index");
        return(1);
    }
    fflush(smb->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 SMBCALL smb_putmsghdr(smb_t* smb, smbmsg_t* msg)
{
    uint16_t  i;
    uint32_t  l;

    if(smb->shd_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    if(msg->idx.offset<sizeof(smbhdr_t)+sizeof(smbstatus_t)
        || msg->idx.offset<smb->status.header_offset) {
        sprintf(smb->last_error,"invalid header offset: %d",msg->idx.offset);
        return(-7);
    }
    clearerr(smb->shd_fp);
    if(fseek(smb->shd_fp,msg->idx.offset,SEEK_SET)) {
        sprintf(smb->last_error,"seeking to %d in index",msg->idx.offset);
        return(-1);
    }

    /**********************************/
    /* Set the message header ID here */
    /**********************************/
    memcpy(&msg->hdr.id,SHD_HEADER_ID,LEN_HEADER_ID);

    /************************************************/
    /* Write the fixed portion of the header record */
    /************************************************/
    if(!fwrite(&msg->hdr,sizeof(msghdr_t),1,smb->shd_fp)) {
        sprintf(smb->last_error,"writing fixed portion of header record");
        return(-2);
    }

    /************************************************/
    /* Write the data fields (each is fixed length) */
    /************************************************/
    for(i=0;i<msg->hdr.total_dfields;i++)
        if(!fwrite(&msg->dfield[i],sizeof(dfield_t),1,smb->shd_fp)) {
            sprintf(smb->last_error,"writing data field");
            return(-3);
        }

    /*******************************************/
    /* Write the variable length header fields */
    /*******************************************/
    for(i=0;i<msg->total_hfields;i++) {
        if(!fwrite(&msg->hfield[i],sizeof(hfield_t),1,smb->shd_fp)) {
            sprintf(smb->last_error,"writing header field");
            return(-4);
        }
        if(msg->hfield[i].length                     /* more then 0 bytes int32_t */
            && !fwrite(msg->hfield_dat[i],msg->hfield[i].length,1,smb->shd_fp)) {
            sprintf(smb->last_error,"writing header field data");
            return(-5);
        }
    }

    l=smb_getmsghdrlen(msg);
    while(l%SHD_BLOCK_LEN) {
        if(fputc(0,smb->shd_fp)==EOF) {
            sprintf(smb->last_error,"padding header block");
            return(-6);                /* pad block with NULL */
        }
        l++;
    }
    fflush(smb->shd_fp);
    return(0);
}

/****************************************************************************/
/* Creates a sub-board's initial header file                                */
/* Truncates and deletes other associated SMB files                         */
/****************************************************************************/
int SMBCALL smb_create(smb_t* smb)
{
    char        str[128];
    smbhdr_t    hdr;

    if(smb->shd_fp==NULL || smb->sdt_fp==NULL || smb->sid_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    if(filelength(fileno(smb->shd_fp))>=sizeof(smbhdr_t)+sizeof(smbstatus_t)
        && smb_locksmbhdr(smb))  /* header exists, so lock it */
        return(1);
    memset(&hdr,0,sizeof(smbhdr_t));
    memcpy(hdr.id,SMB_HEADER_ID,LEN_HEADER_ID);
    hdr.version=SMB_VERSION;
    hdr.length=sizeof(smbhdr_t)+sizeof(smbstatus_t);
    smb->status.last_msg=smb->status.total_msgs=0;
    smb->status.header_offset=sizeof(smbhdr_t)+sizeof(smbstatus_t);
    rewind(smb->shd_fp);
    fwrite(&hdr,1,sizeof(smbhdr_t),smb->shd_fp);
    fwrite(&(smb->status),1,sizeof(smbstatus_t),smb->shd_fp);
    rewind(smb->shd_fp);
    chsize(fileno(smb->shd_fp),sizeof(smbhdr_t)+sizeof(smbstatus_t));
    fflush(smb->shd_fp);

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

    sprintf(str,"%s.sda",smb->file);
    remove(str);                        /* if it exists, delete it */
    sprintf(str,"%s.sha",smb->file);
    remove(str);                        /* if it exists, delete it */
    sprintf(str,"%s.sch",smb->file);
    remove(str);
    smb_unlocksmbhdr(smb);
    return(0);
}

/****************************************************************************/
/* Returns number of data blocks required to store "length" amount of data  */
/****************************************************************************/
uint32_t SMBCALL smb_datblocks(uint32_t length)
{
    uint32_t 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   */
/****************************************************************************/
uint32_t SMBCALL smb_hdrblocks(uint32_t length)
{
    uint32_t 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                                    */
/* smb_close_da() should be called after                                    */
/* Returns negative on error                                                */
/****************************************************************************/
int32_t SMBCALL smb_allocdat(smb_t* smb, uint32_t length, uint16_t headers)
{
    uint16_t  i, j;
    uint32_t  l, blocks, offset = 0;

    if(smb->sda_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    blocks=smb_datblocks(length);
    j=0;    /* j is consecutive unused block counter */
    fflush(smb->sda_fp);
    rewind(smb->sda_fp);
    while(!feof(smb->sda_fp)) {
        if(!fread(&i,2,1,smb->sda_fp))
            break;
        offset+=SDT_BLOCK_LEN;
        if(!i) j++;
        else   j=0;
        if(j==blocks) {
            offset-=(blocks*SDT_BLOCK_LEN);
            break;
        }
    }
    clearerr(smb->sda_fp);
    fseek(smb->sda_fp,(offset/SDT_BLOCK_LEN)*2L,SEEK_SET);
    for(l=0;l<blocks;l++)
        if(!fwrite(&headers,2,1,smb->sda_fp)) {
            sprintf(smb->last_error,"writing allocation bytes");
            return(-1);
        }
    fflush(smb->sda_fp);
    return(offset);
}

/****************************************************************************/
/* Allocates space for data, but doesn't search for unused blocks           */
/* Returns negative on error                                                */
/****************************************************************************/
int32_t SMBCALL smb_fallocdat(smb_t* smb, uint32_t length, uint16_t headers)
{
    uint32_t    l,blocks,offset;

    if(smb->sda_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    fflush(smb->sda_fp);
    clearerr(smb->sda_fp);
    blocks=smb_datblocks(length);
    fseek(smb->sda_fp,0L,SEEK_END);
    offset=(ftell(smb->sda_fp)/2L)*SDT_BLOCK_LEN;
    for(l=0;l<blocks;l++)
        if(!fwrite(&headers,2,1,smb->sda_fp))
            break;
    fflush(smb->sda_fp);
    if(l<blocks) {
        sprintf(smb->last_error,"writing allocation bytes");
        return(-1L);
    }
    return(offset);
}

/****************************************************************************/
/* De-allocates space for data                                              */
/* Returns non-zero on error                                                */
/****************************************************************************/
int SMBCALL smb_freemsgdat(smb_t* smb, uint32_t offset, uint32_t length, uint16_t headers)
{
    int       da_opened = 0;
    int       retval = 0;
    uint16_t  i;
    uint32_t  l, blocks;

    if(smb->status.attr&SMB_HYPERALLOC) /* do nothing */
        return(0);

    blocks=smb_datblocks(length);

    if(smb->sda_fp==NULL) {
        if((i=smb_open_da(smb))!=0)
            return(i);
        da_opened=1;
    }

    clearerr(smb->sda_fp);
    for(l=0;l<blocks;l++) {
        if(fseek(smb->sda_fp,((offset/SDT_BLOCK_LEN)+l)*2L,SEEK_SET)) {
            sprintf(smb->last_error
                ,"seeking to %ld of allocation file"
                ,((offset/SDT_BLOCK_LEN)+l)*2L);
            retval=1;
            break;
        }
        if(!fread(&i,2,1,smb->sda_fp)) {
            sprintf(smb->last_error,"reading allocation bytes");
            retval=2;
            break;
        }
        if(!headers || headers>i)
            i=0;            /* don't want to go negative */
        else
            i-=headers;
        if(fseek(smb->sda_fp,-2L,SEEK_CUR)) {
            sprintf(smb->last_error,"seeking backwards 2 bytes in allocation file");
            retval=3;
            break;
        }
        if(!fwrite(&i,2,1,smb->sda_fp)) {
            sprintf(smb->last_error,"writing allocation bytes");
            retval=4;
            break;
        }
    }
    fflush(smb->sda_fp);
    if(da_opened)
        smb_close_da(smb);
    return(retval);
}

/****************************************************************************/
/* Adds to data allocation records for blocks starting at 'offset'          */
/* Returns non-zero on error                                                */
/****************************************************************************/
int SMBCALL smb_incdat(smb_t* smb, uint32_t offset, uint32_t length, uint16_t headers)
{
    uint16_t  i;
    uint32_t  l, blocks;

    if(smb->sda_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    clearerr(smb->sda_fp);
    blocks=smb_datblocks(length);
    for(l=0;l<blocks;l++) {
        fseek(smb->sda_fp,((offset/SDT_BLOCK_LEN)+l)*2L,SEEK_SET);
        if(!fread(&i,2,1,smb->sda_fp)) {
            sprintf(smb->last_error,"reading allocation record");
            return(1);
        }
        i+=headers;
        fseek(smb->sda_fp,-2L,SEEK_CUR);
        if(!fwrite(&i,2,1,smb->sda_fp)) {
            sprintf(smb->last_error,"writing allocation record");
            return(2);
        }
    }
    fflush(smb->sda_fp);
    return(0);
}

/****************************************************************************/
/* De-allocates blocks for header record                                    */
/* Returns non-zero on error                                                */
/****************************************************************************/
int SMBCALL smb_freemsghdr(smb_t* smb, uint32_t offset, uint32_t length)
{
    uint8_t c = 0;
    uint32_t    l,blocks;

    if(smb->sha_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    clearerr(smb->sha_fp);
    blocks=smb_hdrblocks(length);
    fseek(smb->sha_fp,offset/SHD_BLOCK_LEN,SEEK_SET);
    for(l=0;l<blocks;l++)
        if(!fwrite(&c,1,1,smb->sha_fp)) {
            sprintf(smb->last_error,"writing allocation record");
            return(1);
        }
    fflush(smb->sha_fp);
    return(0);
}

/****************************************************************************/
/* Frees all allocated header and data blocks for 'msg'                     */
/****************************************************************************/
int SMBCALL smb_freemsg(smb_t* smb, smbmsg_t* msg)
{
    int       i;
    uint16_t  x;

    if(smb->status.attr&SMB_HYPERALLOC)  /* Nothing to do */
        return(0);

    for(x=0;x<msg->hdr.total_dfields;x++) {
        if((i=smb_freemsgdat(smb,msg->hdr.offset+msg->dfield[x].offset
            ,msg->dfield[x].length,1))!=0)
            return(i);
    }
    return(smb_freemsghdr(smb,msg->idx.offset-smb->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                                    */
/* smb_close_ha() should be called after                                    */
/* Returns -1L on error                                                     */
/****************************************************************************/
int32_t SMBCALL smb_allochdr(smb_t* smb, uint32_t length)
{
    uint8_t   c;
    uint16_t  i;
    uint32_t  l, blocks, offset = 0;

    if(smb->sha_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    blocks=smb_hdrblocks(length);
    i=0;    /* i is consecutive unused block counter */
    fflush(smb->sha_fp);
    rewind(smb->sha_fp);
    while(!feof(smb->sha_fp)) {
        if(!fread(&c,1,1,smb->sha_fp))
            break;
        offset+=SHD_BLOCK_LEN;
        if(!c) i++;
        else   i=0;
        if(i==blocks) {
            offset-=(blocks*SHD_BLOCK_LEN);
            break;
        }
    }
    clearerr(smb->sha_fp);
    fseek(smb->sha_fp,offset/SHD_BLOCK_LEN,SEEK_SET);
    c=1;
    for(l=0;l<blocks;l++)
        if(!fwrite(&c,1,1,smb->sha_fp)) {
            sprintf(smb->last_error,"writing allocation record");
            return(-1L);
        }
    fflush(smb->sha_fp);
    return(offset);
}

/****************************************************************************/
/* Allocates space for index, but doesn't search for unused blocks          */
/* Returns -1L on error                                                     */
/****************************************************************************/
int32_t SMBCALL smb_fallochdr(smb_t* smb, uint32_t length)
{
    uint8_t c = 1;
    uint32_t    l,blocks,offset;

    if(smb->sha_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    blocks=smb_hdrblocks(length);
    fflush(smb->sha_fp);
    clearerr(smb->sha_fp);
    fseek(smb->sha_fp,0L,SEEK_END);
    offset=ftell(smb->sha_fp)*SHD_BLOCK_LEN;
    for(l=0;l<blocks;l++)
        if(!fwrite(&c,1,1,smb->sha_fp)) {
            sprintf(smb->last_error,"writing allocation record");
            return(-1L);
        }
    fflush(smb->sha_fp);
    return(offset);
}

/************************************************************************/
/* Allocate header blocks using Hyper Allocation                        */
/* this function should be most likely not be called from anywhere but  */
/* smb_addmsghdr()                                                      */
/************************************************************************/
int32_t SMBCALL smb_hallochdr(smb_t* smb)
{
    uint32_t l;

    if(smb->shd_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    fflush(smb->shd_fp);
    fseek(smb->shd_fp,0L,SEEK_END);
    l=ftell(smb->shd_fp);
    if(l<smb->status.header_offset)              /* Header file truncated?!? */
        return(smb->status.header_offset);
    while((l-smb->status.header_offset)%SHD_BLOCK_LEN)  /* Even block boundry */
        l++;
    return(l-smb->status.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                                                      */
/************************************************************************/
int32_t SMBCALL smb_hallocdat(smb_t* smb)
{
    int32_t l;

    if(smb->sdt_fp==NULL) {
        sprintf(smb->last_error,"msgbase not open");
        return(SMB_ERR_NOT_OPEN);
    }
    fflush(smb->sdt_fp);
    fseek(smb->sdt_fp,0L,SEEK_END);
    l=ftell(smb->sdt_fp);
    if(l<=0)
        return(l);
    while(l%SDT_BLOCK_LEN)                  /* Make sure even block boundry */
        l++;
    return(l);
}


int SMBCALL smb_feof(FILE* fp)
{
    return(feof(fp));
}

int SMBCALL smb_ferror(FILE* fp)
{
    return(ferror(fp));
}

int SMBCALL smb_fflush(FILE* fp)
{
    return(fflush(fp));
}

int SMBCALL smb_fgetc(FILE* fp)
{
    return(fgetc(fp));
}

int SMBCALL smb_fputc(int ch, FILE* fp)
{
    return(fputc(ch,fp));
}

int SMBCALL smb_fseek(FILE* fp, int32_t offset, int whence)
{
    return(fseek(fp,offset,whence));
}

int32_t SMBCALL smb_ftell(FILE* fp)
{
    return(ftell(fp));
}

int32_t SMBCALL smb_fgetlength(FILE* fp)
{
    return(filelength(fileno(fp)));
}

int SMBCALL smb_fsetlength(FILE* fp, int32_t length)
{
    return(chsize(fileno(fp),length));
}

void SMBCALL smb_rewind(FILE* fp)
{
    rewind(fp);
}

void SMBCALL smb_clearerr(FILE* fp)
{
    clearerr(fp);
}

int32_t SMBCALL smb_fread(void HUGE16* buf, int32_t bytes, FILE* fp)
{
#ifdef __FLAT__
    return(fread(buf,1,bytes,fp));
#else
    int32_t count;

    for(count=bytes;count>0x7fff;count-=0x7fff,(char*)buf+=0x7fff)
        if(fread((char*)buf,1,0x7fff,fp)!=0x7fff)
            return(bytes-count);
    if(fread((char*)buf,1,(size_t)count,fp)!=(size_t)count)
        return(bytes-count);
    return(bytes);
#endif
}

int32_t SMBCALL smb_fwrite(void HUGE16* buf, int32_t bytes, FILE* fp)
{
#ifdef __FLAT__
    return(fwrite(buf,1,bytes,fp));
#else
    int32_t count;

    for(count=bytes;count>0x7fff;count-=0x7fff,(char*)buf+=0x7fff)
        if(fwrite((char*)buf,1,0x7fff,fp)!=0x7fff)
            return(bytes-count);
    if(fwrite((char*)buf,1,(size_t)count,fp)!=(size_t)count)
        return(bytes-count);
    return(bytes);
#endif
}

/* End of SMBLIB.C */
