/****************************************************************************
*
*                            Open Watcom Project
*
*    Portions Copyright (c) 1983-2002 Sybase, Inc. All Rights Reserved.
*
*  ========================================================================
*
*    This file contains Original Code and/or Modifications of Original
*    Code as defined in and that are subject to the Sybase Open Watcom
*    Public License version 1.0 (the 'License'). You may not use this file
*    except in compliance with the License. BY USING THIS FILE YOU AGREE TO
*    ALL TERMS AND CONDITIONS OF THE LICENSE. A copy of the License is
*    provided with the Original Code and Modifications, and is also
*    available at www.sybase.com/developer/opensource.
*
*    The Original Code and all software distributed under the License are
*    distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
*    EXPRESS OR IMPLIED, AND SYBASE AND ALL CONTRIBUTORS HEREBY DISCLAIM
*    ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF
*    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR
*    NON-INFRINGEMENT. Please see the License for the specific language
*    governing rights and limitations under the License.
*
*  ========================================================================
*
* Description:  handle OMF output format.
*
****************************************************************************/

#include <limits.h>
#include <ctype.h>
#include <time.h>
#include <sys/stat.h>

#include "globals.h"
#include "memalloc.h"
#include "parser.h"
#include "segment.h"
#include "mangle.h"
#include "extern.h"
#include "fixup.h"
#include "omf.h"
#include "omfint.h"
#include "omfspec.h"
#include "fastpass.h"
#include "myassert.h"
#include "tokenize.h" /* needed because of StringBufferEnd usage */
#include "input.h"
#include "linnum.h"

#define TRUNCATE 1
#define MULTIHDR 1     /* write muliple THEADR records (Masm compatible) */
#define WRITEIMPDEF 0  /* write IMPDEF coment records for OPTION DLLIMPORT */

#define MANGLE_BYTES 8 /* extra size required for name decoration */
#define MAX_ID_LEN_OMF 247
#define MAX_LNAME_SIZE 1024
#define MAX_EXT_LENGTH 1020 /* max length ( in chars ) of EXTDEF */
#define MAX_PUB_LENGTH 1024 /* max length of PUBDEF record */


#if TRUNCATE
#if defined(__UNIX__) || defined(__CYGWIN__) || defined(__DJGPP__)
#include <unistd.h>
#else
#include <io.h>
#endif
#endif

#if defined(__UNIX__) || defined(__CYGWIN__)
#define _stat stat
#endif

#define TruncRec(objr)       (void)( (objr)->length = (objr)->curoff )

enum {
    TIME_SEC_B  = 0,
    TIME_SEC_F  = 0x001f,
    TIME_MIN_B  = 5,
    TIME_MIN_F  = 0x07e0,
    TIME_HOUR_B = 11,
    TIME_HOUR_F = 0xf800
};

enum {
    DATE_DAY_B  = 0,
    DATE_DAY_F  = 0x001f,
    DATE_MON_B  = 5,
    DATE_MON_F  = 0x01e0,
    DATE_YEAR_B = 9,
    DATE_YEAR_F = 0xfe00
};

union DOS_DATETIME {
    struct {
        unsigned short time;
        unsigned short date;
    } dos;
    time_t timet;
};

extern void cv_write_debug_tables( struct dsym *, struct dsym *, void * );
extern void SortSegments( int );

extern struct qdesc LinnumQueue;    /* queue of line_num_info items */
extern const char szNull[];

/* LastCodeBufSize stores the size of the code buffer AFTER it has been written in omf_write_ledata().
 * This allows to get the content bytes for the listing.
 * This is a rather hackish "design" and is to be improved.
 */
int_32       LastCodeBufSize;

static uint_32    seg_pos;        /* file pos of SEGDEF record(s) */
static uint_32    public_pos;     /* file pos of PUBDEF record(s) */
static uint_32    end_of_header;  /* file pos of "end of header"  */

/* v2.12: moved from inside omf_write_lnames() */
static int startitem;
static int startext;

#if MULTIHDR
static unsigned ln_srcfile;      /* last file for which line numbers have been written */
#endif
static uint_8 ln_is32;           /* last mode for which line numbers have been written */
static uint_16 ln_size;          /* size of line number info */

/* CodeView symbolic debug info */
enum dbgseg_index {
    DBGS_SYMBOLS,
    DBGS_TYPES,
    DBGS_MAX
};

struct dbg_section {
    const char *name;
    const char *cname;
};

static const struct dbg_section SymDebParm[DBGS_MAX] = {
    { "$$SYMBOLS", "DEBSYM" },
    { "$$TYPES",   "DEBTYP" },
};

static struct dsym *SymDebSeg[DBGS_MAX];

static void omf_InitRec( struct omf_rec *obj, uint_8 command )
/************************************************************/
{
    obj->length = 0;
    obj->curoff = 0;
    obj->data = NULL;
    obj->command = command;
    obj->is_32 = 0;
    DebugMsg1(("omf_InitRec(%X)\n", command ));
    return;
}

static time_t timet2dostime( time_t x )
/*************************************/
{
    struct tm *    ltime;
    union DOS_DATETIME dt;

    ltime = localtime( &x );
    dt.dos.date = (( ltime->tm_year - 80 ) << DATE_YEAR_B )
             | (( ltime->tm_mon + 1 ) << DATE_MON_B )
             | (( ltime->tm_mday ) << DATE_DAY_B );
    dt.dos.time = (( ltime->tm_hour ) << TIME_HOUR_B )
             | (( ltime->tm_min ) << TIME_MIN_B )
             | (( ltime->tm_sec / 2 ) << TIME_SEC_B );
    return( dt.timet );
}

static void Put8( struct omf_rec *objr, uint_8 value )
/****************************************************/
{
/**/myassert( objr != NULL && objr->data != NULL );
    objr->data[ objr->curoff++ ] = value;
}

static void Put16( struct omf_rec *objr, uint_16 value )
/******************************************************/
{
/**/myassert( objr != NULL && objr->data != NULL );

    WriteU16( objr->data + objr->curoff, value );
    objr->curoff += sizeof( uint_16 );
}

static void Put32( struct omf_rec *objr, uint_32 value )
/******************************************************/
{
/**/myassert( objr != NULL && objr->data != NULL );

    WriteU32( objr->data + objr->curoff, value );
    objr->curoff += sizeof( uint_32 );
}

#if 0
static void PutEither( struct omf_rec *objr, uint_32 data )
/*********************************************************/
{
/**/myassert( objr != NULL && objr->data != NULL );
    if( objr->is_32 ) {
        WriteU32( objr->data + objr->curoff, data );
        objr->curoff += sizeof( uint_32 );
    } else {
        WriteU16( objr->data + objr->curoff, data );
        objr->curoff += sizeof( uint_16 );
    }
}
#endif

/* index in OMF is limited to 0-7FFFh
 * index values 0-7Fh are stored in 1 byte.
 * index values 80h-7FFFh are stored in 2 bytes, the high
 * order byte first ( with bit 7=1 ).
 */

static void PutIndex( struct omf_rec *objr, unsigned idx )
/********************************************************/
{
/**/myassert( objr != NULL && objr->data != NULL && idx <= 0x7FFF );
    if( idx > 0x7f ) {
        objr->data[objr->curoff++] = ( idx >> 8 ) | 0x80;
    }
    objr->data[objr->curoff++] = idx & 0xff;
}

static void PutData( struct omf_rec *objr, const uint_8 *data, size_t len )
/*************************************************************************/
{
    /**/myassert( objr != NULL && objr->data != NULL );
    memcpy( objr->data + objr->curoff, data, len );
    objr->curoff += len;
}

static void PutName( struct omf_rec *objr, const char *name, size_t len )
/***********************************************************************/
{
    /**/myassert( objr != NULL && objr->data != NULL );
#if MAX_ID_LEN > MAX_ID_LEN_OMF
    if ( len > MAX_ID_LEN_OMF ) {
        EmitWarn( 1, IDENTIFIER_TOO_LONG );
        len = MAX_ID_LEN_OMF;
    }
#endif
    objr->data[objr->curoff++] = len;
    PutData( objr, (uint_8 *)name, len );
}

