Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 29 Apr 2016 23:27:15 +0000 (UTC)
From:      Baptiste Daroussin <bapt@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r298823 - in head: gnu/usr.bin gnu/usr.bin/sdiff usr.bin usr.bin/sdiff usr.bin/sdiff/tests
Message-ID:  <201604292327.u3TNRFVt067723@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: bapt
Date: Fri Apr 29 23:27:15 2016
New Revision: 298823
URL: https://svnweb.freebsd.org/changeset/base/298823

Log:
  import sdiff(1) from GSoC 2012
  
  Import sdiff(1) from the diff version written by Raymond Lai,
  improved during GSoC 2012 by Jesse Hagewood.
  
  Compared to the version done in during that summer of code:
  - Remove the zlib frontend: zsdiff
  - Compatible output (column size and separators) with GNU sdiff
  
  Compared to GNU sdiff in ports:
  - The only difference is padding using spaces vs tabs
  
  Compared to OpenBSD and NetBSD import:
  - Implement missing options (including long options) from GNU sdiff
  - Improved support for the edition mode (signal handling)
  - Output visually compatible with GNU sdiff: size of columns
  
  While here import regression tests from NetBSD adapted to fit the output as
  expected by GNU sdiff
  
  Reviewed by:	emaste (in part)
  Obtained from:	OpenBSD, NetBSD, GSoC 2012
  Relnotes:	yes
  Differential Revision:	https://reviews.freebsd.org/D5981
  Differential Revision:	https://reviews.freebsd.org/D6032 (diff with NetBSD version)
  Differential Revision:	https://reviews.freebsd.org/D6033 (diff with OpenBSD version)

Added:
  head/usr.bin/sdiff/
  head/usr.bin/sdiff/Makefile   (contents, props changed)
  head/usr.bin/sdiff/common.c   (contents, props changed)
  head/usr.bin/sdiff/common.h   (contents, props changed)
  head/usr.bin/sdiff/edit.c   (contents, props changed)
  head/usr.bin/sdiff/extern.h   (contents, props changed)
  head/usr.bin/sdiff/sdiff.1   (contents, props changed)
  head/usr.bin/sdiff/sdiff.c   (contents, props changed)
  head/usr.bin/sdiff/tests/
  head/usr.bin/sdiff/tests/Makefile   (contents, props changed)
  head/usr.bin/sdiff/tests/d_dot.in   (contents, props changed)
  head/usr.bin/sdiff/tests/d_flags_l.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_flags_s.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_flags_w.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_iflags_a1.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_iflags_a2.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_iflags_b1.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_iflags_b2.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_iflags_c1.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_iflags_c2.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_iflags_d1.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_iflags_d2.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_input1   (contents, props changed)
  head/usr.bin/sdiff/tests/d_input2   (contents, props changed)
  head/usr.bin/sdiff/tests/d_oneline.in   (contents, props changed)
  head/usr.bin/sdiff/tests/d_oneline_a.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_oneline_b.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_same.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_short.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_tabends.in   (contents, props changed)
  head/usr.bin/sdiff/tests/d_tabends_a.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_tabends_b.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_tabends_c.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_tabs.out   (contents, props changed)
  head/usr.bin/sdiff/tests/d_tabs1.in   (contents, props changed)
  head/usr.bin/sdiff/tests/d_tabs2.in   (contents, props changed)
  head/usr.bin/sdiff/tests/sdiff.sh   (contents, props changed)
Deleted:
  head/gnu/usr.bin/sdiff/
Modified:
  head/gnu/usr.bin/Makefile
  head/usr.bin/Makefile

Modified: head/gnu/usr.bin/Makefile
==============================================================================
--- head/gnu/usr.bin/Makefile	Fri Apr 29 22:43:11 2016	(r298822)
+++ head/gnu/usr.bin/Makefile	Fri Apr 29 23:27:15 2016	(r298823)
@@ -13,7 +13,6 @@ SUBDIR= ${_binutils} \
 	grep \
 	${_groff} \
 	${_rcs} \
-	sdiff \
 	${_tests}
 
 SUBDIR_DEPEND_gdb= ${_binutils}

Modified: head/usr.bin/Makefile
==============================================================================
--- head/usr.bin/Makefile	Fri Apr 29 22:43:11 2016	(r298822)
+++ head/usr.bin/Makefile	Fri Apr 29 23:27:15 2016	(r298823)
@@ -134,6 +134,7 @@ SUBDIR=	alias \
 	rusers \
 	rwall \
 	script \
