]> Zhao Yanbai Git Server - minix.git/commitdiff
Importing usr.bin/sdiff 92/1092/1
authorThomas Cort <tcort@minix3.org>
Wed, 30 Oct 2013 14:11:02 +0000 (10:11 -0400)
committerThomas Cort <tcort@minix3.org>
Wed, 30 Oct 2013 14:11:02 +0000 (10:11 -0400)
No Minix-specific changes needed.

Change-Id: Idbe7dcab6024d616022951f7942287fddecc3719

distrib/sets/lists/minix/mi
releasetools/nbsd_ports
usr.bin/Makefile
usr.bin/sdiff/Makefile [new file with mode: 0644]
usr.bin/sdiff/common.c [new file with mode: 0644]
usr.bin/sdiff/common.h [new file with mode: 0644]
usr.bin/sdiff/edit.c [new file with mode: 0644]
usr.bin/sdiff/extern.h [new file with mode: 0644]
usr.bin/sdiff/sdiff.1 [new file with mode: 0644]
usr.bin/sdiff/sdiff.c [new file with mode: 0644]
usr.bin/sdiff/strtonum.c [new file with mode: 0644]

index 123099ab80a6e7fff7d170f7bf767e88d79dd5dd..09b4739a1de2fee7734bc8a814f2477fa8e2574b 100644 (file)
 ./usr/bin/rsh                          minix-sys
 ./usr/bin/rz                           minix-sys
 ./usr/bin/screendump                    minix-sys
+./usr/bin/sdiff                                minix-sys
 ./usr/bin/sed                          minix-sys
 ./usr/bin/seq                          minix-sys
 ./usr/bin/sha1                         minix-sys
 ./usr/man/man1/rmdir.1                 minix-sys
 ./usr/man/man1/rsh.1                   minix-sys
 ./usr/man/man1/rz.1                    minix-sys
+./usr/man/man1/sdiff.1                 minix-sys
 ./usr/man/man1/sed.1                   minix-sys
 ./usr/man/man1/seq.1                   minix-sys
 ./usr/man/man1/set.1                   minix-sys
index f7f898f4a58efadf735d061a91f5f1644048254f..8befb3655d887406e0453571b60d5cb1f5710b0d 100644 (file)
 2013/10/24 12:00:00,usr.bin/pwhash
 2012/10/17 12:00:00,usr.bin/renice
 2013/09/28 12:00:00,usr.bin/rev
+2012/10/17 12:00:00,usr.bin/sdiff
 2010/02/19 16:35:27,usr.bin/sed
 2010/05/27 08:40:19,usr.bin/seq
 2012/10/17 12:00:00,usr.bin/shlock
index 6f9b2663d3661d4c62971d73a3d7b7c57c8ae95c..8dc1f051a2f00390f7f5a69ebf2d2b4803747594 100644 (file)
@@ -23,7 +23,7 @@ SUBDIR= asa \
        printenv printf pwhash \
        renice rev \
        \
-       sed seq shlock \
+       sdiff sed seq shlock \
        shuffle sort split stat su \
        tee tic tput \
        tr tsort tty unexpand unifdef \