static void AttachData( struct omf_rec *objr, uint_8 *data, size_t len )
/**********************************************************************/
{
/**/myassert( objr->data == NULL );
    objr->data = data;
    objr->length = len;
}

#if 0
static void AllocData( struct omf_rec *objr, size_t len )
/*******************************************************/
{
/**/myassert( objr->data == NULL );
    objr->data = LclAlloc( len );
    objr->length = len;
}
#endif

/* return a group's index */

unsigned omf_GetGrpIdx( struct asym *sym )
/****************************************/
{
    return( sym ? ((struct dsym *)sym)->e.grpinfo->grp_idx : 0 );
}

/*
 * write OMF comment records about data in code.
 */

void omf_OutSelect( bool is_data )
/********************************/
{
    struct omf_rec      obj;
    uint_32             currofs;
    int                 sel_idx;
    static uint_32      sel_start;  /* start offset of data items */
    unsigned char       buffer[12]; /* max is 11 ( see below ) */

    if( is_data ) {
        /* do nothing if it isn't the first data item or
         * if current segment isn't code
         */
        if( CurrSeg->e.seginfo->data_in_code ||
           ( CurrSeg->e.seginfo->segtype != SEGTYPE_CODE ) )
            return;
        sel_start = GetCurrOffset();
        CurrSeg->e.seginfo->data_in_code = TRUE;
        DebugMsg(("omf_OutSelect: data in code segment (%s), starting at %" I32_SPEC "X\n", CurrSeg->sym.name, sel_start ));
    } else if ( CurrSeg->e.seginfo->data_in_code ) { /* data items written? */
        
        CurrSeg->e.seginfo->data_in_code = FALSE;

        if( write_to_file == TRUE ) {
            omf_InitRec( &obj, CMD_COMENT );
            obj.d.coment.attr = CMT_TNP;
            obj.d.coment.cmt_class = CMT_DISASM_DIRECTIVE;

            sel_idx = GetSegIdx( &CurrSeg->sym );

            //AllocData( objr, 11 );  /* 11 = 1 + 2 + 4 + 4 */
            AttachData( &obj, buffer, 11 );  /* 11 = 1 + 2 + 4 + 4 */
            currofs = GetCurrOffset();
            DebugMsg(("omf_OutSelect: writing coment record about data in code: start=%" I32_SPEC "X curofs=%" I32_SPEC "X\n", sel_start, currofs ));
            if( ( sel_start > 0xffffUL ) || ( currofs > 0xffffUL ) ) {
                Put8( &obj, DDIR_SCAN_TABLE_32 );
                PutIndex( &obj, sel_idx );
                Put32( &obj, sel_start );
                Put32( &obj, currofs );
            } else {
                Put8( &obj, DDIR_SCAN_TABLE );
                PutIndex( &obj, sel_idx );
                Put16( &obj, sel_start );
                Put16( &obj, currofs );
            }
            TruncRec( &obj );
            omf_write_record( &obj );
        }
    }
}

/* write line number debug info.
 * since v2.11, it's ensured that the size of this info will be < 1024.
 * note the item structure:
 *   uint_16 line_number;
 *   union {
 *       uint_16 ofs16;
 *       uint_32 ofs32;
 *   };
 * v2.11: create 16- or 32-bit data variant here!
 */

static void omf_write_linnum( uint_8 is32 )
/*****************************************/
{
    unsigned             ofssize = ( is32 ? sizeof( uint_32) : sizeof( uint_16 ) );
    unsigned             size;
    uint_8               *data;
    struct line_num_info *node;
    struct line_num_info *next;
    struct omf_rec       obj;

    for( node = LinnumQueue.head, data = (uint_8 *)StringBufferEnd; node; node = next ) {
        next = node->next;
        *(uint_16 *)data = node->number;
        data += sizeof( uint_16 );
        *(uint_32 *)data = node->offset;
        data += ofssize;
        LclFree( node );
    }
    LinnumQueue.head = NULL;

    size = (char *)data - StringBufferEnd;
    if( size ) {
        omf_InitRec( &obj, CMD_LINNUM );
        obj.is_32 = is32;
        AttachData( &obj, (uint_8 *)StringBufferEnd, size );
        obj.d.linnum.base.grp_idx = omf_GetGrpIdx( GetGroup( &CurrSeg->sym ) ); /* fixme ? */
        obj.d.linnum.base.seg_idx = CurrSeg->e.seginfo->seg_idx;
        obj.d.linnum.base.frame = 0; /* field not used here */
        omf_write_record( &obj );
    }
    return;
}

static void omf_write_fixupp( struct dsym *seg, char is32 )
/*********************************************************/
{
    uint_8 *data;
    unsigned size;
    struct fixup *fix;
    enum fixgen_types type = ( is32 ? FIX_GEN_MS386 : FIX_GEN_INTEL );
    struct omf_rec  obj;

    fix = seg->e.seginfo->FixupList.head;
    while ( fix ) {
        for( data = (uint_8 *)StringBufferEnd, size = 0; fix; fix = fix->nextrlc ) {
            switch( fix->type ) {
            case FIX_RELOFF32:
            case FIX_OFF32:
            case FIX_PTR32:
                if ( !is32 ) continue;
                break;
            default:
                if ( is32 ) continue;
                break;
            }
            if ( size > 1020 - FIX_GEN_MAX )
                break;
            data += OmfFixGenFix( fix, seg->e.seginfo->start_loc, data, type );
            size = (char *)data - StringBufferEnd;
#ifdef DEBUG_OUT
            if (!size)
                DebugMsg1(( "omf_write_fixupp: size=0, no fixup to write\n" ));
#endif
        }
        if ( size ) {
            omf_InitRec( &obj, CMD_FIXUPP );
            obj.is_32 = is32;
            AttachData( &obj, (uint_8 *)StringBufferEnd, size );
            omf_write_record( &obj );
        }
    }
}

static uint_8 get_omfalign( uint_8 alignment );

/* write an LEDATA record, optionally write fixups */

static void omf_write_ledata( struct dsym *seg )
/**********************************************/
{
    struct omf_rec  obj;
    int_32          size;

    size = seg->e.seginfo->current_loc - seg->e.seginfo->start_loc;
    DebugMsg1(( "omf_write_ledata enter, buffer=%p start ofs=%" I32_SPEC "X, size=%" I32_SPEC "X\n",
              seg->e.seginfo->CodeBuffer, seg->e.seginfo->start_loc, size ));
    if( size > 0 && write_to_file == TRUE ) {
        LastCodeBufSize = size;
#if COMDATSUPP
        if ( seg->e.seginfo->comdat_selection ) {
            /* if the COMDAT symbol has been referenced in a FIXUPP,
             * a CEXTDEF has to be written.
             */
            if ( seg->sym.used ) {
                omf_InitRec( &obj, CMD_CEXTDEF );
                AttachData( &obj, (uint_8 *)StringBufferEnd, 2 * sizeof( uint_16 ) );
                PutIndex( &obj, seg->e.seginfo->comdat_idx ); /* Index */
                PutIndex( &obj, 0 ); /* Type */
                TruncRec( &obj );
                omf_write_record( &obj );
                if ( seg->e.seginfo->seg_idx == 0 )
                    seg->e.seginfo->seg_idx = startext++;
            }

            omf_InitRec( &obj, CMD_COMDAT );
            AttachData( &obj, seg->e.seginfo->CodeBuffer, size );
            if( seg->e.seginfo->start_loc > 0xffffUL )
                obj.is_32 = 1;
            obj.d.comdat.flags = 0;
            /* low 4-bits is allocation type */
            if ( seg->e.seginfo->segtype == SEGTYPE_CODE ) {
                obj.d.comdat.attributes = ( ModuleInfo.model == MODEL_FLAT ? COMDAT_CODE32 : COMDAT_FAR_CODE );
            } else {
                obj.d.comdat.attributes = ( ModuleInfo.model == MODEL_FLAT ? COMDAT_DATA32 : COMDAT_FAR_DATA );
            }
            obj.d.comdat.align = get_omfalign( seg->e.seginfo->alignment );
            obj.d.comdat.offset = seg->e.seginfo->start_loc;
            obj.d.comdat.type_idx = 0;
            obj.d.comdat.public_lname_idx = seg->e.seginfo->comdat_idx;
            /* todo: error if comdat_idx is 0 */
        } else {
#endif
            omf_InitRec( &obj, CMD_LEDATA );
            AttachData( &obj, seg->e.seginfo->CodeBuffer, size );
            obj.d.ledata.idx = seg->e.seginfo->seg_idx;
            obj.d.ledata.offset = seg->e.seginfo->start_loc;
            if( obj.d.ledata.offset > 0xffffUL )
                obj.is_32 = 1;
#if COMDATSUPP
        }
#endif
        omf_write_record( &obj );

        /* process Fixup, if any */
        if( seg->e.seginfo->FixupList.head != NULL ) {
#if FASTMEM==0
            struct fixup *fix;
            struct fixup *next;
#endif
            omf_write_fixupp( seg, 0 );
            omf_write_fixupp( seg, 1 );
#if FASTMEM==0
            for( fix = seg->e.seginfo->FixupList.head; fix; ) {
                next = fix->nextrlc;
                LclFree( fix );
                fix = next;
            }
#endif
            seg->e.seginfo->FixupList.head = seg->e.seginfo->FixupList.tail = NULL;
        }
    }
    seg->e.seginfo->start_loc = seg->e.seginfo->current_loc;
}