+	sdiff \
 	sed \
 	send-pr \
 	seq \

Added: head/usr.bin/sdiff/Makefile
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/usr.bin/sdiff/Makefile	Fri Apr 29 23:27:15 2016	(r298823)
@@ -0,0 +1,16 @@
+# $FreeBSD$
+
+.include <src.opts.mk>
+
+PROG=	sdiff
+SRCS=	common.c edit.c sdiff.c
+WARNS=	3
+
+LIBADD=	util
+MAN1=	sdiff.1
+
+.if ${MK_TESTS} != "no"
+SUBDIR+= tests
+.endif
+
+.include <bsd.progs.mk>

Added: head/usr.bin/sdiff/common.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/usr.bin/sdiff/common.c	Fri Apr 29 23:27:15 2016	(r298823)
@@ -0,0 +1,24 @@
+/*	$OpenBSD: common.c,v 1.4 2006/05/25 03:20:32 ray Exp $	*/
+
+/*
+ * Written by Raymond Lai <ray@cyth.net>.
+ * Public domain.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <err.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "common.h"
+
+void
+cleanup(const char *filename)
+{
+
+	if (unlink(filename))
+		err(2, "could not delete: %s", filename);
+	exit(2);
+}

Added: head/usr.bin/sdiff/common.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/usr.bin/sdiff/common.h	Fri Apr 29 23:27:15 2016	(r298823)
@@ -0,0 +1,9 @@
+/*	$OpenBSD: common.h,v 1.2 2006/05/25 03:20:32 ray Exp $	*/
+/*	$FreeBSD$	*/
+
+/*
+ * Written by Raymond Lai <ray@cyth.net>.
+ * Public domain.
+ */
+
+void cleanup(const char *) __dead2;

