// THIS IS THE OLD STABLE VERSION, SEE ALSO ../cmpd-devel
#ifdef	ournix	/* { */
/* Bug to check:
 *	find . -type f -exec cmpd -d-v {} /host/blak/av/video/jes_2012-03-20 \;
 * No report of syntax error.
 */
// New: se ts=4
#include "ournix.h"
#endif		/* } */
char sccsID[] = "@(#) cmpd.c V1.20 Copyright Julian H. Stacey 14th May 1987 - 2017-11-13\n";
/* Copyright:
	This code was written independently by Julian H. Stacey before being
	used or delivered with any 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    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);
	}

#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

	int
#ifdef EMBEDDED	/* { */
cmpd(argc, argv)
#else	/* }{ */
main(argc, argv)
#endif	/* } */
	int argc;
	char **argv;
	{
	char	*pointer_rm, *pointer_ref;
#define BUF_SIZE 0x2000
	int	buf_size = BUF_SIZE ;
	char	buf_file_rm[BUF_SIZE] ;	/* Buffer of file that may be deleted */
	char	buf_file_ref[BUF_SIZE];	/* Reference buffer */
	FD	fd_file_rm = (FD)0 ;	/* File to compare & possibly rm */
	FD	fd_file_ref;		/* Reference file, never removed */
	int	length_rm, length_ref, buf_len;
	struct stat	ref_stat ;	/* stat() of reference file/directory */
	struct stat	rm_stat ;	/* stat() & lstat() of file that
						might be removed */
	char	name_ref[1024];		/* Comparison reference file */
	FLAG	is_dir = 0 ;		/* if directory detected as last arg */
	FLAG	delete_f = 0 ;		/* 1 to delete first of 2 equal files */
	FLAG	no_del_f = 0 ;		/* 1 to prevent delete */
	FLAG	suppress_f = 0 ;	/* 1 to silence complaints of absent
					   reference files (when trying to
					   delete duplicate trees) */
	FLAG	suppress_f2 = 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 or news items
						ie dont start compare till after
						blank line */
#endif /* EMBEDDED	} */
	FLAG	pipe_f = 0 ;		/* 1 if comparing a file with a pipe */
	long	letter, lines, bytes ;	/* Line & byte count */
#ifndef NO_SYMLINKS
	FLAG	link_create_f = 0 ;	/* Whether to establish a symbolic link */
	char	*link_dir = (char *)0 ;	/* Where to establish a symbolic link to
						(REF_DIR/file if unset )
					    ie delete file & establish
					    file -> link_dir/file
					    (only works if delete_f asserted) */
	char	link_target[PATH_MAX] ;
	FLAG	link_cmp_f = 0 ;	/* Whether to compare symbolic links */
#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.

	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':	no_del_f = 1 ;	break;
			case 's':	suppress_f = 1 ;break;
			case 'S':	suppress_f2 = 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 ;
					break ;
#endif
			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)
		{

#define REF_DIR ( * (g_argv + g_argc - 1 ) )
	/* scs accepts g_argv[g_argc-1], 486 doesnt */
		// printf("Debug: Looking at reference %s\n",REF_DIR);
		if (stat(REF_DIR,STRADR ref_stat) && !suppress_f )
			{
			fprintf(err_fd, "%s: %s %s\n",
				*g_argv, txt_cant_stat, REF_DIR );
			perror(*g_argv);
			syntax() ;
			}
		// if (ref_stat.st_mode & S_IFLNK) // Fails. Detects normal files. Why?
		if (S_ISLNK(ref_stat.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_DIR,STRADR ref_stat))
				// ref_stat is now reloaded for symbolic link specific.
				{
				fprintf(err_fd,
					"%s: %s %s\n",
					*g_argv, txt_cant_lstat, REF_DIR );
				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 (ref_stat.st_mode & S_IFDIR)
			{
			is_dir = 1;
			/* Detect & warn of construct such as "cmp fred jim ."
			(because == "cmp fred ./fred" && "cmp jim ./jim"
				== TRUE (always!)
			*/
			if (strequ(REF_DIR,"."))
			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_DIR );
			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 rm_stat))
				{
				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_DIR );
			if (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 ref_stat))
					{
					if (!suppress_f) fprintf(err_fd,
						"%s: Error, cannot stat %s\n",
						*g_argv, name_ref) ;
					err_fail = 1 ; diff_no++ ;
					argv++ ; continue ;
					}
				if ( (rm_stat.st_dev == ref_stat.st_dev)
						/* device (dev_t) */
					&& (rm_stat.st_ino == ref_stat.st_ino)
						/* inode (ino_t) */
					&& (rm_stat.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 rm_stat))
						{
						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 /* { */
					(rm_stat.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_f2 == 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_rm != (FD)0) (void) close(fd_file_rm) ;
				err_fail = 1 ; diff_no++ ;
				argv++ ; continue ;
				}
			}
		/* now check if we are comparing device with device */
		if (((rm_stat.st_mode & S_IFMT) != S_IFREG )
			&& ((rm_stat.st_mode & S_IFMT) ==
				(ref_stat.st_mode & S_IFMT))
			&&  (rm_stat.st_rdev == ref_stat.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_rm = 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 ;
			}
		letter = lines = bytes = 1 ;
#ifndef EMBEDDED	/* { */
		if (mail_f)
			{
			skip(fd_file_ref,name_ref) ;
			skip(fd_file_rm, (pipe_f) ? "pipe input" :*argv);
			}
#endif /* EMBEDDED	} */
		for (;;)
			{
			length_rm = 0 ;
			if ( (( length_ref =
				read(fd_file_ref,buf_file_ref,buf_size)) < 0)
			||
				((length_rm =
				read(fd_file_rm,buf_file_rm,
					(length_ref == 0) ? buf_size : length_ref
				/* not just buf_size as read on a pipe cannot
					return more than 4K */	)) < 0) )
				{
				fprintf(err_fd,"%s: Read error on ", *g_argv);
				if (length_rm < 0 )
					fprintf(err_fd,"%s\n",
						(pipe_f) ? "pipe input" :*argv);
				else	{
					if (is_dir) fprintf(err_fd,"%s/%s\n",
						REF_DIR , *argv);
					else fprintf(err_fd,"%s\n",*argv);
					}
				err_fail = 1 ; diff_no++ ; break ;
				}
			if ((length_rm == 0)	&& (length_ref == 0))
				{
				if (verbose_f)
					{
					if (is_dir ) printf("%s ",*argv);
					printf( delete_f ?
						"Deleted\n": "Same\n");
					}
				if (delete_f)
					{
#ifndef NO_SYMLINKS
					if (link_create_f) (void) signal(SIGINT, SIG_IGN);
#endif
					if (!no_del_f)
						{
						if (unlink(*argv))
							{
	/* JJLATER consider adding "if !suppress_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_rm < length_ref ) ?
				length_rm : length_ref ;
			pointer_rm = buf_file_rm; pointer_ref = buf_file_ref ;
			while (buf_len && (*pointer_rm == *pointer_ref) )
				{
				bytes++ ; letter++ ;
				if ((*pointer_rm & 0xFF) == '\n')
					{ letter = 1 ; lines++ ; }
				buf_len--; pointer_rm++; pointer_ref++ ;
				}
			if ((buf_len == 0) && (length_rm == length_ref))
				continue ;
			if (!delete_f || verbose_f)
				{
				if (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_rm);
		argv++ ;
		}
	if (err_fail) return(-diff_no)	; else return(diff_no) ;
	}