/*
 * flush current segment.
 * write_to_file is always TRUE here
 */

void omf_FlushCurrSeg( void )
/***************************/
{
    //unsigned i;
    //unsigned size;

    DebugMsg1(( "omf_FlushCurrSeg() enter, CurrSeg=%s, currsrc=%u\n", CurrSeg ? CurrSeg->sym.name : "NULL", get_curr_srcfile() ));

    omf_write_ledata( CurrSeg );
    /* add line numbers if debugging info is desired */
    //if( write_to_file && Options.line_numbers ) {
    if( Options.line_numbers ) {
        omf_write_linnum( ln_is32 );
        ln_size = 0;
    }
    //if ( Options.no_comment_data_in_code_records == FALSE )
    //    omf_OutSelect( FALSE );
    return;
}

/* Write a THEADR record.
 */
static void omf_write_theadr( const char *name )
/**********************************************/
{
    struct omf_rec obj;
    unsigned    len;
    //const struct fname_item *fn;

    DebugMsg1(("omf_write_theadr(%s) enter\n", name));

    omf_InitRec( &obj, CMD_THEADR );

    /* v2.08: use the name given at the cmdline, that's what Masm does.
     * Masm emits either a relative or a full path, depending on what
     * was given as filename!
     */
    len = strlen( name );
    AttachData( &obj, (uint_8 *)StringBufferEnd, len + 1 );
    PutName( &obj, name, len );
    omf_write_record( &obj );

    DebugMsg1(("omf_write_theadr() exit\n"));
}

/* v2.11: check if
 * - source file is changing
 * - offset magnitude is changing
 * - size of line number info exceeds 1024
 * if at least one of these conditions are met AND there are linnum items
 * in the queue, then flush the current LEDATA buffer.
 * If source file changed, write a THEADR record for the new source file.
 * ( Masm also emits THEADR records, but more frequently -
 * it doesn't care if linnum items are written or have been written. )
 *
 * This function is called by AddLinnumDataRef(), that is, whenever a linnum
 * info is about to be written.
 */

void omf_check_flush( const struct line_num_info *curr )
/******************************************************/
{
    uint_8 is_32;
    uint_16 size;
#if MULTIHDR
    if ( curr->srcfile != ln_srcfile ) {
        if ( LinnumQueue.head )
            omf_FlushCurrSeg();
        /* todo: for Borland, there's a COMENT ( CMT_SRCFILE ) that could be written
         * instead of THEADR.
         */
        omf_write_theadr( GetFName( curr->srcfile )->fname );
        ln_srcfile = curr->srcfile;
        return;
    }
#endif
    /* if there's a change in offset magnitude ( 16 -> 32 or 32 -> 16 ),
     * do flush ( Masm compatible ).
     */
    is_32 = ( curr->offset > 0xffff ? TRUE : FALSE );
    if ( ln_is32 != is_32 ) {
        if ( LinnumQueue.head )
            omf_FlushCurrSeg();
        ln_is32 = is_32;
        return;
    }
    /* line number item consists of 16-bit line# and 16- or 32-bit offset */
    size = sizeof( uint_16 ) + ( is_32 ? sizeof( uint_32 ) : sizeof( uint_16 ) );
    /* if the size of the linnum data exceeds 1016,
     * do flush ( Masm compatible ).
     */
    if ( ln_size + size > 1024 - 8 ) {
        if ( LinnumQueue.head )
            omf_FlushCurrSeg();
    }
    ln_size += size;
    return;
};

/*------------------------------------------------------*/

/* write end of pass 1 record. */

static void omf_end_of_pass1( void )
/**********************************/
{
    struct omf_rec obj;

    omf_InitRec( &obj, CMD_COMENT );
    obj.d.coment.attr = 0x00;
    obj.d.coment.cmt_class = CMT_MS_END_PASS_1;
    AttachData( &obj, (uint_8 *)"\x001", 1 );
    omf_write_record( &obj );
}

/* called when a new path is started
 * the OMF "path 2" records (LEDATA, FIXUP, LINNUM ) are written in all passes.
 */

void omf_set_filepos( void )
/**************************/
{
    DebugMsg1(( "omf_set_filepos: reset file pos to %X\n", end_of_header ));
#if MULTIHDR
#endif
    fseek( CurrFile[OBJ], end_of_header, SEEK_SET );
}

static void omf_write_dosseg( void )
/**********************************/
{
    struct omf_rec obj;

    omf_InitRec( &obj, CMD_COMENT );
    obj.d.coment.attr = CMT_TNP;
    obj.d.coment.cmt_class = CMT_DOSSEG;
    AttachData( &obj, (uint_8 *)"", 0 );
    omf_write_record( &obj );
}

static void omf_write_lib( void )
/*******************************/
{
    struct omf_rec      obj;
    struct qitem        *curr;
    struct qitem        *next;
    char                *name;

    DebugMsg1(("omf_write_lib() enter\n"));
    for( curr = ModuleInfo.g.LibQueue.head; curr; curr = next ) {
        next = curr->next;
        name = curr->value;
        omf_InitRec( &obj, CMD_COMENT );
        obj.d.coment.attr = CMT_TNP;
        obj.d.coment.cmt_class = CMT_DEFAULT_LIBRARY;
        AttachData( &obj, (uint_8 *)name, strlen( name ) );
        omf_write_record( &obj );
    }
    DebugMsg1(("omf_write_lib() exit\n"));
}

#if DLLIMPORT && WRITEIMPDEF /* writing import records in OMF not supported yet */

