/*-
******************************************************************************
******************************************************************************
**
**  ARCHIVE HEADER INFORMATION
**
**  @C-file{
**      FILENAME    = "vvencode.c",
**      VERSION     = "1.00",
**      DATE        = "",
**      TIME        = "",
**
**      AUTHOR      = "Niel Kempson",
**      ADDRESS     = "25 Whitethorn Drive, Cheltenham, GL52 5LL, England",
**      TELEPHONE   = "+44-242 579105",
**      EMAIL       = "kempson@tex.ac.uk (Internet)",
**
**      SUPPORTED   = "yes",
**      ARCHIVED    = "tex.ac.uk, ftp.tex.ac.uk",
**      KEYWORDS    = "VVcode",
**
**      CODETABLE   = "ISO/ASCII",
**      CHECKSUM    = "51492 1481 5732 57976",
**
**      DOCSTRING   = { This file is part of VVcode.
**                  }
**  }
**
**
**  MODULE CONTENTS
**
**
**  COPYRIGHT
**
**      Copyright (c) 1991-1993 by Niel Kempson <kempson@tex.ac.uk>
**
**      This program is free software; you can redistribute it and/or
**      modify it under the terms of the GNU General Public License as
**      published by the Free Software Foundation; either version 1, or
**      (at your option) any later version.
**
**      This program is distributed in the hope that it will be useful,
**      but WITHOUT ANY WARRANTY; without even the implied warranty of
**      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
**      General Public License for more details.
**
**      You should have received a copy of the GNU General Public License
**      along with this program; if not, write to the Free Software
**      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**
**      In other words, you are welcome to use, share and improve this
**      program.  You are forbidden to forbid anyone else to use, share
**      and improve what you give them.   Help stamp out software-hoarding!  
**
**  CHANGE LOG
**
******************************************************************************
******************************************************************************
*/
static char rcsid[] = "$Id$";


#define VVENCODE                1
#define VVCODE_MAIN             1

/*-
**----------------------------------------------------------------------------
** Standard include files
**----------------------------------------------------------------------------
*/
#include <stdio.h>


/*-
**----------------------------------------------------------------------------
** Include the machine specific definitions
**----------------------------------------------------------------------------
*/
#include "checkos.h"
#include "machine.h"
#include "local.h"
#include "globals.h"
#include "specific.h"
#include "vvutils.h"


/*-
**----------------------------------------------------------------------------
** Forward declarations of functions in this file
**----------------------------------------------------------------------------
*/
int main                        ARGS ((CONST int argc,
				    CONST char *argv[]));
Int32 enc_line                	ARGS ((CONST char *ip_buffer,
				    CONST Int32 num_bytes,
				    char *op_buffer));
void encode                     ARGS ((void));
void initialize                 ARGS ((void));
void open_ip_file               ARGS ((CONST Qualifier_Struct *ip_qualifier,
				    File_Info *ip_file));
void open_op_file               ARGS ((CONST Qualifier_Struct *op_qualifier,
				    CONST char *def_file_name,
				    CONST Int16 overwrite_flag,
				    File_Info *op_file));
void set_enc_tbl                ARGS ((CONST Qualifier_Struct *enc_qualifier,
				    CONST Int16 enc_tbl_len,
				    char *encode_table));
void set_format                 ARGS ((CONST Qualifier_Struct *fmt_qualifier,
				    CONST File_Info *ex_file,
				    CONST char *fmt_str_array[],
				    File_Info *ip_file));
void set_hdr_file_spec          ARGS ((CONST Qualifier_Struct *hdr_qualifier,
				    CONST File_Info *ip_file,
				    char **hdr_file_spec));
void set_ip_filespec            ARGS ((CONST Qualifier_Struct *ip_qualifier,
				    File_Info *ip_file));
void set_mode                   ARGS ((CONST Qualifier_Struct *mode_qualifier,
				    CONST File_Info *ex_file,
				    CONST char *mode_str_array[],
				    File_Info *ip_file));
void set_record_len             ARGS ((CONST Qualifier_Struct *rec_qualifer,
				    CONST File_Info *ex_file,
				    File_Info *ip_file));
void set_split_size             ARGS ((CONST File_Info *op_file,
				    Qualifier_Struct *split_qualifier,
				    Int32 *split_size));
void set_timestamp              ARGS ((CONST Qualifier_Struct *time_qualifier,
				    CONST File_Info *ex_file,
				    File_Info *ip_file));
void start_next_op_file         ARGS ((CONST Int16 overwrite_flag,
				    File_Info *op_file,
				    Int16 *op_part_no));
void terminate                  ARGS ((void));
void usage_error                ARGS ((CONST char *reason));
void wr_headers                 ARGS ((CONST Int16 part_no));


