/*
 *  FILE
 *	strings.c
 *
 *  VERSION
 *	V1.02, Not Released
 *
 *  DESCRIPTION
 *	A utility which prints out strings in binary files.
 *
 *	This program can also be seen as a test for my "ALiLib"
 *	project. The program relies on 1) The clop() function for
 *	parsing options and
 *	2) My version of malloc/free is also used. I don't know wheter
 *	or not it differs from the lattice version, but Lattice is
 *	known not to be simple......
 *
 *  STATUS
 *	All rights reserved.
 *
 *  AUTHOR
 *	Anders 'ALi' Lindgren
 *	Mälarblick 8
 *	S-161 51 Bromma
 *	Sweden
 *
 *  CODE HISTORY
 *	Some years ago	Idea
 *	90-Dec-10	Preliminary specs written during some booring
 *			army radio guard night-hours.
 *	90-Dec-16	Start of hacking, Program basically ready.
 *			Several fancy details included, like ctrl-C.
 *	91-Jan-09 V1.01	Skip starting blanks
 *	92-Jan-12 V1.02	View only strings terminated by NIL, CR, LF or
 *			TAB and long strings (which doesn't fit into
 *			the buffer).
 *			Static version string added.
 *
 *  SEE ALSO
 *	ALiLib-documentation
 *	strings.doc
 *
 *  TODO
 *	ALiLib-interfacing.
 *	A 0xFF (˙) remover? (option?)
 */

static char * version_strings = "\0$VER: strings V1.02 (" __DATE__
				") by Anders Lindgren.";

#include <exec/types.h>

#include <libraries/dos.h>

#define _USEOLDEXEC_
#include <proto/exec.h>
#include <proto/dos.h>

#include <ctype.h>
#include "stdlib.h"	/* It doesn't hurt if it's my own. */

#include "clop.h"


/*
 *  DEFINITIONS
 */

#define BUFSIZE	512	/* Remember, it's stored on the stack */


/*
 *  WARNING! These definitions corresponds to the clop_array below!
 */

#define OPTION_SMALL	0
#define OPTION_EXTENDED	1
#define OPTION_BYTE	2
#define OPTION_OUTPUT	3
#define OPTION_USAGE	4
#define OPTION_HELP	5


#define DOS_PRINT(x) Write(Output(),(x),sizeof(x))

#define tohex(x)    ((x)>9?(x)+'A'-10:(x)+'0')


/*
 *  DEFINE
 *	isprint()
 *
 *  DESCRIPTION
 *	A simple Amiga-specific isprint(). This is much shorter than
 *	dragging in a _ctype array.
 */

#ifdef isprint
#undef isprint
#endif

#define isprint(c)	((((c)>=32) && ((c)<=126)) || (((c)>=160) && ((c)<=255)))


/*
 *  FORWARD REFERENCES
 */

extern int  parse_opts(char *, struct clop_array *);
extern int  aloha(BPTR, BPTR, int, int, int);
extern void write_offset(BPTR, unsigned long);


/*
 *  GLOBAL VARIABLES
 */

struct DosLibrary * DOSBase;


/*
 *  FUNCTION
 *	not_main
 *
 *  DESCRIPTION
 */