Added: head/usr.bin/sdiff/edit.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/usr.bin/sdiff/edit.c	Fri Apr 29 23:27:15 2016	(r298823)
@@ -0,0 +1,209 @@
+/*	$OpenBSD: edit.c,v 1.19 2009/06/07 13:29:50 ray Exp $ */
+
+/*
+ * Written by Raymond Lai <ray@cyth.net>.
+ * Public domain.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "extern.h"
+
+int editit(const char *);
+
+/*
+ * Execute an editor on the specified pathname, which is interpreted
+ * from the shell.  This means flags may be included.
+ *
+ * Returns -1 on error, or the exit value on success.
+ */
+int
+editit(const char *pathname)
+{
+	char *argp[] = {"sh", "-c", NULL, NULL}, *ed, *p;
+	sig_t sighup, sigint, sigquit, sigchld;
+	pid_t pid;
+	int saved_errno, st, ret = -1;
+
+	ed = getenv("VISUAL");
+	if (ed == NULL || ed[0] == '\0')
+		ed = getenv("EDITOR");
+	if (ed == NULL || ed[0] == '\0')
+		ed = _PATH_VI;
+	if (asprintf(&p, "%s %s", ed, pathname) == -1)
+		return (-1);
+	argp[2] = p;
+
+	sighup = signal(SIGHUP, SIG_IGN);
+	sigint = signal(SIGINT, SIG_IGN);
+	sigquit = signal(SIGQUIT, SIG_IGN);
+	sigchld = signal(SIGCHLD, SIG_DFL);
+	if ((pid = fork()) == -1)
+		goto fail;
+	if (pid == 0) {
+		execv(_PATH_BSHELL, argp);
+		_exit(127);
+	}
+	while (waitpid(pid, &st, 0) == -1)
+		if (errno != EINTR)
+			goto fail;
+	if (!WIFEXITED(st))
+		errno = EINTR;
+	else
+		ret = WEXITSTATUS(st);
+
+ fail:
+	saved_errno = errno;
+	(void)signal(SIGHUP, sighup);
+	(void)signal(SIGINT, sigint);
+	(void)signal(SIGQUIT, sigquit);
+	(void)signal(SIGCHLD, sigchld);
+	free(p);
+	errno = saved_errno;
+	return (ret);
+}
+
+/*
+ * Parse edit command.  Returns 0 on success, -1 on error.
+ */
+int
+eparse(const char *cmd, const char *left, const char *right)
+{
+	FILE *file;
+	size_t nread;
+	int fd;
+	char *filename;
+	char buf[BUFSIZ], *text;
+
+	/* Skip whitespace. */
+	while (isspace(*cmd))
+		++cmd;
+
+	text = NULL;
+	switch (*cmd) {
+	case '\0':
+		/* Edit empty file. */
+		break;
+
+	case 'b':
+		/* Both strings. */
+		if (left == NULL)
+			goto RIGHT;
+		if (right == NULL)
+			goto LEFT;
+
+		/* Neither column is blank, so print both. */
+		if (asprintf(&text, "%s\n%s\n", left, right) == -1)
+			err(2, "could not allocate memory");
+		break;
+
+	case 'l':
+LEFT:
+		/* Skip if there is no left column. */
+		if (left == NULL)
+			break;
+
+		if (asprintf(&text, "%s\n", left) == -1)
+			err(2, "could not allocate memory");
+
+		break;
+
+	case 'r':
+RIGHT:
+		/* Skip if there is no right column. */
+		if (right == NULL)
+			break;
+
+		if (asprintf(&text, "%s\n", right) == -1)
+			err(2, "could not allocate memory");
+
+		break;
+
+	default:
+		return (-1);
+	}
+
+	/* Create temp file. */
+	if (asprintf(&filename, "%s/sdiff.XXXXXXXXXX", tmpdir) == -1)
+		err(2, "asprintf");
+	if ((fd = mkstemp(filename)) == -1)
+		err(2, "mkstemp");
+	if (text != NULL) {
+		size_t len;
+		ssize_t nwritten;
+
+		len = strlen(text);
+		if ((nwritten = write(fd, text, len)) == -1 ||
+		    (size_t)nwritten != len) {
+			warn("error writing to temp file");
+			cleanup(filename);
+		}
+	}
+	close(fd);
+
+	/* text is no longer used. */
+	free(text);
+
+	/* Edit temp file. */
+	if (editit(filename) == -1) {
+		warn("error editing %s", filename);
+		cleanup(filename);
+	}
+
+	/* Open temporary file. */
+	if (!(file = fopen(filename, "r"))) {
+		warn("could not open edited file: %s", filename);
+		cleanup(filename);
+	}
+
+	/* Copy temporary file contents to output file. */
+	for (nread = sizeof(buf); nread == sizeof(buf);) {
+		size_t nwritten;
+
+		nread = fread(buf, sizeof(*buf), sizeof(buf), file);
+		/* Test for error or end of file. */
+		if (nread != sizeof(buf) &&
+		    (ferror(file) || !feof(file))) {
+			warnx("error reading edited file: %s", filename);
+			cleanup(filename);
+		}
+
+		/*
+		 * If we have nothing to read, break out of loop
+		 * instead of writing nothing.
+		 */
+		if (!nread)
+			break;
+
+		/* Write data we just read. */
+		nwritten = fwrite(buf, sizeof(*buf), nread, outfp);
+		if (nwritten != nread) {
+			warnx("error writing to output file");
+			cleanup(filename);
+		}
+	}
+
+	/* We've reached the end of the temporary file, so remove it. */
+	if (unlink(filename))
+		warn("could not delete: %s", filename);
+	fclose(file);
+
+	free(filename);
+
+	return (0);
+}

Added: head/usr.bin/sdiff/extern.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/usr.bin/sdiff/extern.h	Fri Apr 29 23:27:15 2016	(r298823)
@@ -0,0 +1,12 @@
+/*	$OpenBSD: extern.h,v 1.5 2009/06/07 13:29:50 ray Exp $ */
+/*	$FreeBSD$	*/
+
+/*
+ * Written by Raymond Lai <ray@cyth.net>.
+ * Public domain.
+ */
+
+extern FILE		*outfp;		/* file to save changes to */
+extern const char	*tmpdir;
+
+int eparse(const char *, const char *, const char *);