/*-
**============================================================================
**
** FUNCTION
**
**      encode
**
** DESCRIPTION
**
**      Encode the bytes from the input file and write them to the output
**      file.
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    encode (void)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    encode ()
#endif                          /* (ANSI_SYNTAX) */
{
    char                   *ip_buffer;
    char                   *buf_ptr;
    Int32                   buffer_size;
    Int32                   bytes_left;
    Int32                   bytes_read;
    Int32                   bytes_to_encode;
    Int16		    encode_state;
    SIZE_T                  idx;
    char                    tmp_buffer[MAX_ENCODED_BYTES + 2];
    char		    vv_buffer[2 * MAX_ENCODED_BYTES];
    Int32                   vve_line_bytes;
    Int32                   vve_room_needed;

    /*-
    **------------------------------------------------------------------------
    ** If the input file has variable length format records, then allocate
    ** a buffer to hold the largest possible record.  For fixed or stream
    ** format files, allocate a smaller buffer of IO_BUFFER_SIZE bytes.
    **------------------------------------------------------------------------
    */
    if (G_ip_file.format == FMT_VARIABLE)
    {
	buffer_size = (Int32) MAX_VARIABLE_RECORD_LEN;
    }
    else
    {
	buffer_size = (Int32) IO_BUFFER_SIZE;
    }
    ip_buffer = allocate_buffer ((SIZE_T) buffer_size, "ip_buffer");

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    for (;;)
    {

	/*-
	**--------------------------------------------------------------------
	**
	**--------------------------------------------------------------------
	*/
	if (G_ip_file.format == FMT_VARIABLE)
	{
	    bytes_read = read_record (MAX_VARIABLE_RECORD_LEN, ip_buffer,
				      &G_ip_file);
	    if (bytes_read < 0L)
	    {
		break;
	    }
	    G_ip_file.bytecount += bytes_read;
	    DEBUG_2 ("encode: record length = %5ld, total bytes read = %10ld",
		     bytes_read, G_ip_file.bytecount);
	    encode_state = ENCODE_REC_LEN;
	}

	/*-
	**--------------------------------------------------------------------
	**
	**--------------------------------------------------------------------
	*/
	else
	{
	    bytes_read = read_bytes ((Int32) MAX_ENCODED_BYTES, ip_buffer,
				     &G_ip_file);
	    if (bytes_read <= 0L)
	    {
		break;
	    }
	    G_ip_file.bytecount += (long) bytes_read;
	}

	/*-
	**--------------------------------------------------------------------
	** If the /TRANSLATE command qualifier was specified, translate the
	** bytes read to the destination character set.
	**--------------------------------------------------------------------
	*/
	if (Q_TRANSLATE)
	{
	    for (idx = 0; idx < (SIZE_T) bytes_read; idx++)
	    {
		ip_buffer[idx] =
			    (char) G_xlt_table.xlt_tbl[(Byte) ip_buffer[idx]];
	    }
	}

	/*-
	**--------------------------------------------------------------------
	** Compute the CRC of the bytes to be encoded in the VVE file.
	**--------------------------------------------------------------------
	*/
	for (idx = 0; idx < (SIZE_T) bytes_read; idx++)
	{
	    UPDATE_CRC32 (G_ip_file.crc32, ip_buffer[idx]);
	}

	/*-
	**--------------------------------------------------------------------
	** Write the remaining bytes in chunks of MAX_ENCODED_BYTES.  If the
	** file is in variable length format, each record is likely to have a
	** shorter last line.
	**--------------------------------------------------------------------
	*/
	bytes_left = bytes_read;
	buf_ptr = ip_buffer;

	/*-
	**--------------------------------------------------------------------
	**
	**--------------------------------------------------------------------
	*/
	while (bytes_left > 0L)
	{

	    /*-
	    **----------------------------------------------------------------
	    ** 
	    **----------------------------------------------------------------
	    */
	    if ((G_ip_file.format == FMT_VARIABLE)
		 && (encode_state == ENCODE_REC_LEN))
	    {
		bytes_to_encode = MINIMUM (bytes_left, MAX_ENCODED_BYTES - 2);
		tmp_buffer[0] = (char) (bytes_read / 256);
		tmp_buffer[1] = (char) (bytes_read % 256);

		for (idx = 0; idx < (SIZE_T) bytes_to_encode; idx++)
		{
		    tmp_buffer[2 + idx] = ip_buffer[idx];
		}
		vve_line_bytes = enc_line (tmp_buffer, bytes_to_encode + 2,
					   vv_buffer);
		bytes_left -= bytes_to_encode;
		buf_ptr += (SIZE_T) bytes_to_encode;
		encode_state = ENCODE_DATA;
	    }
	    /*-
	    **----------------------------------------------------------------
	    **
	    **----------------------------------------------------------------
	    */
	    else
	    {
		bytes_to_encode = MINIMUM (bytes_left, MAX_ENCODED_BYTES);
		vve_line_bytes = enc_line (buf_ptr, bytes_to_encode,
					   vv_buffer);
		bytes_left -= bytes_to_encode;
		buf_ptr += (SIZE_T) bytes_to_encode;
	    }

	    /*-
	    **----------------------------------------------------------------
	    ** If a maximum VVE output file size has been specified, check
	    ** whether this encoded line will cause that maximum size to be
	    ** exceeded.
	    **----------------------------------------------------------------
	    */
	    if (Q_SPLIT)
	    {
		vve_room_needed = vve_line_bytes + TEXT_LINE_OVERHEAD
				+ VV_POSTAMBLE_LEN;

		if ((G_oppart_bytes + vve_room_needed) >= G_split_size)
		{
		    start_next_op_file (G_overwrite_flag, &G_op_file,
					&G_oppt_no);
		    G_op_file.bytecount += G_oppart_bytes;
		    G_oppart_bytes = 0L;
		    wr_headers (G_oppt_no);
		}
	    }

	    /*-
	    **----------------------------------------------------------------
	    ** Write the encoded line to the VVE file.
	    **----------------------------------------------------------------
	    */
	    VVEOUT (vv_buffer);
	}			/* while (bytes_left > 0L) */
    }                           /* for (;;) */
}                               /* encode */




/*-
**============================================================================
**
** FUNCTION
**
**      initialize
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    initialize (void)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    initialize ()
#endif                          /* (ANSI_SYNTAX) */
{
    File_Info               ex_file_info;
    char                   *def_file_name;
    File_Info               xlt_file;
        
    /*-
    **------------------------------------------------------------------------
    ** Set up the input file specification from the command line argument.  
    ** The 'pipe_flag' structure field is also set up.
    **------------------------------------------------------------------------
    */
    set_ip_filespec (&G_cmd_ipfile, &G_ip_file);
    def_file_name = allocate_buffer ((MAX_PATH + 1), "def_file_name");
    (void) split_file_spec (G_ip_file.file_spec, NULL, def_file_name,
                            NULL, NULL);

    /*-
    **------------------------------------------------------------------------
    ** 
    **------------------------------------------------------------------------
    */
    if (Q_OVERWRITE)
    {
        G_overwrite_flag = OVERWRITE_YES;
    }
    else
    if (G_ip_file.pipe_flag == TRUE)
    {
        G_overwrite_flag = OVERWRITE_NO;
    }
    else
    {
        G_overwrite_flag = OVERWRITE_ASK;
    }

    /*-
    **------------------------------------------------------------------------
    ** If the 'log' qualifier was specified, open the log file.  On systems
    ** which use 'STICKY_DEFAULTS', this must be done after the input file
    ** specification is known.
    **------------------------------------------------------------------------
    */
    open_log_file (&G_cmdqual[CMDQ_LOG], def_file_name, G_overwrite_flag,
                   &G_log_file);

    /*-
    **------------------------------------------------------------------------
    ** 
    **------------------------------------------------------------------------
    */
    LOGMSG_3 ("VVencode %s, %s release (%s)\n",
              VV_VERSION_STR, VV_IMPLEMENTATION, VV_DATE);
    LOGMSG_1 ("I/P file:                          %s", G_ip_file.file_spec);
    LOGMSG_1 ("Overwrite existing files:          %s",
              (Q_OVERWRITE ? "yes" : "no"));
    LOGMSG_1 ("Log file:                          %s", G_log_file.file_spec);
    LOGMSG   ("Encoding method:                   VVcode\n");

    /*-
    **------------------------------------------------------------------------
    ** Ensure that the local timezone offset from GMT is initialized.
    **------------------------------------------------------------------------
    */
    (void) tz_offset (time ((TIME_T) NULL));

    /*-
    **------------------------------------------------------------------------
    ** If we're not reading from standard input, attempt to determine the 
    ** the key file parameters by examining the file.
    **------------------------------------------------------------------------
    */
    if (G_ip_file.pipe_flag != TRUE)
    {
        strcpy (ex_file_info.file_spec, G_ip_file.file_spec);
        examine_file (&ex_file_info);

        LOGMSG_1 ("I/P file mode:                     %s",
                  (ex_file_info.mode != INV_MODE) ?
                  (char *) G_mode_str[ex_file_info.mode] : "unknown");
        LOGMSG_1 ("I/P file format:                   %s",
                  (ex_file_info.format != INV_FORMAT) ?
                  (char *) G_fmt_str[ex_file_info.format] : "unknown");

        if (ex_file_info.lng_rec_len != INV_RECORD_LEN)
        {
            LOGMSG_1 ("I/P file longest record length:    %ld",
                      ex_file_info.lng_rec_len);
        }
        else
        {
            LOGMSG ("I/P file longest record length:    unknown");
        }

        if (ex_file_info.max_rec_len != INV_RECORD_LEN)
        {
            LOGMSG_1 ("I/P file maximum record length:    %ld",
                      ex_file_info.max_rec_len);
        }
        else
        {
            LOGMSG ("I/P file maximum record length:    unknown");
        }

        LOGMSG_1 ("I/P file timestamp:                %s\n",
                  (ex_file_info.mod_time != INV_TIMESTAMP) ?
                  vv_timestamp (ex_file_info.mod_time) : "unknown");
    }

    /*-
    **------------------------------------------------------------------------
    ** Set the key File_Info parameters, taking into account the file
    ** parameters and the command line qualifier values.
    **------------------------------------------------------------------------
    */
    set_mode (&G_cmdqual[CMDQ_MODE], &ex_file_info, G_mode_str, &G_ip_file);
    set_format (&G_cmdqual[CMDQ_FORMAT], &ex_file_info, G_fmt_str, &G_ip_file);
    set_record_len (&G_cmdqual[CMDQ_RECORDLEN], &ex_file_info, &G_ip_file);
    set_timestamp (&G_cmdqual[CMDQ_TIMESTAMP], &ex_file_info, &G_ip_file);

    /*-
    **------------------------------------------------------------------------
    ** Get and act upon the other command line qualifiers.
    **------------------------------------------------------------------------
    */
    set_hdr_file_spec (&G_cmdqual[CMDQ_HDRFILE], &G_ip_file, &G_hdrf_spec);

    /*-
    **------------------------------------------------------------------------
    ** 
    **------------------------------------------------------------------------
    */
    set_enc_tbl (&G_cmdqual[CMDQ_ENCODE], ENCODE_TABLE_LEN, G_encode_table);
    set_dec_tbl (ENCODE_TABLE_LEN, G_encode_table, 
                 DECODE_TABLE_LEN, G_decode_table);

    /*-
    **------------------------------------------------------------------------
    ** 
    **------------------------------------------------------------------------
    */
    if (Q_TRANSLATE)
    {
        strcpy (xlt_file.file_spec, G_cmdqual[CMDQ_TRANSLATE].value);
        set_xlt_tbl (&xlt_file, &G_xlt_table, G_xlthdr);

        if (STRCMPI (VV_CHARACTERSET, G_xlt_table.xlt_from) != 0)
        {
            WARNMSG_2 ("translation file translates from `%s' to `%s'",
                       G_xlt_table.xlt_from, G_xlt_table.xlt_to);
            INFOMSG_1 ("local character set is `%s'", VV_CHARACTERSET);
        }
    }
    else
    {
        LOGMSG ("Translation performed:             none");
    }
    
    /*-
    **------------------------------------------------------------------------
    ** 
    **------------------------------------------------------------------------
    */
    open_ip_file (&G_cmd_ipfile, &G_ip_file);

    /*-
    **------------------------------------------------------------------------
    ** 
    **------------------------------------------------------------------------
    */
    open_op_file (&G_cmd_opfile, def_file_name, G_overwrite_flag, &G_op_file);
    set_split_size (&G_op_file, &G_cmdqual[CMDQ_SPLIT], &G_split_size);

    /*-
    **------------------------------------------------------------------------
    ** Initialize global variables.
    **------------------------------------------------------------------------
    */
    init_crc32_table (CRC32_TABLE_SIZE, CRC32_POLYNOMIAL, G_crc32_table);
    G_ip_file.crc32 = CRC32_MASK;
    G_ip_file.bytecount = 0L;
    G_op_file.bytecount = 0L;
    G_oppart_bytes = 0L;
    G_oppt_no = 1;
    free (def_file_name);

    /*-
    **------------------------------------------------------------------------
    ** Write the VVE file header lines.
    **------------------------------------------------------------------------
    */
    wr_headers (G_oppt_no);
}                               /* initialize */



