/*
 *	hexdump - dump a file as hexadecimal
 *	Copyright (c) 1991 Peter Miller
 *
 *	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.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <hexdump.h>
#include <mem.h>
#include <error.h>


void
hexdump(infile, outfile, start, finish, by_lines, width)
	char	*infile;
	char	*outfile;
	long	start;
	long	finish;
	int	by_lines;
	int	width;
{
	FILE	*input;
	FILE 	*output;
	long	addr;
	long	nlines;
	int	state;
	int	c;
	unsigned char *line;

	/*
	 * arrange for the width to be a power of 2
	 */
	width = (width - 11)/4;
	for (;;)
	{
		int	x;

		x = (width & -width);
		if (x == width)
			break;
		width -= x;
	}
	if (width < 1)
		fatal("width too small");
	line = (unsigned char *)mem_alloc(width);

	/*
	 * open the files
	 */
	if (infile)
	{
		input = fopen(infile, "rb");
		if (!input)
			nfatal("open \"%s\"", infile);
	}
	else
		input = stdin;
	if (outfile)
	{
		output = fopen(outfile, "w");
		if (!output)
			nfatal("creat \"%s\"", outfile);
	}
	else
		output = stdout;

	/*
	 * scan the file
	 */
	addr = 0;
	nlines = (unsigned long)(-1L) >> 1;
	if (by_lines)
	{
		nlines = finish - start;
		finish = (unsigned long)(-1L) >> 1;
		--start; /* line numbers are one based */
		while (start > 0)
		{
			c = getc(input);
			if (c == EOF)
			{
				if (ferror(input))
					bad_read:
					nfatal("read \"%s\"", infile);
				finish = 0;
				break;
			}
			++addr;
			if (c == '\n')
				--start;
		}
	}
	else
	{
		if (start)
		{
			struct stat	st;
	
			if (fstat(fileno(input), &st))
				nfatal("stat(\"%s\")", infile);
			if ((st.st_mode & S_IFMT) == S_IFREG)
			{
				/*
				 * seek on regular files
				 */
				if (fseek(input, start, 0) == -1)
					nfatal("lseek(\"%s\", %ld)", infile, start);
				addr = start;
			}
			else
			{
				/*
				 * skip characters on any other type of file
				 */
				while (addr < start)
				{
					c = getc(input);
					if (c == EOF)
					{
						if (ferror(input))
							goto bad_read;
						finish = 0;
						break;
					}
					++addr;
				}
			}
		}
	}

	state = 0;
	while (nlines > 0 && addr < finish)
	{
		int	j, k;
		long	base;

		base = addr;
		for
		(
			j = (base & (width - 1));
			nlines > 0 && addr < finish && j < width;
			++j
		)
		{
			c = getc(input);
			if (c == EOF)
			{
				if (ferror(input))
					goto bad_read;
				finish = 0;
				break;
			}
			++addr;
			line[j] = c;
			if (c == '\n')
				--nlines;
		}
		if (addr == base)
			break;
		fprintf(output, "%08X:", (base & -width));
		for (k = 0; k < (base & (width - 1)); ++k)
			fputs("   ", output);
		for ( ; k < j; ++k)
			fprintf(output, " %02X", line[k]);
		for ( ; k < width; ++k)
			fputs("   ", output);
		fputs("  ", output);
		for (k = 0; k < (base & (width-1)); ++k)
			putc(' ', output);
		for ( ; k < j; ++k)
		{
			c = line[k] & 0x7F;
			if (c < ' ' || c > '~')
				c = '.';
			putc(c, output);
		}
		putc('\n', output);
		if (ferror(output))
			bad_write:
			nfatal("write \"%s\"", outfile);
	}

	fclose(input);
	if (fclose(output))
		goto bad_write;
	mem_free((char *)line);
}