Added: head/usr.bin/sdiff/sdiff.1
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/usr.bin/sdiff/sdiff.1	Fri Apr 29 23:27:15 2016	(r298823)
@@ -0,0 +1,174 @@
+.\" $FreeBSD$
+.\" $OpenBSD: sdiff.1,v 1.15 2007/06/29 14:48:07 jmc Exp $
+.\"
+.\" Written by Raymond Lai <ray@cyth.net>.
+.\" Public domain.
+.\"
+.Dd $Mdocdate: July 5 2012 $
+.Dt SDIFF 1
+.Os
+.Sh NAME
+.Nm sdiff
+.Nd side-by-side diff
+.Sh SYNOPSIS
+.Nm
+.Op Fl abdilstW
+.Op Fl I Ar regexp
+.Op Fl o Ar outfile
+.Op Fl w Ar width
+.Ar file1
+.Ar file2
+.Sh DESCRIPTION
+.Nm
+displays two files side by side,
+with any differences between the two highlighted as follows:
+new lines are marked with
+.Sq \*(Gt ;
+deleted lines are marked with
+.Sq \*(Lt ;
+and changed lines are marked with
+.Sq \*(Ba .
+.Pp
+.Nm
+can also be used to interactively merge two files,
+prompting at each set of differences.
+See the
+.Fl o
+option for an explanation.
+.Pp
+The options are:
+.Bl -tag -width Ds
+.It Fl l -left-column
+Only print the left column for identical lines.
+.It Fl o -output Ar outfile
+Interactively merge
+.Ar file1
+and
+.Ar file2
+into
+.Ar outfile .
+In this mode, the user is prompted for each set of differences.
+See
+.Ev EDITOR
+and
+.Ev VISUAL ,
+below,
+for details of which editor, if any, is invoked.
+.Pp
+The commands are as follows:
+.Bl -tag -width Ds
+.It Cm l | 1
+Choose left set of diffs.
+.It Cm r | 2
+Choose right set of diffs.
+.It Cm s
+Silent mode \(en identical lines are not printed.
+.It Cm v
+Verbose mode \(en identical lines are printed.
+.It Cm e
+Start editing an empty file, which will be merged into
+.Ar outfile
+upon exiting the editor.
+.It Cm e Cm l
+Start editing file with left set of diffs.
+.It Cm e Cm r
+Start editing file with right set of diffs.
+.It Cm e Cm b
+Start editing file with both sets of diffs.
+.It Cm q
+Quit
+.Nm .
+.El
+.It Fl s -suppress-common-lines
+Skip identical lines.
+.It Fl w -width Ar width
+Print a maximum of
+.Ar width
+characters on each line.
+The default is 130 characters.
+.El
+.Pp
+Options passed to
+.Xr diff 1
+are:
+.Bl -tag -width Ds
+.It Fl a -text
+Treat
+.Ar file1
+and
+.Ar file2
+as text files.
+.It Fl b -ignore-space-change
+Ignore trailing blank spaces.
+.It Fl d -minimal
+Minimize diff size.
+.It Fl I -ignore-matching-lines Ar regexp
+Ignore line changes matching
+.Ar regexp .
+All lines in the change must match
+.Ar regexp
+for the change to be ignored.
+.It Fl i -ignore-case
+Do a case-insensitive comparison.
+.It Fl t -expand-tabs
+Expand tabs to spaces.
+.It Fl W -ignore-all-space
+Ignore all spaces.
+.It Fl B -ignore-blank-lines
+Ignore blank lines.
+.It Fl E -ignore-tab-expansion
+Treat tabs and eight spaces as the same.
+.It Fl t -ignore-tabs
+Ignore tabs.
+.It Fl H -speed-large-files
+Assume scattered small changes in a large file.
+.It Fl -ignore-file-name-case
+Ignore the case of file names.
+.It Fl -no-ignore-file-name-case
+Do not ignore file name case.
+.It Fl -strip-trailing-cr
+Skip identical lines.
+.It Fl -tabsize Ar NUM
+Change the size of tabs (default is 8.)
+.El
+.Sh ENVIRONMENT
+.Bl -tag -width Ds
+.It Ev EDITOR , VISUAL
+Specifies an editor to use with the
+.Fl o
+option.
+If both
+.Ev EDITOR
+and
+.Ev VISUAL
+are set,
+.Ev VISUAL
+takes precedence.
+If neither
+.Ev EDITOR
+nor
+.Ev VISUAL
+are set,
+the default is
+.Xr vi 1 .
+.It Ev TMPDIR
+Specifies a directory for temporary files to be created.
+The default is
+.Pa /tmp .
+.El
+.Sh SEE ALSO
+.Xr cmp 1 ,
+.Xr diff 1 ,
+.Xr diff3 1 ,
+.Xr vi 1 ,
+.Xr re_format 7
+.Sh AUTHORS
+.Nm
+was written from scratch for the public domain by
+.An Ray Lai Aq ray@cyth.net .
+.Sh CAVEATS
+.Pp
+Tabs are treated as anywhere from one to eight characters wide,
+depending on the current column.
+Terminals that treat tabs as eight characters wide will look best.
+

Added: head/usr.bin/sdiff/sdiff.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/usr.bin/sdiff/sdiff.c	Fri Apr 29 23:27:15 2016	(r298823)
@@ -0,0 +1,1184 @@
+/*	$OpenBSD: sdiff.c,v 1.36 2015/12/29 19:04:46 gsoares Exp $ */
+
+/*
+ * Written by Raymond Lai <ray@cyth.net>.
+ * Public domain.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <paths.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <libutil.h>
+
+#include "common.h"
+#include "extern.h"
+
+#define DIFF_PATH	"/usr/bin/diff"
+
+#define WIDTH 126
+/*
+ * Each column must be at least one character wide, plus three
+ * characters between the columns (space, [<|>], space).
+ */
+#define WIDTH_MIN 5
+
+/* 3 kilobytes of chars */
+#define MAX_CHECK 768
+
+/* A single diff line. */
+struct diffline {
+	STAILQ_ENTRY(diffline) diffentries;
+	char	*left;
+	char	 div;
+	char	*right;
+};
+
+static void astrcat(char **, const char *);
+static void enqueue(char *, char, char *);
+static char *mktmpcpy(const char *);
+static int istextfile(FILE *);
+static void binexec(char *, char *, char *) __dead2;
+static void freediff(struct diffline *);
+static void int_usage(void);
+static int parsecmd(FILE *, FILE *, FILE *);
+static void printa(FILE *, size_t);
+static void printc(FILE *, size_t, FILE *, size_t);
+static void printcol(const char *, size_t *, const size_t);
+static void printd(FILE *, size_t);
+static void println(const char *, const char, const char *);
+static void processq(void);
+static void prompt(const char *, const char *);
+static void usage(void) __dead2;
+static char *xfgets(FILE *);
+
+static STAILQ_HEAD(, diffline) diffhead = STAILQ_HEAD_INITIALIZER(diffhead);
+static size_t line_width;	/* width of a line (two columns and divider) */
+static size_t width;		/* width of each column */
+static size_t file1ln, file2ln;	/* line number of file1 and file2 */
+static int Iflag = 0;	/* ignore sets matching regexp */
+static int	lflag;		/* print only left column for identical lines */
+static int	sflag;		/* skip identical lines */
+FILE *outfp;		/* file to save changes to */
+const char *tmpdir;	/* TMPDIR or /tmp */
+
+enum {
+	HELP_OPT = CHAR_MAX + 1,
+	NORMAL_OPT,
+	FCASE_SENSITIVE_OPT,
+	FCASE_IGNORE_OPT,
+	FROMFILE_OPT,
+	TOFILE_OPT,
+	UNIDIR_OPT,
+	STRIPCR_OPT,
+	HORIZ_OPT,
+	LEFTC_OPT,
+	SUPCL_OPT,
+	LF_OPT,
+	/* the following groupings must be in sequence */
+	OLDGF_OPT,
+	NEWGF_OPT,
+	UNCGF_OPT,
+	CHGF_OPT,
+	OLDLF_OPT,
+	NEWLF_OPT,
+	UNCLF_OPT,
+	/* end order-sensitive enums */
+	TSIZE_OPT,
+	HLINES_OPT,
+	LFILES_OPT,
+	DIFFPROG_OPT,
+	PIPE_FD,
+	/* pid from the diff parent (if applicable) */
+	DIFF_PID,
+
+	NOOP_OPT,
+};
+
+static struct option longopts[] = {
+	/* options only processed in sdiff */
+	{ "left-column",		no_argument,		NULL,	LEFTC_OPT },
+	{ "suppress-common-lines",	no_argument,		NULL,	's' },
+	{ "width",			required_argument,	NULL,	'w' },
+
+	{ "output",			required_argument,	NULL,	'o' },
+	{ "diff-program",		required_argument,	NULL,	DIFFPROG_OPT },
+
+	{ "pipe-fd",			required_argument,	NULL,	PIPE_FD },
+	{ "diff-pid",			required_argument,	NULL,	DIFF_PID },
+	/* Options processed by diff. */
+	{ "ignore-file-name-case",	no_argument,		NULL,	FCASE_IGNORE_OPT },
+	{ "no-ignore-file-name-case",	no_argument,		NULL,	FCASE_SENSITIVE_OPT },
+	{ "strip-trailing-cr",		no_argument,		NULL,	STRIPCR_OPT },
+	{ "tabsize",			required_argument,	NULL,	TSIZE_OPT },
+	{ "help",			no_argument,		NULL,	HELP_OPT },
+	{ "text",			no_argument,		NULL,	'a' },
+	{ "ignore-blank-lines",		no_argument,		NULL,	'B' },
+	{ "ignore-space-change",	no_argument,		NULL,	'b' },
+	{ "minimal",			no_argument,		NULL,	'd' },
+	{ "ignore-tab-expansion",	no_argument,		NULL,	'E' },
+	{ "ignore-matching-lines",	required_argument,	NULL,	'I' },
+	{ "ignore-case",		no_argument,		NULL,	'i' },
+	{ "expand-tabs",		no_argument,		NULL,	't' },
+	{ "speed-large-files",		no_argument,		NULL,	'H' },
+	{ "ignore-all-space",		no_argument,		NULL,	'W' },
+
+	{ NULL,				0,			NULL,	'\0'}
+};
+
+static const char *help_msg[] = {
+	"\nusage: sdiff [-abdilstW] [-I regexp] [-o outfile] [-w width] file1 file2\n",
+	"\t-l, --left-column, Only print the left column for identical lines.",
+	"\t-o OUTFILE, --output=OUTFILE, nteractively merge file1 and file2 into outfile.",
+	"\t-s, --suppress-common-lines, Skip identical lines.",
+	"\t-w WIDTH, --width=WIDTH, Print a maximum of WIDTH characters on each line.",
+	"\tOptions passed to diff(1) are:",
+	"\t\t-a, --text, Treat file1 and file2 as text files.",
+	"\t\t-b, --ignore-trailing-cr, Ignore trailing blank spaces.",
+	"\t\t-d, --minimal, Minimize diff size.",
+	"\t\t-I RE, --ignore-matching-lines=RE, Ignore changes whose line matches RE.",
+	"\t\t-i, --ignore-case, Do a case-insensitive comparison.",
+	"\t\t-t, --expand-tabs Expand tabs to spaces.",
+	"\t\t-W, --ignore-all-spaces, Ignore all spaces.",
+	"\t\t--speed-large-files, Assume large file with scattered changes.",
+	"\t\t--strip-trailing-cr, Strip trailing carriage return.",
+	"\t\t--ignore-file-name-case, Ignore case of file names.",
+	"\t\t--no-ignore-file-name-case, Do not ignore file name case",
+	"\t\t--tabsize NUM, Change size of tabs (default 8.)",
+
+	NULL,
+};
+
+/*
+ * Create temporary file if source_file is not a regular file.
+ * Returns temporary file name if one was malloced, NULL if unnecessary.
+ */
+static char *
+mktmpcpy(const char *source_file)
+{
+	struct stat sb;
+	ssize_t rcount;
+	int ifd, ofd;
+	u_char buf[BUFSIZ];
+	char *target_file;
+
+	/* Open input and output. */
+	ifd = open(source_file, O_RDONLY, 0);
+	/* File was opened successfully. */
+	if (ifd != -1) {
+		if (fstat(ifd, &sb) == -1)
+			err(2, "error getting file status from %s", source_file);
+
+		/* Regular file. */
+		if (S_ISREG(sb.st_mode)) {
+			close(ifd);
+			return (NULL);
+		}
+	} else {
+		/* If ``-'' does not exist the user meant stdin. */
+		if (errno == ENOENT && strcmp(source_file, "-") == 0)
+			ifd = STDIN_FILENO;
+		else
+			err(2, "error opening %s", source_file);
+	}
+
+	/* Not a regular file, so copy input into temporary file. */
+	if (asprintf(&target_file, "%s/sdiff.XXXXXXXXXX", tmpdir) == -1)
+		err(2, "asprintf");
+	if ((ofd = mkstemp(target_file)) == -1) {
+		warn("error opening %s", target_file);
+		goto FAIL;
+	}
+	while ((rcount = read(ifd, buf, sizeof(buf))) != -1 &&
+	    rcount != 0) {
+		ssize_t wcount;
+
+		wcount = write(ofd, buf, (size_t)rcount);
+		if (-1 == wcount || rcount != wcount) {
+			warn("error writing to %s", target_file);
+			goto FAIL;
+		}
+	}
+	if (rcount == -1) {
+		warn("error reading from %s", source_file);
+		goto FAIL;
+	}
+
+	close(ifd);
+	close(ofd);
+
+	return (target_file);
+
+FAIL:
+	unlink(target_file);
+	exit(2);
+}
+
+int
+main(int argc, char **argv)
+{
+	FILE *diffpipe=NULL, *file1, *file2;
+	size_t diffargc = 0, wflag = WIDTH;
+	int ch, fd[2] = {-1}, status;
+	pid_t pid=0; pid_t ppid =-1;
+	const char *outfile = NULL;
+	struct option *popt;
+	char **diffargv, *diffprog = DIFF_PATH, *filename1, *filename2,
+	     *tmp1, *tmp2, *s1, *s2;
+	int i;
+
+	/*
+	 * Process diff flags.
+	 */
+	/*
+	 * Allocate memory for diff arguments and NULL.
+	 * Each flag has at most one argument, so doubling argc gives an
+	 * upper limit of how many diff args can be passed.  argv[0],
+	 * file1, and file2 won't have arguments so doubling them will
+	 * waste some memory; however we need an extra space for the
+	 * NULL at the end, so it sort of works out.
+	 */
+	if (!(diffargv = calloc(argc, sizeof(char **) * 2)))
+		err(2, "main");
+
+	/* Add first argument, the program name. */
+	diffargv[diffargc++] = diffprog;
+
+	/* create a dynamic string for merging single-switch options */
+	if ( asprintf(&diffargv[diffargc++], "-")  < 0 )
+		err(2, "main");
+
+	while ((ch = getopt_long(argc, argv, "aBbdEHI:ilo:stWw:",
+	    longopts, NULL)) != -1) {
+		const char *errstr;
+
+		switch (ch) {
+		/* only compatible --long-name-form with diff */
+		case FCASE_IGNORE_OPT:
+		case FCASE_SENSITIVE_OPT:
+		case STRIPCR_OPT:
+		case TSIZE_OPT:
+		case 'S':
+		break;
+		/* combine no-arg single switches */
+		case 'a':
+		case 'B':
+		case 'b':
+		case 'd':
+		case 'E':
+		case 'i':
+		case 't':
+		case 'H':
+		case 'W':
+			for(popt = longopts; ch != popt->val && popt->name != NULL; popt++);
+			diffargv[1]  = realloc(diffargv[1], sizeof(char) * strlen(diffargv[1]) + 2);
+			/*
+			 * In diff, the 'W' option is 'w' and the 'w' is 'W'.
+			 */
+			if (ch == 'W')
+				sprintf(diffargv[1], "%sw", diffargv[1]);
+			else
+				sprintf(diffargv[1], "%s%c", diffargv[1], ch);
+			break;
+		case DIFFPROG_OPT:
+			diffargv[0] = diffprog = optarg;
+			break;
+		case 'I':
+			Iflag = 1;
+			diffargv[diffargc++] = "-I";
+			diffargv[diffargc++] = optarg;
+			break;
+		case 'l':
+			lflag = 1;
+			break;
+		case 'o':
+			outfile = optarg;
+			break;
+		case 's':
+			sflag = 1;
+			break;
+		case 'w':
+			wflag = strtonum(optarg, WIDTH_MIN,
+			    INT_MAX, &errstr);
+			if (errstr)
+				errx(2, "width is %s: %s", errstr, optarg);
+			break;
+		case DIFF_PID:
+			ppid = strtonum(optarg, 0, INT_MAX, &errstr);
+			if (errstr)
+				errx(2, "diff pid value is %s: %s", errstr, optarg);
+			break;
+		case HELP_OPT:
+			for (i = 0; help_msg[i] != NULL; i++)
+				printf("%s\n", help_msg[i]);
+			exit(0);
+			break;
+		default:
+			usage();
+			break;
+		}
+	}
+
+	/* no single switches were used */
+	if (strcmp(diffargv[1], "-") == 0 ) {
+		for ( i = 1; i < argc-1; i++) {
+			diffargv[i] = diffargv[i+1];
+		}
+		diffargv[diffargc-1] = NULL;
+		diffargc--;
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	if (argc != 2)
+		usage();
+
+	if (outfile && (outfp = fopen(outfile, "w")) == NULL)
+		err(2, "could not open: %s", optarg);
+
+	if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0')
+		tmpdir = _PATH_TMP;
+
+	filename1 = argv[0];
+	filename2 = argv[1];
+
+	/*
+	 * Create temporary files for diff and sdiff to share if file1
+	 * or file2 are not regular files.  This allows sdiff and diff
+	 * to read the same inputs if one or both inputs are stdin.
+	 *
+	 * If any temporary files were created, their names would be
+	 * saved in tmp1 or tmp2.  tmp1 should never equal tmp2.
+	 */
+	tmp1 = tmp2 = NULL;
+	/* file1 and file2 are the same, so copy to same temp file. */
+	if (strcmp(filename1, filename2) == 0) {
+		if ((tmp1 = mktmpcpy(filename1)))
+			filename1 = filename2 = tmp1;
+	/* Copy file1 and file2 into separate temp files. */
+	} else {
+		if ((tmp1 = mktmpcpy(filename1)))
+			filename1 = tmp1;
+		if ((tmp2 = mktmpcpy(filename2)))
+			filename2 = tmp2;
+	}
+
+	diffargv[diffargc++] = filename1;
+	diffargv[diffargc++] = filename2;
+	/* Add NULL to end of array to indicate end of array. */
+	diffargv[diffargc++] = NULL;
+
+	/* Subtract column divider and divide by two. */
+	width = (wflag - 3) / 2;
+	/* Make sure line_width can fit in size_t. */
+	if (width > (SIZE_MAX - 3) / 2)
+		errx(2, "width is too large: %zu", width);
+	line_width = width * 2 + 3;
+
+	if (ppid == -1 ) {
+		if (pipe(fd))
+			err(2, "pipe");
+
+		switch (pid = fork()) {
+		case 0:
+			/* child */
+			/* We don't read from the pipe. */
+			close(fd[0]);
+			if (dup2(fd[1], STDOUT_FILENO) == -1)
+				err(2, "child could not duplicate descriptor");
+			/* Free unused descriptor. */
+			close(fd[1]);
+			execvp(diffprog, diffargv);
+			err(2, "could not execute diff: %s", diffprog);
+			break;
+		case -1:
+			err(2, "could not fork");
+			break;
+		}
+
+		/* parent */
+		/* We don't write to the pipe. */
+		close(fd[1]);
+
+		/* Open pipe to diff command. */
+		if ((diffpipe = fdopen(fd[0], "r")) == NULL)
+			err(2, "could not open diff pipe");
+	}
+	if ((file1 = fopen(filename1, "r")) == NULL)
+		err(2, "could not open %s", filename1);
+	if ((file2 = fopen(filename2, "r")) == NULL)
+		err(2, "could not open %s", filename2);
+	if (!istextfile(file1) || !istextfile(file2)) {
+		/* Close open files and pipe, delete temps */
+		fclose(file1);
+		fclose(file2);
+		fclose(diffpipe);
+		if (tmp1)
+			if (unlink(tmp1))
+				warn("Error deleting %s.", tmp1);
+		if (tmp2)
+			if (unlink(tmp2))
+				warn("Error deleting %s.", tmp2);
+		free(tmp1);
+		free(tmp2);
+		binexec(diffprog, filename1, filename2);
+	}
+	/* Line numbers start at one. */
+	file1ln = file2ln = 1;
+
+	/* Read and parse diff output. */
+	while (parsecmd(diffpipe, file1, file2) != EOF)
+		;
+	fclose(diffpipe);
+
+	/* Wait for diff to exit. */
+	if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status) ||
+	    WEXITSTATUS(status) >= 2)
+		err(2, "diff exited abnormally.");
+
+	/* Delete and free unneeded temporary files. */
+	if (tmp1)
+		if (unlink(tmp1))
+			warn("Error deleting %s.", tmp1);
+	if (tmp2)
+		if (unlink(tmp2))
+			warn("Error deleting %s.", tmp2);
+	free(tmp1);
+	free(tmp2);
+	filename1 = filename2 = tmp1 = tmp2 = NULL;
+
+	/* No more diffs, so print common lines. */
+	if (lflag)
+		while ((s1 = xfgets(file1)))
+			enqueue(s1, ' ', NULL);
+	else
+		for (;;) {
+			s1 = xfgets(file1);
+			s2 = xfgets(file2);
+			if (s1 || s2)
+				enqueue(s1, ' ', s2);
+			else
+				break;
+		}
+	fclose(file1);
+	fclose(file2);
+	/* Process unmodified lines. */
+	processq();
+
+	/* Return diff exit status. */
+	return (WEXITSTATUS(status));
+}
+
+/*
+ * When sdiff/zsdiff detects a binary file as input, executes them with
+ * diff/zdiff to maintain the same behavior as GNU sdiff with binary input.
+ */
+static void
+binexec(char *diffprog, char *f1, char *f2)
+{
+
+	char *args[] = {diffprog, f1, f2, (char *) 0};
+	execv(diffprog, args);
+
+	/* If execv() fails, sdiff's execution will continue below. */
+	errx(1, "Could not execute diff process.\n");
+}
+
+/*
+ * Checks whether a file appears to be a text file.
+ */
+static int
+istextfile(FILE *f)
+{
+	int	i;
+	char ch;
+
+	if (f == NULL)
+		return (1);

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201604292327.u3TNRFVt067723>