root/usr/src/cmd/sdiff/sdiff.c
/*
 * 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   */


        /*
         *      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 *);

int
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"));
                return (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();
        return (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);
}