#ifdef	ournix
#include "ournix.h"
#endif
char sccsID[] =
	"@(#) 78bit.c V1.5 Copyright Julian H. Stacey, Munich, 25th June 1990 - 2021-12-29\n" ;

/* Manual exists */

/* Assume output stream will arrive eventually on a printer that
	usses the epson control sequence method of setting the 8th parity bit
	in bytes (ie ^[>). 
 */

#include	<stdio.h>
#include <stdlib.h>
#include <string.h>

#if ( MSDOS || SVR2 || __FreeBSD__ || __OpenBSD__ ) /* { */
#include	<fcntl.h>
#endif          /* } */

#ifdef M_XENIX /* SCO System V/386 Release 3.2V2.0*/
#include	<sys/fcntl.h>
#define	L_SET	SEEK_SET
/* This lousy SCO product has no symbolic links - hence no lstat */
#endif

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

#ifdef ns32000	/* { */
#define BSD
#endif		/* } */

#ifdef __386BSD__	/* { */
#ifndef BSD
#define BSD
#endif		/* } */
#endif		/* } */

#ifdef	BSD	/* { */
#include	<sys/file.h>
#endif		/* } */

#ifdef	MSDOS	/* { */
#include <sys/utime.h>
#endif		/* } */
#ifdef unix	/* { stop a warning in FreeBSD-12.2-RELEASE */
#include <utime.h>
#endif		/* } */

typedef	char FLAG ;
#define	BUFSIZE	100000	/* was 4096 till 930929,
	 when resume has about 1500 pcl control chars per line */

char	**ARGV ;
int	exit_count = 0 ;
int	src_fd ;
int	new_fd ;

char	buf_in[BUFSIZE + 1] ;

extern int errno ;

FLAG		preserve_date ;
FLAG		f_7_to_8 = 0 ;
FLAG		verbose ;

#define ESC 0x1b

#ifdef scs	/* { */
extern char *mktemp() ;
#define TMP_NAME "78XXXXXX"
#else		/* }{ */
#include <unistd.h>
#ifdef MSDOS	/* { */
#define TMP_NAME "78XXXXXX"
#else		/* }{ */
#define TMP_NAME "78bit.XXXXXX"
#endif		/* } */
#endif		/* } */
#include <sys/param.h>	/* for MAXPATHLEN */
char	tmp_name[MAXPATHLEN] ;		/* temporary file name */



	int
out_ch(ch)
	char ch ;
	{
	if (write(new_fd,&ch,1) < 1)
		{
		perror(*ARGV);
		fprintf(stderr,"%s: Failed to write %d.\n",*ARGV,(int)ch);
		(void) close(src_fd);
		(void) close(new_fd);
		(void) unlink(tmp_name);
		return(1) ;	/* dont exit, as error could have been because
				we couldnt create a large temp. file, & next 
				argument may require a smaller temp file */
		}
	return(0);
	}

/* Strategy: Reads input in large blocks, Writes output one char at a time. */
	int
