/*
 *	fhist - file history and comparison tools
 *	Copyright (C) 1991-1994, 1998-2000, 2002 Peter Miller;
 *	All rights reserved.
 *
 *	Derived from a work
 *	Copyright (C) 1990 David I. Bell.
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 * MANIFEST: functions to read and write ascii files
 *
 * Functions to position within an ascii file and read lines from it.
 */

#include <ac/errno.h>
#include <fcntl.h>
#include <ac/libintl.h>
#include <ac/stdarg.h>
#include <ac/stdio.h>
#include <ac/string.h>
#include <ac/unistd.h>

#include <error_intl.h>
#include <fcheck.h>
#include <fileio.h>
#include <cmalloc.h>
#include <str.h>

#define LINEALLOCSIZE 132	/* allocation size for line buffer */


/*
 * Position absolutely within a file to the indicated byte position,
 * returns a negative value if failed.
 */

void
seekf(FILE *fp, long pos, const char *filename)
{
    if (fseek(fp, pos, SEEK_SET) == -1)
    {
	sub_context_ty	*scp;

	scp = sub_context_new();
	sub_errno_set(scp);
	sub_var_set_charstar(scp, "File_Name", filename);
	sub_var_set_long(scp, "Number", pos);
	fatal_intl(scp, i18n("fseek(\"$filename\", $number): $errno"));
    }
}


/*
 * Skip forwards through a file by the specified number of lines.
 * Returns nonzero on end of file or any error.
 */

int
skipf(FILE *fp, long count, const char *filename)
{
    int             c;

    while (count > 0)
    {
	c = getc(fp);
	if (c == EOF)
	{
	    if (ferror(fp))
	    {
		sub_context_ty	*scp;

		scp = sub_context_new();
		sub_errno_set(scp);
		sub_var_set_charstar(scp, "File_Name", filename);
		fatal_intl(scp, i18n("read \"$filename\": $errno"));
	    }
	    return -1;
	}
	if (c == '\n')
	    --count;
    }
    return 0;
}


/*
 * Function to read in the next line from a file, no matter how long it is.
 * This returns a pointer to the string, and also indirectly its length.
 * The string is normally returned from a static string, which is reused
 * for each call.  But if keep is non-zero, then the returned string
 * belongs to the caller and must later be freed.  The returned line
 * is ended with a newline and null character, but the returned length
 * does not count the null character.  Returns 0 on an error, with the
 * error stored in the fp structure.
 */

char *
readlinef(FILE *fp, long *retlen, int keep, const char *filename,
    int *is_bin_file)
{
    char            *cp;		/* pointer to character string */
    char            *dp;		/* destination pointer */
    long            cursize;		/* current size */
    long            count;		/* length of data read from file */
    long            totallen;		/* total length of data */
    static char     *linebuffer;	/* common line buffer */
    static long     linelength;		/* total length of line */

    totallen = 0;
    if (linelength == 0)
    {
	/* allocate line buffer */
	linebuffer = r_alloc_and_check(LINEALLOCSIZE + 1);
	linelength = LINEALLOCSIZE;
    }
    cp = linebuffer;
    cursize = linelength;
    for (;;)
    {
	count = readf(fp, cp, cursize, filename, is_bin_file);
	if (count <= 0)
	{
	    if (totallen == 0)
	       	return 0;
	    warning_last_line_unterminated(filename);
	    count = 1;
	    cp[0] = '\n';
	}
	totallen += count;
	if (cp[count - 1] == '\n')
	    break;
	linebuffer =
	    r_realloc_and_check(linebuffer, linelength + LINEALLOCSIZE + 1);
	cp = linebuffer + totallen;
	linelength += LINEALLOCSIZE;
	cursize = LINEALLOCSIZE;
    }
    if (retlen)
	*retlen = totallen;
    if (!keep)
	dp = linebuffer;
    else
    {
	dp = cm_alloc_and_check(totallen + 1);
	memcpy(dp, linebuffer, (size_t)totallen);
    }
    dp[totallen] = 0;
    return dp;
}