diff --git a/usr.bin/sdiff/Makefile b/usr.bin/sdiff/Makefile
new file mode 100644 (file)
index 0000000..2fa1567
--- /dev/null
@@ -0,0 +1,11 @@
+#      $NetBSD: Makefile,v 1.2 2009/04/14 22:15:26 lukem Exp $
+
+WARNS?=        2       # XXX -Wcast-qual issues
+
+PROG=  sdiff
+SRCS=  common.c edit.c sdiff.c strtonum.c
+
+LDADD+=        -lutil
+DPADD+=        ${LIBUTIL}
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/sdiff/common.c b/usr.bin/sdiff/common.c
new file mode 100644 (file)
index 0000000..ef3136f
--- /dev/null
@@ -0,0 +1,21 @@
+/*     $NetBSD: common.c,v 1.1 2007/02/18 22:13:42 rmind Exp $ */
+/*     $OpenBSD: common.c,v 1.4 2006/05/25 03:20:32 ray Exp $  */
+
+/*
+ * Written by Raymond Lai <ray@cyth.net>.
+ * Public domain.
+ */
+
+#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);
+}
diff --git a/usr.bin/sdiff/common.h b/usr.bin/sdiff/common.h
new file mode 100644 (file)
index 0000000..f374521
--- /dev/null
@@ -0,0 +1,13 @@
+/*     $NetBSD: common.h,v 1.1 2007/02/18 22:13:42 rmind Exp $ */
+/*     $OpenBSD: common.h,v 1.2 2006/05/25 03:20:32 ray Exp $  */
+
+/*
+ * Written by Raymond Lai <ray@cyth.net>.
+ * Public domain.
+ */
+
+__dead void cleanup(const char *);
+
+long long
+strtonum(const char *numstr, long long minval, long long maxval,
+       const char **errstrp);
diff --git a/usr.bin/sdiff/edit.c b/usr.bin/sdiff/edit.c
new file mode 100644 (file)
index 0000000..f005305
--- /dev/null
@@ -0,0 +1,187 @@
+/*     $NetBSD: edit.c,v 1.4 2011/09/01 07:18:51 plunky Exp $  */
+/*     $OpenBSD: edit.c,v 1.14 2006/05/25 03:20:32 ray Exp $ */
+
+/*
+ * Written by Raymond Lai <ray@cyth.net>.
+ * Public domain.
+ */
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "extern.h"
+
+static void edit(const char *);
+
+/*
+ * Takes the name of a file and opens it with an editor.
+ */
+static void
+edit(const char *filename)
+{
+       int status;
+       pid_t pid;
+       const char *editor;
+
+       editor = getenv("VISUAL");
+       if (editor == NULL)
+               editor = getenv("EDITOR");
+       if (editor == NULL)
+               editor = "vi";
+
+       /* Start editor on temporary file. */
+       switch (pid = fork()) {
+       case 0:
+               /* child */
+               execlp(editor, editor, filename, (void *)NULL);
+               warn("could not execute editor: %s", editor);
+               cleanup(filename);
+       case -1:
+               warn("could not fork");
+               cleanup(filename);
+       }
+
+       /* parent */
+       /* Wait for editor to exit. */
+       if (waitpid(pid, &status, 0) == -1) {
+               warn("waitpid");
+               cleanup(filename);
+       }
+
+       /* Check that editor terminated normally. */
+       if (!WIFEXITED(status)) {
+               warn("%s terminated abnormally", editor);
+               cleanup(filename);
+       }
+}
+
+/*
+ * 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, nwritten;
+       int fd;
+       char *filename;
+       char buf[BUFSIZ], *text;
+
+       /* Skip whitespace. */
+       while (isspace((int)(*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;
+
+               len = strlen(text);
+               if ((size_t)write(fd, text, len) != len) {
+                       warn("error writing to temp file");
+                       cleanup(filename);
+               }
+       }
+       close(fd);
+
+       /* text is no longer used. */
+       free(text);
+
+       /* Edit temp file. */
+       edit(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);) {
+               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, outfile);
+               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);
+}
diff --git a/usr.bin/sdiff/extern.h b/usr.bin/sdiff/extern.h
new file mode 100644 (file)
index 0000000..4cf3236
--- /dev/null
@@ -0,0 +1,12 @@
+/*     $NetBSD: extern.h,v 1.1 2007/02/18 22:13:42 rmind Exp $ */
+/*     $OpenBSD: extern.h,v 1.4 2006/05/25 03:20:32 ray Exp $ */
+
+/*
+ * Written by Raymond Lai <ray@cyth.net>.
+ * Public domain.
+ */
+
+extern FILE            *outfile;       /* file to save changes to */
+extern const char      *tmpdir;
+
+int eparse(const char *, const char *, const char *);
diff --git a/usr.bin/sdiff/sdiff.1 b/usr.bin/sdiff/sdiff.1
new file mode 100644 (file)
index 0000000..09d8f81
--- /dev/null
@@ -0,0 +1,167 @@
+.\" $NetBSD: sdiff.1,v 1.2 2011/04/25 22:42:11 wiz Exp $
+.\" $OpenBSD: sdiff.1,v 1.11 2007/02/22 02:50:56 ray Exp $
+.\"
+.\" Written by Raymond Lai <ray@cyth.net>.
+.\" Public domain.
+.\"
+.Dd February 21, 2007
+.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 | .
+.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
+Only print the left column for identical lines.
+.It Fl o 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
+Choose left set of diffs.
+.It Cm r
+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
+Skip identical lines.
+.It Fl w 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
+Treat
+.Ar file1
+and
+.Ar file2
+as text files.
+.It Fl b
+Ignore trailing blank spaces.
+.It Fl d
+Minimize diff size.
+.It Fl I 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
+Do a case-insensitive comparison.
+.It Fl t
+Expand tabs to spaces.
+.It Fl W
+Ignore all spaces
+(the
+.Fl w
+flag is passed to
+.Xr diff 1 ) .
+.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 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
+Although undocumented,
+.Nm
+supports all options supported by GNU sdiff.
+Some options require GNU diff.
+.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.
+.Sh BUGS
+.Nm
+may not work with binary data.
diff --git a/usr.bin/sdiff/sdiff.c b/usr.bin/sdiff/sdiff.c
new file mode 100644 (file)
index 0000000..cf8260f
--- /dev/null
@@ -0,0 +1,1038 @@
+/*     $NetBSD: sdiff.c,v 1.2 2009/04/13 07:19:55 lukem Exp $  */
+/*     $OpenBSD: sdiff.c,v 1.20 2006/09/19 05:52:23 otto Exp $ */
+
+/*
+ * Written by Raymond Lai <ray@cyth.net>.
+ * Public domain.
+ */
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <util.h>
+
+#include "common.h"
+#include "extern.h"
+
+#define WIDTH 130
+/*
+ * Each column must be at least one character wide, plus three
+ * characters between the columns (space, [<|>], space).
+ */
+#define WIDTH_MIN 5
+
+/* A single diff line. */
+struct diffline {
+       SIMPLEQ_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 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 *);
+__dead static void usage(void);
+static char *xfgets(FILE *);
+
+SIMPLEQ_HEAD(, diffline) diffhead = SIMPLEQ_HEAD_INITIALIZER(diffhead);
+size_t  line_width;    /* width of a line (two columns and divider) */
+size_t  width;         /* width of each column */
+size_t  file1ln, file2ln;      /* line number of file1 and file2 */
+int     Iflag = 0;     /* ignore sets matching regexp */
+int     lflag;         /* print only left column for identical lines */
+int     sflag;         /* skip identical lines */
+FILE   *outfile;       /* file to save changes to */
+const char *tmpdir;    /* TMPDIR or /tmp */
+
+static struct option longopts[] = {
+       { "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' },
+       { "diff-program",               required_argument,      NULL,   'F' },
+       { "speed-large-files",          no_argument,            NULL,   'H' },
+       { "ignore-matching-lines",      required_argument,      NULL,   'I' },
+       { "left-column",                no_argument,            NULL,   'l' },
+       { "output",                     required_argument,      NULL,   'o' },
+       { "strip-trailing-cr",          no_argument,            NULL,   'S' },
+       { "suppress-common-lines",      no_argument,            NULL,   's' },
+       { "expand-tabs",                no_argument,            NULL,   't' },
+       { "ignore-all-space",           no_argument,            NULL,   'W' },
+       { "width",                      required_argument,      NULL,   'w' },
+       { NULL,                         0,                      NULL,    0  }
+};
+
+/*
+ * 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))
+                       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, *file1, *file2;
+       size_t diffargc = 0, wflag = WIDTH;
+       int ch, fd[2], status;
+       pid_t pid;
+       char **diffargv, *diffprog = "diff", *filename1, *filename2,
+           *tmp1, *tmp2, *s1, *s2;
+
+       /*
+        * 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 = malloc(sizeof(char **) * argc * 2)))
+               err(2, "main");
+
+       /* Add first argument, the program name. */
+       diffargv[diffargc++] = diffprog;
+
+       while ((ch = getopt_long(argc, argv, "aBbdEHI:ilo:stWw:",
+           longopts, NULL)) != -1) {
+               const char *errstr;
+
+               switch (ch) {
+               case 'a':
+                       diffargv[diffargc++] = "-a";
+                       break;
+               case 'B':
+                       diffargv[diffargc++] = "-B";
+                       break;
+               case 'b':
+                       diffargv[diffargc++] = "-b";
+                       break;
+               case 'd':
+                       diffargv[diffargc++] = "-d";
+                       break;
+               case 'E':
+                       diffargv[diffargc++] = "-E";
+                       break;
+               case 'F':
+                       diffargv[0] = diffprog = optarg;
+                       break;
+               case 'H':
+                       diffargv[diffargc++] = "-H";
+                       break;
+               case 'I':
+                       Iflag = 1;
+                       diffargv[diffargc++] = "-I";
+                       diffargv[diffargc++] = optarg;
+                       break;
+               case 'i':
+                       diffargv[diffargc++] = "-i";
+                       break;
+               case 'l':
+                       lflag = 1;
+                       break;
+               case 'o':
+                       if ((outfile = fopen(optarg, "w")) == NULL)
+                               err(2, "could not open: %s", optarg);
+                       break;
+               case 'S':
+                       diffargv[diffargc++] = "--strip-trailing-cr";
+                       break;
+               case 's':
+                       sflag = 1;
+                       break;
+               case 't':
+                       diffargv[diffargc++] = "-t";
+                       break;
+               case 'W':
+                       diffargv[diffargc++] = "-w";
+                       break;
+               case 'w':
+                       wflag = strtonum(optarg, WIDTH_MIN,
+                           INT_MAX, &errstr);
+                       if (errstr)
+                               errx(2, "width is %s: %s", errstr, optarg);
+                       break;
+               default:
+                       usage();
+               }
+
+       }
+       argc -= optind;
+       argv += optind;
+
+       if (argc != 2)
+               usage();
+
+       if ((tmpdir = getenv("TMPDIR")) == NULL)                       
+               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_T_MAX - 3) / 2)
+               errx(2, "width is too large: %zu", width);
+       line_width = width * 2 + 3;
+
+       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);
+       case -1:
+               err(2, "could not fork");
+       }
+
+       /* 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);
+
+       /* 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));
+}
+
+/*
+ * Prints an individual column (left or right), taking into account
+ * that tabs are variable-width.  Takes a string, the current column
+ * the cursor is on the screen, and the maximum value of the column.
+ * The column value is updated as we go along.
+ */
+static void
+printcol(const char *s, size_t *col, const size_t col_max)
+{
+
+       for (; *s && *col < col_max; ++s) {
+               size_t new_col;
+
+               switch (*s) {
+               case '\t':
+                       /*
+                        * If rounding to next multiple of eight causes
+                        * an integer overflow, just return.
+                        */
+                       if (*col > SIZE_T_MAX - 8)
+                               return;
+
+                       /* Round to next multiple of eight. */
+                       new_col = (*col / 8 + 1) * 8;
+
+                       /*
+                        * If printing the tab goes past the column
+                        * width, don't print it and just quit.
+                        */
+                       if (new_col > col_max)
+                               return;
+                       *col = new_col;
+                       break;
+
+               default:
+                       ++(*col);
+               }
+
+               putchar(*s);
+       }
+}
+
+/*
+ * Prompts user to either choose between two strings or edit one, both,
+ * or neither.
+ */
+static void
+prompt(const char *s1, const char *s2)
+{
+       char *cmd;
+
+       /* Print command prompt. */
+       putchar('%');
+
+       /* Get user input. */
+       for (; (cmd = xfgets(stdin)); free(cmd)) {
+               const char *p;
+
+               /* Skip leading whitespace. */
+               for (p = cmd; isspace((int)(*p)); ++p)
+                       ;
+
+               switch (*p) {
+               case 'e':
+                       /* Skip `e'. */
+                       ++p;
+
+                       if (eparse(p, s1, s2) == -1)
+                               goto USAGE;
+                       break;
+
+               case 'l':
+                       /* Choose left column as-is. */
+                       if (s1 != NULL)
+                               fprintf(outfile, "%s\n", s1);
+
+                       /* End of command parsing. */
+                       break;
+
+               case 'q':
+                       goto QUIT;
+
+               case 'r':
+                       /* Choose right column as-is. */
+                       if (s2 != NULL)
+                               fprintf(outfile, "%s\n", s2);
+
+                       /* End of command parsing. */
+                       break;
+
+               case 's':
+                       sflag = 1;
+                       goto PROMPT;
+
+               case 'v':
+                       sflag = 0;
+                       /* FALLTHROUGH */
+
+               default:
+                       /* Interactive usage help. */
+USAGE:
+                       int_usage();
+PROMPT:
+                       putchar('%');
+
+                       /* Prompt user again. */
+                       continue;
+               }
+
+               free(cmd);
+               return;
+       }
+
+       /*
+        * If there was no error, we received an EOF from stdin, so we
+        * should quit.
+        */
+QUIT:
+       fclose(outfile);
+       exit(0);
+}
+
+/*
+ * Takes two strings, separated by a column divider.  NULL strings are
+ * treated as empty columns.  If the divider is the ` ' character, the
+ * second column is not printed (-l flag).  In this case, the second
+ * string must be NULL.  When the second column is NULL, the divider
+ * does not print the trailing space following the divider character.
+ *
+ * Takes into account that tabs can take multiple columns.
+ */
+static void
+println(const char *s1, const char divc, const char *s2)
+{
+       size_t col;
+
+       /* Print first column.  Skips if s1 == NULL. */
+       col = 0;
+       if (s1) {
+               /* Skip angle bracket and space. */
+               printcol(s1, &col, width);
+
+       }
+
+       /* Only print left column. */
+       if (divc == ' ' && !s2) {
+               putchar('\n');
+               return;
+       }
+
+       /* Otherwise, we pad this column up to width. */
+       for (; col < width; ++col)
+               putchar(' ');
+
+       /*
+        * Print column divider.  If there is no second column, we don't
+        * need to add the space for padding.
+        */
+       if (!s2) {
+               printf(" %c\n", divc);
+               return;
+       }
+       printf(" %c ", divc);
+       col += 3;
+
+       /* Skip angle bracket and space. */
+       printcol(s2, &col, line_width);
+
+       putchar('\n');
+}
+
+/*
+ * Reads a line from file and returns as a string.  If EOF is reached,
+ * NULL is returned.  The returned string must be freed afterwards.
+ */
+static char *
+xfgets(FILE *file)
+{
+       const char delim[3] = {'\0', '\0', '\0'};
+       char *s;
+
+       /* XXX - Is this necessary? */
+       clearerr(file);
+
+       if (!(s = fparseln(file, NULL, NULL, delim, 0)) &&
+           ferror(file))
+               err(2, "error reading file");
+
+       if (!s) {
+               return (NULL);
+       }
+
+       return (s);
+}
+
+/*
+ * Parse ed commands from diffpipe and print lines from file1 (lines
+ * to change or delete) or file2 (lines to add or change).
+ * Returns EOF or 0.
+ */
+static int
+parsecmd(FILE *diffpipe, FILE *file1, FILE *file2)
+{
+       size_t file1start, file1end, file2start, file2end, n;
+       /* ed command line and pointer to characters in line */
+       char *line, *p, *q;
+       const char *errstr;
+       char c, cmd;
+
+       /* Read ed command. */
+       if (!(line = xfgets(diffpipe)))
+               return (EOF);
+
+       p = line;
+       /* Go to character after line number. */
+       while (isdigit((int)(*p)))
+               ++p;
+       c = *p;
+       *p++ = 0;
+       file1start = strtonum(line, 0, INT_MAX, &errstr);
+       if (errstr)
+               errx(2, "file1 start is %s: %s", errstr, line);
+
+       /* A range is specified for file1. */
+       if (c == ',') {
+
+               q = p;
+               /* Go to character after file2end. */
+               while (isdigit((int)(*p)))
+                       ++p;
+               c = *p;
+               *p++ = 0;
+               file1end = strtonum(q, 0, INT_MAX, &errstr);
+               if (errstr)
+                       errx(2, "file1 end is %s: %s", errstr, line);
+               if (file1start > file1end)
+                       errx(2, "invalid line range in file1: %s", line);
+
+       } else
+               file1end = file1start;
+
+       cmd = c;
+       /* Check that cmd is valid. */
+       if (!(cmd == 'a' || cmd == 'c' || cmd == 'd'))
+               errx(2, "ed command not recognized: %c: %s", cmd, line);
+
+       q = p;
+       /* Go to character after line number. */
+       while (isdigit((int)(*p)))
+               ++p;
+       c = *p;
+       *p++ = 0;
+       file2start = strtonum(q, 0, INT_MAX, &errstr);
+       if (errstr)
+               errx(2, "file2 start is %s: %s", errstr, line);
+
+       /*
+        * There should either be a comma signifying a second line
+        * number or the line should just end here.
+        */
+       if (c != ',' && c != '\0')
+               errx(2, "invalid line range in file2: %c: %s", c, line);
+
+       if (c == ',') {
+
+               file2end = strtonum(p, 0, INT_MAX, &errstr);
+               if (errstr)
+                       errx(2, "file2 end is %s: %s", errstr, line);
+               if (file2start >= file2end)
+                       errx(2, "invalid line range in file2: %s", line);
+       } else
+               file2end = file2start;
+
+       /* Appends happen _after_ stated line. */
+       if (cmd == 'a') {
+               if (file1start != file1end)
+                       errx(2, "append cannot have a file1 range: %s",
+                           line);
+               if (file1start == SIZE_T_MAX)
+                       errx(2, "file1 line range too high: %s", line);
+               file1start = ++file1end;
+       }
+       /*
+        * I'm not sure what the deal is with the line numbers for
+        * deletes, though.
+        */
+       else if (cmd == 'd') {
+               if (file2start != file2end)
+                       errx(2, "delete cannot have a file2 range: %s",
+                           line);
+               if (file2start == SIZE_T_MAX)
+                       errx(2, "file2 line range too high: %s", line);
+               file2start = ++file2end;
+       }
+
+       /*
+        * Continue reading file1 and file2 until we reach line numbers
+        * specified by diff.  Should only happen with -I flag.
+        */
+       for (; file1ln < file1start && file2ln < file2start;
+           ++file1ln, ++file2ln) {
+               char *s1, *s2;
+
+               if (!(s1 = xfgets(file1)))
+                       errx(2, "file1 shorter than expected");
+               if (!(s2 = xfgets(file2)))
+                       errx(2, "file2 shorter than expected");
+
+               /* If the -l flag was specified, print only left column. */
+               if (lflag) {
+                       free(s2);
+                       /*
+                        * XXX - If -l and -I are both specified, all
+                        * unchanged or ignored lines are shown with a
+                        * `(' divider.  This matches GNU sdiff, but I
+                        * believe it is a bug.  Just check out:
+                        * gsdiff -l -I '^$' samefile samefile.
+                        */
+                       if (Iflag)
+                               enqueue(s1, '(', NULL);
+                       else
+                               enqueue(s1, ' ', NULL);
+               } else
+                       enqueue(s1, ' ', s2);
+       }
+       /* Ignore deleted lines. */
+       for (; file1ln < file1start; ++file1ln) {
+               char *s;
+
+               if (!(s = xfgets(file1)))
+                       errx(2, "file1 shorter than expected");
+
+               enqueue(s, '(', NULL);
+       }
+       /* Ignore added lines. */
+       for (; file2ln < file2start; ++file2ln) {
+               char *s;
+
+               if (!(s = xfgets(file2)))
+                       errx(2, "file2 shorter than expected");
+
+               /* If -l flag was given, don't print right column. */
+               if (lflag)
+                       free(s);
+               else
+                       enqueue(NULL, ')', s);
+       }
+
+       /* Process unmodified or skipped lines. */
+       processq();
+
+       switch (cmd) {
+       case 'a':
+               printa(file2, file2end);
+               n = file2end - file2start + 1;
+               break;
+
+       case 'c':
+               printc(file1, file1end, file2, file2end);
+               n = file1end - file1start + 1 + 1 + file2end - file2start + 1;
+               break;
+
+       case 'd':
+               printd(file1, file1end);
+               n = file1end - file1start + 1;
+               break;
+
+       default:
+               errx(2, "invalid diff command: %c: %s", cmd, line);
+       }
+
+       /* Skip to next ed line. */
+       while (n--)
+               if (!xfgets(diffpipe))
+                       errx(2, "diff ended early");
+
+       return (0);
+}
+
+/*
+ * Queues up a diff line.
+ */
+static void
+enqueue(char *left, char divc, char *right)
+{
+       struct diffline *diffp;
+
+       if (!(diffp = malloc(sizeof(struct diffline))))
+               err(2, "enqueue");
+       diffp->left = left;
+       diffp->div = divc;
+       diffp->right = right;
+       SIMPLEQ_INSERT_TAIL(&diffhead, diffp, diffentries);
+}
+
+/*
+ * Free a diffline structure and its elements.
+ */
+static void
+freediff(struct diffline *diffp)
+{
+       free(diffp->left);
+       free(diffp->right);
+       free(diffp);
+}
+
+/*
+ * Append second string into first.  Repeated appends to the same string
+ * are cached, making this an O(n) function, where n = strlen(append).
+ */
+static void
+astrcat(char **s, const char *append)
+{
+       /* Length of string in previous run. */
+       static size_t offset = 0;
+       size_t newsiz;
+       /*
+        * String from previous run.  Compared to *s to see if we are
+        * dealing with the same string.  If so, we can use offset.
+        */
+       static const char *oldstr = NULL;
+       char *newstr;
+
+
+       /*
+        * First string is NULL, so just copy append.
+        */
+       if (!*s) {
+               if (!(*s = strdup(append)))
+                       err(2, "astrcat");
+
+               /* Keep track of string. */
+               offset = strlen(*s);
+               oldstr = *s;
+
+               return;
+       }
+
+       /*
+        * *s is a string so concatenate.
+        */
+
+       /* Did we process the same string in the last run? */
+       /*
+        * If this is a different string from the one we just processed
+        * cache new string.
+        */
+       if (oldstr != *s) {
+               offset = strlen(*s);
+               oldstr = *s;
+       }
+
+       /* Size = strlen(*s) + \n + strlen(append) + '\0'. */
+       newsiz = offset + 1 + strlen(append) + 1;
+
+       /* Resize *s to fit new string. */
+       newstr = realloc(*s, newsiz);
+       if (newstr == NULL)
+               err(2, "astrcat");
+       *s = newstr;
+
+       /* *s + offset should be end of string. */
+       /* Concatenate. */
+       strlcpy(*s + offset, "\n", newsiz - offset);
+       strlcat(*s + offset, append, newsiz - offset);
+
+       /* New string length should be exactly newsiz - 1 characters. */
+       /* Store generated string's values. */
+       offset = newsiz - 1;
+       oldstr = *s;
+}
+
+/*
+ * Process diff set queue, printing, prompting, and saving each diff
+ * line stored in queue.
+ */
+static void
+processq(void)
+{
+       struct diffline *diffp;
+       char divc, *left, *right;
+
+       /* Don't process empty queue. */
+       if (SIMPLEQ_EMPTY(&diffhead))
+               return;
+
+       /* Remember the divider. */
+       divc = SIMPLEQ_FIRST(&diffhead)->div;
+
+       left = NULL;
+       right = NULL;
+       /*
+        * Go through set of diffs, concatenating each line in left or
+        * right column into two long strings, `left' and `right'.
+        */
+       SIMPLEQ_FOREACH(diffp, &diffhead, diffentries) {
+               /*
+                * Print changed lines if -s was given,
+                * print all lines if -s was not given.
+                */
+               if (!sflag || diffp->div == '|' || diffp->div == '<' ||
+                   diffp->div == '>')
+                       println(diffp->left, diffp->div, diffp->right);
+
+               /* Append new lines to diff set. */
+               if (diffp->left)
+                       astrcat(&left, diffp->left);
+               if (diffp->right)
+                       astrcat(&right, diffp->right);
+       }
+
+       /* Empty queue and free each diff line and its elements. */
+       while (!SIMPLEQ_EMPTY(&diffhead)) {
+               diffp = SIMPLEQ_FIRST(&diffhead);
+               SIMPLEQ_REMOVE_HEAD(&diffhead, diffentries);
+               freediff(diffp);
+       }
+
+       /* Write to outfile, prompting user if lines are different. */
+       if (outfile)
+               switch (divc) {
+               case ' ': case '(': case ')':
+                       fprintf(outfile, "%s\n", left);
+                       break;
+               case '|': case '<': case '>':
+                       prompt(left, right);
+                       break;
+               default:
+                       errx(2, "invalid divider: %c", divc);
+               }
+
+       /* Free left and right. */
+       free(left);
+       free(right);
+}
+
+/*
+ * Print lines following an (a)ppend command.
+ */
+static void
+printa(FILE *file, size_t line2)
+{
+       char *line;
+
+       for (; file2ln <= line2; ++file2ln) {
+               if (!(line = xfgets(file)))
+                       errx(2, "append ended early");
+               enqueue(NULL, '>', line);
+       }
+
+       processq();
+}
+
+/*
+ * Print lines following a (c)hange command, from file1ln to file1end
+ * and from file2ln to file2end.
+ */
+static void
+printc(FILE *file1, size_t file1end, FILE *file2, size_t file2end)
+{
+       struct fileline {
+               SIMPLEQ_ENTRY(fileline)  fileentries;
+               char                    *line;
+       };
+       SIMPLEQ_HEAD(, fileline) delqhead = SIMPLEQ_HEAD_INITIALIZER(delqhead);
+
+       /* Read lines to be deleted. */
+       for (; file1ln <= file1end; ++file1ln) {
+               struct fileline *linep;
+               char *line1;
+
+               /* Read lines from both. */
+               if (!(line1 = xfgets(file1)))
+                       errx(2, "error reading file1 in delete in change");
+
+               /* Add to delete queue. */
+               if (!(linep = malloc(sizeof(struct fileline))))
+                       err(2, "printc");
+               linep->line = line1;
+               SIMPLEQ_INSERT_TAIL(&delqhead, linep, fileentries);
+       }
+
+       /* Process changed lines.. */
+       for (; !SIMPLEQ_EMPTY(&delqhead) && file2ln <= file2end;
+           ++file2ln) {
+               struct fileline *del;
+               char *add;
+
+               /* Get add line. */
+               if (!(add = xfgets(file2)))
+                       errx(2, "error reading add in change");
+
+               del = SIMPLEQ_FIRST(&delqhead);
+               enqueue(del->line, '|', add);
+               SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries);
+               /*
+                * Free fileline structure but not its elements since
+                * they are queued up.
+                */
+               free(del);
+       }
+       processq();
+
+       /* Process remaining lines to add. */
+       for (; file2ln <= file2end; ++file2ln) {
+               char *add;
+
+               /* Get add line. */
+               if (!(add = xfgets(file2)))
+                       errx(2, "error reading add in change");
+
+               enqueue(NULL, '>', add);
+       }
+       processq();
+
+       /* Process remaining lines to delete. */
+       while (!SIMPLEQ_EMPTY(&delqhead)) {
+               struct fileline *filep;
+
+               filep = SIMPLEQ_FIRST(&delqhead);
+               enqueue(filep->line, '<', NULL);
+               SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries);
+               free(filep);
+       }
+       processq();
+}
+
+/*
+ * Print deleted lines from file, from file1ln to file1end.
+ */
+static void
+printd(FILE *file1, size_t file1end)
+{
+       char *line1;
+
+       /* Print out lines file1ln to line2. */
+       for (; file1ln <= file1end; ++file1ln) {
+               /* XXX - Why can't this handle stdin? */
+               if (!(line1 = xfgets(file1)))
+                       errx(2, "file1 ended early in delete");
+               enqueue(line1, '<', NULL);
+       }
+       processq();
+}
+
+/*
+ * Interactive mode usage.
+ */
+static void
+int_usage(void)
+{
+       puts("e:\tedit blank diff\n"
+           "eb:\tedit both diffs concatenated\n"
+           "el:\tedit left diff\n"
+           "er:\tedit right diff\n"
+           "l:\tchoose left diff\n"
+           "r:\tchoose right diff\n"
+           "s:\tsilent mode--don't print identical lines\n"
+           "v:\tverbose mode--print identical lines\n"
+           "q:\tquit");
+}
+
+static void
+usage(void)
+{
+       extern char *__progname;
+
+       fprintf(stderr,
+           "usage: %s [-abdilstW] [-I regexp] [-o outfile] [-w width] file1 file2\n",
+           __progname);
+       exit(2);
+}
diff --git a/usr.bin/sdiff/strtonum.c b/usr.bin/sdiff/strtonum.c
new file mode 100644 (file)
index 0000000..53fe59b
--- /dev/null
@@ -0,0 +1,68 @@
+/*     $NetBSD: strtonum.c,v 1.1 2007/02/18 22:13:43 rmind Exp $       */
+/*     $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $    */
+
+/*
+ * Copyright (c) 2004 Ted Unangst and Todd Miller
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "common.h"
+
+#define INVALID                1
+#define TOOSMALL       2
+#define TOOLARGE       3
+
+long long
+strtonum(const char *numstr, long long minval, long long maxval,
+    const char **errstrp)
+{
+       long long ll = 0;
+       char *ep;
+       int error = 0;
+       struct errval {
+               const char *errstr;
+               int err;
+       } ev[4] = {
+               { NULL,         0 },
+               { "invalid",    EINVAL },
+               { "too small",  ERANGE },
+               { "too large",  ERANGE },
+       };
+
+       ev[0].err = errno;
+       errno = 0;
+       if (minval > maxval)
+               error = INVALID;
+       else {
+               ll = strtoll(numstr, &ep, 10);
+               if (numstr == ep || *ep != '\0')
+                       error = INVALID;
+               else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
+                       error = TOOSMALL;
+               else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
+                       error = TOOLARGE;
+       }
+       if (errstrp != NULL)
+               *errstrp = ev[error].errstr;
+       errno = ev[error].err;
+       if (error)
+               ll = 0;
+
+       return (ll);
+}