do_file(src_name)
	char	*src_name ;
	{
#define	INNOCUOUS	' '
	char	last_ch = INNOCUOUS  ;
	char	parity = 0 ;
	struct stat	stat_buf ;
	int	len1, len2 ;

	struct utimbuf set_time ;// MSDOS & New Unix eg FreeBSD-12.2-RELEASE 2021-12-29
	// time_t	set_time[2];	// Old Unix

	char	*base_p, *top_p ;
	char	*tmp_p ;
#ifdef	MSDOS	/* { */
	char	mv_cmd[500] ;
#endif		/* } */
	int	byte_count ;
	FLAG	esoteric = 0 ;
	char ch = INNOCUOUS ;
	long line_no = 1L ;

	if (src_name == (char *)0)
		{ /* no args ; pipe operation, standard input to output */
		src_fd = 0 ;	/* stdin */
		new_fd = 1 ;	/* stdout */
		}
	else	{
		if (verbose) { printf("%s",src_name); (void)fflush(stdout) ; }
#ifdef	unix	/* { */
#ifndef	M_XENIX	/* { */
		if ( lstat(src_name, &stat_buf) != 0)
			{
			fprintf(stderr,
				"%s: lstat failed on %s.\n", *ARGV,src_name);
			return(1) ;
			}
		if ( (stat_buf.st_nlink != 1 ) ||
	       		( (stat_buf.st_mode & S_IFMT ) != S_IFREG )
			) esoteric = 1 ;
#endif	     	/* } */
#endif	     	/* } */
		if ( stat(src_name, &stat_buf) != 0)
			{
			fprintf(stderr,"%s: stat failed on %s.\n",
				*ARGV,src_name);
			return(1) ;
			}
		if (stat_buf.st_size == (off_t)0 ) return(0) ;
		/* now create a name in the same directory as the source,
			so we can guarantee no linking across devices 
			will be attempted */
		(void)strcpy(tmp_name,src_name) ;
		/* to end of string JJ could use strrchr instead */
		for ( tmp_p = tmp_name ; (*tmp_p++ != '\0') ; ) ;
		for ( tmp_p -= 1 ; (*--tmp_p != '/')
#ifdef	MSDOS
				&& ( *tmp_p != '\\') 
#endif
				&& ( tmp_p >= tmp_name)	; ) ;
		(void)strcpy(tmp_p + 1,TMP_NAME);
		if (NULL == mktemp(tmp_name))
			{
			perror(*ARGV);
			fprintf(stderr,
			"%s: Program error, mktemp %s failed, exiting.\n",
				*ARGV,tmp_name);
			exit(++exit_count);
			}
		(void)strcat(tmp_name,".tmp");
		if ((src_fd = open(src_name,O_RDWR
#ifdef 	MSDOS	/* { */
						| O_BINARY
#endif		/* } */
		)) == -1)
			{
			fprintf(stderr,"%s: Can't open %s.\n",*ARGV,src_name) ;
			return(1) ;
			}

		/* create output temporary file */
		/* JJLATER malloc would be faster, & avoid using disc io if 
			file is small  but malloc wont allow much for msdos */
		if ((new_fd = open(tmp_name,O_RDWR | O_CREAT /* | O_TRUNC */
#ifdef 	MSDOS	/* { */
						| O_BINARY
#endif		/* } */
			, S_IREAD | S_IWRITE )) == -1)
			/* S_IREAD is read permission for owner */
			{
			perror(*ARGV);
			fprintf(stderr,
				"%s: Cant open temporary file %s. Continuing.\n"
				, *ARGV,tmp_name) ;
			(void) close(src_fd) ;
			return(1) ;	/* dont exit, as we just be out of
					unix inodes (or msdos directory entries
					in root directory) on this device,
					next argument may be a different file
					system, so keep trying. */
			}
		}

	while ( (byte_count = read(src_fd,buf_in, (int)BUFSIZE ) ) > 0) 
		{	
		for (tmp_p = buf_in, top_p = buf_in + byte_count ;
			byte_count-- ;
			tmp_p++ )
			{
			if (ch == '\n') line_no++ ;
			ch = *tmp_p ;
			if (f_7_to_8)
				{ /* set \0 to null char, then
					set parity bits where relevant */
				if (ch & 0x80)
					{
					fprintf(stderr,
			"%s: %s <%c>, (0x%x), line %ld\n,aborting %s.\n",
						*ARGV,
						"Error, parity bit in character"
						,ch & 0x7f,(int)(ch & 0xff),
						line_no, src_name) ;
					(void) close(src_fd) ;
					(void) close(new_fd) ;
					(void) unlink(tmp_name) ;
					return(1);
					}
				if (last_ch == '\\')
					{
					if (ch == '0') out_ch(((char) 0)|parity) ;	
					else if (ch == '\\') out_ch('\\'|parity) ;	
					else{
						/* default for other/unknown */
						fprintf(stderr,
				"%s: %s %c%c in line %ld,aborting %s.\n",
							*ARGV,
						"Error, unrecognised sequence",
							'\\',ch,line_no,
							src_name) ;
						(void) close(src_fd) ;
						(void) close(new_fd) ;
						(void) unlink(tmp_name) ;
						return(1);
						}
					last_ch = INNOCUOUS ;
					}
				else if (last_ch == ESC)
					{
					/* test for parity on */
					if (ch == '>') parity = 0x80 ;
					/* test for backslash */
					else if (ch == '#') parity = 0 ;
					else if (ch == ESC) out_ch(ESC|parity) ;
					/* default for other/unknown */
					else 	{
						out_ch(ESC | parity) ;
						out_ch(ch | parity) ;
						} 
					last_ch = INNOCUOUS ;
					}
				else	{	/* no pre-existing memorised
						special conditions */
					last_ch = ch ;
					if ((ch !='\\')  && (ch != ESC))
						out_ch(ch | parity) ;
					}
				}
			else	{ /* !f_7_to_8 */
				/* remove parity bits where relevant,
					 and use set/reset parity force
					 escape sequence; and convert
					 each null char to \\ */
				if (ch & 0x80)
					{
					/* tell printer to assert 8th bit 
					   for all following data	*/
					if (!parity)
						{ /* parity on comand */
						out_ch(ESC) ; out_ch('>') ; 
						parity = 0x80 ;
						}
					ch &= 0x7f ;
					}
				else	{
					if (parity)
						{ /* parity off command */
						out_ch(ESC) ; out_ch('#') ;
						parity = 0 ;
						}
					}
				if (ch == 0) { out_ch('\\') ; out_ch('0') ; }
				else if (ch == '\\') 
					{ out_ch('\\') ; out_ch('\\') ; }
				else if (ch == ESC) 
					{ out_ch(ESC) ; out_ch(ESC) ; }
				else out_ch(ch) ;
				}
			}
		if (!f_7_to_8)
			{ /* flush */
			if (parity)
				{ /* parity off command */
				out_ch(ESC) ;
				out_ch('#') ;
				}
			}
		else	{ /* f_7_to_8 */
			if ((ch == ESC) || (ch == '\\'))
				{
				fprintf(stderr,
					"%s: %s %s in line %ld,aborting %s.\n",
					*ARGV,
					"Error, unmatched terminal ",
					(ch == ESC) ? "backslash" : "escape",
					line_no, src_name) ;
				(void) close(src_fd) ;
				(void) close(new_fd) ;
				(void) unlink(tmp_name) ;
				return(1);
				}
			}
		}

	if (src_name != (char *)0)
		{
		/* Now return data to source */
		/* differentiate between regular file as opposed to the more
			esoteric cases of hard or symbolic link, character or
				block special, socket, or directory. */
		if (!esoteric)
			{ /* regular file, move file name */
			(void) close(src_fd) ;
			(void) close(new_fd) ;
#ifndef		BSD	/* not necessary for BSD */
			(void) unlink(src_name) ;
#endif
#ifndef		SVR2	/* { */
			if ( rename(tmp_name,src_name) )
				/* Microsoft C Compiler Version 3 manual 
				says arguments should be the opposite way
				round to BSD, but V4 compiler produces code
				that works OK with BSD parameter order - 
				thus this seems to be a bug/incompatability
				detected with V3 manual, (& possibly
				compiler, though untested) */
				{
				perror(*ARGV);
				fprintf(stderr,"%s: moving %s to %s failed.\n",
					*ARGV, tmp_name, src_name ) ;
				exit(++exit_count);	
					/* rename failure is serious, possible
					program error or other unexpected
					contingency, so dont just return, 
					but exit */
				}
#else			/* } { */
			sprintf(mv_cmd,"mv %s %s",tmp_name, src_name);
			system(mv_cmd);
#endif			/* } */
			}
#ifndef 	MSDOS	/* { */
		else	{ /* esoteric file, copy data back */
     			if ((lseek(src_fd, (off_t)0, L_SET) != 0) ||
     				(lseek(new_fd, (off_t)0, L_SET) != 0))
				{
				perror(*ARGV) ;
				fprintf(stderr,"%s: Fatal error, aborting.\n",
					*ARGV);
				exit(1);
				}
			while ( (byte_count = read(new_fd,buf_in,BUFSIZE) ) > 0)
				{
				if (write(src_fd,buf_in,byte_count) 
					!= byte_count )
					{
					/* JJLATER we should restore source
						here to original name */
					perror(*ARGV) ;
					fprintf(stderr,
						"%s: Fatal error, aborting.\n",
						*ARGV);
					exit(1);
					}
				}
			(void) close(src_fd) ;
			(void) close(new_fd) ;
			(void) unlink(tmp_name) ;
			}
#endif			/* } */

		if (preserve_date)
			{
			/* set time stamp */
			set_time.actime = stat_buf.st_atime ;
			set_time.modtime = stat_buf.st_mtime ;
			if (utime(src_name,&set_time))
				/* Old Unix :
				 * set_time[0] = stat_buf.st_atime ;
				 * set_time[1] = stat_buf.st_mtime ;
				 * if (utime(src_name,set_time))
				 */
				{
				fprintf(stderr,
				"%s: Failed to retain old time stamp on %s.\n"
					,*ARGV, src_name ) ;
				return(1) ;
				}
			}
		if (verbose)
			{
			len1 = len2 = strlen(src_name) ;
#define	BS	(char)8
			while (len1--) putchar(BS) ;
			for ( len1 = len2 ; len1-- ; putchar(' ') ) ;
			for ( len1 = len2 ; len1-- ; putchar(BS) ) ;
			(void)fflush(stdout) ;
			}
		}
	return(0) ;
	}

	int
