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 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 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 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 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 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 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 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 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 619 onintr(void) 620 { 621 sremove(); 622 exit(rcode); 623 } 624 625 static 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 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 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 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 * 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 * 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