/*-
******************************************************************************
******************************************************************************
**
**  ARCHIVE HEADER INFORMATION
**
**  @C-file{
**      FILENAME    = "unix.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
**
**      apply_defaults      -   Apply default file name and extension to a
**                              full file specification if either of these 
**                              components is missing.
**      confirm_yesno       -   Display a message to the user and wait for a
**                              yes/no answer.
**      examine_file        -   Examine a file and determine its key features, 
**                              including mode, format, record length and 
**                              timestamp.
**      explain_error       -   Explain the reason for an error.
**      f_open_in           -   Open a file for input using the appropriate 
**                              mode, format etc for the file.
**      f_open_out          -   Open a file for output using the appropriate
**                              mode, format etc for the file.
**      file_exists         -   Determine whether a file exists.
**      force_file_ext      -   Force the file extension of a full file
**                              specification to a specified value.
**      is_a_file           -   Determine whether a file stream is connected
**                              to a real file rather than a character
**                              device, pipe etc.
**      legal_filespec      -   Takes an arbitrary string which may a file 
**                              specification from another operating system
**                              and manipulates it to produce a legal file 
**                              specification for the current operating
**                              system.
**      make_filespec       -   Construct a full file specification from
**                              component parts.
**      prompt_for_string   -   Present a prompt string and accept a string 
**                              from the user.
**      read_bytes          -   Read bytes from a currently open file.
**      read_line           -   Read a line from a currently open (text) file.
**      read_record         -   Read a record from a currently open file.
**      scan_cmd_line       -   [tbs]
**      set_ftime           -   Set the timestamp of a file to a specified 
**                              value.
**      set_pipe_mode       -   Set the mode of a file stream connected to a
**                              pipe, character device, redirected
**                              stdin/stdout/stderr, in fact any non-file.
**      split_file_spec     -   Split a full file specification into its
**                              component parts.
**      tz_offset           -   Determine the offset of local time from 
**                              Greenwich Mean Time (Coordinated Universal
**                              Time) at a specified date and time.  
**      user_message        -   Present a message to the user.
**      vv_exit             -   Exit the program, returning the appropriate
**                              status to the operating system.
**      write_bytes         -   Write bytes to a currently open file.
**      write_record        -   Write a record to a currently open file.
**
**  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$";

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

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

/*-
**----------------------------------------------------------------------------
** Implementation (compiler) specific include files.  The default 
**----------------------------------------------------------------------------
*/
# if (UNIX_BSD)
#  include <sys/timeb.h>

#  ifndef HAS_UTIME_H
#   undef HAS_SYSUTIME_H
#   define HAS_SYSUTIME_H           1
#  endif                        /* HAS_UTIME_H */
# endif                         /* (UNIX_BSD) */

# if (UNIX_SYSV)
#  ifndef HAS_SYSUTIME_H
#   undef HAS_UTIME_H
#   define HAS_UTIME_H              1
#  endif
# endif                         /* UNIX_SYSV */

#ifdef HAS_UTIME_H
# include <utime.h>
#endif                          /* HAS_UTIME_H */

#ifdef HAS_SYSUTIME_H
# include <sys/utime.h>
#endif                          /* HAS_SYSUTIME_H */



/*-
**============================================================================
**
** FUNCTION
**
**      apply_defaults
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    apply_defaults (CONST char *default_name,
                                            CONST char *default_ext,
                                            char *full_spec)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    apply_defaults (default_name, default_ext, 
                                            full_spec)
        CONST char             *default_name;
        CONST char             *default_ext;
        char                   *full_spec;
#endif                          /* (ANSI_SYNTAX) */
{
    char                    name[MAX_NAME + 1];
    char                    extension[MAX_EXT + 1];
    Unsigned16              path_parts;
    char                    preamble[MAX_PREAMBLE + 1];

    path_parts = split_file_spec (full_spec, preamble, name, extension, NULL);


    if (((path_parts & FS_NAME) == 0) && (default_name != NULL))
    {
        strncpy (name, default_name, MAX_NAME);
    }

    if (((path_parts & FS_EXTENSION) == 0) && (default_ext != NULL))
    {
        strncpy (extension, default_ext, MAX_EXT);
    }

    make_file_spec (preamble, name, extension, NULL, full_spec);
}                               /* apply_defaults */



/*-
**============================================================================
**
** FUNCTION
**
**      confirm_yesno
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    Boolean                 confirm_yesno (CONST char *confirm_str)
#else                           /* NOT (ANSI_SYNTAX) */
    Boolean                 confirm_yesno (confirm_str)
        CONST char             *confirm_str;