void omf_write_import( void )
/***************************/
{
    struct dsym    *imp;
    int            len;
    struct omf_rec obj;
    uint_8         *data;

    /* don't do anything if -Fd isn't set or if -Fd was given with a file name */
    if ( (!Options.write_impdef) || Options.names[OPTN_LNKDEF_FN] )
        return;
    for ( imp = SymTables[TAB_EXT].head; imp; imp = imp->next ) {
        if ( imp->sym.isproc && ( imp->sym.weak == FALSE || imp->sym.iat_used == TRUE ) ) {
            if ( imp->sym.dll && *imp->sym.dll->name ) {
                omf_InitRec( &obj, CMD_COMENT );
                obj.d.coment.attr = 0x00;
                obj.d.coment.cmt_class = CMT_OMF_EXT;

                /* structure of IMPDEF "comment":
                 * type          db CMT_EXT_IMPDEF (=01)
                 * ordinal_flag  db ?  ;0=import by name
                 * int_name_len  db ?
                 * int_name      db int_name_len dup (?)
                 * mod_name_len  db ?
                 * mod_name      db mod_name_len dup (?)
                 * union
                 *  entry_ident   dw ?   ;if by ordinal
                 *  struct               ;if by name
                 *   imp_name_len db ?   ;may be 0
                 *   imp_name     db imp_name_len dup (?)
                 *  ends
                 * ends
                 */

                data = StringBufferEnd;
                len = Mangle( &imp->sym, data+3 );
                AttachData( &obj, data, len + strlen( imp->sym.dll->name ) + 5 ); /* 2 bytes prefix, 2*len prefix, 1 for imp_name_len */

                Put8( &obj, CMT_EXT_IMPDEF );
                Put8( &obj, 0 ); /* import by name */
                Put8( &obj, len );
                obj.curoff += len;
                PutName( &obj, imp->sym.dll->name, strlen( imp->sym.dll->name ) );
                Put8( &obj, 0 );
                omf_write_record( &obj );
            }
        }
    }
}
#endif

static void omf_write_export( void )
/**********************************/
{
    uint_8      parmcnt;
    struct dsym *dir;
    struct dsym *parm;
    struct omf_rec obj;
    int         len;
    uint_8      *data;

#if DLLIMPORT && WRITEIMPDEF /* writing import records in OMF not supported yet */
    omf_write_import();
#endif

    for( dir = SymTables[TAB_PROC].head; dir != NULL; dir = dir->nextproc ) {
        if( dir->e.procinfo->isexport ) {

            omf_InitRec( &obj, CMD_COMENT );
            obj.d.coment.attr = 0x00;
            obj.d.coment.cmt_class = CMT_OMF_EXT;
            data = (uint_8 *)StringBufferEnd;

            /* structure of EXPDEF "comment":
             * type          db CMT_EXT_EXPDEF (=02)
             * exported_flag db ?
             * ex_name_len   db ?
             * exported_name db ex_name_len dup (?)
             * int_name_len  db 0     ;always 0
             * ;internal_name db int_name_len dup (?)
             * ;ordinal      dw ?     ;optional
             */
            if ( Options.no_export_decoration == FALSE )
                len = Mangle( &dir->sym, (char *)data+3 );
            else {
                strcpy( (char *)data+3, dir->sym.name );
                len = dir->sym.name_size;
            }
            /* v2.11: case mapping was missing */
            if ( ModuleInfo.convert_uppercase )
                _strupr( (char *)data+3 );
#if MAX_ID_LEN > 255
            if ( len > 255 )
                len = 255; /* restrict name to 255 chars */
#endif
            AttachData( &obj, data, len + 4 );
            Put8( &obj, CMT_EXT_EXPDEF );
            /* write the "Exported Flag" byte:
             * bits 0-4: parameter count
             * bit 5: no data (entry doesn't use initialized data )
             * bit 6: resident (name should be kept resident)
             * bit 7: ordinal ( if 1, 2 byte index must follow name)
             */
            for ( parm = dir->e.procinfo->paralist, parmcnt = 0; parm; parm = parm->nextparam, parmcnt++ );
            parmcnt &= 0x1F; /* ensure bits 5-7 are still 0 */
            Put8( &obj, parmcnt ); /* v2.01: changed from fix 0x00 */
            Put8( &obj, len );
            obj.curoff += len;
            Put8( &obj, 0 );
            omf_write_record( &obj );
        }
    }
}

/* write OMF GRPDEF records
 */

static void omf_write_grpdef( void )
/**********************************/
{
    struct dsym     *curr;
    struct dsym     *segminfo;
    struct seg_item *seg;
    struct omf_rec  grp;
    //char            writeseg;

    DebugMsg1(("omf_write_grpdef enter\n"));
    //line_num = LineNumber;

    /* size of group records may exceed 1024! */
    for( curr = SymTables[TAB_GRP].head; curr; curr = curr->next ) {

        omf_InitRec( &grp, CMD_GRPDEF );

        grp.d.grpdef.idx = curr->e.grpinfo->grp_idx;

        /* we might need:
         * - 1 or 2 bytes for the group name index
         * - 2 or 3 bytes for each segment in the group
         */
        AttachData( &grp, (uint_8 *)StringBufferEnd, 2 + 3 * curr->e.grpinfo->numseg );
        /* v2.01: the LName index of the group may be > 0xff */
        /* v2.03: use the group index directly */
        PutIndex( &grp, curr->e.grpinfo->lname_idx );

        for( seg = curr->e.grpinfo->seglist; seg; seg = seg->next ) {
            //writeseg = TRUE;
            segminfo = (struct dsym *)(seg->seg);
            Put8( &grp, GRP_SEGIDX );
            PutIndex( &grp, segminfo->e.seginfo->seg_idx );
            /* truncate the group record if it comes near output buffer limit! */
            if ( grp.curoff > OBJ_BUFFER_SIZE - 10 ) {
                EmitWarn( 2, GROUP_DEFINITION_TOO_LARGE, curr->sym.name );
                break;
            }
        }
        TruncRec( &grp );
        omf_write_record( &grp );
    }
    DebugMsg1(("omf_write_grpdef exit\n"));
}

static uint_8 get_omfalign( uint_8 alignment )
/********************************************/
{
    switch ( alignment ) {
    case  1:  return( SEGDEF_ALIGN_WORD );
    case  2:  return( SEGDEF_ALIGN_DWORD );
    case  4:  return( SEGDEF_ALIGN_PARA );
    case  8:  return( SEGDEF_ALIGN_PAGE );
#if PAGE4K /* PharLab peculiarity; this is invalid for MS OMF */
    case 12:
        if ( Parse_Pass == PASS_1 )
            EmitWarn( 2, NO_4KPAGE_ALIGNED_SEGMENTS_IN_MS386 );
        return( SEGDEF_ALIGN_4KPAGE );
#endif
    case MAX_SEGALIGNMENT: return( SEGDEF_ALIGN_ABS );
    }
    /* value 0 is byte alignment, anything elso is "unexpected" */
    /**/myassert( alignment == 0 );
    return( SEGDEF_ALIGN_BYTE );
}

/* write segment table.
 * This is done after pass 1.
 * There might exist entries of undefined segments in
 * the segment list!
 */

