lobase

Linux port of OpenBSDs userland.
Log | Files | Refs | README

commit dd1611d7e5bade415f1fd4e559b06b222c24786e
parent cae07fc79ff94a13893ba0d33ab234fa76da6cad
Author: Duncaen <mail@duncano.de>
Date:   Wed,  8 Mar 2017 06:28:28 +0100

usr.bin/sdiff: import

Diffstat:
usr.bin/Makefile | 2+-
usr.bin/sdiff/Makefile | 10++++++++++
usr.bin/sdiff/common.c | 20++++++++++++++++++++
usr.bin/sdiff/common.h | 8++++++++
usr.bin/sdiff/edit.c | 206+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/sdiff/extern.h | 11+++++++++++
usr.bin/sdiff/sdiff.1 | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
usr.bin/sdiff/sdiff.c | 1049+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 1485 insertions(+), 1 deletion(-)

diff --git a/usr.bin/Makefile b/usr.bin/Makefile @@ -4,6 +4,6 @@ SUBDIR = apply awk basename dc bc biff cal calendar cmp colrm col column comm \ hexdump id indent join jot lam lndir logger logname look mktemp nice \ nl nohup paste patch printenv printf readlink renice rev rs sed shar \ sort split stat tee time touch tr true tsort tty uname unexpand uniq unvis \ - uudecode uuencode vis wc what which xinstall hexdump cu newsyslog + uudecode uuencode vis wc what which xinstall hexdump cu newsyslog sdiff SKIPDIR = sed file cu include bsd.subdir.mk diff --git a/usr.bin/sdiff/Makefile b/usr.bin/sdiff/Makefile @@ -0,0 +1,10 @@ +# $OpenBSD: Makefile,v 1.4 2006/02/20 08:38:18 otto Exp $ + +PROG=sdiff +SRCS=common.c edit.c sdiff.c +COPTS+=-Wall -W + +LDADD+= -lutil +DPADD+= ${LIBUTIL} + +include bsd.prog.mk diff --git a/usr.bin/sdiff/common.c b/usr.bin/sdiff/common.c @@ -0,0 +1,20 @@ +/* $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 @@ -0,0 +1,8 @@ +/* $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 *); diff --git a/usr.bin/sdiff/edit.c b/usr.bin/sdiff/edit.c @@ -0,0 +1,206 @@ +/* $OpenBSD: edit.c,v 1.20 2013/11/26 21:08:12 deraadt 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 <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((unsigned char)*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 || + 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); +} diff --git a/usr.bin/sdiff/extern.h b/usr.bin/sdiff/extern.h @@ -0,0 +1,11 @@ +/* $OpenBSD: extern.h,v 1.5 2009/06/07 13:29:50 ray Exp $ */ + +/* + * 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 *); diff --git a/usr.bin/sdiff/sdiff.1 b/usr.bin/sdiff/sdiff.1 @@ -0,0 +1,180 @@ +.\" $OpenBSD: sdiff.1,v 1.18 2015/02/28 21:51:56 bentley Exp $ +.\" +.\" Written by Raymond Lai <ray@cyth.net>. +.\" Public domain. +.\" +.Dd $Mdocdate: February 28 2015 $ +.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 > ; +deleted lines are marked with +.Sq < ; +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 | 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 +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 EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It 0 +No differences were found. +.It 1 +Differences were found. +.It \*(Gt1 +An error occurred. +.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 Mt ray@cyth.net . +.Sh CAVEATS +Although undocumented, +.Nm +supports most long options supported by GNU sdiff, +though some 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 @@ -0,0 +1,1049 @@ +/* $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/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 <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 *outfp; /* 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' }, + { "ignore-case", no_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)) { + 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, *file1, *file2; + size_t diffargc = 0, wflag = WIDTH; + int ch, fd[2], status; + pid_t pid; + const char *outfile = NULL; + char **diffargv, *diffprog = "diff", *filename1, *filename2, + *tmp1, *tmp2, *s1, *s2; + + if (pledge("stdio rpath wpath cpath proc exec", NULL) == -1) + err(2, "pledge"); + + /* + * 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; + + 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': + outfile = 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 (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 (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_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((unsigned char)*p); ++p) + ; + + switch (*p) { + case 'e': + /* Skip `e'. */ + ++p; + + if (eparse(p, s1, s2) == -1) + goto USAGE; + break; + + case 'l': + case '1': + /* Choose left column as-is. */ + if (s1 != NULL) + fprintf(outfp, "%s\n", s1); + + /* End of command parsing. */ + break; + + case 'q': + goto QUIT; + + case 'r': + case '2': + /* Choose right column as-is. */ + if (s2 != NULL) + fprintf(outfp, "%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(outfp); + 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 div, 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 (div == ' ' && !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", div); + return; + } + printf(" %c ", div); + 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((unsigned char)*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((unsigned char)*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((unsigned char)*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_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_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); + } + free(line); + + /* Skip to next ed line. */ + while (n--) { + if (!(line = xfgets(diffpipe))) + errx(2, "diff ended early"); + free(line); + } + + return (0); +} + +/* + * Queues up a diff line. + */ +static void +enqueue(char *left, char div, char *right) +{ + struct diffline *diffp; + + if (!(diffp = malloc(sizeof(struct diffline)))) + err(2, "enqueue"); + diffp->left = left; + diffp->div = div; + 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 outfp, prompting user if lines are different. */ + if (outfp) + switch (divc) { + case ' ': case '(': case ')': + fprintf(outfp, "%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) { + 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 | 1:\tchoose left diff\n" + "r | 2:\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); +}