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