static void omf_write_segdef( void )
/**********************************/
{
    struct dsym    *curr;
    struct omf_rec obj;

    DebugMsg1(("omf_write_segdef enter\n"));

    for( curr = SymTables[TAB_SEG].head; curr; curr = curr->next ) {

#if COMDATSUPP
        if ( curr->e.seginfo->comdat_selection )
            continue;
#endif
        omf_InitRec( &obj, CMD_SEGDEF );
        if ( curr->e.seginfo->Ofssize > USE16 ) {
            obj.is_32 = ( ( curr->e.seginfo->force32 || ( curr->sym.max_offset >= 0x10000 ) ) ? 1 : 0 );
        } else {
            obj.is_32 = 0;
        }

        obj.d.segdef.idx             = curr->e.seginfo->seg_idx;
        obj.d.segdef.use_32          = ( curr->e.seginfo->Ofssize > USE16 ? 1 : 0 );
        obj.d.segdef.align           = get_omfalign( curr->e.seginfo->alignment );
        obj.d.segdef.combine         = curr->e.seginfo->combine;
        obj.d.segdef.abs.frame       = curr->e.seginfo->abs_frame;
        obj.d.segdef.abs.offset      = curr->e.seginfo->abs_offset;
        obj.d.segdef.seg_length      = curr->sym.max_offset;
        obj.d.segdef.seg_lname_idx   = curr->e.seginfo->lname_idx;
        obj.d.segdef.class_lname_idx = ( curr->e.seginfo->clsym ? curr->e.seginfo->clsym->class_lname_idx : 1 );
        obj.d.segdef.ovl_lname_idx   = 1;

        omf_write_record( &obj );
        DebugMsg1(("omf_write_segdef(%s): len=%" I32_SPEC "X use32=%u align=%u comb=%u seg_lname=%u class_lname=%u ovl_lname=%u\n",
                  curr->sym.name,
                  obj.d.segdef.seg_length,
                  obj.d.segdef.use_32,
                  obj.d.segdef.align,
                  obj.d.segdef.combine,
                  obj.d.segdef.seg_lname_idx,
                  obj.d.segdef.class_lname_idx,
                  obj.d.segdef.ovl_lname_idx
                 ));
        /* write a comment for the linker.
         * this is something not done by Masm, it has
         * been inherited from Wasm.
         */
        if( curr->e.seginfo->segtype == SEGTYPE_CODE && Options.no_opt_farcall == FALSE ) {
            uint_8 buffer[4];
            omf_InitRec( &obj, CMD_COMENT );
            obj.d.coment.attr = CMT_TNP;
            obj.d.coment.cmt_class = CMT_LINKER_DIRECTIVE;
            AttachData( &obj, buffer, 3 );
            Put8( &obj, LDIR_OPT_FAR_CALLS );
            PutIndex( &obj, curr->e.seginfo->seg_idx );
            /* v2.04: added. cut off the 3. byte if not needed */
            TruncRec( &obj );
            omf_write_record( &obj );
        }
    }
    DebugMsg1(("omf_write_segdef exit\n"));
}

/* the lnames are stored in a queue. read
 * the items one by one and take care that
 * the record size doesn't exceed 1024 bytes.
 */

static void omf_write_lnames( void )
/**********************************/
{
    int         size;
    int         items;
    unsigned char *p;
    //void        *pv = NULL;
    struct qnode *curr;
    struct asym *sym;
    struct omf_rec obj;
    unsigned char buffer[MAX_LNAME_SIZE];

    DebugMsg1(("omf_write_lnames() enter\n"));
    p = buffer;
    *p++ = NULLC; /* start with the NULL entry */
    items = 1;
    startitem = 1;

    for ( curr = ModuleInfo.g.LnameQueue.head; ; curr = curr->next ) {
        //sym = GetLnameData( &pv );
        sym = ( curr ? (struct asym *)(curr->elmt) : NULL );
        size = p - buffer;
        /* v2.04: changed extra bytes from 1 to 4 (CMD, RECLEN, CHKSUM) */
        //if ( sym == NULL || ( ( size + sym->name_size + 1 ) > MAX_LNAME_SIZE )) {
        if ( sym == NULL || ( ( size + sym->name_size + 4 ) > MAX_LNAME_SIZE )) {
            if( size ) {
                omf_InitRec( &obj, CMD_LNAMES );
                /* first_idx and num_names are NOT
                 * written to the LNAMES record!
                 * In fact, they aren't used at all.
                 */
                obj.d.lnames.first_idx = startitem;
                obj.d.lnames.num_names = items;
                AttachData( &obj, buffer, size );
                omf_write_record( &obj );
                startitem = items;
            }
            if ( sym == NULL )
                break;
            p = buffer;
        }
        *p++ = (char)sym->name_size;
        /* copy 1 byte more - the NULLC - for _strupr() */
        memcpy( p, sym->name, sym->name_size + 1 );
        /* lnames are converted for casemaps ALL and NOTPUBLIC */
        if ( ModuleInfo.case_sensitive == FALSE )
            _strupr( (char *)p );
        DebugMsg1(("omf_write_lnames: %u=%s\n", items, p ));
        p += sym->name_size; /* overwrite the null char */
        items++;
        /* v2.12: lname_idx fields now set in OMF only */
        switch ( sym->state ) {
        case SYM_SEG: ((struct dsym *)sym)->e.seginfo->lname_idx = items; break;
        case SYM_GRP: ((struct dsym *)sym)->e.grpinfo->lname_idx = items; break;
        default:      sym->class_lname_idx = items;                       break;
        }
    };

    DebugMsg1(("omf_write_lnames() exit, items=%u\n", items ));
}

struct readext {
    struct dsym *p;
    uint_16 index;
    uint_8 method;
};

/* read items for EXTDEF records.
 * there are 2 sources:
 * - the AltQueue of weak externals
 * - the TAB_EXT queue of externals
 * v2.09: index (ext_idx1, ext_idx2 ) is now set inside this function.
 */

static struct asym *GetExt( struct readext *r )
/*********************************************/
{
    struct asym *sym;
    if ( r->method == 0 ) {
        for ( ; r->p; ) {
            sym = (struct asym *)r->p;
            r->p = r->p->next;
            if ( sym->iscomm == TRUE )
                continue;
            if ( sym->altname && sym->altname->included == FALSE ) {
                /**/ myassert( r->index ); /* overflow occured? */
                sym->altname->ext_idx2 = r->index++;
                sym->altname->included = TRUE;
                return( sym->altname );
            }
        }
        r->method++;
        r->p = SymTables[TAB_EXT].head;
    }
    for ( ; r->p; ) {
        sym = (struct asym *)r->p;
        r->p = r->p->next;
        if ( sym->iscomm == TRUE || sym->weak == TRUE )
            continue;
        /**/ myassert( r->index ); /* overflow occured? */
        sym->ext_idx1 = r->index++;
        //r->p->sym.included = TRUE;
        return( sym );
    }
    return( NULL );
}

/* write EXTDEF records.
 * this is done once, after pass 1.
 * v2.09: external index is now set here.
 */

static uint_16 omf_write_extdef( void )
/*************************************/
{
    struct omf_rec obj;
    struct asym *sym;
    struct dsym *symext;
    unsigned    rec_size;
    unsigned    len;
    struct readext r;
    char        data[MAX_EXT_LENGTH];
    unsigned char buffer[MAX_ID_LEN + MANGLE_BYTES + 1];

    DebugMsg1(("omf_write_extdef enter\n"));

    r.p = SymTables[TAB_EXT].head;
    r.index = 1;
    r.method = 0;
    obj.d.extdef.first_idx = 0;

    /* scan the EXTERN/EXTERNDEF items */

    sym = GetExt( &r );
    while ( sym ) {
        for ( rec_size = 0, obj.d.extdef.num_names = 0; sym; sym = GetExt( &r ) ) {
            //DebugMsg(("omf_write_extdef: %s, weak=%u, used=%u\n", curr->sym.name, curr->sym.weak, curr->sym.used ));
            DebugMsg1(("omf_write_extdef: %s\n", sym->name));
            len = Mangle( sym, (char *)buffer );
#if MAX_ID_LEN > 255
            if ( len > 255 )
                len = 255; /* length is 1 byte only */
#endif
            if ( ModuleInfo.convert_uppercase )
                _strupr( (char *)buffer );

            if( rec_size + len + 2 >= MAX_EXT_LENGTH ) {
                break;
            }
            obj.d.extdef.num_names++;

            data[rec_size++] = (char)len;
            memcpy( data + rec_size, buffer, len );
            rec_size += len;
            data[rec_size++] = 0;      /* for the type index */
        }
        if ( rec_size ) {
            DebugMsg1(("omf_write_extdef: write record, names=%u, size=%u, MAX=%u\n", obj.d.extdef.num_names, rec_size, MAX_EXT_LENGTH ));
            omf_InitRec( &obj, CMD_EXTDEF );
            AttachData( &obj, (uint_8 *)data, rec_size );
            omf_write_record( &obj );
            obj.d.extdef.first_idx += obj.d.extdef.num_names;
        }
    }

    /* v2.04: write WKEXT coment records.
     * those items are defined via "EXTERN (altname)" syntax.
     * After the records have been written, the indices in
     * altname are no longer needed.
     */

    for ( symext = SymTables[TAB_EXT].head; symext; symext = symext->next ) {
        if ( symext->sym.iscomm == FALSE && symext->sym.altname ) {
            omf_InitRec( &obj, CMD_COMENT );
            obj.d.coment.attr = CMT_TNP;
            obj.d.coment.cmt_class = CMT_WKEXT;
            AttachData( &obj, buffer, 4 );
            PutIndex( &obj, symext->sym.ext_idx1 );
            PutIndex( &obj, symext->sym.altname->ext_idx2 );
            TruncRec( &obj );
            omf_write_record( &obj );
        }
    }
    /* v2.05: reset the indices - this must be done only after ALL WKEXT
     * records have been written!
     */
    for ( symext = SymTables[TAB_EXT].head; symext; symext = symext->next ) {
            /* v2.09: don't touch the index if the alternate name is an external
             * - else an invalid object file will be created!
             */
        if ( symext->sym.iscomm == FALSE && symext->sym.altname && symext->sym.altname->state != SYM_EXTERNAL )
            symext->sym.altname->ext_idx = 0;
    }
    /* v2.09: write COMM items here. This allows to handle
     * the external index field entirely in omf.c
     * v2.11: removed again, since omf_write_extdef() is no
     * longer called externally.
     */
    //omf_write_comdef( r.index );

    DebugMsg1(("omf_write_extdef exit, index=%u\n", r.index ));
    return( r.index );
}