main(argc, argv)
	int	argc ;
	char	**argv ;
	{

	ARGV = argv ;
#ifdef	VSL	/* { */
#include	"../../include/vsl.h"
#endif		/* } */

	while (--argc && ((**++argv == '-') || (**argv == '+')))
		switch (*++*argv)
			{
			case '8':
			case 'r': /* convert escape sequences to use 
					appropriate parity bits and null bytes*/
				f_7_to_8 = 1 ;
				break ;
			case '7':	/* convert null bytes & parirty bits to
						escape sequences */
				/* initial condition: f_7_to_8 = 0 ; */
				break ;
			case 'd': /* preserve date */
				preserve_date = 1 ;
				break ;
			case 'v': /* verbose */
				verbose = 1 ;
				break ;
			case 'V': /* version number identification */
				printf("%s",sccsID);
				break ;
			default:
				fprintf(stderr,
			"Aborting, Syntax error, Correct Usage:\n\t%s%s\n"
				,*ARGV, " [-d] [-r] [-v] [-V] [files]") ;
					exit(1) ;
				break ;
			}
	if (argc == 0) exit(do_file((char *)0)) ;
	else	{
		argc++ ; argv-- ;
		while (--argc > 0) if (do_file(*++argv)) exit_count++ ;
		}
	exit(exit_count) ;
	}