/*-
**============================================================================
**
** FUNCTION
**
**      open_ip_file
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    open_ip_file (CONST Qualifier_Struct *ip_qualifier,
                                          File_Info *ip_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    open_ip_file (ip_qualifier, ip_file)
        CONST Qualifier_Struct *ip_qualifier;
        File_Info              *ip_file;
#endif                          /* (ANSI_SYNTAX) */
{

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (ip_qualifier->present != TRUE)
    {
        INTERNAL_ERROR ("open_ip_file");
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (ip_file->pipe_flag == TRUE)
    {
        ip_file->file_ptr = stdin;
    }
    else
    {
        DEBUG_1 ("encode: opening input file `%s'", ip_file->file_spec);
        ip_file->file_ptr = f_open_in (ip_file);
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (ip_file->file_ptr == (FILE *) NULL)
    {
        FATALMSG_1 ("error opening input file `%s'", ip_file->file_spec);
        vv_exit ();
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if ((ip_file->pipe_flag == TRUE) || (is_a_file (ip_file) == FALSE))
    {
        DEBUG_1 ("open_ip_file: `%s' is not a regular file",
                 ip_file->file_spec);
        ip_file->pipe_flag = TRUE;
        set_pipe_mode (ip_file);
    }
}                               /* open_ip_file */



/*-
**============================================================================
**
** FUNCTION
**
**      open_op_file
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    open_op_file (CONST Qualifier_Struct *op_qualifier,
                                          CONST char *def_file_name,
                                          CONST Int16 overwrite_flag,
                                          File_Info *op_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    open_op_file (op_qualifier, def_file_name,
                                          overwrite_flag, op_file)
        CONST Qualifier_Struct *op_qualifier;
        CONST char             *def_file_name;
        CONST Int16             overwrite_flag;
        File_Info              *op_file;
#endif                          /* (ANSI_SYNTAX) */
{

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    op_file->file_ptr = (FILE *) NULL;
    op_file->mode = MODE_TEXT;
    op_file->format = DEF_TEXT_FMT;
    op_file->max_rec_len = INV_RECORD_LEN;
    op_file->lng_rec_len = INV_RECORD_LEN;

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (op_qualifier->present == TRUE)
    {

        /*-
        **--------------------------------------------------------------------
        **
        **--------------------------------------------------------------------
        */
#if (SUPPORT_PIPING)
        if (STRCMP (op_qualifier->value, STDIO_SYMBOL) == 0)
        {
            op_file->pipe_flag = TRUE;
            strcpy (op_file->file_spec, "stdout");
            op_file->file_ptr = stdout;
            op_file->format = FMT_STREAM;
        }
        else
#endif                          /* (SUPPORT_PIPING) */

        {
            strcpy (op_file->file_spec, op_qualifier->value);

            /*-
            **----------------------------------------------------------------
            ** The default file spec for the VVencoded file is the name of the
            ** input file with the default extension for VVencoded files.
            **----------------------------------------------------------------
            */
#if (STICKY_DEFAULTS)
            apply_defaults (def_file_name, DEF_VV_EXT, op_file->file_spec);
#endif                          /* (STICKY_DEFAULTS) */

            DEBUG_1 ("open_op_file: opening output file `%s'",
                     op_file->file_spec);
            op_file->file_ptr = f_open_out (overwrite_flag, op_file);
        }
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    else
    {

#if (STDOUT_OUTPUT)
        strcpy (op_file->file_spec, "stdout");
        op_file->file_ptr = stdout;
        op_file->pipe_flag = TRUE;

#else                           /* NOT (STDOUT_OUTPUT) */

        strcpy (op_file->file_spec, "");
        make_file_spec (NULL, def_file_name, NULL, NULL, op_file->file_spec);
        apply_defaults (NULL, DEF_VV_EXT, op_file->file_spec);
        DEBUG_1 ("open_op_file: opening output file `%s'", op_file->file_spec);
        op_file->file_ptr = f_open_out (overwrite_flag, op_file);

#endif                          /* (STDOUT_OUTPUT) */
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (op_file->file_ptr == (FILE *) NULL)
    {
        FATALMSG_1 ("error opening output file `%s'", op_file->file_spec);
        vv_exit ();
    }
    LOGMSG_1 ("\nVVE O/P file:                      %s", op_file->file_spec);

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if ((op_file->pipe_flag == TRUE) || (is_a_file (op_file) == FALSE))
    {
        DEBUG_1 ("open_op_file: `%s' is not a regular file", 
                 op_file->file_spec);
        op_file->pipe_flag = TRUE;
        set_pipe_mode (op_file);
    }
}                               /* open_op_file */



/*-
**============================================================================
**
** FUNCTION
**
**      set_enc_tbl
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    set_enc_tbl (CONST Qualifier_Struct *enc_qualifier,
                                         CONST Int16 enc_tbl_len,
                                         char *encode_table)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    set_enc_tbl (enc_qualifier, enc_tbl_len,
                                         encode_table)
        CONST Qualifier_Struct *enc_qualifier;
        CONST Int16             enc_tbl_len;
        char                   *encode_table;
#endif                          /* (ANSI_SYNTAX) */
{
    File_Info               enc_file;
    int                     status;

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (enc_qualifier->present == TRUE)
    {
        strcpy (enc_file.file_spec, enc_qualifier->value);

        /*-
        **--------------------------------------------------------------------
        ** If an extension has not been provided, use the default extension
        ** for encoding table files.
        **--------------------------------------------------------------------
        */
#if (STICKY_DEFAULTS)
        apply_defaults (NULL, DEF_ENC_EXT, enc_file.file_spec);
#endif                          /* (STICKY_DEFAULTS) */

        status = rd_enc_tbl (&enc_file, enc_tbl_len, encode_table);

        if (!status)
        {
            FATALMSG_1 ("error reading encoding table file `%s'",
                        enc_file.file_spec);
            vv_exit ();
        }
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    else
    {
        DEBUG ("encoding table not specified on the command line");
        strcpy (enc_file.file_spec, "default VVCODE");
        strcpy ((char *) encode_table, VV_ENCODE_TABLE);
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    LOGMSG_1 ("\nEncoding table source:             %s", enc_file.file_spec);
    LOGMSG_1 ("Encoding table contents:           %.32s", encode_table);
    LOGMSG_1 ("                                   %.32s", encode_table + 32);
}                               /* set_enc_tbl */



/*-
**============================================================================
**
** FUNCTION
**
**      set_format
**
** DESCRIPTION
**
**      Set the format (fixed/stream/variable) to use when reading and
**      encoding the input file.  The format may be set from the command
**      line, examine_file() or the default specified in machine.h.  The
**      logic used is:
**
**          Cmd     ExFile  Pipe    Result
**          ------------------------------
**           Y        *       *       Cmd
**           N        N       N       Def
**           N        Y       N       ExF
**           N        *       Y       Stm
**          ------------------------------
**
**      NOTE:   if the input file is standard input, examine_file() is not
**              called and stream format is used unless it is overrriden
**              from the command line.
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    set_format (CONST Qualifier_Struct *fmt_qualifier,
                                        CONST File_Info *ex_file,
                                        CONST char *fmt_str_array[],
                                        File_Info *ip_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    set_format (fmt_qualifier, ex_file, fmt_str_array,
                                        ip_file)
        CONST Qualifier_Struct *fmt_qualifier;
        CONST File_Info        *ex_file;
        CONST char             *fmt_str_array[];
        File_Info              *ip_file;
#endif                          /* (ANSI_SYNTAX) */
{
    Int16                   cmd_format;

    /*-
    **------------------------------------------------------------------------
    ** Get the format specified on the command line (if any).  The number of
    ** 'if' statements could be reduced by making the second expression more
    ** complicated at a cost of less readable code.
    **------------------------------------------------------------------------
    */
    cmd_format = getc_format (fmt_qualifier, fmt_str_array);

    if (cmd_format != INV_FORMAT)
    {
        ip_file->format = cmd_format;
    }
    else
    if (ip_file->pipe_flag == TRUE)
    {
        ip_file->format = FMT_STREAM;
    }
    else
    if (ex_file->format != INV_FORMAT)
    {
        ip_file->format = ex_file->format;
    }
    else
    {
        if (ip_file->mode == MODE_BINARY)
        {
            ip_file->format = DEF_BINARY_FMT;
        }
        else
        {
            ip_file->format = DEF_TEXT_FMT;
        }
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    LOGMSG_1 ("VVE encoding format:               %s",
              (ip_file->format != INV_FORMAT) ?
              (char *) fmt_str_array[ip_file->format] : "not recorded");
}                               /* set_format */



/*-
**============================================================================
**
** FUNCTION
**
**      set_hdr_file_spec
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    set_hdr_file_spec (
                                        CONST Qualifier_Struct *hdr_qualifier,
                                        CONST File_Info *ip_file,
                                        char **hdr_file_spec)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    set_hdr_file_spec (hdr_qualifier, ip_file,
                                               hdr_file_spec)
        CONST Qualifier_Struct *hdr_qualifier;
        CONST File_Info        *ip_file;
        char                  **hdr_file_spec;
#endif                          /* (ANSI_SYNTAX) */
{
    char                    file_name[MAX_NAME + 1];
    char                    file_ext[MAX_EXT + 1];

#if (UPPERCASE_FILESYSTEM)
    char                   *tmp_ptr;
#endif                          /* (UPPERCASE_FILESYSTEM) */


    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    *hdr_file_spec = allocate_buffer ((MAX_IP_LINE_LEN + 1), "hdr_file_spec");
    if (hdr_qualifier->present == TRUE)
    {
        strcpy (*hdr_file_spec, hdr_qualifier->value);
    }
    else
    {
        (void) split_file_spec (ip_file->file_spec, NULL, file_name,
                                file_ext, NULL);
        make_file_spec (NULL, file_name, file_ext, NULL, *hdr_file_spec);

#if (UPPERCASE_FILESYSTEM)
        for (tmp_ptr = *hdr_file_spec; *tmp_ptr != '\0'; tmp_ptr++)
        {
            *tmp_ptr = (char) TOLOWER ((int) *tmp_ptr);
        }
#endif                          /* (UPPERCASE_FILESYSTEM) */

    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    LOGMSG_1 ("VVE header file spec:              %s", *hdr_file_spec);
}                               /* set_hdr_file_spec */



/*-
**============================================================================
**
** FUNCTION
**
**      set_ip_filespec
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    set_ip_filespec (
                                        CONST Qualifier_Struct *ip_qualifier,
                                        File_Info *ip_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    set_ip_filespec (ip_qualifier, ip_file)
        CONST Qualifier_Struct *ip_qualifier;
        File_Info              *ip_file;
#endif                          /* (ANSI_SYNTAX) */
{
    if (ip_qualifier->present != TRUE)
    {
        INTERNAL_ERROR ("set_ip_filespec");
    }

#if (SUPPORT_PIPING)

    if (STRCMP (ip_qualifier->value, STDIO_SYMBOL) == 0)
    {
        strcpy (ip_file->file_spec, "stdin");
        ip_file->pipe_flag = TRUE;
    }
    else
    {
        strcpy (ip_file->file_spec, ip_qualifier->value);
    }

#else                           /* NOT (SUPPORT_PIPING) */

    strcpy (ip_file->file_spec, ip_qualifier->value);

#endif                          /* (SUPPORT_PIPING) */

}                               /* set_ip_filespec */



/*-
**============================================================================
**
** FUNCTION
**
**      set_mode
**
** DESCRIPTION
**
**      Set the mode (text/binary) to use when reading and encoding the input
**      file.  The mode may be set from the command line, examine_file() or
**      the default specified in machine.h.  The logic used is:
**
**          Cmd     ExFile  Pipe    Result
**          ------------------------------
**           Y        *       *       Cmd
**           N        N       N       Def
**           N        Y       N       ExF
**           N        *       Y       Def
**          ------------------------------
**
**      NOTE:   if the input file is standard input, examine_file() is not
**              called and the default mode is used unless it is overrriden
**              from the command line.
**
** INPUT PARAMETERS
**
**      ex_file_info    -   a File_Info structure containing the results of
**                          the call to examine_file().  If the input file
**                          was specified as standard input, this structure
**                          will not be examined.
**
** OUTPUT PARAMETERS
**
**      none
**
** RETURN VALUE
**
**      none
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    set_mode (CONST Qualifier_Struct *mode_qualifier,
                                      CONST File_Info *ex_file,
                                      CONST char *mode_str_array[],
                                      File_Info *ip_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    set_mode (mode_qualifier, ex_file, mode_str_array,
                                      ip_file)
        CONST Qualifier_Struct *mode_qualifier;
        CONST File_Info        *ex_file;
        CONST char             *mode_str_array[];
        File_Info              *ip_file;
#endif                          /* (ANSI_SYNTAX) */
{
    Int16                   cmd_mode;

    /*-
    **------------------------------------------------------------------------
    ** Get the mode specified on the command line (if any).  The number of
    ** 'if' statements could be reduced by making the second expression more
    ** complicated at a cost of less readable code.
    **------------------------------------------------------------------------
    */
    cmd_mode = getc_mode (mode_qualifier, mode_str_array);

    if (cmd_mode != INV_MODE)
    {
        ip_file->mode = cmd_mode;
    }
    else
    if (ip_file->pipe_flag == TRUE)
    {
        ip_file->mode = DEF_MODE;
    }
    else
    if (ex_file->mode != INV_MODE)
    {
        ip_file->mode = ex_file->mode;
    }
    else
    {
        ip_file->mode = DEF_MODE;
    }

    /*-
    **------------------------------------------------------------------------
    ** One final check to make sure that the mode is valid.  The mode can only
    ** come from a validated command line value, examine_file() or the default
    ** setting, so it should not be wrong.
    **------------------------------------------------------------------------
    */
    switch (ip_file->mode)
    {
        case MODE_BINARY:
        case MODE_TEXT:
            break;
        default:
            FATALMSG_1 ("invalid file encoding mode set (%d)", ip_file->mode);
            vv_exit ();
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    LOGMSG_1 ("VVE encoding mode:                 %s",
              mode_str_array[ip_file->mode]);
}                               /* set_mode */



/*-
**============================================================================
**
** FUNCTION
**
**      set_record_len
**
** DESCRIPTION
**
**      Set the maximum record length to use when reading and encoding the 
**      input file.  The record length may be set from the command line, 
**      examine_file() or the default specified in machine.h.  The logic used
**      is:
**
**          Cmd     ExFile  Pipe    Result
**          ------------------------------
**           Y        *       *       Cmd
**           N        N       N       Def
**           N        Y       N       ExF
**           N        *       Y       Def
**          ------------------------------
**
**      NOTE:   if the input file is standard input, examine_file() is not
**              called and the default record length is used unless it is 
**              overrriden from the command line.
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    set_record_len (
                                        CONST Qualifier_Struct *rec_qualifier,
                                        CONST File_Info *ex_file,
                                        File_Info *ip_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    set_record_len (rec_qualifier, ex_file, ip_file)
        CONST Qualifier_Struct *rec_qualifier;
        CONST File_Info        *ex_file;
        File_Info              *ip_file;
#endif                          /* (ANSI_SYNTAX) */
{
    Int32                   cmd_reclen;

    /*-
    **------------------------------------------------------------------------
    ** Get the record length specified on the command line (if any).  The 
    ** number of 'if' statements could be reduced by making the second 
    ** expression more complicated at a cost of less readable code.
    **------------------------------------------------------------------------
    */
    cmd_reclen = getc_reclen (rec_qualifier, ip_file);

    if (cmd_reclen != INV_RECORD_LEN)
    {
        ip_file->max_rec_len = cmd_reclen;
	ip_file->lng_rec_len = INV_RECORD_LEN;
    }
    else
    if (ip_file->pipe_flag == TRUE)
    {
        ip_file->max_rec_len = INV_RECORD_LEN;
        ip_file->lng_rec_len = INV_RECORD_LEN;
    }
    else
    if (ex_file->max_rec_len != INV_RECORD_LEN)
    {
        ip_file->max_rec_len = ex_file->max_rec_len;
        ip_file->lng_rec_len = ex_file->lng_rec_len;
    }
    else
    {
        ip_file->max_rec_len = INV_RECORD_LEN;
        ip_file->lng_rec_len = INV_RECORD_LEN;
    }

    /*-
    **------------------------------------------------------------------------
    ** One final check to make sure that the maximum record length is valid.
    ** The longest record length can only come from examine_file() so it 
    ** should not be wrong.  There is an exception: the default may be set to
    ** INV_RECORD_LEN so that f_open_in() will use a default value.
    **------------------------------------------------------------------------
    */
    if (ip_file->lng_rec_len == INV_RECORD_LEN)
    {
        LOGMSG ("VVE longest record length:         not recorded");
    }
    else if (ip_file->format == FMT_VARIABLE)
    {
        if ((ip_file->lng_rec_len < MIN_VARIABLE_RECORD_LEN)
            || (ip_file->lng_rec_len > MAX_VARIABLE_RECORD_LEN))
        {
            FATALMSG_1 ("invalid longest record length set (%ld)",
                        ip_file->lng_rec_len);
            vv_exit ();
        }
        LOGMSG_1 ("VVE longest record length:         %ld",
                  ip_file->lng_rec_len);
    }
    else if (ip_file->format == FMT_FIXED)
    {
        if ((ip_file->lng_rec_len < MIN_FIXED_RECORD_LEN)
            || (ip_file->lng_rec_len > MAX_FIXED_RECORD_LEN))
        {
            FATALMSG_1 ("invalid longest record length set (%ld)",
                        ip_file->lng_rec_len);
            vv_exit ();
        }
        LOGMSG_1 ("VVE longest record length:         %ld",
                  ip_file->lng_rec_len);
    }


    /*-
    **------------------------------------------------------------------------
    ** One final check to make sure that the maximum record length is valid.
    ** The record length can only come from a validated command line value, 
    ** examine_file() or the default setting, so it should not be wrong.
    ** There is an exception: the default may be set to INV_RECORD_LEN so that
    ** f_open_in() will use a default value.
    **------------------------------------------------------------------------
    */
    if (ip_file->max_rec_len == INV_RECORD_LEN)
    {
        LOGMSG ("VVE maximum record length:         not recorded");
    }
    else if (ip_file->format == FMT_VARIABLE)
    {
        if ((ip_file->max_rec_len < MIN_VARIABLE_RECORD_LEN)
            || (ip_file->max_rec_len > MAX_VARIABLE_RECORD_LEN))
        {
            FATALMSG_1 ("invalid maximum record length set (%ld)",
                        ip_file->max_rec_len);
            vv_exit ();
        }
        LOGMSG_1 ("VVE maximum record length:         %ld",
                  ip_file->max_rec_len);
    }
    else if (ip_file->format == FMT_FIXED)
    {
        if ((ip_file->max_rec_len < MIN_FIXED_RECORD_LEN)
            || (ip_file->max_rec_len > MAX_FIXED_RECORD_LEN))
        {
            FATALMSG_1 ("invalid maximum record length set (%ld)",
                        ip_file->max_rec_len);
            vv_exit ();
        }
        LOGMSG_1 ("VVE maximum record length:         %ld",
                  ip_file->max_rec_len);
    }
}                               /* set_record_len */



/*-
**============================================================================
**
** FUNCTION
**
**      set_split_size
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    set_split_size (CONST File_Info *op_file,
					    Qualifier_Struct *split_qualifier,
                                            Int32 *split_size)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    set_split_size (op_file, split_qualifier,
                                            split_size)
        CONST File_Info        *op_file;
        Qualifier_Struct       *split_qualifier;
        Int32                  *split_size;
#endif                          /* (ANSI_SYNTAX) */
{
    Int32                   tmp_split_size;
    int                     status;

    if (split_qualifier->present != TRUE)
    {
        DEBUG ("split size not specified on the command line");
        LOGMSG ("VVE O/P file maximum size:         no maximum");
        return;
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (op_file->pipe_flag == TRUE)
    {
        split_qualifier->present = FALSE;
        WARNMSG ("not encoding to a file - file splitting disabled");
        LOGMSG ("VVE O/P file maximum size:         no maximum");
	return;
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    tmp_split_size = -1;
    status = sscanf (split_qualifier->value, "%ld", &tmp_split_size);

    if ((status != 1) || (tmp_split_size <= 0))
    {
        USAGE_1 ("invalid split size specified `%s'", split_qualifier->value);
    }

    if (tmp_split_size < (Int32) MIN_SPLIT_SIZE)
    {
        USAGE_1 ("split size must be at least %ld kB", (Int32) MIN_SPLIT_SIZE);
    }
    *split_size = tmp_split_size * 1024L;

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    LOGMSG_1 ("VVE O/P file maximum size:         %ld kB", tmp_split_size);
}                               /* set_split_size */



/*-
**============================================================================
**
** FUNCTION
**
**      set_timestamp
**
** DESCRIPTION
**
**      Set the timestamp to record in the VVE file.  The timestamp may be
**      set from the command line or by examine_file().  The logic used is:
**
**          Cmd     ExFile  Pipe    Result
**          ------------------------------
**           Y        *       *       Cmd
**           N        N       N       Inv
**           N        Y       N       ExF
**           N        *       Y       Inv
**          ------------------------------
**
**      NOTE:   if the input file is standard input, examine_file() is not
**              called and the timestamp is set to INV_TIMESTAMP unless it is
**              overrriden from the command line.
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
void                        set_timestamp (
                                    CONST Qualifier_Struct *time_qualifier,
                                    CONST File_Info *ex_file,
                                    File_Info *ip_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    set_timestamp (time_qualifier, ex_file, ip_file)
        CONST Qualifier_Struct *time_qualifier;
        CONST File_Info        *ex_file;
        File_Info              *ip_file;
#endif                          /* (ANSI_SYNTAX) */
{
    TIME_T                  cmd_timestamp;

    /*-
    **------------------------------------------------------------------------
    ** Get the timestamp specified on the command line (if any).  The number
    ** of 'if' statements could be reduced by making the second expression 
    ** more complicated at a cost of less readable code.
    **------------------------------------------------------------------------
    */
    cmd_timestamp = getc_timestamp (time_qualifier);

    if (cmd_timestamp != INV_TIMESTAMP)
    {
        ip_file->mod_time = cmd_timestamp;
    }
    else
    if (ip_file->pipe_flag == TRUE)
    {
        ip_file->mod_time = INV_TIMESTAMP;
    }
    else
    if (ex_file->mod_time != INV_TIMESTAMP)
    {
        ip_file->mod_time = ex_file->mod_time;
    }
    else
    {
        ip_file->mod_time = INV_TIMESTAMP;
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    LOGMSG_1 ("VVE file timestamp:                %s",
              (ip_file->mod_time != INV_TIMESTAMP) ?
              vv_timestamp (ip_file->mod_time) : "not recorded");
}                               /* set_timestamp */



/*-
**============================================================================
**
** FUNCTION
**
**      start_next_op_file
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    start_next_op_file (CONST Int16 overwrite_flag,
                                                File_Info *op_file,
                                                Int16 *op_part_no)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    start_next_op_file (overwrite_flag, op_file,
                                                op_part_no)
        CONST Int16             overwrite_flag;
	File_Info              *op_file;
	Int16                  *op_part_no;
#endif                          /* (ANSI_SYNTAX) */
{
    char                    next_file_ext[MAX_EXT + 1];
    char		    tmp_buffer[32];

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (++(*op_part_no) > MAX_VV_PARTS)
    {
	FATALMSG_1 ("maximum number of parts reached (%d)", MAX_VV_PARTS);
	vv_exit ();
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    (void) enc_line ("", 0L, tmp_buffer);
    VVEOUT (tmp_buffer);

    VVEOUT_2 ("%sskipto %d\n", VV_PREFIX, *op_part_no);
    f_close (op_file);

    LOGMSG_2 ("Part %3d bytes written:            %ld",
	      (*op_part_no - 1), G_oppart_bytes);

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    SPRINTF (next_file_ext, VV_EXT_FMT, *op_part_no);
    force_file_ext (next_file_ext, op_file->file_spec);
    op_file->file_ptr = f_open_out (overwrite_flag, op_file);

    if (op_file->file_ptr == (FILE *) NULL)
    {
	FATALMSG_1 ("error opening output file `%s'", op_file->file_spec);
	vv_exit ();
    }
}                               /* start_next_op_file */



/*-
**============================================================================
**
** FUNCTION
**
**      terminate
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    terminate (void)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    terminate ()
#endif                          /* (ANSI_SYNTAX) */
{
    char		    tmp_buffer[32];

    G_ip_file.crc32 = G_ip_file.crc32 ^ CRC32_MASK;

    /*-
    **------------------------------------------------------------------------
    ** Write the line of zero encoded bytes to signify the end of encoded
    ** data.
    **------------------------------------------------------------------------
    */
    (void) enc_line ("", 0L, tmp_buffer);
    VVEOUT (tmp_buffer);

    /*-
    **------------------------------------------------------------------------
    ** End header.
    **------------------------------------------------------------------------
    */
    VVEOUT_2 ("%s%s\n", VV_PREFIX, G_vvehdr[HDR_END].h_string);

    /*-
    **------------------------------------------------------------------------
    ** Bytecount header.
    **------------------------------------------------------------------------
    */
    VVEOUT_3 ("%s%s %ld\n", VV_PREFIX,
              G_vvehdr[HDR_BYTECOUNT].h_string, G_ip_file.bytecount);

    /*-
    **------------------------------------------------------------------------
    ** Crc32 header.
    **------------------------------------------------------------------------
    */
    VVEOUT_3 ("%s%s %08lx\n", VV_PREFIX,
              G_vvehdr[HDR_CRC32].h_string, G_ip_file.crc32);
    G_op_file.bytecount += G_oppart_bytes;
    LOGMSG_2 ("Part %3d bytes written:            %ld",
              G_oppt_no, G_oppart_bytes);

    /*-
    **------------------------------------------------------------------------
    ** Close the input file if it's not connected to stdin.
    **------------------------------------------------------------------------
    */
    if (G_ip_file.file_ptr != stdin)
    {
        f_close (&G_ip_file);
    }

    /*-
    **------------------------------------------------------------------------
    ** Close the output file if it's not connected to stdout.
    **------------------------------------------------------------------------
    */
    if (G_op_file.file_ptr != stdout)
    {
        f_close (&G_op_file);
    }

    /*-
    **------------------------------------------------------------------------
    ** Finish off the log file, then close it if it's not connected to stderr.
    **------------------------------------------------------------------------
    */
    LOGMSG_1 ("\nCRC-32:                            %08lx", G_ip_file.crc32);
    LOGMSG_1 ("Total bytes read:                  %ld", G_ip_file.bytecount);
    LOGMSG_1 ("Total bytes written:               %ld", G_op_file.bytecount);
    LOGMSG_1 ("Total O/P parts written:           %d", G_oppt_no);

    if (G_log_file.file_ptr != stderr)
    {
        f_close (&G_log_file);
    }

}                               /* terminate */



/*-
**============================================================================
**
** FUNCTION
**
**      usage_error
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    usage_error (CONST char *reason)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    usage_error (reason)
        CONST char             *reason;
#endif                          /* (ANSI_SYNTAX) */
{

    /*-
    **------------------------------------------------------------------------
    ** Initial error message, followed by Unix style usage message.
    **------------------------------------------------------------------------
    */
    FPRINTF (stderr, "\nVVencode %s, %s release (%s)\n\n",
             VV_VERSION_STR, VV_IMPLEMENTATION, VV_DATE);
    FPRINTF (stderr, "VVencode: %s\n\n", reason);
    FPRINTF (stderr,
             "Usage: VVencode  [qualifiers]  input_file  [output_file]\n\n");
    FPRINTF (stderr, "  Valid qualifiers are:\n\n");

    /*-
    **------------------------------------------------------------------------
    ** qualifier: 'debug' (if support compiled in)
    **------------------------------------------------------------------------
    */
#if (VV_DEBUG_SUPPORT)
    FPRINTF (stderr, "  %.1s%s\n", QUAL_INTRO, G_cmdqual[CMDQ_DEBUG].q_string);
#endif                          /* (VV_DEBUG_SUPPORT) */

    /*-
    **------------------------------------------------------------------------
    ** qualifier: 'encoding_table'
    **------------------------------------------------------------------------
    */
    FPRINTF (stderr, "  %.1s%s%.1sfile-spec\n",
             QUAL_INTRO, G_cmdqual[CMDQ_ENCODE].q_string, QUAL_SEP);

    /*-
    **------------------------------------------------------------------------
    ** qualifier: 'format'
    **------------------------------------------------------------------------
    */
    FPRINTF (stderr, "  %.1s%s%.1sfile-format  (formats supported:%s%s%s%s%s%s)\n",
             QUAL_INTRO, G_cmdqual[CMDQ_FORMAT].q_string, QUAL_SEP,

#if (SUPPORT_FIXED_FMT)
             " ", G_fmt_str[FMT_FIXED],
#else                           /* NOT (SUPPORT_FIXED_FMT) */
             "", "",
#endif                          /* (SUPPORT_FIXED_FMT) */

#if (SUPPORT_STREAM_FMT)
             " ", G_fmt_str[FMT_STREAM],
#else                           /* NOT (SUPPORT_STREAM_FMT) */
             "", "",
#endif                          /* (SUPPORT_STREAM_FMT) */

#if (SUPPORT_VARIABLE_FMT)
             " ", G_fmt_str[FMT_VARIABLE]);
#else                           /* NOT (SUPPORT_VARIABLE_FMT) */
             "", "");
#endif                          /* (SUPPORT_VARIABLE_FMT) */

    /*-
    **------------------------------------------------------------------------
    ** qualifiers: 'header_filespec', 'help', 'log', 'mode'
    **------------------------------------------------------------------------
    */
    FPRINTF (stderr, "  %.1s%s%.1sfile-spec\n",
             QUAL_INTRO, G_cmdqual[CMDQ_HDRFILE].q_string, QUAL_SEP);
    FPRINTF (stderr, "  %.1s%s\n",
             QUAL_INTRO, G_cmdqual[CMDQ_HELP].q_string);
    FPRINTF (stderr, "  %.1s%s[%.1sfile-spec]\n",
             QUAL_INTRO, G_cmdqual[CMDQ_LOG].q_string, QUAL_SEP);
    FPRINTF (stderr, "  %.1s%s%.1sfile-mode  (modes supported: binary text)\n",
             QUAL_INTRO, G_cmdqual[CMDQ_MODE].q_string, QUAL_SEP);

    /*-
    **------------------------------------------------------------------------
    ** qualifiers: 'overwrite', 'record_length', 'split_size'
    **------------------------------------------------------------------------
    */
    FPRINTF (stderr, "  %.1s%s\n",
             QUAL_INTRO, G_cmdqual[CMDQ_OVERWRITE].q_string);
    FPRINTF (stderr, "  %.1s%s%.1sbytes\n",
             QUAL_INTRO, G_cmdqual[CMDQ_RECORDLEN].q_string, QUAL_SEP);
    FPRINTF (stderr, "  %.1s%s%.1skbytes\n",
	     QUAL_INTRO, G_cmdqual[CMDQ_SPLIT].q_string, QUAL_SEP);

    /*-
    **------------------------------------------------------------------------
    ** qualifiers: 'timestamp', 'translation_file'
    **------------------------------------------------------------------------
    */
    FPRINTF (stderr, "  %.1s%s[%.1stime]\n",
             QUAL_INTRO, G_cmdqual[CMDQ_TIMESTAMP].q_string, QUAL_SEP);
    FPRINTF (stderr, "  %.1s%s%.1sfile-spec\n\n",
             QUAL_INTRO, G_cmdqual[CMDQ_TRANSLATE].q_string, QUAL_SEP);

    G_status = FATAL_STATUS;
    vv_exit ();
}                               /* usage_error */



/*-
**============================================================================
**
** FUNCTION
**
**      enc_line
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    Int32                   enc_line (CONST char *ip_buffer,
				      CONST Int32 num_bytes,
				      char *op_buffer)
#else                           /* NOT (ANSI_SYNTAX) */
    Int32		    enc_line (ip_buffer, num_bytes, op_buffer)
	CONST char             *ip_buffer;
	CONST Int32             num_bytes;
	char 		       *op_buffer;
#endif                          /* (ANSI_SYNTAX) */
{
    int                     c1;
    int                     c2;
    int                     c3;
    int                     c4;
    SIZE_T                  idx;
    int                     offset;

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    strcpy (op_buffer, VV_PREFIX);
    offset = strlen (op_buffer);
    op_buffer[offset++] = ENCODE ((SIZE_T) num_bytes);

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    for (idx = 0; idx < (SIZE_T) num_bytes; idx += 3)
    {
	c1 = (int) ip_buffer[idx] >> 2;
	c2 = (int) ((ip_buffer[idx] << 4) & 0x30)
		      | ((ip_buffer[idx + 1] >> 4) & 0x0f);
	c3 = (int) ((ip_buffer[idx + 1] << 2) & 0x3c)
		      | ((ip_buffer[idx + 2] >> 6) & 0x03);
	c4 = (int) (ip_buffer[idx + 2] & 0x3f);

	op_buffer[offset++] = ENCODE (c1);
	op_buffer[offset++] = ENCODE (c2);
	op_buffer[offset++] = ENCODE (c3);
	op_buffer[offset++] = ENCODE (c4);
    }
    op_buffer[offset] = '\0';
    strcat (op_buffer, VV_EOL_MARKER);
    strcat (op_buffer, "\n");
    return ((Int32) strlen (op_buffer));
}                               /* enc_line */



/*-
**============================================================================
**
** FUNCTION
**
**      wr_headers
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    wr_headers (CONST Int16 part_no)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    wr_headers (part_no)
        CONST Int16             part_no;
#endif                          /* (ANSI_SYNTAX) */
{
    Int16                   adv_line;

    LOGMSG_2 ("\nPart %3d O/P file:                 %s", 
              G_oppt_no, G_op_file.file_spec);
    LOGMSG_2 ("Part %3d started at I/P byte:      %ld",
              G_oppt_no, G_ip_file.bytecount);

    /*-
    **------------------------------------------------------------------------
    ** Write the VVcode advert text at the top of the VVE file.
    **------------------------------------------------------------------------
    */
    for (adv_line = 0; G_vv_advert[adv_line] != NULL; adv_line++)
    {
        VVEOUT_1 ("%s\n", G_vv_advert[adv_line]);
    }
    VVEOUT_1 ("Written by VVencode %s,\n", VV_VERSION_STR);
    VVEOUT_2 ("           %s release (%s)\n", VV_IMPLEMENTATION, VV_DATE);
    VVEOUT ("\n");

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (part_no == 1)
    {

        /*-
        **--------------------------------------------------------------------
        ** VVE header: 'decodeversion'
	**--------------------------------------------------------------------
        */
        VVEOUT_3 ("%s%s %d\n", VV_PREFIX,
                  G_vvehdr[HDR_DECODEVERSION].h_string, VV_MAJOR_VERSION_NO);

        /*-
        **--------------------------------------------------------------------
        ** VVE header: 'operating system'
        **--------------------------------------------------------------------
        */
        VVEOUT_3 ("%s%s %s\n", VV_PREFIX, 
                  G_vvehdr[HDR_OPERATINGSYSTEM].h_string, VV_OPERATING_SYSTEM);

        /*-
        **--------------------------------------------------------------------
        ** VVE header: 'characterset'
        ** 
        ** If the /TRANSLATE qualifier was used, add a 'comment' header 
        ** recording the original character set (even if it was the same as
        ** the translated character set).
	**--------------------------------------------------------------------
        */
        if (Q_TRANSLATE)
        {
            VVEOUT_3 ("%s%s %s\n", VV_PREFIX, 
                      G_vvehdr[HDR_CHARACTERSET].h_string, G_xlt_table.xlt_to);
            VVEOUT_3 ("%s%s Original file character set was %s\n",
                      VV_PREFIX, G_vvehdr[HDR_COMMENT].h_string, 
                      VV_CHARACTERSET);
        }
        else
        {
            VVEOUT_3 ("%s%s %s\n", VV_PREFIX,
                      G_vvehdr[HDR_CHARACTERSET].h_string, VV_CHARACTERSET);
        }

        /*-
        **--------------------------------------------------------------------
        ** VVE header: 'mode'
        **--------------------------------------------------------------------
        */
        VVEOUT_3 ("%s%s %s\n", VV_PREFIX,
                  G_vvehdr[HDR_MODE].h_string, G_mode_str[G_ip_file.mode]);

        /*-
        **--------------------------------------------------------------------
        ** VVE header: 'format'
        **--------------------------------------------------------------------
        */
	VVEOUT_3 ("%s%s %s\n", VV_PREFIX,
                  G_vvehdr[HDR_FORMAT].h_string, G_fmt_str[G_ip_file.format]);

        /*-
        **--------------------------------------------------------------------
        ** VVE header: 'recordlength'
        **
        ** This header is included only if a valid record length was 
        ** determined.
        **--------------------------------------------------------------------
        */
        if (G_ip_file.max_rec_len != INV_RECORD_LEN)
        {
            if (G_ip_file.lng_rec_len != INV_RECORD_LEN)
            {
                VVEOUT_4 ("%s%s %ld %ld\n", VV_PREFIX, 
			  G_vvehdr[HDR_RECORDLEN].h_string,
                          G_ip_file.max_rec_len, G_ip_file.lng_rec_len);
            }
            else
            {
		VVEOUT_3 ("%s%s %ld\n", VV_PREFIX,
                          G_vvehdr[HDR_RECORDLEN].h_string, 
                          G_ip_file.max_rec_len);
            }
        }

        /*-
        **--------------------------------------------------------------------
        ** VVE header: 'timestamp'
        **
        ** This header is included only if a valid timestamp was determined.
        **--------------------------------------------------------------------
        */
        if (G_ip_file.mod_time != INV_TIMESTAMP)
        {
            VVEOUT_3 ("%s%s %s\n", VV_PREFIX,
                      G_vvehdr[HDR_TIMESTAMP].h_string,
                      vv_timestamp (G_ip_file.mod_time));
        }

        /*-
        **--------------------------------------------------------------------
        ** VVE header: 'table'
        **
	** The 'table' header is followed by the encoding table.
        **--------------------------------------------------------------------
        */
        VVEOUT_2 ("%s%s\n", VV_PREFIX, G_vvehdr[HDR_TABLE].h_string);
        VVEOUT_2 ("%s%.32s\n", VV_PREFIX, G_encode_table);
        VVEOUT_2 ("%s%.32s\n", VV_PREFIX, G_encode_table + 32);

        /*-
        **--------------------------------------------------------------------
        ** VVE header: 'begin'
        **--------------------------------------------------------------------
	*/
        VVEOUT_3 ("%s%s %s\n", VV_PREFIX,
                  G_vvehdr[HDR_BEGIN].h_string, G_hdrf_spec);
    }
    else
    {

        /*-
        **--------------------------------------------------------------------
        ** VVE header: 'table'
        **
        ** The 'table' header is followed by the encoding table.
        **--------------------------------------------------------------------
        */
        VVEOUT_2 ("%s%s\n", VV_PREFIX, G_vvehdr[HDR_TABLE].h_string);
        VVEOUT_2 ("%s%.32s\n", VV_PREFIX, G_encode_table);
        VVEOUT_2 ("%s%.32s\n", VV_PREFIX, G_encode_table + 32);

        /*-
        **--------------------------------------------------------------------
        ** VVE header: 'skipfrom'
        **--------------------------------------------------------------------
        */
        VVEOUT_4 ("%s%s %d %s\n", VV_PREFIX,
                  G_vvehdr[HDR_SKIPFROM].h_string, G_oppt_no, G_hdrf_spec);
    }
}                               /* wr_headers */



/*-
**============================================================================
**
** FUNCTION
**
**      main
**
** DESCRIPTION
**
**      [tbs]
**
** INPUT PARAMETERS
**
**      [tbs]
**
** OUTPUT PARAMETERS
**
**      [tbs]
**
** RETURN VALUE
**
**      [tbs]
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    int                     main (CONST int argc, CONST char *argv[])
#else                           /* NOT (ANSI_SYNTAX) */
    int                     main (argc, argv)
        CONST int               argc;
        CONST char             *argv[];
#endif                          /* (ANSI_SYNTAX) */
{
    SIZE_T                  bytes_reqd;

    G_status = LOG_STATUS;
    G_debugging = FALSE;

    /*-
    **------------------------------------------------------------------------
    ** Allocate the global message buffer manually since it is used by the
    ** various MESSAGE_x and DEBUG_x macros.
    **------------------------------------------------------------------------
    */
    bytes_reqd = sizeof (char) * (SIZE_T) MAX_MSG_LEN;
    G_tmp_msg = (char *) malloc (bytes_reqd);

    if (G_tmp_msg == NULL)
    {
	FPRINTF (stderr, "%s %ld bytes for the message buffer\n",
                 "FATAL: unable to allocate", bytes_reqd);

        G_status = FATAL_STATUS;
        vv_exit ();
    }
    DEBUG_2 ("main: allocated %ld bytes for buffer `%s'",
	     (Int32) bytes_reqd, "G_tmp_msg");

    /*-
    **------------------------------------------------------------------------
    ** Allocate the other global buffers.
    **------------------------------------------------------------------------
    */
    G_ip_buffer = allocate_buffer ((SIZE_T) IP_BUFFER_SIZE, "G_ip_buffer");
    G_op_buffer = allocate_buffer ((SIZE_T) OP_BUFFER_SIZE, "G_op_buffer");

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    scan_cmd_line (argc, argv, QUAL_INTRO, QUAL_SEP, G_cmdqual,
		   &G_cmd_ipfile, &G_cmd_opfile);

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
#if (VV_DEBUG_SUPPORT)
#else                           /* NOT (VV_DEBUG_SUPPORT) */
    if (Q_DEBUG)
    {
	WARNMSG ("Debugging support was not compiled into VVencode");
    }
    G_debugging = FALSE;
    G_cmdqual[CMDQ_DEBUG].present = FALSE;
#endif                          /* (VV_DEBUG_SUPPORT) */

    /*-
    **------------------------------------------------------------------------
    ** If the 'help' option was selected, call usage_error() and stop.
    **------------------------------------------------------------------------
    */
    if (Q_HELP)
    {
        USAGE ("help requested");
    }

    /*-
    **------------------------------------------------------------------------
    ** If the 'debug' option was selected, ensure that the 'log' option is 
    ** selected.
    **------------------------------------------------------------------------
    */
    if (Q_DEBUG)
    {
        G_debugging = TRUE;
        if (!Q_LOG)
        {
            G_cmdqual[CMDQ_LOG].present = TRUE;
            G_cmdqual[CMDQ_LOG].value = NULL;
        }
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (!Q_INPUT_FILE)
    {
        USAGE ("input file must be specified");
    }

    initialize ();
    encode ();
    terminate ();

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    vv_exit ();
    return (0);
}                               /* main */