#define THREE_BYTE_MAX ( (1UL << 24) - 1 )

static int get_size_of_comdef_number( uint_32 value )
/***************************************************/
{
    /* The spec allows up to 128 in a one byte size field, but lots
       of software has problems with that, so we'll restrict ourselves
       to 127.
    */
    if( value < 128 ) {
        return( 1 );    /* 1 byte value */
    } else if( value <= USHRT_MAX ) {
        return( 3 );    /* 1 byte flag + 2 byte value */
    } else if( value <= THREE_BYTE_MAX ) {
        return( 4 );    /* 1 byte flag + 3 byte value */
    } else { /* if( value <= ULONG_MAX ) */
        return( 5 );    /* 1 byte flag + 4 byte value */
    }
}

/* for COMDEF: write item size (or number of items) */

static unsigned put_comdef_number( uint_8 *buffer, uint_32 value )
/****************************************************************/
{
    unsigned i;
    unsigned symsize;

    symsize = get_size_of_comdef_number( value );
    switch( symsize ) {
    case 1:  *buffer = value; break;
    case 3:  *buffer++ = COMDEF_LEAF_2;  break; /* 0x81 */
    case 4:  *buffer++ = COMDEF_LEAF_3;  break; /* 0x84 */
    case 5:  *buffer++ = COMDEF_LEAF_4;  break; /* 0x88 */
    }

    for( i = 1; i < symsize; i++ ) {
        *buffer++ = value % ( UCHAR_MAX + 1 );
        value >>= 8;
    }
    return( symsize );
}

/* write OMF COMDEF records.
 * this is done once, after pass 1.
 * v2.09: external index is now set inside this function.
 * important: the size of the communal variables must be known.
 * If the size is the difference of two code labels, it might
 * change in subsequent passes. Both Masm and JWasm won't adjust
 * the size then!
 */

static uint_16 omf_write_comdef( uint_16 index )
/**********************************************/
{
    struct omf_rec obj;
    struct dsym    *curr;
    unsigned    num;
    unsigned    recsize;
    unsigned    numsize;
    unsigned    symsize;
    uint_32     varsize;
    unsigned    start = 0; /* record's start index (not used) */
    char        buffer[MAX_ID_LEN + MANGLE_BYTES + 1];
    char        data[MAX_EXT_LENGTH];
    char        number[16];

    DebugMsg1(("omf_write_comdef enter\n"));
    curr = SymTables[TAB_EXT].head;
    while ( curr ) {
        for( num = 0, recsize = 0; curr != NULL ; curr = curr->next ) {
            if ( curr->sym.iscomm == FALSE )
                continue;
            symsize = Mangle( &curr->sym, buffer );
#if MAX_ID_LEN > 255
            if ( symsize > 255 )
                symsize = 255; /* length is 1 byte only */
#endif
            /* v2.11: case mapping was missing */
            if ( ModuleInfo.convert_uppercase )
                _strupr( buffer );

            varsize = SizeFromMemtype( curr->sym.mem_type, ModuleInfo.Ofssize, curr->sym.type );

            /**/ myassert( index );
            curr->sym.ext_idx = index++; /* v2.09: set external index here */

            DebugMsg1(("omf_write_comdef: %s, size=%u, sym.total_size=%u, sym.total_length=%u, sym.isfar=%u\n",
                      curr->sym.name, varsize, curr->sym.total_size, curr->sym.total_length, curr->sym.isfar ));
            if ( varsize == 0 )
                varsize = curr->sym.total_size / curr->sym.total_length;

            numsize = 1;
            if ( curr->sym.isfar == TRUE ) {
                number[0] = COMDEF_FAR;  /* 0x61 */
                numsize += put_comdef_number( (uint_8 *)&number[1], curr->sym.total_length );
                numsize += put_comdef_number( (uint_8 *)&number[numsize], varsize );
                DebugMsg(("omf_write_comdef: numsize=%u, length=%u, varsize=%u\n",
                          numsize, curr->sym.total_length, varsize ));
            } else {
                number[0] = COMDEF_NEAR; /* 0x62 */
                numsize += put_comdef_number( (uint_8 *)&number[1], curr->sym.total_length * varsize );
                DebugMsg1(("omf_write_comdef: numsize=%u, value=%u\n",
                          numsize, curr->sym.total_length * varsize ));
            }
            /* make sure the record's size doesn't exceed 1024.
             * 2 = 1 (name len) + 1 (type index)
             */
            if ( ( recsize + symsize + numsize + 2 ) > MAX_EXT_LENGTH )
                break;

            /* copy name ( including size prefix ), type, number */
            data[recsize++] = (char)symsize;
            memcpy( data + recsize, buffer, symsize );
            recsize += symsize;
            data[recsize++] = 0;      /* for the type index */
            memcpy( data + recsize, number, numsize );
            recsize += numsize;

            num++;

        } /* end for */

        if( num > 0 ) {
            omf_InitRec( &obj, CMD_COMDEF );
            obj.d.comdef.first_idx = start; /* unused */
            AttachData( &obj, (uint_8 *)data, recsize );
            obj.d.comdef.num_names = num; /* unused */
            omf_write_record( &obj );
            start += num;
        }
    }
    DebugMsg1(("omf_write_comdef exit\n"));
    return( index );
}

static time_t GetFileTimeStamp( const char *filename )
/****************************************************/
{
#if defined(__DJGPP__)
#define _stat stat
#endif
    struct _stat statbuf;

    if( _stat( filename, &statbuf ) != 0 ) {
        DebugMsg(("GetFileTimeStamp(%s): _stat() did return 0\n", filename ));
        return( 0 );
    }
    return( statbuf.st_mtime );
}

/*
 * write COMENT dependency records (CMT_DEPENDENCY) for debugging
 * if line numbers are on; this is a Borland/OW thing.
 */
