// THIS IS THE NEW DEVELOPMENT VERSION, SEE ALSO ../cmpd
#ifdef	ournix	/* { */
// Edit using vi with directive:		:se ts=4
#include "ournix.h"
#endif		/* } */
char sccsID[] = "@(#) cmpd.c V1.21 Copyright Julian H. Stacey 14th May 1987 - 2020-04-08\n";
/* Copyright:
	This code was written independently by Julian H. Stacey before also being
	used or delivered with other code for Stacey/VSL clients.
	Clients who receive such code are permitted to use & resell code etc,
	but copyright to this file is Not assigned to recipients.
	& Liability is disclaimed, & No Warranty granted for this free code
	that clients/recipients have Not paid for development of.
*/
/* MANUAL EXISTS - See cmpd.1
	Synopsis: similar to unix cmp.c, but with much added functionality. */

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

#ifdef hpux	/* { define PATH_MAX */
#include	<limits.h> /* see also sys/fs/vx_hpux.h, X11R5/X11/Xos.h */
#endif		/* } */

#ifdef _M_SYS5 /* { SCO */
#include	<ansi/limits.h>
#define 	PATH_MAX	_POSIX_PATH_MAX
#endif		/* } */

/*	cpp -dM /dev/null | grep BSD
 *	#define __FreeBSD_cc_version 700003
 *	#define __VERSION__ "4.2.1 20070719  [FreeBSD]"
 *	#define __FreeBSD__ 7
 */
#ifdef __FreeBSD__ /* { */
#include	<sys/syslimits.h>
	// #define	PATH_MAX	1024   /* max bytes in pathname */
#endif		/*}*/

#ifndef 	scs	/*{Not Symmetric S375 */
#include	<unistd.h>
#else		/*}{*/
#include	<sys/param.h>
#endif		/*}*/

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

#include	<sys/errno.h>	// for perror()

#define 	MAXPATHLEN	PATH_MAX	/* 1024 */

typedef char	FLAG ;
#define STRADR	&
typedef int	FD ;

#define strequ(a,b) (strcmp((a),(b)) == 0 )

static char	**g_argv ;
static int	g_argc;

static int	err_fail = 0 ;		/* if an open or unlink error occurs */
static int	diff_no = 0 ;
/* For scs & msdos:
	typedef struct	_iobuf FILE ;
	or
	#define FILE struct  _iobuf
*/
static FILE *err_fd ;
static char **ARGV ;


static char    txt_cant_stat[] = "cannot stat";
static char    txt_cant_lstat[] = "cannot lstat";

extern char *strcat() ;

/* EMBEDDED is defined if the program is embedded into OCE/oceres */
#ifndef EMBEDDED /* { */
/* Only used with -m flag, skips past email/news header until a blank line
   is detected,
   Code could have been integrated, for efficiency, but wont often be needed,
   & clearer to read like this.  I admit single char input is horrible !
   Note blank line is "\n\n", "\n\020\n" is not recognised as a mail
   header/body separator, though it would be nice if it was.
   */
	static int
skip(descrip,name)
	FD	descrip ;
	char	*name ;
	{
	char this ;
	char last = '\n' ;
	int	rslt ;

	while ( (rslt = read(descrip,&this,1)) == 1)
		{
		if ((this == '\n') && (last == '\n')) return(0) ;
		else last = this ;
		}
	if (rslt == 0) return(0) ;
	fprintf(err_fd,"%s: Read error while skipping %s mail header\n",
		*g_argv, name);
	err_fail = 1 ;
	diff_no++  ;
	return(1)  ;
	}
#endif /* EMBEDDED } */

	static void
syntax()
	{
	fprintf(err_fd,
		"Syntax:\n%s [-L] [-M] [-b] [-d] [-l[directory]] [-e] %s[-v] [--] file[s] %s\n",
		"reference_file_or_directory_if_not_stdin.",
#ifndef EMBEDDED	/* { */
		"[-m] ",
#else			/* }{*/
		" ",
#endif /* EMBEDDED	} */
		*g_argv);
	}

void exit(int);

#if MSDOS
/* MSDOS and some old unixes don't have symbolic links */
#define NO_SYMLINKS
#endif
#if BSD
/* define DONT_USE_SYMLINK_SYS_CALL if you don't want to use the raw system
 call, and prefer to use system(".....");	*/
#undef	DONT_USE_SYMLINK_SYS_CALL
#endif

