From ce982eb75720d2a0e553066b5b6315c7adf622a6 Mon Sep 17 00:00:00 2001 From: Thomas Cort Date: Wed, 30 Oct 2013 10:11:02 -0400 Subject: [PATCH] Importing usr.bin/sdiff No Minix-specific changes needed. Change-Id: Idbe7dcab6024d616022951f7942287fddecc3719 --- distrib/sets/lists/minix/mi | 2 + releasetools/nbsd_ports | 1 + usr.bin/Makefile | 2 +- usr.bin/sdiff/Makefile | 11 + usr.bin/sdiff/common.c | 21 + usr.bin/sdiff/common.h | 13 + usr.bin/sdiff/edit.c | 187 +++++++ usr.bin/sdiff/extern.h | 12 + usr.bin/sdiff/sdiff.1 | 167 ++++++ usr.bin/sdiff/sdiff.c | 1038 +++++++++++++++++++++++++++++++++++ usr.bin/sdiff/strtonum.c | 68 +++ 11 files changed, 1521 insertions(+), 1 deletion(-) create mode 100644 usr.bin/sdiff/Makefile create mode 100644 usr.bin/sdiff/common.c create mode 100644 usr.bin/sdiff/common.h create mode 100644 usr.bin/sdiff/edit.c create mode 100644 usr.bin/sdiff/extern.h create mode 100644 usr.bin/sdiff/sdiff.1 create mode 100644 usr.bin/sdiff/sdiff.c create mode 100644 usr.bin/sdiff/strtonum.c diff --git a/distrib/sets/lists/minix/mi b/distrib/sets/lists/minix/mi index 123099ab8..09b4739a1 100644 --- a/distrib/sets/lists/minix/mi +++ b/distrib/sets/lists/minix/mi @@ -470,6 +470,7 @@ ./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 @@ -2047,6 +2048,7 @@ ./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 diff --git a/releasetools/nbsd_ports b/releasetools/nbsd_ports index f7f898f4a..8befb3655 100644 --- a/releasetools/nbsd_ports +++ b/releasetools/nbsd_ports @@ -206,6 +206,7 @@ 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 diff --git a/usr.bin/Makefile b/usr.bin/Makefile index 6f9b2663d..8dc1f051a 100644 --- a/usr.bin/Makefile +++ b/usr.bin/Makefile @@ -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 index 000000000..2fa1567c3 --- /dev/null +++ b/usr.bin/sdiff/Makefile @@ -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 diff --git a/usr.bin/sdiff/common.c b/usr.bin/sdiff/common.c new file mode 100644 index 000000000..ef3136f1a --- /dev/null +++ b/usr.bin/sdiff/common.c @@ -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 . + * Public domain. + */ + +#include +#include +#include + +#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 index 000000000..f3745218a --- /dev/null +++ b/usr.bin/sdiff/common.h @@ -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 . + * 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 index 000000000..f00530552 --- /dev/null +++ b/usr.bin/sdiff/edit.c @@ -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 . + * Public domain. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 index 000000000..4cf323684 --- /dev/null +++ b/usr.bin/sdiff/extern.h @@ -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 . + * 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 index 000000000..09d8f8179 --- /dev/null +++ b/usr.bin/sdiff/sdiff.1 @@ -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 . +.\" 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 index 000000000..cf8260fac --- /dev/null +++ b/usr.bin/sdiff/sdiff.c @@ -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 . + * Public domain. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 000000000..53fe59ba1 --- /dev/null +++ b/usr.bin/sdiff/strtonum.c @@ -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 +#include +#include + +#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); +}