static ret_code omf_write_autodep( void )
/***************************************/
{
    struct omf_rec  obj;
    struct fname_item *curr;
    char            *p = StringBufferEnd;
    unsigned int    len;
    unsigned        idx;

    DebugMsg(("omf_write_autodep() enter\n"));
    for( idx = 0, curr = ModuleInfo.g.FNames; idx < ModuleInfo.g.cnt_fnames; idx++, curr++ ) {
        omf_InitRec( &obj, CMD_COMENT );
        obj.d.coment.attr = CMT_TNP;
        obj.d.coment.cmt_class = CMT_DEPENDENCY; /* 0xE9 */

        len = strlen( curr->fname );
#if MAX_STRING_LEN > 255
        if ( len > 255 )
            len = 255; /* length is 1 byte only */
#endif
        /* v2.11: field mtime removed, timestamp read when needed */
        //*((time_t *)p) = timet2dostime( curr->mtime );
        *((time_t *)p) = timet2dostime( GetFileTimeStamp( curr->fname ) );
        *(p + 4) = (unsigned char)len;
        memcpy( p + 5, curr->fname, len );
        AttachData( &obj, (uint_8 *)p, len + 5 );
        DebugMsg(("omf_write_autodep(): write record: file=%s, time=%X\n", curr->fname, *((time_t *)p) ));
        omf_write_record( &obj );
    }
    /* one NULL dependency record must be on the end */
    omf_InitRec( &obj, CMD_COMENT );
    obj.d.coment.attr = CMT_TNP;
    obj.d.coment.cmt_class = CMT_DEPENDENCY;
    AttachData( &obj, (uint_8 *)"", 0 );
    omf_write_record( &obj );
    DebugMsg(("omf_write_autodep() exit\n"));
    return( NOT_ERROR );
}

static void omf_write_alias( void )
/*********************************/
{
    struct omf_rec      obj;
    char                *p;
    uint_8              len1;
    uint_8              len2;
    //bool                first = TRUE;
    struct dsym         *curr;
    char                tmp[MAX_ID_LEN + MANGLE_BYTES + 1];
    unsigned char       buff[2*MAX_ID_LEN_OMF + 2];

    for( curr = SymTables[TAB_ALIAS].head; curr; curr = curr->next ) {

        /* output an alias record for this alias */
        /* v2.10: use the decorated names */
        //len1 = curr->sym.name_size;
        len1 = Mangle( &curr->sym, tmp );
#if MAX_ID_LEN > MAX_ID_LEN_OMF
        if ( len1 > MAX_ID_LEN_OMF )
            len1 = MAX_ID_LEN_OMF;
#endif
        p = (char *)buff;
        *p++ = len1;
        memcpy( p, tmp, len1 );
        p += len1;

        //len2 = curr->sym.substitute->name_size;
        len2 = Mangle( curr->sym.substitute, tmp );
#if MAX_ID_LEN > MAX_ID_LEN_OMF
        if ( len2 > MAX_ID_LEN_OMF )
            len2 = MAX_ID_LEN_OMF;
#endif
        *p++ = len2;
        memcpy( p, tmp, len2 );

        omf_InitRec( &obj, CMD_ALIAS );
        AttachData( &obj, buff, len1 + len2 + 2 );
        omf_write_record( &obj );
        //first = FALSE;
    }
}

/* the PUBDEF record consists of:
 * - base_info ( segment, group, frame ), max size is 4
 * - items
 *   - 1 byte name len
 *   - name[]
 *   - offset ( 2/4 bytes )
 *   - type ( index field, here always 0 )
 * PUBDEFs are written after pass 1 and finally ( when the offsets are known ).
 */
static ret_code omf_write_pubdef( void )
/**************************************/
{
    struct qnode        *q;

    DebugMsg1(("omf_write_pub enter\n"));

    /* v2.07: struct pubdef_data has been modified to match
     * the data to be written to the object module more closely.
     * This fixed a possible overrun if too many publics were written.
     * v2.11: now the data matches exactly the OMF PUBDEF record
     * and is just attached.
     */

    q = ModuleInfo.g.PubQueue.head;
    while ( q ) {
        struct asym     *curr_seg;
        uint_8          *data;
        unsigned        size;
        uint_8          curr32;
        uint_8          is32;

        for ( size = 0, data = (uint_8 *)StringBufferEnd; q; q = q->next ) {
            unsigned    recsize;
            unsigned    len;
            struct asym *sym;
            sym = q->sym;
#if COMDATSUPP
            /* COMDAT symbol? Then write an LNAME record */
            if ( sym->segment && ((struct dsym *)sym->segment)->e.seginfo->comdat_selection ) {
                struct dsym *seg = (struct dsym *)sym->segment;
                if ( seg->e.seginfo->comdat_idx == 0 ) {
                    struct omf_rec obj;
                    seg->e.seginfo->comdat_idx = ++startitem;
                    seg->sym.used = sym->used;
                    omf_InitRec( &obj, CMD_LNAMES );
                    len = Mangle( sym, StringBufferEnd + 1 );
                    *StringBufferEnd = len;
                    if ( ModuleInfo.case_sensitive == FALSE )
                        _strupr( StringBufferEnd + 1 );
                    AttachData( &obj, (uint_8 *)StringBufferEnd, len + 1 );
                    omf_write_record( &obj );
                } else if ( Parse_Pass == PASS_1 ) {
                    /* ??? */
                }
                continue;
            }
#endif
            /* for constants, Masm checks if the value will fit in a 16-bit field,
             * either signed ( -32768 ... 32767 ) or unsigned ( 0 ... 65635 ).
             * As a result, the following code:
             * E1 equ 32768
             * E2 equ -32768
             * PUBLIC E1, E2
             * will store both equates with value 8000h in the 16-bit PUBDEF record!!!
             * JWasm behaves differently, resulting in negative values to be stored as 32-bit.
             */
#if 1
            is32 = ( sym->offset > 0xffffUL ? 1 : 0 );
#else
            is32 = ( sym->offset > 0xffff || sym->offset < -32768 ? 1 : 0 ); /* this is what Masm does */
#endif
            /* check if public fits in current record yet.
             * 4 bytes omf record overhead, 4 for base info, 1+1+4/2 for name_size, type & offset
             */
            recsize = size + sym->name_size + MANGLE_BYTES + 4+4+1+1+( is32 ? 4 : 2 );
            /* exit loop if segment or offset magnitude changes, or record becomes too big */
            if( size && ( sym->segment != curr_seg || is32 != curr32 || recsize > MAX_PUB_LENGTH ) )
                break;
            len = Mangle( sym, (char *)data + 1 );
#if MAX_ID_LEN > MAX_ID_LEN_OMF
            if ( len > 255 )
                len = 255; /* length is 1 byte only */
#endif
            if ( ModuleInfo.convert_uppercase )
                _strupr( (char *)data + 1 );
            curr_seg = sym->segment;
            curr32 = is32;

            *data = len;
            data += len + 1;
            if ( curr32 ) {
                *(uint_32 *)data = sym->offset;
                data += sizeof( uint_32 );
            } else {
                *(uint_16 *)data = sym->offset;
                data += sizeof( uint_16 );
            }
            *data++ = 0; /* type field */
            size = (char *)data - StringBufferEnd;
            DebugMsg1(("omf_write_pub(%s)\n", sym->name ));
        }
        if ( size ) {
            struct omf_rec      obj;
            omf_InitRec( &obj, CMD_PUBDEF );
            AttachData( &obj, (uint_8 *)StringBufferEnd, size );
            obj.is_32 = curr32;
            if( curr_seg == NULL ) { /* absolute symbol, no segment */
                obj.d.pubdef.base.grp_idx = 0;
                obj.d.pubdef.base.seg_idx = 0;
            } else {
                obj.d.pubdef.base.seg_idx = GetSegIdx( curr_seg );
                obj.d.pubdef.base.grp_idx = omf_GetGrpIdx( GetGroup( curr_seg ) );
            }
            obj.d.pubdef.base.frame = 0;
            DebugMsg1(("omf_write_pub: is_32=%u, size=%u\n", obj.is_32, size));
            omf_write_record( &obj );
        }
    }

    DebugMsg1(("omf_write_pub exit\n"));
    return( NOT_ERROR );
}

static void omf_write_modend( struct fixup *fixup, uint_32 displ )
/****************************************************************/
{
    struct omf_rec  obj;
    uint_8 buffer[FIX_GEN_MODEND_MAX];

    DebugMsg1(("omf_write_modend( fixup=%p, displ=%" I32_SPEC "X)\n", fixup, displ ));

    omf_InitRec( &obj, CMD_MODEND );

    if( fixup == NULL ) {
        obj.d.modend.main_module = FALSE;
        obj.d.modend.start_addrs = FALSE;
    } else {
        obj.d.modend.start_addrs = TRUE;
        obj.d.modend.main_module = TRUE;
        obj.is_32 = ( GetSymOfssize( fixup->sym ) > USE16 ? 1 : 0 ); /* USE16 or USE32 */
        AttachData( &obj, buffer, 0 );
        obj.length = OmfFixGenFixModend( fixup, buffer, displ, obj.is_32 ? FIX_GEN_MS386 : FIX_GEN_INTEL );
    }
    omf_write_record( &obj );
}

