sdiff.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* sdiff [-l] [-s] [-w #] [-o output] file1 file2
* does side by side diff listing
* -l leftside only for identical lines
* -s silent; only print differences
* -w # width of output
* -o output interactive creation of new output commands:
* s silent; do not print identical lines
* v turn off silent
* l copy left side to output
* r copy right side to output
* e l call ed with left side
* e r call ed with right side
* e b call ed with cat of left and right
* e call ed with empty file
* q exit from program
*
* functions:
* cmd decode diff commands
* put1 output left side
* put2 output right side
* putmid output gutter
* putline output n chars to indicated file
* getlen calculate length of strings with tabs
* cmdin read and process interactive cmds
* cpp copy from file to file
* edit call ed with file
*/
#include <stdio.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <locale.h>
#include <limits.h>
#include <string.h>
#include <wchar.h>
#define LMAX BUFSIZ
#define BMAX BUFSIZ
#define STDOUT 1
#define WGUTTER 6
#define WLEN (WGUTTER * 2 + WGUTTER + 2)
#define PROMPT '%'
static const char twoblanks[3] = " ";
static const char *DIFF = "diff -b ";
static char diffcmd[BMAX];
static char inbuf[10];
static int llen = 130; /* Default maximum line length written out */
static int hlen; /* Half line length with space for gutter */
static int len1; /* Calculated length of left side */
static int nchars; /* Number of characters in left side - */
/* used for tab expansion */
static char change = ' ';
static int leftonly = 0; /* if set print left side only for */
/* identical lines */
static int silent = 0; /* if set do not print identical lines */
static int midflg = 0; /* set after middle was output */
static int rcode = 0; /* return code */
static char *file1;
static FILE *fdes1;
static char *file2;
static FILE *fdes2;
static FILE *diffdes;
static int oflag;
static char *ofile;
static FILE *odes;
static char *ltemp;
static FILE *left;
static char *rtemp;
static FILE *right;
static FILE *tempdes;
static char *temp;
/* decoded diff cmd- left side from to; right side from, to */
static int from1, to1, from2, to2;
static int num1, num2; /* line count for left side file and right */
static int tempfd = -1;
static char *filename(char *, char *);
static char *fgetline(FILE *);
static int put1(void);
static int put2(void);
static void putline(FILE *, char *, int);
static int cmd(char *);
static int getlen(int, char *);
static void putmid(int);
static void error(char *, char *);
static void onintr(void);
static void sremove(void);
static void cmdin(void);
static void cpp(char *, FILE *, FILE *);
static void edit(char *);
void
main(int argc, char **argv)
{
int com;
int n1, n2, n;
char *bp;
int lfd = -1;
int rfd = -1;
if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
(void) signal((int)SIGHUP, (void (*)(int))onintr);
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
(void) signal((int)SIGINT, (void (*)(int))onintr);
if (signal(SIGPIPE, SIG_IGN) != SIG_IGN)
(void) signal((int)SIGPIPE, (void (*)(int))onintr);
if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
(void) signal((int)SIGTERM, (void (*)(int))onintr);
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
while (--argc > 1 && **++argv == '-') {
switch (*++*argv) {
case 'w':
/* -w# instead of -w # */
if (*++*argv)
llen = atoi(*argv);
else {
argc--;
llen = atoi(*++argv);
}
if (llen < WLEN)
error(gettext("Wrong line length %s"), *argv);
if (llen > LMAX)
llen = LMAX;
break;
case 'l':
leftonly++;
break;
case 's':
silent++;
break;
case 'o':
oflag++;
argc--;
ofile = *++argv;
break;
default:
error(gettext("Illegal argument: %s"), *argv);
}
}
if (argc != 2) {
(void) fprintf(stderr, gettext(
"Usage: sdiff [-l] [-s] [-o output] [-w #] file1 file2\n"));
exit(2);
}
file1 = *argv++;
file2 = *argv;
file1 = filename(file1, file2);
file2 = filename(file2, file1);
hlen = (llen - WGUTTER +1)/2;
if ((fdes1 = fopen(file1, "r")) == NULL)
error(gettext("Cannot open: %s"), file1);
if ((fdes2 = fopen(file2, "r")) == NULL)
error(gettext("Cannot open: %s"), file2);
if (oflag) {
if (tempfd == -1) {
temp = strdup("/tmp/sdiffXXXXXX");
tempfd = mkstemp(temp);
if (tempfd == -1) {
error(gettext(
"Cannot open/create temp %s"), temp);
free(temp);
temp = 0;
}
}
ltemp = strdup("/tmp/sdifflXXXXXX");
if ((lfd = mkstemp(ltemp)) == -1 ||
(left = fdopen(lfd, "w")) == NULL)
error(gettext(
"Cannot open/create temp %s"),
ltemp);
rtemp = strdup("/tmp/sdiffrXXXXXX");
if ((rfd = mkstemp(rtemp)) == -1 ||
(right = fdopen(rfd, "w")) == NULL)
error(gettext(
"Cannot open/create temp file %s"),
rtemp);
if ((odes = fopen(ofile, "w")) == NULL)
error(gettext("Cannot open output %s"), ofile);
}
/* Call DIFF command */
(void) strcpy(diffcmd, DIFF);
(void) strcat(diffcmd, file1);
(void) strcat(diffcmd, " ");
(void) strcat(diffcmd, file2);
diffdes = popen(diffcmd, "r");
num1 = num2 = 0;
/*
* Read in diff output and decode commands
* "change" is used to determine character to put in gutter
* num1 and num2 counts the number of lines in file1 and 2
*/
n = 0;
while ((bp = fgetline(diffdes)) != NULL) {
change = ' ';
com = cmd(bp);
/*
* handles all diff output that is not cmd
* lines starting with <, >, ., ---
*/
if (com == 0)
continue;
/* Catch up to from1 and from2 */
rcode = 1;
n1 = from1 - num1;
n2 = from2 - num2;
n = n1 > n2 ? n2 : n1;
if (com == 'c' && n > 0)
n--;
if (silent)
(void) fputs(bp, stdout);
while (n-- > 0) {
(void) put1();
(void) put2();
if (!silent)
(void) putc('\n', stdout);
midflg = 0;
}
/* Process diff cmd */
switch (com) {
case 'a':
change = '>';
while (num2 < to2) {
(void) put2();
(void) putc('\n', stdout);
midflg = 0;
}
break;
case 'd':
change = '<';
while (num1 < to1) {
(void) put1();
(void) putc('\n', stdout);
midflg = 0;
}
break;
case 'c':
n1 = to1 - from1;
n2 = to2 - from2;
n = n1 > n2 ? n2 : n1;
change = '|';
do {
(void) put1();
(void) put2();
(void) putc('\n', stdout);
midflg = 0;
} while (n--);
change = '<';
while (num1 < to1) {
(void) put1();
(void) putc('\n', stdout);
midflg = 0;
}
change = '>';
while (num2 < to2) {
(void) put2();
(void) putc('\n', stdout);
midflg = 0;
}
break;
default:
(void) fprintf(stderr, gettext(
"%c: cmd not found\n"), cmd);
break;
}
if (oflag == 1 && com != 0) {
cmdin();
if ((left = fopen(ltemp, "w")) == NULL)
error(gettext(
"main: Cannot open temp %s"), ltemp);
if ((right = fopen(rtemp, "w")) == NULL)
error(gettext(
"main: Cannot open temp %s"), rtemp);
}
}
/* put out remainder of input files */
while (put1()) {
(void) put2();
if (!silent)
(void) putc('\n', stdout);
midflg = 0;
}
if (odes)
(void) fclose(odes);
sremove();
exit(rcode);
}
static int
put1(void)
{
/* len1 = length of left side */
/* nchars = num of chars including tabs */
char *bp;
if ((bp = fgetline(fdes1)) != NULL) {
len1 = getlen(0, bp);
if ((!silent || change != ' ') && len1 != 0)
putline(stdout, bp, nchars);
if (oflag) {
/*
* put left side either to output file
* if identical to right
* or left temp file if not
*/
if (change == ' ')
putline(odes, bp, strlen(bp));
else
putline(left, bp, strlen(bp));
}
if (change != ' ')
putmid(1);
num1++;
return (1);
} else
return (0);
}
static int
put2(void)
{
char *bp;
if ((bp = fgetline(fdes2)) != NULL) {
(void) getlen((hlen + WGUTTER) % 8, bp);
/*
* if the left and right are different they are always
* printed.
* If the left and right are identical
* right is only printed if leftonly is not specified
* or silent mode is not specified
* or the right contains other than white space (len1 !=0)
*/
if (change != ' ') {
/*
* put right side to right temp file only
* because left side was written to output for
* identical lines
*/
if (oflag)
putline(right, bp, strlen(bp));
if (midflg == 0)
putmid(1);
putline(stdout, bp, nchars);
} else
if (!silent && !leftonly && len1 != 0) {
if (midflg == 0)
putmid(1);
putline(stdout, bp, nchars);
}
num2++;
len1 = 0;
return (1);
} else {
len1 = 0;
return (0);
}
}
static void
putline(FILE *file, char *start, int num)
{
char *cp, *end;
int i, len, d_col;
wchar_t wc;
cp = start;
end = cp + num;
while (cp < end) {
if (isascii(*cp)) {
(void) putc(*cp++, file);
continue;
}
if ((len = end - cp) > MB_LEN_MAX)
len = MB_LEN_MAX;
if ((len = mbtowc(&wc, cp, len)) <= 0) {
(void) putc(*cp++, file);
continue;
}
if ((d_col = wcwidth(wc)) <= 0)
d_col = len;
if ((cp + d_col) > end)
return;
for (i = 0; i < len; i++)
(void) putc(*cp++, file);
}
}
static int
cmd(char *start)
{
unsigned char *cp;
char *cps;
int com;
if (*start == '>' || *start == '<' || *start == '-' || *start == '.')
return (0);
cp = (unsigned char *)start;
cps = start;
while (isdigit(*cp))
cp++;
from1 = atoi(cps);
to1 = from1;
if (*cp == ',') {
cp++;
cps = (char *)cp;
while (isdigit(*cp))
cp++;
to1 = atoi(cps);
}
com = *cp++;
cps = (char *)cp;
while (isdigit(*cp))
cp++;
from2 = atoi(cps);
to2 = from2;
if (*cp == ',') {
cp++;
cps = (char *)cp;
while (isdigit(*cp))
cp++;
to2 = atoi(cps);
}
return (com);
}
static int
getlen(int startpos, char *buffer)
{
/*
* get the length of the string in buffer
* expand tabs to next multiple of 8
*/
unsigned char *cp;
int slen, tlen, len, d_col;
int notspace;
wchar_t wc;
nchars = 0;
notspace = 0;
tlen = startpos;
for (cp = (unsigned char *)buffer; (*cp != '\n') && (*cp); cp++) {
if (*cp == '\t') {
slen = tlen;
tlen += 8 - (tlen % 8);
if (tlen >= hlen) {
tlen = slen;
break;
}
nchars++;
continue;
}
if (isascii(*cp)) {
slen = tlen;
tlen++;
if (tlen >= hlen) {
tlen = slen;
break;
}
if (!isspace(*cp))
notspace = 1;
nchars++;
continue;
}
if ((len = mbtowc(&wc, (char *)cp, MB_LEN_MAX)) <= 0) {
slen = tlen;
tlen++;
if (tlen >= hlen) {
tlen = slen;
break;
}
notspace = 1;
nchars++;
continue;
}
if ((d_col = wcwidth(wc)) <= 0)
d_col = len;
slen = tlen;
tlen += d_col;
if (tlen > hlen) {
tlen = slen;
break;
}
notspace = 1;
cp += len - 1;
nchars += len;
}
return (notspace ? tlen : 0);
}
static void
putmid(int bflag)
{
int i;
/*
* len1 set by getlen to the possibly truncated
* length of left side
* hlen is length of half line
*/
midflg = 1;
if (bflag) {
for (i = 0; i < hlen - len1; i++)
(void) putc(' ', stdout);
}
(void) fputs(twoblanks, stdout);
(void) putc((int)change, stdout);
(void) fputs(twoblanks, stdout);
}
static void
error(char *s1, char *s2)
{
(void) fprintf(stderr, "sdiff: ");
(void) fprintf(stderr, s1, s2);
(void) putc('\n', stderr);
sremove();
exit(2);
}
static void
onintr(void)
{
sremove();
exit(rcode);
}
static void
sremove(void)
{
if (ltemp) {
(void) unlink(ltemp);
free(ltemp);
}
if (rtemp) {
(void) unlink(rtemp);
free(rtemp);
}
if (temp) {
(void) unlink(temp);
free(temp);
}
}
static void
cmdin(void)
{
char *cp, *ename;
int notacc;
(void) fclose(left);
(void) fclose(right);
notacc = 1;
while (notacc) {
(void) putc(PROMPT, stdout);
if ((cp = fgets(inbuf, 10, stdin)) == NULL) {
(void) putc('\n', stdout);
break;
}
switch (*cp) {
case 's':
silent = 1;
break;
case 'v':
silent = 0;
break;
case 'q':
sremove();
exit(rcode);
/* NOTREACHED */
break;
case 'l':
cpp(ltemp, left, odes);
notacc = 0;
break;
case 'r':
cpp(rtemp, right, odes);
notacc = 0;
break;
case 'e':
while (*++cp == ' ')
;
switch (*cp) {
case 'l':
case '<':
notacc = 0;
ename = ltemp;
edit(ename);
break;
case 'r':
case '>':
notacc = 0;
ename = rtemp;
edit(ename);
break;
case 'b':
case '|':
if ((tempdes = fopen(temp, "w")) == NULL)
error(gettext(
"Cannot open temp file %s"),
temp);
cpp(ltemp, left, tempdes);
cpp(rtemp, right, tempdes);
(void) fclose(tempdes);
notacc = 0;
ename = temp;
edit(ename);
break;
case '\n':
if ((tempdes = fopen(temp, "w")) == NULL)
error(gettext(
"Cannot open temp file %s"),
temp);
(void) fclose(tempdes);
notacc = 0;
ename = temp;
edit(ename);
break;
default:
(void) fprintf(stderr, gettext(
"Illegal command %s reenter\n"),
cp);
break;
}
if (notacc == 0)
cpp(ename, tempdes, odes);
break;
default:
(void) fprintf(stderr, gettext(
"Illegal command reenter\n"));
break;
}
}
}
static void
cpp(char *from, FILE *fromdes, FILE *todes)
{
char tempbuf[BMAX + 1];
if ((fromdes = fopen(from, "r")) == NULL)
error(gettext(
"cpp: Cannot open %s"), from);
while ((fgets(tempbuf, BMAX, fromdes) != NULL))
(void) fputs(tempbuf, todes);
(void) fclose(fromdes);
}
static void
edit(char *file)
{
int i;
pid_t pid;
void (*oldintr)(int);
switch (pid = fork()) {
case (pid_t)-1:
error(gettext("Cannot fork"), NULL);
/* NOTREACHED */
break;
case (pid_t)0:
(void) execl("/usr/bin/ed", "ed", file, NULL);
}
oldintr = signal(SIGINT, SIG_IGN); /* ignore interrupts in ed */
while (pid != wait(&i))
;
/* restore previous interrupt proc */
(void) signal(SIGINT, oldintr);
}
static char *
filename(char *pa1, char *pa2)
{
int c;
char *a1, *b1, *a2;
struct stat stbuf;
a1 = pa1;
a2 = pa2;
if (stat(a1, &stbuf) != -1 && ((stbuf.st_mode&S_IFMT) == S_IFDIR)) {
b1 = pa1 = (char *)malloc(strlen(a1) + strlen(a2) + 2);
while (*b1++ = *a1++);
b1[-1] = '/';
a1 = b1;
while (*a1++ = *a2++)
if (*a2 && *a2 != '/' && a2[-1] == '/')
a1 = b1;
} else if (a1[0] == '-' && a1[1] == 0 && temp == 0) {
if (fstat(fileno(stdin), &stbuf) == -1)
error(gettext("Cannot process stdin"), NULL);
pa1 = temp = strdup("/tmp/sdiffXXXXXX");
if ((tempfd = mkstemp(temp)) == -1 ||
(tempdes = fdopen(tempfd, "w")) == NULL)
error(gettext("Cannot open/create temp %s"),
temp);
while ((c = getc(stdin)) != EOF)
(void) putc(c, tempdes);
(void) fclose(tempdes);
}
return (pa1);
}
/*
* like fgets, but reads upto and including a newline,
* the data is stored in a reusable dynamic buffer that grows to fit
* the largest line in the file, the buffer is NULL terminated
* returns a pointer to the dynamic buffer.
*/
static char *
fgetline(FILE *fp)
{
static char *bp = NULL;
static int blen = 0;
int sl;
if (bp == NULL) {
/* allocate it for the first time */
bp = (char *)malloc(BUFSIZ);
if (bp == NULL)
error(gettext("fgetline: malloc failed"), NULL);
blen = BUFSIZ;
}
/* check for error or nothing read */
if (fgets(bp, blen, fp) == NULL)
return (NULL);
if (feof(fp))
return (bp);
while ((sl = strlen(bp)) == blen-1 && *(bp+blen-2) != '\n') {
/* still more data, grow the buffer */
blen *= 2;
bp = (char *)realloc(bp, blen);
if (bp == NULL)
error(gettext("fgetline: realloc failed"), NULL);
/* continue reading and add to end of buffer */
(void) fgets(bp+sl, blen-sl, fp);
}
return (bp);
}