/*
 * Read data from the current position in a file until a newline is found
 * or the supplied buffer is full.  These two cases can be distinguished by
 * checking the last character of the returned data.  Returns the number of
 * characters in the line (including the newline), or -1 if an error or end
 * of file in the middle of the line is found.  Line is terminated by a null
 * character if space allows.  Returns zero if the end of file occurs at
 * the front of the line.
 */

long
readf(FILE *fp, char *buf, long size, const char *filename, int *is_bin_file)
{
    char            *bp;
    char            *ep;
    int             c;

    bp = buf;
    ep = buf + size;
    while (bp < ep)
    {
	c = getc(fp);
	if (c == EOF)
	{
	    if (ferror(fp))
	    {
		sub_context_ty	*scp;

		scp = sub_context_new();
		sub_errno_set(scp);
		sub_var_set_charstar(scp, "File_Name", filename);
		fatal_intl(scp, i18n("read \"$filename\": $errno"));
	    }
	    if (bp != buf)
		break;
	    return -1;
	}
	if (c == 0)
	{
	    c = 0x80;
	    *is_bin_file = 1;
	}
	*bp++ = c;
	if (c == '\n')
	    break;
    }
    return (bp - buf);
}


/*
 * Write data at the current position in a file.
 * This buffers the data in the current block, thus flushing is needed later.
 * Prints a fatal error message and exits on errors.
 */

void
writefx(FILE *fp, char *buf, long size, const char *filename)
{
    fwrite(buf, 1, size, fp);
    if (ferror(fp))
    {
	sub_context_ty	*scp;

	scp = sub_context_new();
	sub_errno_set(scp);
	sub_var_set_charstar(scp, "File_Name", filename);
	fatal_intl(scp, i18n("write \"$filename\": $errno"));
    }
}


/*
 * Function to copy data from one file to another.  Data is copied from
 * the current position of the input file to the current position of the
 * output file until end of file is reached on the input file, or until
 * the specified number of characters has been read.  Minus one means
 * there is no limit to the amount to be copied.  Line boundaries and
 * space expansions are ignored.  Returns nonzero on errors.
 */

int
copyf(FILE *ip, FILE *op, long count, const char *ifn, const char *ofn)
{
    char            buffer[1 << 10];
    long            len;

    while (count)
    {
	if (count < 0 || count > sizeof(buffer))
	    len = sizeof(buffer);
	else
	    len = count;
	len = fread(buffer, 1, len, ip);
	if (len < 0)
	    return EOF;
	if (len == 0)
	    break;
	fwrite(buffer, 1, len, op);
	if (ferror(op))
	    return EOF;
	if (count >= 0)
	    count -= len;
    }
    return 0;
}

void
copyfx(FILE *ip, FILE *op, long count, const char *ifn, const char *ofn)
{
    char            buffer[1 << 10];
    long            len;
    sub_context_ty  *scp;

    while (count)
    {
	if (count < 0 || count > sizeof(buffer))
	    len = sizeof(buffer);
	else
	    len = count;
	len = fread(buffer, 1, len, ip);
	if (len < 0)
	{
	    scp = sub_context_new();
	    sub_errno_set(scp);
	    sub_var_set_charstar(scp, "File_Name", ifn);
	    fatal_intl(scp, i18n("read \"$filename\": $errno"));
	}
	if (len == 0)
	    break;
	fwrite(buffer, 1, len, op);
	if (ferror(op))
	{
	    scp = sub_context_new();
	    sub_errno_set(scp);
	    sub_var_set_charstar(scp, "File_Name", ofn);
	    fatal_intl(scp, i18n("write \"$filename\": $errno"));
	}
	if (count >= 0)
	    count -= len;
    }
    fflush_and_check(op, ofn);
}


/*
 * Function to type out the rest of an input file to the terminal.
 * It is assumed that we are positioned at the beginning of a line.
 */

int
typef(FILE *fp, const char *filename)
{
    return copyf(fp, stdout, -1L, filename, gettext("standard output"));
}


void
typefx(FILE *fp, const char *filename)
{
    copyfx(fp, stdout, -1L, filename, gettext("standard output"));
}


syntax highlighted by Code2HTML, v. 0.9.1