#define REF ( * (g_argv + g_argc - 1 ) )
	/* scs accepts g_argv[g_argc-1], 486 doesnt */
	/* usully a reference directory when I use it, but could be a
	   reference file, or a symbolic link to either a file or directory */

	int
#ifdef EMBEDDED /* { */
cmpd(argc, argv)
#else	/* }{ */
main(argc, argv)
#endif	/* } */
	int argc;
	char **argv;
	{
	char	*pointer_del, *pointer_ref;
#define BUF_SIZE 16384				/* 16384 = 0x4000
									JJLATER for maximum speed I could try
									to ensure same as shown by eg:
										statv /etc/rc
											Optimal I/O 16384
										but consider
											stat_buf_ref.st_blksize &
											stat_buf_del.st_blksize
										may vary in size on
										different file systems
									 */
	int	buf_size = BUF_SIZE ;
	char	buf_file_del[BUF_SIZE] ; /* Buffer of file that may be deleted */
	char	buf_file_ref[BUF_SIZE]; /* Reference buffer */
	FD	fd_file_del = (FD)0 ;		/* File to compare & possibly rm */
	FD	fd_file_ref;				/* Reference file (or directory?),
										never removed */
	int	length_del, length_ref, buf_len;
	struct stat	stat_buf_ref ;		/* stat() of reference file/directory */
	struct stat	stat_buf_del ;		/* stat() & lstat() of file
										that may be removed */
	char	name_ref[1024];			/* Comparison reference file */
	FLAG	last_is_dir = 0 ;		/* 1 If last arg detected as directory */
	FLAG	delete_f = 0 ;			/* 1 to delete first of 2 equal files */
	FLAG	skip_f = 0 ;			/* 1 to prevent delete (after evaluating) */
	FLAG	suppress_absent_f = 0 ;	/* 1 to silence complaints of absent
					   					reference files (when trying to
					   					delete duplicate trees) */
	FLAG	suppress_link_f = 0 ;	/* 1 to silence complaints of
					   					cmpd file link_to_file
					   					(which occurs eg from
					   					distfiles_cmpd script )
					   					It only suppresses complaint,
										but error count is still incremented. */
	FLAG	verbose_f = 0 ; 		/* 1 for verbose commentary */
#ifndef EMBEDDED	/* { */
	FLAG	mail_f = 0 ;			/* 1 if comparing email content
										(but not eg subject: etc )
										 or news items, ie dont start compare
										 till after blank line */
#endif /* EMBEDDED	} */
	FLAG	pipe_f = 0 ;			/* 1 if comparing a pipe with a file */
	long	letter, lines, bytes ;	/* Line & byte count */
#ifndef NO_SYMLINKS
	FLAG	link_create_f = 0 ;		/* 1 to to establish a symbolic link
										after deleting a file */
	char	*link_dir = (char *)0 ; /* Where to establish a symbolic link to
										(If unset, then to REF/file )
					    				ie delete file & establish
					    				file -> link_dir/file
					    				(only works if delete_f asserted) */
	char	link_target[PATH_MAX] ;
	FLAG	link_cmp_f = 0 ;		/* 1 to compare symbolic links instead
										 of file data */
	FLAG	mode_f = 0 ;			/* 1 if to compare modes as well as
										2 file content */
#if DONT_USE_SYMLINK_SYS_CALL
	char	link_str_cmd[20 + 2 * PATH_MAX] ;
#endif
	int system_result ;
#endif
	int lnklen;
	char target[MAXPATHLEN + 1]; 	/* Allow null terminator. */

	fprintf(stderr,
		"THIS IS DEVELOPMENT VERSION - BROKEN\n");

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

	g_argc = argc;
	err_fd = stderr ;
	for (argv++, argc--; argc > 0 ; )
		{
		if (**argv != '-') break ;
		switch(*++*argv)
			{
			case 'd':	delete_f = 1 ;	break;
			case 'n':	skip_f = 1 ;	break;
			case 's':	suppress_absent_f = 1 ;break;
			case 'S':	suppress_link_f = 1 ;break;
			case 'v':	verbose_f = 1;	break;
#ifndef EMBEDDED	/* { */
			case 'm':	mail_f = 1;	break;
#endif /* EMBEDDED	} */
			case 'b':	buf_size = 512; break;
			case '-':	pipe_f = 1 ;	break;
			case 'e':	err_fd = stdout ; break ;
#ifndef NO_SYMLINKS
			case 'l':	link_create_f = 1 ;
					if (*++*argv != '\0') link_dir = *argv ;
					delete_f = 1 ;
					break ;
			case 'L':	link_cmp_f = 1 ;
					fprintf(stderr,"'-L' not supported yet\n");
					exit(1);
					break ;
#endif
			case 'M':	mode_f = 1 ;
					break ;
			default:	syntax() ;
					return(-1) ;
					break;
			}
		argv++ ; argc-- ;
		}
	if ( (argc < 1) || ((argc == 1 ) && (pipe_f == 0))
		|| ((argc > 1 ) && pipe_f ))
		{
		syntax() ;
		return(-1) ;
		}
	if (!pipe_f)
		{

		// printf("Debug: Looking at reference %s\n",REF);
		if (stat(REF,STRADR stat_buf_ref_old) && !suppress_absent_f )
			{
			fprintf(err_fd, "%s: %s %s\n",
				*g_argv, txt_cant_stat, REF );
			perror(*g_argv);
			syntax() ;
			}
		// if (stat_buf_ref_old.st_mode & S_IFLNK) // Fails. Detects normal files. Why?
		if (S_ISLNK(stat_buf_ref_old.st_mode)) // OK. Does not detect normal files.
			{
			fprintf(err_fd, "%s: Warning: %s\n", *g_argv, "Symbolic Link" ) ;
#if 0		// { Code not complete yet.
			//   So just let it drop through to & S_IFDIR
			//	Can one see both S_IFLNK & S_IFDIR.
			if (lstat(REF,STRADR stat_buf_ref_old))
				// stat_buf_ref_old is now reloaded for symbolic link specific.
				{
				fprintf(err_fd,
					"%s: %s %s\n",
					*g_argv, txt_cant_lstat, REF );
				syntax() ;
				}
				// Get link path content
				// SEE man 1 readlink, man 2 readlink, as used in
				// /usr/src/usr.bin/find/ls.c
			// Store that path somewhere for later comparison.
#endif		// }
			}
		if (stat_buf_ref_old.st_mode & S_IFDIR)
			{
			last_is_dir = 1;
			/* Detect & warn of construct such as "cmp fred jim ."
			(because == "cmp fred ./fred" && "cmp jim ./jim"
				== TRUE (always!)
			*/
			if (strequ(REF,"."))
			fprintf(err_fd, "%s: Warning: %s\n%s\n",
				*g_argv,
				"Comparison using \".\" as reference directory,",
				"will always result in equality.") ;
			}
		else if ( argc > 2 )
			{
			fprintf(err_fd,
				"%s: Error: %s should be a directory\n",
				*g_argv, REF );
			syntax() ;
			}
		}
	while (argc-- >= (2 - pipe_f))
		{
		if (pipe_f)
			{
			fd_file_ref = fileno(stdin) ;
#ifdef MSDOS	/* { */
			/* JJLATER For Msdos: I dont know whether stdin is
			   already open in binary mode, or whether something
			   extra should be done here */
#endif		/* } */
			}
		else	{
			if (stat(*argv,STRADR stat_buf_del_old))
				{
				fprintf(err_fd,
					"%s: Warning, cannot stat %s\n",
/* JJLATER why is it Warning, cannot stat here but Error, cannot stat below */
					*g_argv,*argv);
				err_fail = 1 ; diff_no++ ; argv++ ; continue ;
				}
			/* Assemble name of reference file */
			(void) strcpy(name_ref, REF );
			if (last_is_dir)
				{
				(void)strcat(name_ref,"/");
				(void)strcat(name_ref,*argv);
				}
#ifndef MSDOS	/* { */
			/* Avoid deleting a single file pointed to
			 * by different paths, such as fred &
			 * ../freds_dir/fred, whether by direct
			 * naming or by symbolic link.	Compare device
			 * major minor + inode numbers here, if all
			 * the same refuse to delete!
			 * Unfortunately Msdos doesnt offer inode numbers,
			 * so we cant check if we are referring to the same
			 * file by 2 different pathnames ( such as
			 * ../../freds_parents_dir/freds_dir/fred, &
			 * ../freds_dir/fred )
			 */
			if (delete_f)
				{
				/* avoid deleting a file that is named by
				   2 different paths, but has only one link,
				   such as cmp -d fred ../freds_dir/fred */
				if (stat(name_ref,STRADR stat_buf_ref_old))
					{
					if (!suppress_absent_f) fprintf(err_fd,
						"%s: Error, cannot stat %s\n",
						*g_argv, name_ref) ;
					err_fail = 1 ; diff_no++ ;
					argv++ ; continue ;
					}
				if ( (stat_buf_del_old.st_dev == stat_buf_ref_old.st_dev)
						/* device (dev_t) */
					&& (stat_buf_del_old.st_ino == stat_buf_ref_old.st_ino)
						/* inode (ino_t) */
					&& (stat_buf_del_old.st_nlink == 1 ) )
					{
					/* both names point to same inode */
#ifdef	SYM_LINKS_AVAIL /* { */
					/* see if first name is just a symbolic
					  link, if so allow deletion */
					if (lstat(*argv,STRADR stat_buf_del_old))
						{
						fprintf(err_fd,
							"%s: Error, cannot lstat %s\n",
							*g_argv, *argv) ;
						perror(*g_argv);
						err_fail = 1 ; diff_no++ ;
						argv++ ; continue ;
						}
#endif		/* } */
					if (
#ifdef	SYM_LINKS_AVAIL /* { */
					(stat_buf_del_old.st_mode & S_IFMT) != S_IFLNK
						/* IFLNK == sym link */
#else		/* }{ */
	/* no symbolic linked files on non SYM_LINKS_AVAIL unix */	1
#endif		/* } */
						) {
						/* Not an innocuous symbolic
						 * link, so avoid deleting.
						 */
					    if ( suppress_link_f == 0 ) /* Complain */
						fprintf(err_fd,
#ifdef	SYM_LINKS_AVAIL /* { */
				"%s: %s %s & %s %s,\n\t%s, & %s %s; %s\n",
#else		/* }{ */
				"%s: %s %s & %s %s,\n\t%s; %s\n",
#endif		/* } */
						*g_argv, "Error: ",
						*argv, name_ref,
				"share same device (major & minor) & inode ",
						"link count is 1",
#ifdef	SYM_LINKS_AVAIL /* { */
						*argv,
						"isn't a symbolic link",
#endif		/* } */
						"Skipping."
							);
						diff_no++ ; argv++ ; continue ;
						}
					}
				}
#endif		/* } */
			if ((fd_file_ref = open(name_ref,O_RDONLY
#ifdef	MSDOS	/* { */
							| O_BINARY
#endif		/* } */
								)) == -1)
				{
				fprintf(err_fd,"%s: Cannot open %s\n",
					*g_argv,name_ref);
				if (fd_file_del != (FD)0) (void) close(fd_file_del) ;
				err_fail = 1 ; diff_no++ ;
				argv++ ; continue ;
				} // JJLATER what if name_ref is a dir not a file ?
			if (mode_f)	// flag 'M'
				{
				if (stat(name_ref, &stat_buf_ref_new))
					{
					perror(*g_argv) ;
					fprintf(stderr,"%s\n",name_ref);
					err_fail = 1 ; diff_no++ ;
					argv++ ; continue ;
					}
				} // JJLATER what if name_ref is a dir not a file ?

			}
		/* now check if we are comparing device with device */
		if (((stat_buf_del_old.st_mode & S_IFMT) != S_IFREG )
			&& ((stat_buf_del_old.st_mode & S_IFMT) ==
				(stat_buf_ref_old.st_mode & S_IFMT))
			&&  (stat_buf_del_old.st_rdev == stat_buf_ref_old.st_rdev) )
			{
		/* JJ add code here, do something about comparing 2 sym links,
		   & a sym link to a fifo */
				/* 386bsd values:
				S_IFMT		0170000 type of file
				S_IFIFO 	0010000 named pipe (fifo)
				S_IFCHR 	0020000 character special
				S_IFDIR 	0040000 directory
				S_IFBLK 	0060000 block special
				S_IFREG 	0100000 regular
				S_IFLNK 	0120000 symbolic link
				S_IFSOCK	0140000 socket
				*/
			}
		if ((fd_file_del = open(*argv,O_RDONLY
#ifdef	MSDOS	/* { */
			| O_BINARY
#endif		/* } */
			))	== -1)
			{
			fprintf(err_fd,"%s: Cannot open %s\n",*g_argv,*argv);
			err_fail = 1 ; diff_no++ ; argv++ ; continue ;
			}
		if (mode_f)	// flag 'M'
			{
			if (stat(*argv, &stat_buf_del_new))
				{
				perror(*g_argv) ;
				fprintf(stderr,"%s\n",*argv);
				err_fail = 1 ; diff_no++ ;
				argv++ ; continue ;
				}
			}
		letter = lines = bytes = 1 ;
#ifndef EMBEDDED	/* { */
		if (mail_f)
			{
			skip(fd_file_ref,name_ref) ;
			skip(fd_file_del, (pipe_f) ? "pipe input" :*argv);
			}
#endif /* EMBEDDED	} */
		for (;;)
			{
			length_del = 0 ;
			if ( ( ( length_ref =
				read(fd_file_ref, buf_file_ref, buf_size) ) < 0)
			|| ( ( length_del =
				read(fd_file_del, buf_file_del,
					(length_ref == 0) ? buf_size : length_ref
					/* not just buf_size as buf_size might be > 4K,
					   but read on a pipe cannot return more than 4K */	))
					< 0 ) )
				{
				fprintf(err_fd,"%s: Read error on ", *g_argv);
				if (length_del < 0 )
					fprintf(err_fd,"%s\n",
						(pipe_f) ? "pipe input" : *argv);
				else	{
					if (last_is_dir) fprintf(err_fd,"%s/%s\n",
						REF , *argv);
					else fprintf(err_fd,"%s\n",*argv);
					}
				err_fail = 1 ; diff_no++ ; break ;
				}
			if ((length_del == 0) && (length_ref == 0))
				{
				if (verbose_f)
					{
					if (last_is_dir )
						// JJLATER is last_is_dir wrong ?
						// Surely I want to delete even if just 2 files are
						// the same & no directory as last arg ?
						{
						printf("%s ",*argv);
						printf( delete_f ?
							"Zero size, Can Delete\n": "Same Content\n");
						if (mode_f)	// delete if modes also match
							{
							printf("%s ",*argv);
							if (stat_buf_ref_new.st_mode == stat_buf_del_new.st_mode)
							printf("Same Modes\n");
							else printf("Different Modes.\n");
							}
						}
					}
				if (delete_f)
					{
#ifndef NO_SYMLINKS
					if (link_create_f) (void) signal(SIGINT, SIG_IGN);
#endif
					if (!skip_f &&
						! ( mode_f &&
							( stat_buf_ref_new.st_mode ==
                            stat_buf_del_new.st_mode ) ) )
						{
						if (verbose_f) printf("Deleting %s\n", *argv);
						if (unlink(*argv))
							{
	/* JJLATER consider adding "if !suppress_absent_f" */	perror(*g_argv);
							diff_no++ ;
							err_fail = 1 ;
							}
						}
#ifndef NO_SYMLINKS		/* { */
					if (link_create_f)
						{
						/* create a sym. link */
						if (link_dir == (char *)0)
							strcpy(link_target,
							name_ref );
						else sprintf(link_target,
							"%s/%s",link_dir,*argv);
#if DONT_USE_SYMLINK_SYS_CALL	/* { */
						sprintf(link_str_cmd,
							"ln -s %s %s\n",
							link_target,*argv);
						if (system_result =
							system(link_str_cmd))
#else							/* }{ */
						if ((system_result =
							symlink(link_target,
							*argv)))
							/* I put un-necessary brackets in
							   to silence the gcc -Wall warning:
								"suggest parentheses around assignment
									 used as truth value"
							*/
#endif							/* } */
							{
							extern int errno;
							perror(*g_argv);
							printf("Error %d sym. linking %s -> %s\n",
								errno,*argv,link_target);
							err_fail = 1 ;
							}
						(void) signal(SIGINT, SIG_DFL);
						}
#endif							/* } */
					}
				break ;
				}
			buf_len = (length_del < length_ref ) ?
				length_del : length_ref ;
			pointer_del = buf_file_del; pointer_ref = buf_file_ref ;
			while (buf_len && (*pointer_del == *pointer_ref) )
				{
				bytes++ ; letter++ ;
				if ((*pointer_del & 0xFF) == '\n')
					{ letter = 1 ; lines++ ; }
				buf_len--; pointer_del++; pointer_ref++ ;
				}
			if ((buf_len == 0) && (length_del == length_ref))
				continue ;
			if (!delete_f || verbose_f)
				{
				if (last_is_dir) printf("%s ",*argv);
				printf(
			"Different: char %ld in line %ld, (byte %ld)\n",
					letter, lines, bytes);
				}
			diff_no++ ;
			break ;
			}
		if (!pipe_f) (void) close(fd_file_ref) ;
		(void) close(fd_file_del);
		argv++ ;
		}
	if (err_fail) return(-diff_no)	; else return(diff_no) ;
	}
