#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
#define WIDTH_MIN 5
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;
size_t width;
size_t file1ln, file2ln;
int Iflag = 0;
int lflag;
int sflag;
FILE *outfile;
const char *tmpdir;
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 }
};
static char *
mktmpcpy(const char *source_file)
{
struct stat sb;
ssize_t rcount;
int ifd, ofd;
u_char buf[BUFSIZ];
char *target_file;
ifd = open(source_file, O_RDONLY, 0);
if (ifd != -1) {
if (fstat(ifd, &sb) == -1)
err(2, "error getting file status from %s", source_file);
if (S_ISREG(sb.st_mode))
return (NULL);
} else {
if (errno == ENOENT && strcmp(source_file, "-") == 0)
ifd = STDIN_FILENO;
else
err(2, "error opening %s", source_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;
if (!(diffargv = malloc(sizeof(char **) * argc * 2)))
err(2, "main");
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];
tmp1 = tmp2 = NULL;
if (strcmp(filename1, filename2) == 0) {
if ((tmp1 = mktmpcpy(filename1)))
filename1 = filename2 = tmp1;
} else {
if ((tmp1 = mktmpcpy(filename1)))
filename1 = tmp1;
if ((tmp2 = mktmpcpy(filename2)))
filename2 = tmp2;
}
diffargv[diffargc++] = filename1;
diffargv[diffargc++] = filename2;
diffargv[diffargc++] = NULL;
width = (wflag - 3) / 2;
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:
close(fd[0]);
if (dup2(fd[1], STDOUT_FILENO) == -1)
err(2, "child could not duplicate descriptor");
close(fd[1]);
execvp(diffprog, diffargv);
err(2, "could not execute diff: %s", diffprog);
case -1:
err(2, "could not fork");
}
close(fd[1]);
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);
file1ln = file2ln = 1;
while (parsecmd(diffpipe, file1, file2) != EOF)
;
fclose(diffpipe);
if (waitpid(pid, &status, 0) == -1 || !WIFEXITED(status) ||
WEXITSTATUS(status) >= 2)
err(2, "diff exited abnormally");
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;
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);
processq();
return (WEXITSTATUS(status));
}
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 (*col > SIZE_T_MAX - 8)
return;
new_col = (*col / 8 + 1) * 8;
if (new_col > col_max)
return;
*col = new_col;
break;
default:
++(*col);
}
putchar(*s);
}
}
static void
prompt(const char *s1, const char *s2)
{
char *cmd;
putchar('%');
for (; (cmd = xfgets(stdin)); free(cmd)) {
const char *p;
for (p = cmd; isspace((unsigned char)(*p)); ++p)
;
switch (*p) {
case 'e':
++p;
if (eparse(p, s1, s2) == -1)
goto USAGE;
break;
case 'l':
if (s1 != NULL)
fprintf(outfile, "%s\n", s1);
break;
case 'q':
goto QUIT;
case 'r':
if (s2 != NULL)
fprintf(outfile, "%s\n", s2);
break;
case 's':
sflag = 1;
goto PROMPT;
case 'v':
sflag = 0;
default:
USAGE:
int_usage();
PROMPT:
putchar('%');
continue;
}
free(cmd);
return;
}
QUIT:
fclose(outfile);
exit(0);
}
static void
println(const char *s1, const char divc, const char *s2)
{
size_t col;
col = 0;
if (s1) {
printcol(s1, &col, width);
}
if (divc == ' ' && !s2) {
putchar('\n');
return;
}
for (; col < width; ++col)
putchar(' ');
if (!s2) {
printf(" %c\n", divc);
return;
}
printf(" %c ", divc);
col += 3;
printcol(s2, &col, line_width);
putchar('\n');
}
static char *
xfgets(FILE *file)
{
const char delim[3] = {'\0', '\0', '\0'};
char *s;
clearerr(file);
if (!(s = fparseln(file, NULL, NULL, delim, 0)) &&
ferror(file))
err(2, "error reading file");
if (!s) {
return (NULL);
}
return (s);
}
static int
parsecmd(FILE *diffpipe, FILE *file1, FILE *file2)
{
size_t file1start, file1end, file2start, file2end, n;
char *line, *p, *q;
const char *errstr;
char c, cmd;
if (!(line = xfgets(diffpipe)))
return (EOF);
p = line;
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);
if (c == ',') {
q = p;
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;
if (!(cmd == 'a' || cmd == 'c' || cmd == 'd'))
errx(2, "ed command not recognized: %c: %s", cmd, line);
q = p;
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);
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;
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;
}
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;
}
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 (lflag) {
free(s2);
if (Iflag)
enqueue(s1, '(', NULL);
else
enqueue(s1, ' ', NULL);
} else
enqueue(s1, ' ', s2);
}
for (; file1ln < file1start; ++file1ln) {
char *s;
if (!(s = xfgets(file1)))
errx(2, "file1 shorter than expected");
enqueue(s, '(', NULL);
}
for (; file2ln < file2start; ++file2ln) {
char *s;
if (!(s = xfgets(file2)))
errx(2, "file2 shorter than expected");
if (lflag)
free(s);
else
enqueue(NULL, ')', s);
}
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);
}
while (n--)
if (!xfgets(diffpipe))
errx(2, "diff ended early");
return (0);
}
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);
}
static void
freediff(struct diffline *diffp)
{
free(diffp->left);
free(diffp->right);
free(diffp);
}
static void
astrcat(char **s, const char *append)
{
static size_t offset = 0;
size_t newsiz;
static const char *oldstr = NULL;
char *newstr;
if (!*s) {
if (!(*s = strdup(append)))
err(2, "astrcat");
offset = strlen(*s);
oldstr = *s;
return;
}
if (oldstr != *s) {
offset = strlen(*s);
oldstr = *s;
}
newsiz = offset + 1 + strlen(append) + 1;
newstr = realloc(*s, newsiz);
if (newstr == NULL)
err(2, "astrcat");
*s = newstr;
strlcpy(*s + offset, "\n", newsiz - offset);
strlcat(*s + offset, append, newsiz - offset);
offset = newsiz - 1;
oldstr = *s;
}
static void
processq(void)
{
struct diffline *diffp;
char divc, *left, *right;
if (SIMPLEQ_EMPTY(&diffhead))
return;
divc = SIMPLEQ_FIRST(&diffhead)->div;
left = NULL;
right = NULL;
SIMPLEQ_FOREACH(diffp, &diffhead, diffentries) {
if (!sflag || diffp->div == '|' || diffp->div == '<' ||
diffp->div == '>')
println(diffp->left, diffp->div, diffp->right);
if (diffp->left)
astrcat(&left, diffp->left);
if (diffp->right)
astrcat(&right, diffp->right);
}
while (!SIMPLEQ_EMPTY(&diffhead)) {
diffp = SIMPLEQ_FIRST(&diffhead);
SIMPLEQ_REMOVE_HEAD(&diffhead, diffentries);
freediff(diffp);
}
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);
free(right);
}
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();
}
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);
for (; file1ln <= file1end; ++file1ln) {
struct fileline *linep;
char *line1;
if (!(line1 = xfgets(file1)))
errx(2, "error reading file1 in delete in change");
if (!(linep = malloc(sizeof(struct fileline))))
err(2, "printc");
linep->line = line1;
SIMPLEQ_INSERT_TAIL(&delqhead, linep, fileentries);
}
for (; !SIMPLEQ_EMPTY(&delqhead) && file2ln <= file2end;
++file2ln) {
struct fileline *del;
char *add;
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(del);
}
processq();
for (; file2ln <= file2end; ++file2ln) {
char *add;
if (!(add = xfgets(file2)))
errx(2, "error reading add in change");
enqueue(NULL, '>', add);
}
processq();
while (!SIMPLEQ_EMPTY(&delqhead)) {
struct fileline *filep;
filep = SIMPLEQ_FIRST(&delqhead);
enqueue(filep->line, '<', NULL);
SIMPLEQ_REMOVE_HEAD(&delqhead, fileentries);
free(filep);
}
processq();
}
static void
printd(FILE *file1, size_t file1end)
{
char *line1;
for (; file1ln <= file1end; ++file1ln) {
if (!(line1 = xfgets(file1)))
errx(2, "file1 ended early in delete");
enqueue(line1, '<', NULL);
}
processq();
}
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);
}