/* this callback function is called during codeview debug info generation */

static uint_8 *omf_cv_flushfunc( struct dsym *seg, uint_8 *curr, unsigned size, void *pv )
/****************************************************************************************/
{
    uint_8 *buffer = seg->e.seginfo->CodeBuffer;

    if ( ( curr - buffer ) && ( ( curr - buffer ) + size ) > ( 1024 - 8 ) ) {
        seg->e.seginfo->current_loc = seg->e.seginfo->start_loc + ( curr - buffer );
        omf_write_ledata( seg );
        return( buffer );
    }
    return( curr );
}

/*
 * If -Zi is set, a comment class
 * A1 record (MS extensions present) is written.
 *
 * Additionally, segments $$SYMBOLS, $$TYPES are added to the segment table
 */
static void omf_write_header_dbgcv( void )
/****************************************/
{
    int i;
    struct omf_rec obj;

    omf_InitRec( &obj, CMD_COMENT );
    obj.d.coment.attr = 0x00;
    obj.d.coment.cmt_class = CMT_MS_OMF; /* MS extensions present */
    AttachData( &obj, (uint_8 *)"\001CV", 3 );
    omf_write_record( &obj );
    for ( i = 0; i < DBGS_MAX; i++ ) {
        if ( SymDebSeg[i] = (struct dsym *)CreateIntSegment( SymDebParm[i].name, SymDebParm[i].cname, 0, USE32, TRUE ) ) {
            SymDebSeg[i]->e.seginfo->force32 = TRUE; /* without this a 32-bit segdef is emitted only if segsize > 64kB */
            SymDebSeg[i]->e.seginfo->flushfunc = omf_cv_flushfunc;
        }
    }
    return;
}

/* write contents of segments $$SYMBOLS and $$TYPES */

static void omf_write_debug_tables( void )
/****************************************/
{
    if ( SymDebSeg[DBGS_SYMBOLS] && SymDebSeg[DBGS_TYPES] ) {
        SymDebSeg[DBGS_SYMBOLS]->e.seginfo->CodeBuffer = (uint_8 *)CurrSource;
        SymDebSeg[DBGS_TYPES]->e.seginfo->CodeBuffer = (uint_8 *)CurrSource + 1024;
        cv_write_debug_tables( SymDebSeg[DBGS_SYMBOLS], SymDebSeg[DBGS_TYPES], NULL );
    }
}

/* write OMF module.
 * this is called after the last pass.
 * since the OMF records are written "on the fly",
 * the "normal" section contents are already written at this time.
 */

static ret_code omf_write_module( struct module_info *modinfo )
/*************************************************************/
{
#if TRUNCATE
    int fh;
    uint_32 size;
#endif
    /* -if Zi is set, write symbols and types */
    if ( Options.debug_symbols )
        omf_write_debug_tables();
    omf_write_modend( modinfo->g.start_fixup, modinfo->g.start_displ );
#if FASTMEM==0
    LclFree( modinfo->g.start_fixup );
#endif

#if TRUNCATE
    /* under some very rare conditions, the object
     * module might become shorter! Hence the file
     * must be truncated now. The problem is that there
     * is no stream function for this task.
     * the final solution will be to save the segment contents
     * in buffers and write the object module once everything
     * is done ( as it is done for the other formats already).
     * v2.03: most likely no longer necessary, since the file
     * won't become shorter anymore.
     */
    size = ftell( CurrFile[OBJ] );
#if defined(__UNIX__) || defined(__CYGWIN__) || defined(__DJGPP__)
    fh = fileno( CurrFile[OBJ] );
    if ( ftruncate( fh, size ) ) /* gcc warns if return value of ftruncate() is "ignored" */
        ; /* c2.12: CLang wants the semicolon in a separate line */
#elif defined(__BORLANDC__)
    fh = _fileno( CurrFile[OBJ] );
    chsize( fh, size );
#else
    fh = _fileno( CurrFile[OBJ] );
    _chsize( fh, size );
#endif

#endif

    /* write SEGDEF records. Since these records contain the segment's length,
     * the records have to be written again after the final assembly pass.
     */
    fseek( CurrFile[OBJ] , seg_pos, SEEK_SET );
    omf_write_segdef();
    /* write PUBDEF records. Since the final value of offsets isn't known after
     * the first pass, this has to be called again after the final pass.
     */
    fseek( CurrFile[OBJ], public_pos, SEEK_SET);
    omf_write_pubdef();
    return( NOT_ERROR );
}

/* write OMF header info after pass 1 */

static ret_code omf_write_header_initial( struct module_info *modinfo )
/*********************************************************************/
{
    uint_16 ext_idx;

    if ( write_to_file == FALSE )
        return( NOT_ERROR );

    /* v2.15: check added */
    if ( modinfo->g.num_segs > 0x7fff ) {
        DebugMsg(("omf_write_header_initial: too many sections defined: %u\n", modinfo->g.num_segs));
        return ( EmitErr( TOO_MANY_SEGMENTS_DEFINED ) );
    }

    omf_write_theadr( CurrFName[ASM] ); /* write THEADR record, main src filename */
    /* v2.11: coment record "ms extensions present" now written here */
    if ( Options.debug_symbols ) /* -Zi option set? */
        omf_write_header_dbgcv();
    /* if( Options.no_dependencies == FALSE ) */
    if( Options.line_numbers )
        omf_write_autodep(); /* write dependency COMENT records ( known by Borland & OW ) */
    if( ModuleInfo.segorder == SEGORDER_DOSSEG )
        omf_write_dosseg(); /* write dosseg COMENT records */
    else if( ModuleInfo.segorder == SEGORDER_ALPHA )
        SortSegments( 1 );
    omf_write_lib(); /* write default lib COMENT records */
    omf_write_lnames(); /* write LNAMES records */

    /* write SEGDEF records. Since these records contain the segment's length,
     * the records have to be written again after the final assembly pass.
     * hence the start position of those records has to be saved.
     */
    seg_pos = ftell( CurrFile[OBJ] );
    omf_write_segdef();
    omf_write_grpdef(); /* write GRPDEF records */
    ext_idx = omf_write_extdef(); /* write EXTDEF records */
    startext = omf_write_comdef( ext_idx ); /* write COMDEF records */
    omf_write_alias(); /* write ALIAS records */

    /* write PUBDEF records. Since the final value of offsets isn't known after
     * the first pass, this has to be called again after the final pass.
     */
    public_pos = ftell( CurrFile[OBJ] );
    omf_write_pubdef();
    omf_write_export(); /* write export COMENT records */

    /* (optionally) write end-of-pass-one COMENT record
     * v2.10: don't write record if starting address is present.
     * the TIS OMF spec v1.1. warns that this
     * comment record is NOT to be present if
     * the MODEND record contains a starting address!
     */
    if ( !ModuleInfo.g.start_fixup )
        omf_end_of_pass1();
    end_of_header = ftell( CurrFile[OBJ] );
    return( NOT_ERROR );
}

/* init. called once per module */

void omf_init( struct module_info *modinfo )
/******************************************/
{
    DebugMsg(("omf_init enter\n"));
    modinfo->g.WriteModule = omf_write_module;
    modinfo->g.Pass1Checks = omf_write_header_initial;
    SymDebSeg[DBGS_SYMBOLS] = NULL;
    SymDebSeg[DBGS_TYPES] = NULL;
#if MULTIHDR
    ln_srcfile = modinfo->srcfile;
#endif
    ln_size = 0;
    return;
}
