#ifdef	ournix
#include "ournix.h"
#endif
char sccsID[] =
	"@(#) addcr.c V1.8 Copyright Julian H. Stacey 28th June 1989\n" ;

/* see manual */

#include	<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if ( hpux || MSDOS || SVR2 || __OpenBSD__ )
#include	<fcntl.h>
#endif
#if ( defined i386 || defined __FreeBSD__ /* allow for FreeBSD amd64 */ )
 /* Find std includes with: cp /dev/null ~/tmp/tmp.c ; cc -E -dM ~/tmp/tmp.c */
#include	<sys/fcntl.h>
#endif
#include	<sys/types.h>
#include	<sys/stat.h>
#ifdef ns32000	/* { */
#define BSD
#endif		/* } */
#ifdef	BSD	/* { */
#include	<sys/file.h>
#endif		/* } */
#ifdef	MSDOS	/* { */
#include <sys/utime.h>
#endif		/* } */

typedef char FLAG ;

#define BUFSIZE 65536
/* BUFSIZE was 4096 until 15 Dec 2000, when increased to 65536,
   when I discovered that even 32768 was not enough for processing long lines
   produced by script command wrapping logs of FreeBSD
	cd /usr/src/release ; make release
   JJLATER perhaps I should make this a -l atol parameter
*/

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

char	buf_in[BUFSIZE + 1] ;
char	buf_out[BUFSIZE + 2] ;

extern int errno ;

FLAG	preserve_date ;
FLAG	reverse = 0 ;
FLAG	verbose ;

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

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

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

	while (--argc && **++argv == '-')
		switch (*++*argv)
			{
			case 'r': /* reverse the process, rid trailing \r */
				reverse = 1 ;
				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) ;
	}

/* Strategy: Reads input in large blocks, Writes output one line at a time. */
do_file(src_name)
	char	*src_name ;
	{
	struct stat	stat_buf ;
	int		len1, len2 ;
#ifdef	MSDOS	/* { */
	struct		utimbuf set_time ;
#endif		/* } */
#ifdef	BSD	/* { */
	time_t	timep[2];
#endif		/* } */

	char	*base_p, *top_p ;
	char	*tmp_p ;
#ifdef	MSDOS	/* { */
	char	mv_cmd[500] ;
#endif		/* } */
	int	byte_count ;
	unsigned remnant = 0 ;	/* number of bytes left over from last
						block read */
	FLAG	esoteric = 0 ;

	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	/* { */
		if ( lstat(src_name, &stat_buf) != 0)
			{
			fprintf(stderr,"%s: stat failed on %s.\n",
				*ARGV,src_name);
			return(1) ;
			}
		if (
#ifdef	MSDOS	/* { */
			0
#endif		/* } */
#ifdef	unix	/* { */
			(stat_buf.st_nlink != 1 ) ||
			( (stat_buf.st_mode & S_IFMT ) != S_IFREG )
#endif		/* } */
			) esoteric = 1 ;
#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) ; ) ;
#ifdef DEBUG
		printf("BLA X%sX X%sX\n",tmp_p + 1,TMP_NAME);
#endif
		(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, exiting.\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 + remnant,
		(int)(BUFSIZE - remnant) ) ) > 0) /* read blocks*/
		{
		byte_count += remnant ;
		buf_in[byte_count] = '\0' ;
		for (base_p = top_p = buf_in ; (top_p < buf_in + byte_count) ; )
			{
			if (*top_p == '\n' )
				{
				if (add_line(base_p,top_p -1)) return(1) ;
				base_p = ++top_p ;
				}
			else top_p++ ;
			}
		if (base_p == buf_in)
			{
			buf_in[byte_count] = '\0' ;
			fprintf(stderr,"%s: File %s, Line too long:\n %s\n",
				*ARGV, src_name, buf_in ) ;
			(void) close(src_fd) ;
			(void) close(new_fd) ;
			(void) unlink(tmp_name) ;
			return(1);	/* dont exit, because next file
						might be more reasonable. */
			}
		/* now collect remnant of input buffer excluding last \n,
			including last character in buffer, & move it down */
		if ( base_p < ( buf_in + byte_count))
			{
			(void)strcpy(buf_in,base_p);
			remnant = strlen(buf_in) ;
			}
		else remnant = 0 ;
		}

	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 */
#ifdef	i386	/* { */
#define L_SET SEEK_SET
	/* the pointer is set to offset bytes. */
#define L_INCR SEEK_CUR
	/* the pointer is set to its current location plus offset. */
#define L_XTND SEEK_END
	/* the pointer is set to the size of the file plus offset. */
#endif		/* } */
#ifdef hpux
#define L_SET SEEK_SET
#define L_INCR SEEK_CUR
#define L_XTND SEEK_END
#endif
			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 */
#ifdef	MSDOS	/* { */
			set_time.actime = stat_buf.st_atime ;
			set_time.modtime = stat_buf.st_mtime ;
			if (utime(src_name,&set_time))
#endif		/* } */
#ifdef	BSD	/* { */
			timep[0] = stat_buf.st_atime ;
			timep[1] = stat_buf.st_mtime ;
			if (utime(src_name,timep))
#endif		/* } */

				{
				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) ;
	}

/* Output chars starting from base_p, incrementing up to top_p inclusive
	return 0 == OK , 1 == error*/
add_line(base_p,top_p)
	char *base_p, *top_p;
	{
	unsigned	len ;
	char		*current_p ;

	for (current_p = buf_out ; (base_p <= top_p); base_p++ )
		if (*base_p != '\r' ) *current_p++ = *base_p ; /* suppress
		multiple \r\r before \n at end of line, afer addcr has been run
		several times on the same file, after a period of time	*/
#ifndef MSDOS	/* { */
	if (!reverse) *current_p++ = '\r' ;
#endif		/* } */
	*current_p++ = '\n' ;
	*current_p = '\0' ;
	len = strlen(buf_out) ;
	if (write(new_fd,buf_out,(int)len) < len)
		{
		perror(*ARGV);
		fprintf(stderr,"%s: Failed to write %s.\n",*ARGV,tmp_name);
		(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) ;
	}