#endif                          /* (ANSI_SYNTAX) */
{
    int                     answer;

    FPRINTF (stderr, "\n%s [Y/N]: ", confirm_str);
    fflush (stderr);

    do
    {
        answer = getchar ();

        if (answer == EOF)
        {
            ERRORMSG ("EOF detected on standard input");
            answer = 'N';
        }
        else
        {
            answer = TOUPPER (answer);
        }
    } while (!((answer == 'N') || (answer == 'Y')));

    return ((answer == 'Y') ? (Boolean) TRUE : (Boolean) FALSE);
}                               /* confirm_yesno */



/*-
**============================================================================
**
** FUNCTION
**
**      examine_file
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#define EX_SAMPLE_SIZE         512

#if (ANSI_SYNTAX)
    void                    examine_file (File_Info *ex_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    examine_file (ex_file)
        File_Info              *ex_file;
#endif                          /* (ANSI_SYNTAX) */
{
    char                    tmp_buffer[EX_SAMPLE_SIZE];
    int                     bytes_read;
    int                     ctrl_count;
    int                     eol_count;
    int                     i;
    struct stat             stat_buffer;

    /*-
    **------------------------------------------------------------------------
    ** Initialize the File_Info structure before starting work.  If the input
    ** file is not a real file (i.e. a device or pipe), these are the values
    ** that will be returned.
    **------------------------------------------------------------------------
    */
    ex_file->mode = INV_MODE;
    ex_file->format = INV_FORMAT;
    ex_file->max_rec_len = INV_RECORD_LEN;
    ex_file->lng_rec_len = INV_RECORD_LEN;
    ex_file->mod_time = INV_TIMESTAMP;

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    ex_file->file_ptr = fopen (ex_file->file_spec, "r");

    if (ex_file->file_ptr == (FILE *) NULL)
    {
        ERRORMSG_1 ("couldn't examine (open) file `%s'", ex_file->file_spec);
        explain_error ();
        return;
    }

    /*-
    **------------------------------------------------------------------------
    ** Test to see if the file is 'real'.
    **------------------------------------------------------------------------
    */
    if (is_a_file (ex_file) == FALSE)
    {
        DEBUG_1 ("examine_file: `%s' is not a regular file",
                 ex_file->file_spec);
        f_close (ex_file);
        ex_file->pipe_flag = TRUE;
        ex_file->file_ptr = (FILE *) NULL;
        return;
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    bytes_read = fread (tmp_buffer, sizeof (char), EX_SAMPLE_SIZE,
                        ex_file->file_ptr);
    eol_count = 0;
    ctrl_count = 0;
    
    for (i = 0; i < (bytes_read - 1); i++)
    {
        if (tmp_buffer[i + 1] == '\n')
        {
            ++eol_count;
        }
        if (tmp_buffer[i + 1] < 0x08)
        {
            ++ctrl_count;
        }
    }
    f_close (ex_file);
    ex_file->file_ptr = (FILE *) NULL;

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if ((bytes_read > (80 * eol_count)) || (bytes_read < (80 * ctrl_count)))
    {
        ex_file->mode = MODE_BINARY;
        ex_file->format = DEF_BINARY_FMT;
    }
    else
    {
        ex_file->mode = MODE_TEXT;
        ex_file->format = DEF_TEXT_FMT;
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    i = stat (ex_file->file_spec, &stat_buffer);

    if (i != FALSE)
    {
        ERRORMSG_1 ("couldn't examine (stat) file `%s'", ex_file->file_spec);
        explain_error ();
        return;
    }
    ex_file->mod_time = stat_buffer.st_mtime;

}                               /* examine_file */



/*-
**============================================================================
**
** FUNCTION
**
**      explain_error
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    explain_error (void)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    explain_error ()
#endif                          /* (ANSI_SYNTAX) */
{
    INFOMSG_1 ("Error number:          %ld", (long) errno);
}                               /* explain_error */



/*-
**============================================================================
**
** FUNCTION
**
**      f_open_in
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    FILE                   *f_open_in (File_Info *ip_file)
#else                           /* NOT (ANSI_SYNTAX) */
    FILE                   *f_open_in (ip_file)
        File_Info              *ip_file;
#endif                          /* (ANSI_SYNTAX) */
{
    FILE                   *file_ptr;

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    switch (ip_file->mode)
    {
        case MODE_BINARY:
        case MODE_TEXT:
            file_ptr = fopen (ip_file->file_spec, "rb");
            break;
        default:
            ERRORMSG_1 ("invalid file mode specified (%d)", ip_file->mode);
            return ((FILE *) NULL);
    }

    if (file_ptr == (FILE *) NULL)
    {
        ERRORMSG_1 ("error opening file `%s' for input", ip_file->file_spec);
        explain_error ();
        return ((FILE *) NULL);
    }
    return (file_ptr);
}                               /* f_open_in */



/*-
**============================================================================
**
** FUNCTION
**
**      f_open_out
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    FILE                   *f_open_out (CONST Int16 overwrite_files,
                                        File_Info *op_file)
#else                           /* NOT (ANSI_SYNTAX) */
    FILE                   *f_open_out (overwrite_files, op_file)
        CONST Int16             overwrite_files;
        File_Info              *op_file;
#endif                          /* (ANSI_SYNTAX) */
{
    FILE                   *file_ptr;

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if ((overwrite_files != OVERWRITE_YES)
        && (file_exists (op_file->file_spec)))
    {
        if (overwrite_files == OVERWRITE_ASK)
        {
            SPRINTF (G_tmp_msg, "File `%s' exists.  Overwrite ?",
                     op_file->file_spec);

            if (!confirm_yesno (G_tmp_msg))
            {
                INFOMSG ("operation cancelled");
                return ((FILE *) NULL);
            }
        }
        else
        {
            ERRORMSG_1 ("file `%s' exists", op_file->file_spec);
            return ((FILE *) NULL);
        }
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    switch (op_file->mode)
    {
        case MODE_BINARY:
        case MODE_TEXT:
            file_ptr = fopen (op_file->file_spec, "wb");
            break;
        default:
            ERRORMSG_1 ("invalid file mode specified (%d)", op_file->mode);
            return ((FILE *) NULL);
    }

    if (file_ptr == (FILE *) NULL)
    {
        ERRORMSG_1 ("error opening file `%s' for output", op_file->file_spec);
        explain_error ();
        return ((FILE *) NULL);
    }
    return (file_ptr);
}                               /* f_open_out */



/*-
**============================================================================
**
** FUNCTION
**
**      file_exists
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    Boolean                 file_exists (CONST char *file_spec)
#else                           /* NOT (ANSI_SYNTAX) */
    Boolean                 file_exists (file_spec)
        CONST char             *file_spec;
#endif                          /* (ANSI_SYNTAX) */
{
    return ((Boolean) (access ((char *) file_spec, 0) == 0));
}                               /* file_exists */



/*-
**============================================================================
**
** FUNCTION
**
**      force_file_ext
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    force_file_ext (CONST char *forced_ext,
                                            char *full_spec)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    force_file_ext (forced_ext, full_spec)
        CONST char             *forced_ext;
        char                   *full_spec;
#endif                          /* (ANSI_SYNTAX) */
{
    char                    name[MAX_NAME + 1];
    char                    preamble[MAX_PREAMBLE + 1];

    if (forced_ext == NULL)
    {
        return;
    }
    (void) split_file_spec (full_spec, preamble, name, NULL, NULL);
    make_file_spec (preamble, name, forced_ext, NULL, full_spec);
}                               /* force_file_ext */



/*-
**============================================================================
**
** FUNCTION
**
**      is_a_file
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    Boolean                 is_a_file (CONST File_Info *ex_file)
#else                           /* NOT (ANSI_SYNTAX) */
    Boolean                 is_a_file (ex_file)
        CONST File_Info        *ex_file;
#endif                          /* (ANSI_SYNTAX) */
{
    int                     file_handle;
    int                     status;

    file_handle = fileno (ex_file->file_ptr);
    status = isatty (file_handle);

    if (status == 0)
    {
        return (TRUE);
    }
    else
    {
        return (FALSE);
    }
}                               /* is_a_file */



/*-
**============================================================================
**
** FUNCTION
**
**      legal_filespec
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    char                   *legal_filespec (CONST char *hdrf_spec)
#else                           /* NOT (ANSI_SYNTAX) */
    char                   *legal_filespec (hdrf_spec)
        CONST char             *hdrf_spec;
#endif                          /* (ANSI_SYNTAX) */
{
    char                    tmpf_name[MAX_NAME + 1];
    char                    tmpf_ext[MAX_EXT + 1];
    char                   *tmpf_spec;
    char                   *tmp_ptr;
    static char             opf_spec[MAX_PATH + 1];

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    tmpf_spec = STRDUP (hdrf_spec);
    tmp_ptr = STRCHR (tmpf_spec, '.');

    if (tmp_ptr != NULL)
    {
        strncpy (tmpf_ext, tmp_ptr, MAX_EXT);
        tmpf_ext[MAX_EXT] = '\0';
        *tmp_ptr = '\0';
    }
    else
    {
        strcpy (tmpf_ext, "");
    }
    strncpy (tmpf_name, tmpf_spec, MAX_NAME);
    tmpf_name[MAX_NAME] = '\0';

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    for (tmp_ptr = tmpf_name; *tmp_ptr != '\0'; tmp_ptr++)
    {
        if (STRCHR (LEGAL_FILESPEC_CHARS, *tmp_ptr) == NULL)
        {
            *tmp_ptr = REPLACEMENT_FILESPEC_CHAR;
        }
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    for (tmp_ptr = tmpf_ext + 1; *tmp_ptr != '\0'; tmp_ptr++)
    {
        if (STRCHR (LEGAL_FILESPEC_CHARS, *tmp_ptr) == NULL)
        {
            *tmp_ptr = REPLACEMENT_FILESPEC_CHAR;
        }
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    strcpy (opf_spec, tmpf_name);
    strcat (opf_spec, tmpf_ext);

    if (strcmp (hdrf_spec, opf_spec) != 0)
    {
        WARNMSG_2 ("suspicious header file spec `%s' changed to `%s'",
                   hdrf_spec, opf_spec);
    }
    return (opf_spec);
}                               /* legal_filespec */



/*-
**============================================================================
**
** FUNCTION
**
**      make_file_spec
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    make_file_spec (CONST char *preamble,
                                            CONST char *name,
                                            CONST char *extension,
                                            CONST char *postamble,
                                            char *full_spec)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    make_file_spec (preamble, name, extension,
                                            postamble, full_spec)
        CONST char             *preamble;
        CONST char             *name;
        CONST char             *extension;
        CONST char             *postamble;
        char                   *full_spec;
#endif                          /* (ANSI_SYNTAX) */
{
    int                     len;
    char                   *tmp_ptr;

    /*-
    **------------------------------------------------------------------------
    ** Start with an empty string and append components of the file
    ** specification only if they exist.
    **------------------------------------------------------------------------
    */
    strcpy (full_spec, "");

    /*-
    **------------------------------------------------------------------------
    ** The preamble component corresponds to the Unix network node and/or path
    ** components in the form:
    **
    **      node:           node:dir1       node:/dir1      node:/dir1/
    **      node:/dir1/dir2 dir1            /dir1           /dir1/dir2
    **      
    **
    ** If the preamble component does not end in '/' or ':', '/' is appended
    ** only if the component is not empty.
    **------------------------------------------------------------------------
    */
    if ((preamble != NULL) && (*preamble != '\0'))
    {
        strncat (full_spec, preamble, MAX_PREAMBLE);
        len = strlen (preamble);
        tmp_ptr = STRCHR ("/:", preamble[len - 1]);

        if ((tmp_ptr == NULL) && (len < MAX_PREAMBLE))
        {
            strcat (full_spec, "/");
        }
    }

    /*-
    **------------------------------------------------------------------------
    ** Simply append the name if it is present.
    **------------------------------------------------------------------------
    */
    if (name != NULL)
    {
        strncat (full_spec, name, MAX_NAME);
    }

    /*-
    **------------------------------------------------------------------------
    ** If the extension component does not begin with '.', prepend '.' before
    ** appending the extension to the file specification.    
    **------------------------------------------------------------------------
    */
    if ((extension != NULL) && (*extension != '\0'))
    {
        if (*extension != '.')
        {
            strcat (full_spec, ".");
            strncat (full_spec, extension, MAX_EXT - 1);
        }
        else
        {
            strncat (full_spec, extension, MAX_EXT);
        }
    }
}                               /* make_file_spec */



/*-
**============================================================================
**
** FUNCTION
**
**      prompt_for_string
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    prompt_for_string (CONST char *confirm_str,
                                               CONST Int16 buf_size,
                                               char *return_buffer)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    prompt_for_string (confirm_str, buf_size,
                                               return_buffer)
        CONST char             *confirm_str;
        CONST Int16             buf_size;
        char                   *return_buffer;
#endif                          /* (ANSI_SYNTAX) */
{
    char                   *status;

    FPRINTF (stderr, "\n%s", confirm_str);
    fflush (stderr);
    status = fgets (return_buffer, (int) buf_size, stdin);

    if (status == NULL)
    {
        FATALMSG ("error reading string from stdin");
        explain_error ();
        vv_exit ();
    }

    status = STRCHR (return_buffer, '\n');
    if (status != NULL)
    {
        *status = '\0';
    }
}                               /* prompt_for_string */



/*-
**============================================================================
**
** FUNCTION
**
**      read_bytes
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    Int32                   read_bytes (CONST Int32 max_bytes,
                                        char *buffer,
                                        File_Info *ip_file)
#else                           /* NOT (ANSI_SYNTAX) */
    Int32                   read_bytes (max_bytes, buffer, ip_file)
        CONST Int32             max_bytes;
        char                   *buffer;
        File_Info              *ip_file;
#endif                          /* (ANSI_SYNTAX) */
{
    Int32                   idx;
    int                     temp;

    for (idx = 0L; idx < max_bytes; idx++)
    {
        temp = fgetc (ip_file->file_ptr);

        if (temp == EOF)
        {
            if (ferror (ip_file->file_ptr))
            {
                return (-2L);
            }

            if (idx == 0)
            {
                return (-1L);
            }
            break;
        }
        buffer[(SIZE_T) idx] = (char) temp;
    }
    return (idx);
}                               /* read_bytes */



/*-
**============================================================================
**
** FUNCTION
**
**      read_line
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    Int32                   read_line (CONST Int32 max_bytes,
                                       char *buffer,
                                       File_Info *ip_file)
#else                           /* NOT (ANSI_SYNTAX) */
    Int32                   read_line (max_bytes, buffer, ip_file)
        CONST Int32             max_bytes;
        char                   *buffer;
        File_Info              *ip_file;
#endif                          /* (ANSI_SYNTAX) */
{
    char                   *tmp_ptr;

    tmp_ptr = fgets (buffer, (int) max_bytes, ip_file->file_ptr);
    buffer[(SIZE_T) (max_bytes - 1)] = '\0';

    if (tmp_ptr == NULL)
    {
        if (ferror (ip_file->file_ptr))
        {
            return (-2L);
        }

        if (feof (ip_file->file_ptr))
        {
            return (-1L);
        }
    }
    return ((Int32) strlen (buffer));
}                               /* read_line */



/*-
**============================================================================
**
** FUNCTION
**
**      read_record
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    Int32                   read_record (CONST Int32 max_bytes,
                                         char *buffer,
                                         File_Info *ip_file)
#else                           /* NOT (ANSI_SYNTAX) */
    Int32                   read_record (max_bytes, buffer, ip_file)
        CONST Int32             max_bytes;
        char                   *buffer;
        File_Info              *ip_file;
#endif                          /* (ANSI_SYNTAX) */
{
    INTERNAL_ERROR ("read_record");
    return (-2L);
}                               /* read_record */



/*-
**============================================================================
**
** FUNCTION
**
**      scan_cmd_line
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    scan_cmd_line (CONST int qargc,
                                           CONST char *qargv[],
                                           CONST char *q_intro_str,
                                           CONST char *q_sep_str,
                                           Qualifier_Struct *qual_array,
                                           Qualifier_Struct *qual_ipfile,
                                           Qualifier_Struct *qual_opfile)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    scan_cmd_line (qargc, qargv, q_intro_str,
                                           q_sep_str, qual_array, qual_ipfile,
                                           qual_opfile)
        CONST int               qargc;
        CONST char             *qargv[];
        CONST char             *q_intro_str;
        CONST char             *q_sep_str;
        Qualifier_Struct       *qual_array;
        Qualifier_Struct       *qual_ipfile;
        Qualifier_Struct       *qual_opfile;
#endif                          /* (ANSI_SYNTAX) */
{
    char                   *arg;
    int                     arg_no;
    int                     len;
    int                     qual_found;
    int                     q_no;
    char                   *qsep_ptr;
    char                   *qvalue_ptr;
    char                   *tmp_qstr_ptr;


    /*-
    **------------------------------------------------------------------------
    ** Loop through each of the user supplied command line arguments.
    **------------------------------------------------------------------------
    */
    tmp_qstr_ptr = NULL;

    for (arg_no = 1; arg_no < qargc; arg_no++)
    {
        arg = (char *) qargv[arg_no];

        /*-
        **--------------------------------------------------------------------
        **
        **--------------------------------------------------------------------
        */
        if (qual_array[CMDQ_DEBUG].present == TRUE)
        {
            G_debugging = TRUE;
        }
        DEBUG_1 ("scan_cmd_line: command line argument: `%s'", arg);

        /*-
        **--------------------------------------------------------------------
        ** If the first character of the argument is one of the allowable
        ** 'q_intro_str' characters then parse it as a qualifier, unless
        ** the argument is the 'STDIO_SYMBOL' string when it is interpreted as
        ** the standard input or output file.
        **--------------------------------------------------------------------
        */
        if (((STRCHR (q_intro_str, *arg) != NULL)
             && STRCMP (arg, STDIO_SYMBOL) != 0))
        {

            /*-
            **----------------------------------------------------------------
            ** Extract the component of the qualifier string after the
            ** 'q_intro_str' character before the 'q_sep_str' character
            ** if it is present.
            **----------------------------------------------------------------
            */
            if (tmp_qstr_ptr != NULL)
            {
                free (tmp_qstr_ptr);
            }
            tmp_qstr_ptr = STRDUP (arg + 1);
            qsep_ptr = STRSTR (tmp_qstr_ptr, q_sep_str);
            qvalue_ptr = NULL;

            if (qsep_ptr != NULL)
            {
                *qsep_ptr = '\0';
                qvalue_ptr = qsep_ptr + 1;
            }

            /*-
            **----------------------------------------------------------------
            ** Search the command qualifier structure array for this
            ** qualifier string.
            **----------------------------------------------------------------
            */
            len = strlen (tmp_qstr_ptr);
            qual_found = -1;

            for (q_no = 0; qual_array[q_no].q_string != NULL; q_no++)
            {
                if (STRNCMPI (qual_array[q_no].q_string, tmp_qstr_ptr, (Int16) len) == 0)
                {

                    /*-
                    **--------------------------------------------------------
                    **
                    **--------------------------------------------------------
                    */
                    if (qual_found < 0)
                    {
                        qual_found = q_no;
                        DEBUG_1 ("scan_cmd_line: matches qualifier:      `%s'",
                                 qual_array[q_no].q_string);
                    }
                    else
                    {
                        USAGE_1 ("ambiguous qualifier `%s'", qargv[arg_no]);
                    }
                }
            }

            /*-
            **----------------------------------------------------------------
            ** If the qualifier string is unknown, return that status.
            **----------------------------------------------------------------
            */
            if (qual_found < 0)
            {
                USAGE_1 ("unknown qualifier `%s'", qargv[arg_no]);
            }

            /*-
            **----------------------------------------------------------------
            **
            **----------------------------------------------------------------
            */
            if (qsep_ptr == NULL)
            {
                if (qual_array[qual_found].val_needed == VALUE_NEEDED)
                {
                    if ((arg_no + 1) < qargc)
                    {
                        qual_array[qual_found].present = TRUE;
                        qual_array[qual_found].value = STRDUP (qargv[++arg_no]);
                        DEBUG_1 ("scan_cmd_line: qualifier value:        `%s'",
                                 qual_array[qual_found].value);
                    }
                    else
                    {
                        USAGE_2 ("qualifier `%.1s%s' must take a value",
                                 QUAL_INTRO, qual_array[qual_found].q_string);
                    }
                }
                else
                {
                    qual_array[qual_found].present = TRUE;
                    qual_array[qual_found].value = NULL;
                }
            }
            else
            {
                /*-
                **------------------------------------------------------------
                **
                **------------------------------------------------------------
                */
                if (*qvalue_ptr == '\0')
                {
                    USAGE_1 ("value missing after qualifier `%s'",
                             qargv[arg_no]);
                }
                else if (qual_array[qual_found].val_needed == VALUE_NONE)
                {
                    USAGE_2 ("qualifier `%.1s%s' does not take a value",
                             QUAL_INTRO, qual_array[qual_found].q_string);
                }
                else
                {
                    qual_array[qual_found].value = STRDUP (qvalue_ptr);
                    qual_array[qual_found].present = TRUE;
                    DEBUG_1 ("scan_cmd_line: qualifier value:        `%s'",
                             qual_array[qual_found].value);
                }
            }
        }

        /*-
        **--------------------------------------------------------------------
        **
        **--------------------------------------------------------------------
        */
        else
        {
            if (qual_ipfile->present == FALSE)
            {
                qual_ipfile->present = TRUE;
                qual_ipfile->value = STRDUP (qargv[arg_no]);
            }
            else if (qual_opfile->present == FALSE)
            {
                qual_opfile->present = TRUE;
                qual_opfile->value = STRDUP (qargv[arg_no]);
            }
            else
            {
                USAGE ("too many file names specified");
            }
        }
    }
}                               /* scan_cmd_line */



/*-
**============================================================================
**
** FUNCTION
**
**      set_ftime
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    set_ftime (File_Info *target_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    set_ftime (target_file)
        File_Info              *target_file;
#endif                          /* (ANSI_SYNTAX) */
{
    int                     status;

#if (UNIX_BSD)
    TIME_T                  timep[2];
#endif				/* (UNIX_BSD) */

#if (UNIX_SYSV)
    struct utimbuf          utb;
#endif				/* (UNIX_SYSV) */


    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    DEBUG_2 ("set_ftime: setting file `%s' time to %.24s",
             target_file->file_spec, ctime (&(target_file->mod_time)));

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
#if (UNIX_SYSV)
    utb.actime  = target_file->mod_time;
    utb.modtime = target_file->mod_time; 
    status = utime ((char *) target_file->file_spec, &utb);
#endif				/* (UNIX_SYSV) */

#if (UNIX_BSD)
    timep[0] = target_file->mod_time;  
    timep[1] = target_file->mod_time;  
    status = utime ((char *) target_file->file_spec, timep);
#endif				/* (UNIX_BSD) */


    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (status != 0)
    {
        ERRORMSG_2 ("error setting file `%s' time to %.24s",
                    target_file->file_spec, ctime (&(target_file->mod_time)));
        explain_error ();
    }
    return;

}                               /* set_ftime */



/*-
**============================================================================
**
** FUNCTION
**
**      set_pipe_mode
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    set_pipe_mode (File_Info *target_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    set_pipe_mode (target_file)
        File_Info              *target_file;
#endif                          /* (ANSI_SYNTAX) */
{
    return;
}                               /* set_pipe_mode */



/*-
**============================================================================
**
** FUNCTION
**
**      split_file_spec
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    Unsigned16              split_file_spec (CONST char *full_spec,
                                             char *preamble,
                                             char *name,
                                             char *extension,
                                             char *postamble)
#else                           /* NOT (ANSI_SYNTAX) */
    Unsigned16              split_file_spec (full_spec, preamble, name,
                                             extension, postamble)
        CONST char             *full_spec;
        char                   *preamble;
        char                   *name;
        char                   *extension;
        char                   *postamble;
#endif                          /* (ANSI_SYNTAX) */
{
    char                    tmp_full_spec[MAX_PATH + 1];
    char                   *start_ptr;
    char                   *tmp_ptr;
    int                     idx;
    Unsigned16              path_components;


    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (preamble != NULL)
    {
        *preamble = '\0';
    }
    if (name != NULL)
    {
        *name = '\0';
    }
    if (extension != NULL)
    {
        *extension = '\0';
    }
    if (postamble != NULL)
    {
        *postamble = '\0';
    }
    path_components = FS_NOTHING;

    if ((full_spec == NULL) || (*full_spec == '\0'))
    {
        return (path_components);
    }

    strcpy (tmp_full_spec, full_spec);
    start_ptr = (char *) tmp_full_spec;

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    tmp_ptr = STRRSTR (start_ptr, ":/");

    if (tmp_ptr != NULL)
    {
        path_components |= FS_PREAMBLE;

        if (preamble != NULL)
        {
            for (idx = 0; ((idx < MAX_PREAMBLE) && (start_ptr <= tmp_ptr));
                 idx++)
            {
                *preamble++ = *start_ptr++;
            }
            *preamble = '\0';
        }
        start_ptr = ++tmp_ptr;
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    tmp_ptr = STRCHR (start_ptr, '.');

    if (tmp_ptr != NULL)
    {
        path_components |= FS_EXTENSION;

        if (extension != NULL)
        {
            strncpy (extension, tmp_ptr, MAX_EXT);
        }
        *tmp_ptr = '\0';
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (*start_ptr != '\0')
    {
        path_components |= FS_NAME;

        if (name != NULL)
        {
            strncpy (name, start_ptr, MAX_NAME);
        }
    }

    return (path_components);
}                               /* split_file_spec */



/*-
**============================================================================
**
** FUNCTION
**
**      tz_offset
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    TIME_T                  tz_offset (TIME_T the_time)
#else                           /* NOT (ANSI_SYNTAX) */
    TIME_T                  tz_offset (the_time)
        TIME_T                  the_time;
#endif                          /* (ANSI_SYNTAX) */
{
    struct tm              *tm;

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
#if (UNIX_BSD)
    TIME_T                  timezone;
    struct timeb            timeb_struct;

    ftime (&timeb_struct);
    timezone = (TIME_T) timeb_struct.timezone * (TIME_T) 60;
#endif				/* (UNIX_BSD) */

    tm = localtime (&the_time);

    if (tm->tm_isdst > 0)
    {
        return (timezone - (TIME_T) 3600);
    }
    else
    {
        return (timezone);
    }
}                               /* tz_offset */



/*-
**============================================================================
**
** FUNCTION
**
**      user_message
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    user_message (CONST Int16 status,
                                          CONST char *msg_str,
                                          File_Info *log_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    user_message (status, msg_str, log_file)
        CONST Int16             status;
        CONST char             *msg_str;
        File_Info              *log_file;
#endif                          /* (ANSI_SYNTAX) */
{
    int                     to_stderr;
    int                     to_logfile;
    char                   *status_str;


    /*-
    **------------------------------------------------------------------------
    ** Check the value supplied for the message status and set the output
    ** destinations accordingly.
    **------------------------------------------------------------------------
    */
    to_logfile = TRUE;
    to_stderr = FALSE;

    switch (status)
    {
        case NORMAL_STATUS:
        case LOG_STATUS:
            status_str = "";
            break;
        case INFO_STATUS:
            status_str = " -- ";
            to_stderr = TRUE;
            break;
        case WARNING_STATUS:
            status_str = "\nWARNING: ";
            to_stderr = TRUE;
            break;
        case ERROR_STATUS:
            status_str = "\nERROR: ";
            to_stderr = TRUE;
            break;
        case FATAL_STATUS:
            status_str = "\nFATAL: ";
            to_stderr = TRUE;
            break;
        default:
            INTERNAL_ERROR ("user_message");
            vv_exit ();
    }

    /*-
    **------------------------------------------------------------------------
    ** If the message is NULL, just update the status and/or the message
    ** show status.
    **------------------------------------------------------------------------
    */
    if ((msg_str == NULL) || (*msg_str == '\0'))
    {
        if (status > G_status)
        {
            G_status = status;
        }
        return;
    }

    /*-
    **------------------------------------------------------------------------
    ** Make sure that we don't try to write to a NULL log file pointer.
    **------------------------------------------------------------------------
    */
    if (log_file->file_ptr == (FILE *) NULL)
    {
        to_logfile = FALSE;
    }

    /*-
    **------------------------------------------------------------------------
    ** If the message is to be sent to the log file and stderr, check whether
    ** the log file output is also to stderr.  If it is, disable the 'stderr'
    ** output.
    **------------------------------------------------------------------------
    */
    if (to_logfile && to_stderr)
    {
        if (log_file->file_ptr == stderr)
        {
            to_stderr = FALSE;
        }
    }

    /*-
    **------------------------------------------------------------------------
    ** If the message is to be sent to stderr, output it here.
    **------------------------------------------------------------------------
    */
    if (to_stderr)
    {
        FPRINTF (stderr, "%s%s\n", status_str, msg_str);
    }

    /*-
    **------------------------------------------------------------------------
    ** If the message is to be sent to the log file, output it here.  If
    ** debugging is enabled, report also the name of the calling function.
    **------------------------------------------------------------------------
    */
    if (to_logfile)
    {
        FPRINTF (log_file->file_ptr, "%s%s\n", status_str, msg_str);
    }

    /*-
    **------------------------------------------------------------------------
    **
    **------------------------------------------------------------------------
    */
    if (status > G_status)
    {
        G_status = status;
    }

}                               /* user_message */



/*-
**============================================================================
**
** FUNCTION
**
**      vv_exit
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    vv_exit (void)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    vv_exit ()
#endif                          /* (ANSI_SYNTAX) */
{
    switch (G_status)
    {
        case LOG_STATUS:
        case NORMAL_STATUS:
            exit (0);
            break;
        case WARNING_STATUS:
            exit (1);
            break;
        case ERROR_STATUS:
            exit (2);
            break;
        case FATAL_STATUS:
        default:
            exit (3);
            break;
    }
}                               /* vv_exit */



/*-
**============================================================================
**
** FUNCTION
**
**      write_bytes
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    write_bytes (CONST Int32 max_bytes,
                                         CONST char *buffer,
                                         File_Info *op_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    write_bytes (max_bytes, buffer, op_file)
        CONST Int32             max_bytes;
        CONST char             *buffer;
        File_Info              *op_file;
#endif                          /* (ANSI_SYNTAX) */
{
    Int32                   idx;

    for (idx = 0L; idx < max_bytes; idx++)
    {
        fputc (buffer[(SIZE_T) idx], op_file->file_ptr);
    }
}                               /* write_bytes */



/*-
**============================================================================
**
** FUNCTION
**
**      write_record
**
** NOTE: see related file 'specific.h' for functional description.
**
**============================================================================
*/
#if (ANSI_SYNTAX)
    void                    write_record (CONST Int32 max_bytes,
                                          CONST char *buffer,
                                          File_Info *op_file)
#else                           /* NOT (ANSI_SYNTAX) */
    void                    write_record (max_bytes, buffer, op_file)
        CONST Int32             max_bytes;
        CONST char             *buffer;
        File_Info              *op_file;
#endif                          /* (ANSI_SYNTAX) */
{
    INTERNAL_ERROR ("write_record");
}                               /* write_record */