int __saveds __asm
not_main(register __a0 char * cline, register __d0 long len)
{
    int	   rc;
    char * args;

    struct clop_list clop_list = { NULL, '\0' };

    struct clop_array clop_array [ ] = {
	{ 'n', CLOPT_UNUMBER, 0, 0, 6L   },	/* Shortest string to print */
	{ 'e', CLOPT_BOOL,    0, 0, 0L   },	/* Extended character set   */
	{ 'b', CLOPT_BOOL,    0, 0, 0L   },	/* Byte offsets		    */
	{ 'o', CLOPT_STRING,  0, 0, NULL },	/* Output file		    */
	{ 'u', CLOPT_BOOL,    0, 0, 0L   },
	{ '?', CLOPT_BOOL,    0, 0, 0L	},	/* Help page		    */
	{ '\0', CLOPT_END,    0, 0, NULL }
    };

    rc = RETURN_FAIL;

    if (DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 0L)) {
	cline[len - 1] = '\0';
	if (args = clop(cline, & clop_list, clop_array)) {

	    if (clop_array[OPTION_HELP].value.bool ||
		clop_array[OPTION_USAGE].value.bool) {

		rc = RETURN_OK;

		DOS_PRINT(
"Strings V1.01  Written by Anders Lindgren.\n"
"usage: strings [-nX] [-e] [-b] [-ooutfile] [infile]\n"
"    -n Smallest string to print.  (default 6)\n"
"    -e Use extended character set.(default disabled)\n"
"    -b Print out byte offset.     (default disabled)\n"
"    -o Print out to a file insted of standard out.\n" );

	    }
	    else {
		rc = parse_opts( args, clop_array );
	    }

	}
	else {
	    if (clop_list.error == CLOPE_UNKNOWN_OPTION) {
		DOS_PRINT("strings:Unknown switch: -");
		Write(Output(), & clop_list.failing_opt, 1);
		DOS_PRINT("\n");
	    }
	    else {
		DOS_PRINT("strings:Incorrect usage of switch: -");
		Write(Output(), & clop_list.failing_opt, 1);
		DOS_PRINT("\n");
	    }
	}
	/*
	 * Free everything allocated.
	 */
	 
	freeclop(clop_array);

	CloseLibrary((struct Library *)DOSBase);
    }
    return (rc);
}


/*
 *  FUNCTION
 *	parse_opts
 *
 *  DESCRIPTION
 *	Open all files, and establish all flags etc.
 *
 *	Rename function? Guess so....
 */

int
parse_opts( char * filename, struct clop_array * clop_array)
{
    BPTR   input;
    BPTR   output;

	/* Flags, using opened input/output FH, instead of std* */
    int	   closein;
    int    closeout;

    unsigned int smallest;
    char * ofn;		/* Output file name	*/
    int	   rc;		/* Returncode		*/

    rc = RETURN_FAIL;

    smallest = clop_array[OPTION_SMALL].value.unumber;

    if ((smallest < BUFSIZE) && (smallest > 0) ) {

	/*
	 * Establish an input stream.
	 */

	if (* filename) {
	    if (input = Open(filename, MODE_OLDFILE)) {
		closein = TRUE;
	    }
	    else {
		DOS_PRINT("strings:Didn't find input file.\n");
		return (RETURN_FAIL);
	    }
	}
	else {
	    input = Input();
	    closein = FALSE;
	}

	/*
	 * Establish output stream.
	 */

	if (ofn = clop_array[OPTION_OUTPUT].value.string) {

	    if (* ofn) {
		closeout = TRUE;
		if ( (output = Open(ofn, MODE_NEWFILE)) == NULL) {
		    if (closein) {
			Close(input);
		    }
		    DOS_PRINT("strings:Error opening output file.\n");
		    return(RETURN_FAIL);
		}
	    }
	    else {
		if (closein) {
		    Close(input);
		}
		DOS_PRINT("strings:Missing output filename.\n");
		return(RETURN_FAIL);
	    }
	}
	else {
	    output = Output();
	    closeout = FALSE;
	}

	/*
	 * Call the actual heart of the program, and serv it all
	 * it want to know on a silver plate.
	 */

	rc = aloha(input, output, smallest,
		clop_array[OPTION_EXTENDED].value.bool,
		clop_array[OPTION_BYTE].value.bool);

	/*
	 * Clean up files
	 */

	if (closein) {
	    Close(input);
	}

	if (closeout) {
	    Close(output);
	}
    }
    else {
	DOS_PRINT("strings:Illegal -n number.\n");
    }
    return(rc);
}


/*
 *  FUNCTION
 *	aloha
 *
 *  DESCRIPTION
 *	The actual main loop.
 *
 *	The name? Naw, I really can't think of any good, since
 *	it's only a main, level 3.
 *
 *  SYNOPSIS
 *	aloha(input, output, smallest, extended, byteflg)
 *		input, output - AmigaDOS filehandlers.
 *		smallest      - Shortest length to bother to print
 *		extended      - (flag) Consider the extended characters.
 *		byteflg	      - (flag) Print out the byteoffset.
 */

