1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22 /*
23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 */
26
27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
29
30
31 /*
32 * sdiff [-l] [-s] [-w #] [-o output] file1 file2
33 * does side by side diff listing
34 * -l leftside only for identical lines
35 * -s silent; only print differences
36 * -w # width of output
37 * -o output interactive creation of new output commands:
38 * s silent; do not print identical lines
39 * v turn off silent
40 * l copy left side to output
41 * r copy right side to output
42 * e l call ed with left side
43 * e r call ed with right side
44 * e b call ed with cat of left and right
45 * e call ed with empty file
46 * q exit from program
47 *
48 * functions:
49 * cmd decode diff commands
50 * put1 output left side
51 * put2 output right side
52 * putmid output gutter
53 * putline output n chars to indicated file
54 * getlen calculate length of strings with tabs
55 * cmdin read and process interactive cmds
56 * cpp copy from file to file
57 * edit call ed with file
58 */
59
60 #include <stdio.h>
61 #include <ctype.h>
62 #include <signal.h>
63 #include <sys/types.h>
64 #include <sys/stat.h>
65 #include <sys/wait.h>
66 #include <unistd.h>
67 #include <stdlib.h>
68 #include <locale.h>
69 #include <limits.h>
70 #include <string.h>
71 #include <wchar.h>
72
73 #define LMAX BUFSIZ
74 #define BMAX BUFSIZ
75 #define STDOUT 1
76 #define WGUTTER 6
77 #define WLEN (WGUTTER * 2 + WGUTTER + 2)
78 #define PROMPT '%'
79
80 static const char twoblanks[3] = " ";
81
82 static const char *DIFF = "diff -b ";
83 static char diffcmd[BMAX];
84 static char inbuf[10];
85
86 static int llen = 130; /* Default maximum line length written out */
87 static int hlen; /* Half line length with space for gutter */
88 static int len1; /* Calculated length of left side */
89 static int nchars; /* Number of characters in left side - */
90 /* used for tab expansion */
91 static char change = ' ';
92 static int leftonly = 0; /* if set print left side only for */
93 /* identical lines */
94 static int silent = 0; /* if set do not print identical lines */
95 static int midflg = 0; /* set after middle was output */
96 static int rcode = 0; /* return code */
97
98
99 static char *file1;
100 static FILE *fdes1;
101
102 static char *file2;
103 static FILE *fdes2;
104
105 static FILE *diffdes;
106
107 static int oflag;
108 static char *ofile;
109 static FILE *odes;
110
111 static char *ltemp;
112 static FILE *left;
113
114 static char *rtemp;
115 static FILE *right;
116
117 static FILE *tempdes;
118 static char *temp;
119
120 /* decoded diff cmd- left side from to; right side from, to */
121
122 static int from1, to1, from2, to2;
123
124 static int num1, num2; /* line count for left side file and right */
125 static int tempfd = -1;
126
127 static char *filename(char *, char *);
128 static char *fgetline(FILE *);
129 static int put1(void);
130 static int put2(void);
131 static void putline(FILE *, char *, int);
132 static int cmd(char *);
133 static int getlen(int, char *);
134 static void putmid(int);
135 static void error(char *, char *);
136 static void onintr(void);
137 static void sremove(void);
138 static void cmdin(void);
139 static void cpp(char *, FILE *, FILE *);
140 static void edit(char *);
141
142 int
main(int argc,char ** argv)143 main(int argc, char **argv)
144 {
145 int com;
146 int n1, n2, n;
147 char *bp;
148 int lfd = -1;
149 int rfd = -1;
150
151 if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
152 (void) signal((int)SIGHUP, (void (*)(int))onintr);
153 if (signal(SIGINT, SIG_IGN) != SIG_IGN)
154 (void) signal((int)SIGINT, (void (*)(int))onintr);
155 if (signal(SIGPIPE, SIG_IGN) != SIG_IGN)
156 (void) signal((int)SIGPIPE, (void (*)(int))onintr);
157 if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
158 (void) signal((int)SIGTERM, (void (*)(int))onintr);
159
160 (void) setlocale(LC_ALL, "");
161 #if !defined(TEXT_DOMAIN)
162 #define TEXT_DOMAIN "SYS_TEST"
163 #endif
164 (void) textdomain(TEXT_DOMAIN);
165
166 while (--argc > 1 && **++argv == '-') {
167 switch (*++*argv) {
168
169 case 'w':
170 /* -w# instead of -w # */
171 if (*++*argv)
172 llen = atoi(*argv);
173 else {
174 argc--;
175 llen = atoi(*++argv);
176 }
177 if (llen < WLEN)
178 error(gettext("Wrong line length %s"), *argv);
179 if (llen > LMAX)
180 llen = LMAX;
181 break;
182
183 case 'l':
184 leftonly++;
185 break;
186
187 case 's':
188 silent++;
189 break;
190 case 'o':
191 oflag++;
192 argc--;
193 ofile = *++argv;
194 break;
195 default:
196 error(gettext("Illegal argument: %s"), *argv);
197 }
198 }
199 if (argc != 2) {
200 (void) fprintf(stderr, gettext(
201 "Usage: sdiff [-l] [-s] [-o output] [-w #] file1 file2\n"));
202 return (2);
203 }
204
205 file1 = *argv++;
206 file2 = *argv;
207 file1 = filename(file1, file2);
208 file2 = filename(file2, file1);
209 hlen = (llen - WGUTTER +1)/2;
210
211 if ((fdes1 = fopen(file1, "r")) == NULL)
212 error(gettext("Cannot open: %s"), file1);
213
214 if ((fdes2 = fopen(file2, "r")) == NULL)
215 error(gettext("Cannot open: %s"), file2);
216
217 if (oflag) {
218 if (tempfd == -1) {
219 temp = strdup("/tmp/sdiffXXXXXX");
220 tempfd = mkstemp(temp);
221 if (tempfd == -1) {
222 error(gettext(
223 "Cannot open/create temp %s"), temp);
224 free(temp);
225 temp = 0;
226 }
227 }
228 ltemp = strdup("/tmp/sdifflXXXXXX");
229 if ((lfd = mkstemp(ltemp)) == -1 ||
230 (left = fdopen(lfd, "w")) == NULL)
231 error(gettext(
232 "Cannot open/create temp %s"),
233 ltemp);
234 rtemp = strdup("/tmp/sdiffrXXXXXX");
235 if ((rfd = mkstemp(rtemp)) == -1 ||
236 (right = fdopen(rfd, "w")) == NULL)
237 error(gettext(
238 "Cannot open/create temp file %s"),
239 rtemp);
240 if ((odes = fopen(ofile, "w")) == NULL)
241 error(gettext("Cannot open output %s"), ofile);
242 }
243 /* Call DIFF command */
244 (void) strcpy(diffcmd, DIFF);
245 (void) strcat(diffcmd, file1);
246 (void) strcat(diffcmd, " ");
247 (void) strcat(diffcmd, file2);
248 diffdes = popen(diffcmd, "r");
249
250 num1 = num2 = 0;
251
252 /*
253 * Read in diff output and decode commands
254 * "change" is used to determine character to put in gutter
255 * num1 and num2 counts the number of lines in file1 and 2
256 */
257
258 n = 0;
259 while ((bp = fgetline(diffdes)) != NULL) {
260 change = ' ';
261 com = cmd(bp);
262
263 /*
264 * handles all diff output that is not cmd
265 * lines starting with <, >, ., ---
266 */
267 if (com == 0)
268 continue;
269
270 /* Catch up to from1 and from2 */
271 rcode = 1;
272 n1 = from1 - num1;
273 n2 = from2 - num2;
274 n = n1 > n2 ? n2 : n1;
275 if (com == 'c' && n > 0)
276 n--;
277 if (silent)
278 (void) fputs(bp, stdout);
279 while (n-- > 0) {
280 (void) put1();
281 (void) put2();
282 if (!silent)
283 (void) putc('\n', stdout);
284 midflg = 0;
285 }
286
287 /* Process diff cmd */
288 switch (com) {
289
290 case 'a':
291 change = '>';
292 while (num2 < to2) {
293 (void) put2();
294 (void) putc('\n', stdout);
295 midflg = 0;
296 }
297 break;
298
299 case 'd':
300 change = '<';
301 while (num1 < to1) {
302 (void) put1();
303 (void) putc('\n', stdout);
304 midflg = 0;
305 }
306 break;
307
308 case 'c':
309 n1 = to1 - from1;
310 n2 = to2 - from2;
311 n = n1 > n2 ? n2 : n1;
312 change = '|';
313 do {
314 (void) put1();
315 (void) put2();
316 (void) putc('\n', stdout);
317 midflg = 0;
318 } while (n--);
319
320 change = '<';
321 while (num1 < to1) {
322 (void) put1();
323 (void) putc('\n', stdout);
324 midflg = 0;
325 }
326
327 change = '>';
328 while (num2 < to2) {
329 (void) put2();
330 (void) putc('\n', stdout);
331 midflg = 0;
332 }
333 break;
334
335 default:
336 (void) fprintf(stderr, gettext(
337 "%c: cmd not found\n"), cmd);
338 break;
339 }
340
341 if (oflag == 1 && com != 0) {
342 cmdin();
343 if ((left = fopen(ltemp, "w")) == NULL)
344 error(gettext(
345 "main: Cannot open temp %s"), ltemp);
346 if ((right = fopen(rtemp, "w")) == NULL)
347 error(gettext(
348 "main: Cannot open temp %s"), rtemp);
349 }
350 }
351 /* put out remainder of input files */
352
353 while (put1()) {
354 (void) put2();
355 if (!silent)
356 (void) putc('\n', stdout);
357 midflg = 0;
358 }
359 if (odes)
360 (void) fclose(odes);
361 sremove();
362 return (rcode);
363 }
364
365 static int
put1(void)366 put1(void)
367 {
368 /* len1 = length of left side */
369 /* nchars = num of chars including tabs */
370
371 char *bp;
372
373
374 if ((bp = fgetline(fdes1)) != NULL) {
375 len1 = getlen(0, bp);
376 if ((!silent || change != ' ') && len1 != 0)
377 putline(stdout, bp, nchars);
378
379 if (oflag) {
380 /*
381 * put left side either to output file
382 * if identical to right
383 * or left temp file if not
384 */
385
386 if (change == ' ')
387 putline(odes, bp, strlen(bp));
388 else
389 putline(left, bp, strlen(bp));
390 }
391 if (change != ' ')
392 putmid(1);
393 num1++;
394 return (1);
395 } else
396 return (0);
397 }
398
399 static int
put2(void)400 put2(void)
401 {
402 char *bp;
403
404 if ((bp = fgetline(fdes2)) != NULL) {
405 (void) getlen((hlen + WGUTTER) % 8, bp);
406
407 /*
408 * if the left and right are different they are always
409 * printed.
410 * If the left and right are identical
411 * right is only printed if leftonly is not specified
412 * or silent mode is not specified
413 * or the right contains other than white space (len1 !=0)
414 */
415 if (change != ' ') {
416
417 /*
418 * put right side to right temp file only
419 * because left side was written to output for
420 * identical lines
421 */
422
423 if (oflag)
424 putline(right, bp, strlen(bp));
425
426 if (midflg == 0)
427 putmid(1);
428 putline(stdout, bp, nchars);
429 } else
430 if (!silent && !leftonly && len1 != 0) {
431 if (midflg == 0)
432 putmid(1);
433 putline(stdout, bp, nchars);
434 }
435 num2++;
436 len1 = 0;
437 return (1);
438 } else {
439 len1 = 0;
440 return (0);
441 }
442 }
443
444 static void
putline(FILE * file,char * start,int num)445 putline(FILE *file, char *start, int num)
446 {
447 char *cp, *end;
448 int i, len, d_col;
449 wchar_t wc;
450
451 cp = start;
452 end = cp + num;
453 while (cp < end) {
454 if (isascii(*cp)) {
455 (void) putc(*cp++, file);
456 continue;
457 }
458
459 if ((len = end - cp) > MB_LEN_MAX)
460 len = MB_LEN_MAX;
461
462 if ((len = mbtowc(&wc, cp, len)) <= 0) {
463 (void) putc(*cp++, file);
464 continue;
465 }
466
467 if ((d_col = wcwidth(wc)) <= 0)
468 d_col = len;
469
470 if ((cp + d_col) > end)
471 return;
472
473 for (i = 0; i < len; i++)
474 (void) putc(*cp++, file);
475 }
476 }
477
478 static int
cmd(char * start)479 cmd(char *start)
480 {
481 unsigned char *cp;
482 char *cps;
483 int com;
484
485 if (*start == '>' || *start == '<' || *start == '-' || *start == '.')
486 return (0);
487
488 cp = (unsigned char *)start;
489 cps = start;
490 while (isdigit(*cp))
491 cp++;
492 from1 = atoi(cps);
493 to1 = from1;
494 if (*cp == ',') {
495 cp++;
496 cps = (char *)cp;
497 while (isdigit(*cp))
498 cp++;
499 to1 = atoi(cps);
500 }
501
502 com = *cp++;
503 cps = (char *)cp;
504
505 while (isdigit(*cp))
506 cp++;
507 from2 = atoi(cps);
508 to2 = from2;
509 if (*cp == ',') {
510 cp++;
511 cps = (char *)cp;
512 while (isdigit(*cp))
513 cp++;
514 to2 = atoi(cps);
515 }
516 return (com);
517 }
518
519 static int
getlen(int startpos,char * buffer)520 getlen(int startpos, char *buffer)
521 {
522 /*
523 * get the length of the string in buffer
524 * expand tabs to next multiple of 8
525 */
526 unsigned char *cp;
527 int slen, tlen, len, d_col;
528 int notspace;
529 wchar_t wc;
530
531 nchars = 0;
532 notspace = 0;
533 tlen = startpos;
534 for (cp = (unsigned char *)buffer; (*cp != '\n') && (*cp); cp++) {
535 if (*cp == '\t') {
536 slen = tlen;
537 tlen += 8 - (tlen % 8);
538 if (tlen >= hlen) {
539 tlen = slen;
540 break;
541 }
542 nchars++;
543 continue;
544 }
545
546 if (isascii(*cp)) {
547 slen = tlen;
548 tlen++;
549 if (tlen >= hlen) {
550 tlen = slen;
551 break;
552 }
553 if (!isspace(*cp))
554 notspace = 1;
555 nchars++;
556 continue;
557 }
558
559 if ((len = mbtowc(&wc, (char *)cp, MB_LEN_MAX)) <= 0) {
560 slen = tlen;
561 tlen++;
562 if (tlen >= hlen) {
563 tlen = slen;
564 break;
565 }
566 notspace = 1;
567 nchars++;
568 continue;
569 }
570
571 if ((d_col = wcwidth(wc)) <= 0)
572 d_col = len;
573
574 slen = tlen;
575 tlen += d_col;
576 if (tlen > hlen) {
577 tlen = slen;
578 break;
579 }
580 notspace = 1;
581 cp += len - 1;
582 nchars += len;
583 }
584 return (notspace ? tlen : 0);
585 }
586
587 static void
putmid(int bflag)588 putmid(int bflag)
589 {
590 int i;
591
592 /*
593 * len1 set by getlen to the possibly truncated
594 * length of left side
595 * hlen is length of half line
596 */
597
598 midflg = 1;
599 if (bflag) {
600 for (i = 0; i < hlen - len1; i++)
601 (void) putc(' ', stdout);
602 }
603 (void) fputs(twoblanks, stdout);
604 (void) putc((int)change, stdout);
605 (void) fputs(twoblanks, stdout);
606 }
607
608 static void
error(char * s1,char * s2)609 error(char *s1, char *s2)
610 {
611 (void) fprintf(stderr, "sdiff: ");
612 (void) fprintf(stderr, s1, s2);
613 (void) putc('\n', stderr);
614 sremove();
615 exit(2);
616 }
617
618 static void
onintr(void)619 onintr(void)
620 {
621 sremove();
622 exit(rcode);
623 }
624
625 static void
sremove(void)626 sremove(void)
627 {
628 if (ltemp) {
629 (void) unlink(ltemp);
630 free(ltemp);
631 }
632 if (rtemp) {
633 (void) unlink(rtemp);
634 free(rtemp);
635 }
636 if (temp) {
637 (void) unlink(temp);
638 free(temp);
639 }
640 }
641
642 static void
cmdin(void)643 cmdin(void)
644 {
645 char *cp, *ename;
646 int notacc;
647
648 (void) fclose(left);
649 (void) fclose(right);
650 notacc = 1;
651 while (notacc) {
652 (void) putc(PROMPT, stdout);
653 if ((cp = fgets(inbuf, 10, stdin)) == NULL) {
654 (void) putc('\n', stdout);
655 break;
656 }
657 switch (*cp) {
658
659 case 's':
660 silent = 1;
661 break;
662
663 case 'v':
664 silent = 0;
665 break;
666
667 case 'q':
668 sremove();
669 exit(rcode);
670 /* NOTREACHED */
671 break;
672
673 case 'l':
674 cpp(ltemp, left, odes);
675 notacc = 0;
676 break;
677
678 case 'r':
679 cpp(rtemp, right, odes);
680 notacc = 0;
681 break;
682
683 case 'e':
684 while (*++cp == ' ')
685 ;
686 switch (*cp) {
687 case 'l':
688 case '<':
689 notacc = 0;
690 ename = ltemp;
691 edit(ename);
692 break;
693
694 case 'r':
695 case '>':
696 notacc = 0;
697 ename = rtemp;
698 edit(ename);
699 break;
700
701 case 'b':
702 case '|':
703 if ((tempdes = fopen(temp, "w")) == NULL)
704 error(gettext(
705 "Cannot open temp file %s"),
706 temp);
707 cpp(ltemp, left, tempdes);
708 cpp(rtemp, right, tempdes);
709 (void) fclose(tempdes);
710 notacc = 0;
711 ename = temp;
712 edit(ename);
713 break;
714
715 case '\n':
716 if ((tempdes = fopen(temp, "w")) == NULL)
717 error(gettext(
718 "Cannot open temp file %s"),
719 temp);
720 (void) fclose(tempdes);
721 notacc = 0;
722 ename = temp;
723 edit(ename);
724 break;
725 default:
726 (void) fprintf(stderr, gettext(
727 "Illegal command %s reenter\n"),
728 cp);
729 break;
730 }
731 if (notacc == 0)
732 cpp(ename, tempdes, odes);
733 break;
734
735 default:
736 (void) fprintf(stderr, gettext(
737 "Illegal command reenter\n"));
738 break;
739 }
740 }
741 }
742
743 static void
cpp(char * from,FILE * fromdes,FILE * todes)744 cpp(char *from, FILE *fromdes, FILE *todes)
745 {
746 char tempbuf[BMAX + 1];
747
748 if ((fromdes = fopen(from, "r")) == NULL)
749 error(gettext(
750 "cpp: Cannot open %s"), from);
751 while ((fgets(tempbuf, BMAX, fromdes) != NULL))
752 (void) fputs(tempbuf, todes);
753 (void) fclose(fromdes);
754 }
755
756 static void
edit(char * file)757 edit(char *file)
758 {
759 int i;
760 pid_t pid;
761 void (*oldintr)(int);
762
763 switch (pid = fork()) {
764 case (pid_t)-1:
765 error(gettext("Cannot fork"), NULL);
766 /* NOTREACHED */
767 break;
768 case (pid_t)0:
769 (void) execl("/usr/bin/ed", "ed", file, NULL);
770 }
771
772 oldintr = signal(SIGINT, SIG_IGN); /* ignore interrupts in ed */
773 while (pid != wait(&i))
774 ;
775 /* restore previous interrupt proc */
776 (void) signal(SIGINT, oldintr);
777 }
778
779 static char *
filename(char * pa1,char * pa2)780 filename(char *pa1, char *pa2)
781 {
782 int c;
783 char *a1, *b1, *a2;
784 struct stat stbuf;
785 a1 = pa1;
786 a2 = pa2;
787 if (stat(a1, &stbuf) != -1 && ((stbuf.st_mode&S_IFMT) == S_IFDIR)) {
788 b1 = pa1 = (char *)malloc(strlen(a1) + strlen(a2) + 2);
789 while (*b1++ = *a1++);
790 b1[-1] = '/';
791 a1 = b1;
792 while (*a1++ = *a2++)
793 if (*a2 && *a2 != '/' && a2[-1] == '/')
794 a1 = b1;
795 } else if (a1[0] == '-' && a1[1] == 0 && temp == 0) {
796 if (fstat(fileno(stdin), &stbuf) == -1)
797 error(gettext("Cannot process stdin"), NULL);
798 pa1 = temp = strdup("/tmp/sdiffXXXXXX");
799 if ((tempfd = mkstemp(temp)) == -1 ||
800 (tempdes = fdopen(tempfd, "w")) == NULL)
801 error(gettext("Cannot open/create temp %s"),
802 temp);
803 while ((c = getc(stdin)) != EOF)
804 (void) putc(c, tempdes);
805 (void) fclose(tempdes);
806 }
807 return (pa1);
808 }
809
810 /*
811 * like fgets, but reads upto and including a newline,
812 * the data is stored in a reusable dynamic buffer that grows to fit
813 * the largest line in the file, the buffer is NULL terminated
814 * returns a pointer to the dynamic buffer.
815 */
816 static char *
fgetline(FILE * fp)817 fgetline(FILE *fp)
818 {
819 static char *bp = NULL;
820 static int blen = 0;
821 int sl;
822
823 if (bp == NULL) {
824 /* allocate it for the first time */
825 bp = (char *)malloc(BUFSIZ);
826 if (bp == NULL)
827 error(gettext("fgetline: malloc failed"), NULL);
828 blen = BUFSIZ;
829 }
830
831 /* check for error or nothing read */
832 if (fgets(bp, blen, fp) == NULL)
833 return (NULL);
834
835 if (feof(fp))
836 return (bp);
837
838 while ((sl = strlen(bp)) == blen-1 && *(bp+blen-2) != '\n') {
839 /* still more data, grow the buffer */
840 blen *= 2;
841 bp = (char *)realloc(bp, blen);
842 if (bp == NULL)
843 error(gettext("fgetline: realloc failed"), NULL);
844 /* continue reading and add to end of buffer */
845 (void) fgets(bp+sl, blen-sl, fp);
846 }
847 return (bp);
848 }
849