int
aloha(BPTR input, BPTR output, int smallest, int extended, int byteflg)
{
    unsigned char inbuf [BUFSIZE];
    unsigned char outbuf[BUFSIZE];

    int soffset;
    int doffset = 0;

    long counter = 0;
    long byteoffset = 0;
    unsigned char ch;


    int al;	/* Actual length read */

	/* A _very_ long output line, longer than one buffer. */
    int longflg = FALSE;

    int ctrlc = FALSE;

    do {	/* Until eof, or ctrl-C pressed */

	al = Read(input, inbuf, BUFSIZE);

		/* Check each character in the input buffer */
	for (soffset = 0; ( (soffset < al) && ! ctrlc ); soffset++) {

	    ch = inbuf[soffset];

		/*
		 * Check if the character is printable.
		 */
	    if ( isprint(ch) && (extended || isascii(ch)) ) {

		    /*
		     * Don't register spaces if they start a string.
		     */
		if (ch != ' ' || doffset != 0 || longflg) {

			/*
			 * Save the position for this string
			 */
		    if ((doffset == 0) && (! longflg)) {
			byteoffset = counter;
		    }

		    outbuf[doffset++] = ch;

			/*
			 * Is the output buffer full?
			 * Let's empty it and flag.
			 */
		    if (doffset == BUFSIZE) {
			if ( (! longflg) && (byteflg) ) {
			    write_offset(output, byteoffset);
			}
			Write(output, outbuf, BUFSIZE);
			longflg = TRUE;
			doffset = 0;
		    }
		}
	    }
	    else {

		/*
		 * A nonprintable character found, let's see
		 * if the characters found before if it's long
		 * enough to print, or if the characters in
		 * the buffer is part of a earlier, very long,
		 * string.
		 *
		 * 1992-01-12:  Just strings which are terminated
		 *		by NIL, CR, LF or TAB are considered
		 *		strings, if the string aren't so
		 *		big so it doesn't fit the buffer,
		 *		in which case we will se it anyway.
		 */

		if ( ( ((ch == '\0') ||
			(ch == 10)  ||
			(ch == 13)  ||
			(ch == 9))    && (doffset >= smallest) ) || 
		     longflg ) {
		    if ( (! longflg) && (byteflg) ) {
			write_offset(output, byteoffset);
		    }
		    Write(output, outbuf, doffset);
		    Write(output, "\n", 1);
		}
		doffset = 0;
		longflg = FALSE;
	    }
	    counter++;	/* Byte counter */

	    if(SetSignal(0L,0L) & SIGBREAKF_CTRL_C) {
		ctrlc = TRUE;
	    }
	}
    } while((al > 0) && (!ctrlc));

	/*
	 * If the file ended, and we have a correct strings in our
	 * buffer, print it out.
	 */

    if (ctrlc) {
	DOS_PRINT("*** Break!\n");
    }
    else if ((doffset >= smallest) || longflg) {
	if ( (! longflg) && (byteflg) ) {
	    write_offset(output, byteoffset);
	}
	Write(output, outbuf, doffset);
	Write(output, "\n", 1);
    }

    return(al >= 0 ? RETURN_OK : RETURN_ERROR);
}


/*
 *  FUNCTION
 *	write_offset
 *
 *  DESCRIPTION
 *	This function writes out the offset in the form:
 *	"X:" where X are at four or eigth hexadecimal
 *	digits.
 */

void
write_offset(BPTR output, unsigned long offset)
{
    char outbuf[9];

    int i;
    int firstn0;

    outbuf[8] = ':';

	/*
	 * Decide if we shall print out four hexadecial digits, or
	 * if we need all eight.
	 */
    firstn0 = offset & 0xFFFF0000 ? 0 : 4;

    for (i=7; i>=0; i--) {
	outbuf[i] = tohex(offset & 0x0F);
	offset >>= 4;
    }

    Write(output, & outbuf[firstn0], 9-firstn0);

    return;
}
