master xplshn/aruu / cmd / posix / diff.c
   1/* See LICENSE file for copyright and license details. */
   2#include "paths.h"
   3#include "util.h"
   4#include "wexec.h"
   5
   6#include <sys/stat.h>
   7#include <sys/wait.h>
   8
   9#include <ctype.h>
  10#include <dirent.h>
  11#include <errno.h>
  12#include <fcntl.h>
  13#include <fnmatch.h>
  14#include <getopt.h>
  15#include <limits.h>
  16#include <math.h>
  17#include <poll.h>
  18#include <regex.h>
  19#include <signal.h>
  20#include <stdint.h>
  21#include <stdio.h>
  22#include <stdlib.h>
  23#include <string.h>
  24#include <time.h>
  25#include <unistd.h>
  26
  27#ifndef __dead
  28#define __dead __attribute__((__noreturn__))
  29#endif
  30
  31#define d_status d_type
  32
  33/* output format options */
  34#define D_NORMAL     0
  35#define D_EDIT       -1
  36#define D_REVERSE    1
  37#define D_CONTEXT    2
  38#define D_UNIFIED    3
  39#define D_IFDEF      4
  40#define D_NREVERSE   5
  41#define D_BRIEF      6
  42#define D_GFORMAT    7
  43#define D_SIDEBYSIDE 8
  44
  45#define D_UNSET -2
  46
  47/* algorithms */
  48#define D_DIFFNONE     0
  49#define D_DIFFSTONE    1
  50#define D_DIFFMYERS    2
  51#define D_DIFFPATIENCE 3
  52
  53/* output flags */
  54#define D_HEADER 0x001
  55#define D_EMPTY1 0x002
  56#define D_EMPTY2 0x004
  57
  58/* command line flags */
  59#define D_FORCEASCII     0x008
  60#define D_FOLDBLANKS     0x010
  61#define D_MINIMAL        0x020
  62#define D_IGNORECASE     0x040
  63#define D_PROTOTYPE      0x080
  64#define D_EXPANDTABS     0x100
  65#define D_IGNOREBLANKS   0x200
  66#define D_STRIPCR        0x400
  67#define D_SKIPBLANKLINES 0x800
  68#define D_MATCHLAST      0x1000
  69
  70/* features supported by new algorithms */
  71#define D_NEWALGO_FLAGS (D_FORCEASCII | D_PROTOTYPE | D_IGNOREBLANKS)
  72
  73/* status values for print_status and diffreg return values */
  74#define D_SAME      0
  75#define D_DIFFER    1
  76#define D_BINARY    2
  77#define D_MISMATCH1 3
  78#define D_MISMATCH2 4
  79#define D_SKIPPED1  5
  80#define D_SKIPPED2  6
  81#define D_ERROR     7
  82
  83/* color options */
  84#define COLORFLAG_NEVER  0
  85#define COLORFLAG_AUTO   1
  86#define COLORFLAG_ALWAYS 2
  87
  88#define FUNCTION_CONTEXT_SIZE 55
  89
  90#ifndef roundup
  91#define roundup(x, y) ((((x) + ((y) - 1)) / (y)) * (y))
  92#endif
  93
  94struct Excludes {
  95  char            *pattern;
  96  struct Excludes *next;
  97};
  98
  99struct Cand {
 100  int x;
 101  int y;
 102  int pred;
 103};
 104
 105struct Line {
 106  int serial;
 107  int value;
 108};
 109
 110struct ContextVec {
 111  int a;
 112  int b;
 113  int c;
 114  int d;
 115};
 116
 117struct Pr {
 118  int   ostdout;
 119  pid_t cpid;
 120};
 121
 122struct Algorithm {
 123  const char *name;
 124  int         id;
 125};
 126
 127static struct Algorithm algorithms[] = {
 128    {"stone", D_DIFFSTONE}, {"myers", D_DIFFMYERS}, {"patience", D_DIFFPATIENCE}, {NULL, D_DIFFNONE}
 129};
 130
 131/* options */
 132#define OPTIONS "0123456789A:aBbC:cdD:efF:HhI:iL:lnNPpqrS:sTtU:uwW:X:x:y"
 133enum {
 134  OPT_TSIZE = CHAR_MAX + 1,
 135  OPT_STRIPCR,
 136  OPT_IGN_FN_CASE,
 137  OPT_NO_IGN_FN_CASE,
 138  OPT_NORMAL,
 139  OPT_HELP,
 140  OPT_HORIZON_LINES,
 141  OPT_CHANGED_GROUP_FORMAT,
 142  OPT_SUPPRESS_COMMON,
 143  OPT_COLOR,
 144  OPT_NO_DEREFERENCE,
 145  OPT_VERSION,
 146};
 147
 148static struct option longopts[] = {
 149    {"algorithm", required_argument, 0, 'A'},
 150    {"text", no_argument, 0, 'a'},
 151    {"ignore-space-change", no_argument, 0, 'b'},
 152    {"context", optional_argument, 0, 'C'},
 153    {"ifdef", required_argument, 0, 'D'},
 154    {"minimal", no_argument, 0, 'd'},
 155    {"ed", no_argument, 0, 'e'},
 156    {"forward-ed", no_argument, 0, 'f'},
 157    {"show-function-line", required_argument, 0, 'F'},
 158    {"speed-large-files", no_argument, NULL, 'H'},
 159    {"ignore-blank-lines", no_argument, 0, 'B'},
 160    {"ignore-matching-lines", required_argument, 0, 'I'},
 161    {"ignore-case", no_argument, 0, 'i'},
 162    {"paginate", no_argument, NULL, 'l'},
 163    {"label", required_argument, 0, 'L'},
 164    {"new-file", no_argument, 0, 'N'},
 165    {"rcs", no_argument, 0, 'n'},
 166    {"unidirectional-new-file", no_argument, 0, 'P'},
 167    {"show-c-function", no_argument, 0, 'p'},
 168    {"brief", no_argument, 0, 'q'},
 169    {"recursive", no_argument, 0, 'r'},
 170    {"report-identical-files", no_argument, 0, 's'},
 171    {"starting-file", required_argument, 0, 'S'},
 172    {"expand-tabs", no_argument, 0, 't'},
 173    {"initial-tab", no_argument, 0, 'T'},
 174    {"unified", optional_argument, 0, 'U'},
 175    {"ignore-all-space", no_argument, 0, 'w'},
 176    {"width", required_argument, 0, 'W'},
 177    {"exclude", required_argument, 0, 'x'},
 178    {"exclude-from", required_argument, 0, 'X'},
 179    {"side-by-side", no_argument, NULL, 'y'},
 180    {"ignore-file-name-case", no_argument, NULL, OPT_IGN_FN_CASE},
 181    {"help", no_argument, NULL, OPT_HELP},
 182    {"horizon-lines", required_argument, NULL, OPT_HORIZON_LINES},
 183    {"no-dereference", no_argument, NULL, OPT_NO_DEREFERENCE},
 184    {"no-ignore-file-name-case", no_argument, NULL, OPT_NO_IGN_FN_CASE},
 185    {"normal", no_argument, NULL, OPT_NORMAL},
 186    {"strip-trailing-cr", no_argument, NULL, OPT_STRIPCR},
 187    {"tabsize", required_argument, NULL, OPT_TSIZE},
 188    {"changed-group-format", required_argument, NULL, OPT_CHANGED_GROUP_FORMAT},
 189    {"suppress-common-lines", no_argument, NULL, OPT_SUPPRESS_COMMON},
 190    {"color", optional_argument, NULL, OPT_COLOR},
 191    {"version", no_argument, NULL, OPT_VERSION},
 192    {NULL, 0, 0, '\0'}
 193};
 194
 195static const char diff_version[] = "FreeBSD diff 20240307";
 196int               lflag, Nflag, Pflag, rflag, sflag, Tflag, cflag;
 197int               ignore_file_case, suppress_common, color, noderef;
 198static int        help = 0;
 199int               diff_format, diff_context, diff_algorithm, status;
 200int               diff_algorithm_set;
 201int               tabsize = 8, width = 130;
 202static int        colorflag = COLORFLAG_NEVER;
 203char             *start, *ifdefname, *diffargs, *label[2];
 204char             *ignore_pats, *most_recent_pat;
 205char             *group_format = NULL;
 206const char       *add_code, *del_code;
 207struct stat       stb1, stb2;
 208struct Excludes  *excludes_list;
 209regex_t           ignore_re, most_recent_re;
 210
 211enum Readhash { RH_BINARY, RH_OK, RH_EOF };
 212
 213static struct Line *file[2];
 214
 215static int *J;
 216static int *class;
 217static int         *klist;
 218static int         *member;
 219static int          clen;
 220static int          inifdef;
 221static size_t       len[2];
 222static size_t       pref, suff;
 223static size_t       slen[2];
 224static int          anychange;
 225static int          hw, lpad, rpad;
 226static int          edoffset;
 227static long        *ixnew;
 228static long        *ixold;
 229static struct Cand *clist;
 230static int          clistlen;
 231static struct Line *sfile[2];
 232static int (*chrtran)(int);
 233static struct ContextVec *context_vec_start;
 234static struct ContextVec *context_vec_end;
 235static struct ContextVec *context_vec_ptr;
 236static char               lastbuf[FUNCTION_CONTEXT_SIZE];
 237static int                lastline;
 238static int                lastmatchline;
 239
 240static int           sigpipe[2] = {-1, -1};
 241static struct pollfd poll_fd;
 242
 243static void  checked_regcomp(char const *, regex_t *);
 244static void  usage(void);
 245static void  conflicting_format(void);
 246static void  push_excludes(char *);
 247static void  push_ignore_pats(char *);
 248static void  read_excludes_file(char *);
 249static void  set_argstr(char **, char **);
 250static char *diff_splice(char *, char *);
 251static int   do_color(void);
 252static int   cup2low(int);
 253static int   clow2low(int);
 254static void  xasprintf(char **, const char *, ...);
 255
 256int  diffreg(char *, char *, int, int);
 257void diffdir(char *, char *, int);
 258void print_status(int, char *, char *, const char *);
 259
 260static int  selectfile(const struct dirent *);
 261static void diffit(struct dirent *, char *, size_t, struct dirent *, char *, size_t, int);
 262static void print_only(const char *, size_t, const char *);
 263
 264static FILE         *opentemp(const char *);
 265static void          output(char *, FILE *, char *, FILE *, int);
 266static void          check(FILE *, FILE *, int);
 267static void          range(int, int, const char *);
 268static void          uni_range(int, int);
 269static void          dump_context_vec(FILE *, FILE *, int);
 270static void          dump_unified_vec(FILE *, FILE *, int);
 271static int           prepare(int, FILE *, size_t, int);
 272static void          prune(void);
 273static void          equiv(struct Line *, int, struct Line *, int, int *);
 274static void          unravel(int);
 275static void          unsort(struct Line *, int, int *);
 276static void          change(char *, FILE *, char *, FILE *, int, int, int, int, int *);
 277static void          sort(struct Line *, int);
 278static void          print_header(const char *, const char *);
 279static void          print_space(int, int, int);
 280static int           ignoreline_pattern(char *);
 281static int           ignoreline(char *, int);
 282static int           asciifile(FILE *);
 283static int           fetch(long *, int, int, FILE *, int, int, int);
 284static int           newcand(int, int, int);
 285static int           search(int *, int, int);
 286static int           skipline(FILE *);
 287static int           stone(int *, int, int *, int *, int);
 288static enum Readhash readhash(FILE *, int, unsigned *);
 289static int           files_differ(FILE *, FILE *, int);
 290static char         *match_function(const long *, int, FILE *);
 291static char         *preadline(int, size_t, off_t);
 292
 293static void handle_sig(int);
 294struct Pr  *start_pr(char *, char *);
 295void        stop_pr(struct Pr *);
 296
 297static void
 298handle_sig(int signo)
 299{
 300  write(sigpipe[1], &signo, sizeof(signo));
 301}
 302
 303struct Pr *
 304start_pr(char *file1, char *file2)
 305{
 306  int        pfd[2];
 307  pid_t      pid;
 308  char      *header;
 309  struct Pr *pr;
 310
 311  pr = ecalloc(1, sizeof(*pr));
 312  xasprintf(&header, "%s %s %s", diffargs, file1, file2);
 313  signal(SIGPIPE, SIG_IGN);
 314  fflush(stdout);
 315  if (pipe(pfd) == -1)
 316    enprintf(2, "pipe");
 317  if (sigpipe[0] < 0) {
 318    if (pipe(sigpipe) == -1)
 319      enprintf(2, "pipe");
 320    if (fcntl(sigpipe[0], F_SETFD, FD_CLOEXEC) == -1)
 321      enprintf(2, "fcntl");
 322    if (fcntl(sigpipe[1], F_SETFD, FD_CLOEXEC) == -1)
 323      enprintf(2, "fcntl");
 324    if (signal(SIGCHLD, handle_sig) == SIG_ERR)
 325      enprintf(2, "signal");
 326    poll_fd.fd     = sigpipe[0];
 327    poll_fd.events = POLLIN;
 328  }
 329  poll_fd.revents = 0;
 330  switch ((pid = fork())) {
 331    case -1:
 332      status |= 2;
 333      free(header);
 334      enprintf(2, "no more processes");
 335      /* fallthrough */
 336    case 0:
 337      if (pfd[0] != STDIN_FILENO) {
 338        dup2(pfd[0], STDIN_FILENO);
 339        close(pfd[0]);
 340      }
 341      close(pfd[1]);
 342      {
 343        char *pr_argv[4];
 344        pr_argv[0] = "pr";
 345        pr_argv[1] = "-h";
 346        pr_argv[2] = header;
 347        pr_argv[3] = NULL;
 348        wexecvp_self("pr", pr_argv);
 349      }
 350      _exit(127);
 351    default:
 352      if (pfd[1] != STDOUT_FILENO) {
 353        pr->ostdout = dup(STDOUT_FILENO);
 354        dup2(pfd[1], STDOUT_FILENO);
 355        close(pfd[1]);
 356      }
 357      close(pfd[0]);
 358      free(header);
 359      pr->cpid = pid;
 360  }
 361  return pr;
 362}
 363
 364void
 365stop_pr(struct Pr *pr)
 366{
 367  int wstatus;
 368  int done = 0;
 369
 370  if (!pr)
 371    return;
 372
 373  fflush(stdout);
 374  if (pr->ostdout != STDOUT_FILENO) {
 375    close(STDOUT_FILENO);
 376    dup2(pr->ostdout, STDOUT_FILENO);
 377    close(pr->ostdout);
 378  }
 379  while (!done) {
 380    pid_t wpid;
 381    int   npe = poll(&poll_fd, 1, -1);
 382    if (npe == -1) {
 383      if (errno == EINTR)
 384        continue;
 385      enprintf(2, "poll");
 386    }
 387    if (poll_fd.revents != POLLIN)
 388      continue;
 389    if (read(poll_fd.fd, &npe, sizeof(npe)) < 0)
 390      enprintf(2, "read");
 391    while ((wpid = waitpid(-1, &wstatus, WNOHANG)) > 0) {
 392      if (wpid != pr->cpid)
 393        continue;
 394      if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) != 0)
 395        enprintf(2, "pr exited abnormally");
 396      else if (WIFSIGNALED(wstatus))
 397        enprintf(2, "pr killed by signal %d", WTERMSIG(wstatus));
 398      done = 1;
 399      break;
 400    }
 401  }
 402  free(pr);
 403}
 404
 405// ?man diff: differential file and directory comparator
 406// ?man arguments: file1 file2
 407// ?man synopsis: [-aBbdipTtw] [-c|-e|-f|-n|-q|-u|-y] [-A algo] [--brief]
 408// [--color=when] [--changed-group-format GFMT] [--ed] [--expand-tabs]
 409// [--forward-ed] [--ignore-all-space] [--ignore-case] [--ignore-space-change]
 410// [--initial-tab] [--minimal] [--no-dereference] [--no-ignore-file-name-case]
 411// [--normal] [--rcs] [--show-c-function] [--starting-file]
 412// [--speed-large-files] [--strip-trailing-cr] [--tabsize number] [--text] [-I
 413// pattern] [-F pattern] [-L label] file1 file2
 414int
 415main(int argc, char **argv)
 416{
 417  const char *errstr = NULL;
 418  char       *ep, **oargv;
 419  long        l;
 420  int         ch, dflags, lastch, gotstdin, prevoptind, newarg;
 421
 422  oargv              = argv;
 423  gotstdin           = 0;
 424  dflags             = 0;
 425  lastch             = '\0';
 426  prevoptind         = 1;
 427  newarg             = 1;
 428  diff_context       = 3;
 429  diff_format        = D_UNSET;
 430  diff_algorithm     = D_DIFFMYERS;
 431  diff_algorithm_set = 0;
 432#define FORMAT_MISMATCHED(type) (diff_format != D_UNSET && diff_format != (type))
 433  while ((ch = getopt_long(argc, argv, OPTIONS, longopts, NULL)) != -1) {
 434    // ?man -0: context length
 435    // ?man -1: context length
 436    // ?man -2: context length
 437    // ?man -3: context length
 438    // ?man -4: context length
 439    // ?man -5: context length
 440    // ?man -6: context length
 441    // ?man -7: context length
 442    // ?man -8: context length
 443    // ?man -9: context length
 444    switch (ch) {
 445      case '0':
 446      case '1':
 447      case '2':
 448      case '3':
 449      case '4':
 450      case '5':
 451      case '6':
 452      case '7':
 453      case '8':
 454      case '9':
 455        if (newarg)
 456          usage();
 457        else if (lastch == 'c' || lastch == 'u')
 458          diff_context = 0;
 459        else if (!isdigit(lastch) || diff_context > INT_MAX / 10)
 460          usage();
 461        diff_context = (diff_context * 10) + (ch - '0');
 462        break;
 463      // ?man -A:algo: algorithm (stone, myers, patience)
 464      case 'A':
 465        diff_algorithm = D_DIFFNONE;
 466        for (struct Algorithm *a = algorithms; a->name; a++) {
 467          if (strcasecmp(optarg, a->name) == 0) {
 468            diff_algorithm     = a->id;
 469            diff_algorithm_set = 1;
 470            break;
 471          }
 472        }
 473        if (diff_algorithm == D_DIFFNONE) {
 474          printf("unknown algorithm: %s\n", optarg);
 475          usage();
 476        }
 477        break;
 478      // ?man -a: force text mode
 479      case 'a':
 480        dflags |= D_FORCEASCII;
 481        break;
 482      // ?man -b: ignore space changes
 483      case 'b':
 484        dflags |= D_FOLDBLANKS;
 485        break;
 486      // ?man -C:lines: context format
 487      case 'C':
 488      // ?man -c: context format
 489      case 'c':
 490        if (FORMAT_MISMATCHED(D_CONTEXT))
 491          conflicting_format();
 492        cflag       = 1;
 493        diff_format = D_CONTEXT;
 494        if (optarg != NULL) {
 495          l = strtol(optarg, &ep, 10);
 496          if (*ep != '\0' || l < 0 || l >= INT_MAX)
 497            usage();
 498          diff_context = (int)l;
 499        }
 500        break;
 501      // ?man -d: minimal diff search
 502      case 'd':
 503        dflags |= D_MINIMAL;
 504        break;
 505      // ?man -D:name: ifdef format
 506      case 'D':
 507        if (FORMAT_MISMATCHED(D_IFDEF))
 508          conflicting_format();
 509        diff_format = D_IFDEF;
 510        ifdefname   = optarg;
 511        break;
 512      // ?man -e: ed script format
 513      case 'e':
 514        if (FORMAT_MISMATCHED(D_EDIT))
 515          conflicting_format();
 516        diff_format = D_EDIT;
 517        break;
 518      // ?man -f: forward ed script format
 519      case 'f':
 520        if (FORMAT_MISMATCHED(D_REVERSE))
 521          conflicting_format();
 522        diff_format = D_REVERSE;
 523        break;
 524      // ?man -H: speed up large files (stub)
 525      case 'H':
 526        break;
 527      // ?man -h: backward compatibility (stub)
 528      case 'h':
 529        break;
 530      // ?man -B: ignore blank lines
 531      case 'B':
 532        dflags |= D_SKIPBLANKLINES;
 533        break;
 534      // ?man -F:pat: show function matching pattern
 535      case 'F':
 536        if (dflags & D_PROTOTYPE)
 537          conflicting_format();
 538        dflags |= D_MATCHLAST;
 539        most_recent_pat = estrdup(optarg);
 540        break;
 541      // ?man -I:pat: ignore pattern matching lines
 542      case 'I':
 543        push_ignore_pats(optarg);
 544        break;
 545      // ?man -i: ignore case
 546      case 'i':
 547        dflags |= D_IGNORECASE;
 548        break;
 549      // ?man -L:label: custom label
 550      case 'L':
 551        if (label[0] == NULL)
 552          label[0] = optarg;
 553        else if (label[1] == NULL)
 554          label[1] = optarg;
 555        else
 556          usage();
 557        break;
 558      // ?man -l: paginate output using pr
 559      case 'l':
 560        lflag = 1;
 561        break;
 562      // ?man -N: treat missing files as empty
 563      case 'N':
 564        Nflag = 1;
 565        break;
 566      // ?man -n: rcs format
 567      case 'n':
 568        if (FORMAT_MISMATCHED(D_NREVERSE))
 569          conflicting_format();
 570        diff_format = D_NREVERSE;
 571        break;
 572      // ?man -p: show C prototype context
 573      case 'p':
 574        if (dflags & D_MATCHLAST)
 575          conflicting_format();
 576        dflags |= D_PROTOTYPE;
 577        break;
 578      // ?man -P: treat missing destination files as empty
 579      case 'P':
 580        Pflag = 1;
 581        break;
 582      // ?man -r: recursive directory comparison
 583      case 'r':
 584        rflag = 1;
 585        break;
 586      // ?man -q: brief output (differ / same only)
 587      case 'q':
 588        if (FORMAT_MISMATCHED(D_BRIEF))
 589          conflicting_format();
 590        diff_format = D_BRIEF;
 591        break;
 592      // ?man -S:name: starting file in directory comparison
 593      case 'S':
 594        start = optarg;
 595        break;
 596      // ?man -s: report identical files
 597      case 's':
 598        sflag = 1;
 599        break;
 600      // ?man -T: initial tab alignment
 601      case 'T':
 602        Tflag = 1;
 603        break;
 604      // ?man -t: expand tabs in output
 605      case 't':
 606        dflags |= D_EXPANDTABS;
 607        break;
 608      // ?man -U:lines: unified context format
 609      case 'U':
 610      // ?man -u: unified context format
 611      case 'u':
 612        if (FORMAT_MISMATCHED(D_UNIFIED))
 613          conflicting_format();
 614        diff_format = D_UNIFIED;
 615        if (optarg != NULL) {
 616          l = strtol(optarg, &ep, 10);
 617          if (*ep != '\0' || l < 0 || l >= INT_MAX)
 618            usage();
 619          diff_context = (int)l;
 620        }
 621        break;
 622      // ?man -w: ignore all whitespace
 623      case 'w':
 624        dflags |= D_IGNOREBLANKS;
 625        break;
 626      // ?man -W:cols: column width for side-by-side
 627      case 'W':
 628        width = (int)strtonum(optarg, 1, INT_MAX, &errstr);
 629        if (errstr) {
 630          weprintf("invalid width argument");
 631          usage();
 632        }
 633        break;
 634      // ?man -X:file: exclude patterns file
 635      case 'X':
 636        read_excludes_file(optarg);
 637        break;
 638      // ?man -x:pat: exclude pattern
 639      case 'x':
 640        push_excludes(optarg);
 641        break;
 642      // ?man -y: side by side format
 643      case 'y':
 644        if (FORMAT_MISMATCHED(D_SIDEBYSIDE))
 645          conflicting_format();
 646        diff_format = D_SIDEBYSIDE;
 647        break;
 648      case OPT_CHANGED_GROUP_FORMAT:
 649        if (FORMAT_MISMATCHED(D_GFORMAT))
 650          conflicting_format();
 651        diff_format  = D_GFORMAT;
 652        group_format = optarg;
 653        break;
 654      case OPT_HELP:
 655        help = 1;
 656        usage();
 657        break;
 658      case OPT_HORIZON_LINES:
 659        break;
 660      case OPT_IGN_FN_CASE:
 661        ignore_file_case = 1;
 662        break;
 663      case OPT_NO_IGN_FN_CASE:
 664        ignore_file_case = 0;
 665        break;
 666      case OPT_NORMAL:
 667        if (FORMAT_MISMATCHED(D_NORMAL))
 668          conflicting_format();
 669        diff_format = D_NORMAL;
 670        break;
 671      case OPT_TSIZE:
 672        tabsize = (int)strtonum(optarg, 1, INT_MAX, &errstr);
 673        if (errstr) {
 674          weprintf("invalid tabsize argument");
 675          usage();
 676        }
 677        break;
 678      case OPT_STRIPCR:
 679        dflags |= D_STRIPCR;
 680        break;
 681      case OPT_SUPPRESS_COMMON:
 682        suppress_common = 1;
 683        break;
 684      case OPT_COLOR:
 685        if (optarg == NULL || strncmp(optarg, "auto", 4) == 0)
 686          colorflag = COLORFLAG_AUTO;
 687        else if (strncmp(optarg, "always", 6) == 0)
 688          colorflag = COLORFLAG_ALWAYS;
 689        else if (strncmp(optarg, "never", 5) == 0)
 690          colorflag = COLORFLAG_NEVER;
 691        else
 692          enprintf(2, "unsupported color option %s", optarg);
 693        break;
 694      case OPT_NO_DEREFERENCE:
 695        noderef = 1;
 696        break;
 697      case OPT_VERSION:
 698        printf("%s\n", diff_version);
 699        exit(0);
 700      default:
 701        usage();
 702        break;
 703    }
 704    lastch     = ch;
 705    newarg     = optind != prevoptind;
 706    prevoptind = optind;
 707  }
 708  if (diff_format == D_UNSET && (dflags & D_PROTOTYPE) != 0)
 709    diff_format = D_CONTEXT;
 710  if (diff_format == D_UNSET)
 711    diff_format = D_NORMAL;
 712  argc -= optind;
 713  argv += optind;
 714
 715  if (do_color()) {
 716    char       *p;
 717    const char *env;
 718
 719    color    = 1;
 720    add_code = "32";
 721    del_code = "31";
 722    env      = getenv("DIFFCOLORS");
 723    if (env != NULL && *env != '\0' && (p = estrdup(env))) {
 724      add_code = p;
 725      strsep(&p, ":");
 726      if (p != NULL)
 727        del_code = p;
 728    }
 729  }
 730
 731  if (argc != 2)
 732    usage();
 733  checked_regcomp(ignore_pats, &ignore_re);
 734  checked_regcomp(most_recent_pat, &most_recent_re);
 735  if (strcmp(argv[0], "-") == 0) {
 736    fstat(STDIN_FILENO, &stb1);
 737    gotstdin = 1;
 738  } else if (stat(argv[0], &stb1) != 0) {
 739    if (!Nflag || errno != ENOENT)
 740      enprintf(2, "%s", argv[0]);
 741    dflags |= D_EMPTY1;
 742    memset(&stb1, 0, sizeof(struct stat));
 743  }
 744
 745  if (strcmp(argv[1], "-") == 0) {
 746    fstat(STDIN_FILENO, &stb2);
 747    gotstdin = 1;
 748  } else if (stat(argv[1], &stb2) != 0) {
 749    if (!Nflag || errno != ENOENT)
 750      enprintf(2, "%s", argv[1]);
 751    dflags |= D_EMPTY2;
 752    memset(&stb2, 0, sizeof(stb2));
 753    stb2.st_mode = stb1.st_mode;
 754  }
 755
 756  if (dflags & D_EMPTY1 && dflags & D_EMPTY2) {
 757    weprintf("%s", argv[0]);
 758    weprintf("%s", argv[1]);
 759    exit(2);
 760  }
 761
 762  if (stb1.st_mode == 0)
 763    stb1.st_mode = stb2.st_mode;
 764
 765  if (gotstdin && (S_ISDIR(stb1.st_mode) || S_ISDIR(stb2.st_mode)))
 766    enprintf(2, "cant compare - to directory");
 767  set_argstr(oargv, argv);
 768  if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) {
 769    if (diff_format == D_IFDEF)
 770      enprintf(2, "-D option not supported with directories");
 771    diffdir(argv[0], argv[1], dflags);
 772  } else {
 773    if (S_ISDIR(stb1.st_mode)) {
 774      argv[0] = diff_splice(argv[0], argv[1]);
 775      if (stat(argv[0], &stb1) == -1)
 776        enprintf(2, "%s", argv[0]);
 777    }
 778    if (S_ISDIR(stb2.st_mode)) {
 779      argv[1] = diff_splice(argv[1], argv[0]);
 780      if (stat(argv[1], &stb2) == -1)
 781        enprintf(2, "%s", argv[1]);
 782    }
 783    print_status(diffreg(argv[0], argv[1], dflags, 1), argv[0], argv[1], "");
 784  }
 785  if (fflush(stdout) != 0)
 786    enprintf(2, "stdout");
 787  exit(status);
 788}
 789
 790static void
 791checked_regcomp(char const *pattern, regex_t *comp)
 792{
 793  char buf[BUFSIZ];
 794  int  error;
 795
 796  if (pattern == NULL)
 797    return;
 798
 799  error = regcomp(comp, pattern, REG_NEWLINE | REG_EXTENDED);
 800  if (error != 0) {
 801    regerror(error, comp, buf, sizeof(buf));
 802    if (*pattern != '\0')
 803      enprintf(2, "%s: %s", pattern, buf);
 804    else
 805      enprintf(2, "%s", buf);
 806  }
 807}
 808
 809static void
 810set_argstr(char **av, char **ave)
 811{
 812  size_t argsize;
 813  char **ap;
 814
 815  argsize = strlen("diff") + 1;
 816  for (ap = av + 1; ap < ave; ap++) {
 817    if (strcmp(*ap, "--") != 0)
 818      argsize += 1 + strlen(*ap);
 819  }
 820  diffargs = emalloc(argsize);
 821  strlcpy(diffargs, "diff", argsize);
 822  for (ap = av + 1; ap < ave; ap++) {
 823    if (strcmp(*ap, "--") != 0) {
 824      strlcat(diffargs, " ", argsize);
 825      strlcat(diffargs, *ap, argsize);
 826    }
 827  }
 828}
 829
 830static void
 831read_excludes_file(char *file)
 832{
 833  FILE   *fp;
 834  char   *pattern = NULL;
 835  size_t  blen    = 0;
 836  ssize_t len;
 837
 838  if (strcmp(file, "-") == 0)
 839    fp = stdin;
 840  else if ((fp = fopen(file, "r")) == NULL)
 841    enprintf(2, "%s", file);
 842  while ((len = getline(&pattern, &blen, fp)) >= 0) {
 843    if ((len > 0) && (pattern[len - 1] == '\n'))
 844      pattern[len - 1] = '\0';
 845    push_excludes(pattern);
 846    pattern = NULL;
 847    blen    = 0;
 848  }
 849  free(pattern);
 850  if (strcmp(file, "-") != 0)
 851    fclose(fp);
 852}
 853
 854static void
 855push_excludes(char *pattern)
 856{
 857  struct Excludes *entry;
 858
 859  entry          = emalloc(sizeof(*entry));
 860  entry->pattern = pattern;
 861  entry->next    = excludes_list;
 862  excludes_list  = entry;
 863}
 864
 865static void
 866push_ignore_pats(char *pattern)
 867{
 868  size_t len;
 869
 870  if (ignore_pats == NULL)
 871    ignore_pats = estrdup(pattern);
 872  else {
 873    len         = strlen(ignore_pats) + strlen(pattern) + 2;
 874    ignore_pats = ereallocarray(ignore_pats, 1, len);
 875    strlcat(ignore_pats, "|", len);
 876    strlcat(ignore_pats, pattern, len);
 877  }
 878}
 879
 880void
 881print_status(int val, char *path1, char *path2, const char *entry)
 882{
 883  if (label[0] != NULL)
 884    path1 = label[0];
 885  if (label[1] != NULL)
 886    path2 = label[1];
 887
 888  switch (val) {
 889    case D_BINARY:
 890      printf("Binary files %s%s and %s%s differ\n", path1, entry, path2, entry);
 891      break;
 892    case D_DIFFER:
 893      if (diff_format == D_BRIEF)
 894        printf("Files %s%s and %s%s differ\n", path1, entry, path2, entry);
 895      break;
 896    case D_SAME:
 897      if (sflag)
 898        printf("Files %s%s and %s%s are identical\n", path1, entry, path2, entry);
 899      break;
 900    case D_MISMATCH1:
 901      printf("File %s%s is directory, %s%s is file\n", path1, entry, path2, entry);
 902      break;
 903    case D_MISMATCH2:
 904      printf("File %s%s is file, %s%s is directory\n", path1, entry, path2, entry);
 905      break;
 906    case D_SKIPPED1:
 907      printf("File %s%s is not regular file or directory\n", path1, entry);
 908      break;
 909    case D_SKIPPED2:
 910      printf("File %s%s is not regular file or directory\n", path2, entry);
 911      break;
 912  }
 913}
 914
 915static void
 916usage(void)
 917{
 918  fprintf(
 919      stderr,
 920      "usage: diff [-aBbdipTtw] [-c|-e|-f|-n|-q|-u|-y] [-A algo] "
 921      "[-I pattern] [-F pattern] [-L label] file1 file2\n"
 922  );
 923  exit(2);
 924}
 925
 926static void
 927conflicting_format(void)
 928{
 929  enprintf(2, "conflicting output format options");
 930}
 931
 932static char *
 933diff_splice(char *dir, char *file)
 934{
 935  char *p, *res;
 936
 937  p = strrchr(file, '/');
 938  p = p ? p + 1 : file;
 939  xasprintf(&res, "%s/%s", dir, p);
 940  return res;
 941}
 942
 943static void
 944xasprintf(char **strp, const char *fmt, ...)
 945{
 946  va_list ap;
 947  int     r;
 948
 949  va_start(ap, fmt);
 950  r = vasprintf(strp, fmt, ap);
 951  va_end(ap);
 952  if (r == -1)
 953    enprintf(2, "asprintf");
 954}
 955
 956static int
 957cup2low(int c)
 958{
 959  return tolower(c);
 960}
 961
 962static int
 963clow2low(int c)
 964{
 965  return c;
 966}
 967
 968static int
 969do_color(void)
 970{
 971  if (colorflag == COLORFLAG_ALWAYS)
 972    return 1;
 973  if (colorflag == COLORFLAG_NEVER)
 974    return 0;
 975  if (isatty(STDOUT_FILENO)) {
 976    char *term = getenv("COLORTERM");
 977    if (term && *term != '\0')
 978      return 1;
 979  }
 980  return 0;
 981}
 982
 983void
 984diffdir(char *p1, char *p2, int flags)
 985{
 986  struct dirent *dent1, **dp1, **edp1, **dirp1 = NULL;
 987  struct dirent *dent2, **dp2, **edp2, **dirp2 = NULL;
 988  size_t         dirlen1, dirlen2;
 989  char           path1[PATH_MAX], path2[PATH_MAX];
 990  int            pos;
 991
 992  edp1 = edp2 = NULL;
 993  dirlen1     = strlcpy(path1, *p1 ? p1 : ".", sizeof(path1));
 994  if (dirlen1 >= sizeof(path1) - 1) {
 995    errno = ENAMETOOLONG;
 996    weprintf("%s", p1);
 997    status |= 2;
 998    return;
 999  }
1000  if (path1[dirlen1 - 1] != '/') {
1001    path1[dirlen1++] = '/';
1002    path1[dirlen1]   = '\0';
1003  }
1004  dirlen2 = strlcpy(path2, *p2 ? p2 : ".", sizeof(path2));
1005  if (dirlen2 >= sizeof(path2) - 1) {
1006    errno = ENAMETOOLONG;
1007    weprintf("%s", p2);
1008    status |= 2;
1009    return;
1010  }
1011  if (path2[dirlen2 - 1] != '/') {
1012    path2[dirlen2++] = '/';
1013    path2[dirlen2]   = '\0';
1014  }
1015
1016  pos = scandir(path1, &dirp1, selectfile, alphasort);
1017  if (pos == -1) {
1018    if (errno == ENOENT && (Nflag || Pflag))
1019      pos = 0;
1020    else {
1021      weprintf("%s", path1);
1022      goto closem;
1023    }
1024  }
1025  dp1  = dirp1;
1026  edp1 = dirp1 + pos;
1027
1028  pos = scandir(path2, &dirp2, selectfile, alphasort);
1029  if (pos == -1) {
1030    if (errno == ENOENT && Nflag)
1031      pos = 0;
1032    else {
1033      weprintf("%s", path2);
1034      goto closem;
1035    }
1036  }
1037  dp2  = dirp2;
1038  edp2 = dirp2 + pos;
1039
1040  if (start != NULL) {
1041    while (dp1 != edp1 && strcmp((*dp1)->d_name, start) < 0)
1042      dp1++;
1043    while (dp2 != edp2 && strcmp((*dp2)->d_name, start) < 0)
1044      dp2++;
1045  }
1046
1047  while (dp1 != edp1 || dp2 != edp2) {
1048    dent1 = dp1 != edp1 ? *dp1 : NULL;
1049    dent2 = dp2 != edp2 ? *dp2 : NULL;
1050
1051    pos = dent1 == NULL      ? 1
1052          : dent2 == NULL    ? -1
1053          : ignore_file_case ? strcasecmp(dent1->d_name, dent2->d_name)
1054                             : strcmp(dent1->d_name, dent2->d_name);
1055    if (pos == 0) {
1056      diffit(dent1, path1, dirlen1, dent2, path2, dirlen2, flags);
1057      dp1++;
1058      dp2++;
1059    } else if (pos < 0) {
1060      if (Nflag)
1061        diffit(dent1, path1, dirlen1, dent2, path2, dirlen2, flags);
1062      else {
1063        print_only(path1, dirlen1, dent1->d_name);
1064        status |= 1;
1065      }
1066      dp1++;
1067    } else {
1068      if (Nflag || Pflag)
1069        diffit(dent2, path1, dirlen1, dent1, path2, dirlen2, flags);
1070      else {
1071        print_only(path2, dirlen2, dent2->d_name);
1072        status |= 1;
1073      }
1074      dp2++;
1075    }
1076  }
1077
1078closem:
1079  if (dirp1 != NULL) {
1080    for (dp1 = dirp1; dp1 < edp1; dp1++)
1081      free(*dp1);
1082    free(dirp1);
1083  }
1084  if (dirp2 != NULL) {
1085    for (dp2 = dirp2; dp2 < edp2; dp2++)
1086      free(*dp2);
1087    free(dirp2);
1088  }
1089}
1090
1091static void
1092diffit(
1093    struct dirent *dp,
1094    char          *path1,
1095    size_t         plen1,
1096    struct dirent *dp2,
1097    char          *path2,
1098    size_t         plen2,
1099    int            flags
1100)
1101{
1102  flags |= D_HEADER;
1103  strlcpy(path1 + plen1, dp->d_name, PATH_MAX - plen1);
1104
1105  if (ignore_file_case && strcasecmp(dp->d_name, dp2->d_name) == 0)
1106    strlcpy(path2 + plen2, dp2->d_name, PATH_MAX - plen2);
1107  else
1108    strlcpy(path2 + plen2, dp->d_name, PATH_MAX - plen2);
1109
1110  if (noderef) {
1111    if (lstat(path1, &stb1) != 0) {
1112      if (!(Nflag || Pflag) || errno != ENOENT) {
1113        weprintf("%s", path1);
1114        return;
1115      }
1116      flags |= D_EMPTY1;
1117      memset(&stb1, 0, sizeof(stb1));
1118    }
1119
1120    if (lstat(path2, &stb2) != 0) {
1121      if (!Nflag || errno != ENOENT) {
1122        weprintf("%s", path2);
1123        return;
1124      }
1125      flags |= D_EMPTY2;
1126      memset(&stb2, 0, sizeof(stb2));
1127      stb2.st_mode = stb1.st_mode;
1128    }
1129    if (stb1.st_mode == 0)
1130      stb1.st_mode = stb2.st_mode;
1131    if (S_ISLNK(stb1.st_mode) || S_ISLNK(stb2.st_mode)) {
1132      if (S_ISLNK(stb1.st_mode) && S_ISLNK(stb2.st_mode)) {
1133        char    buf1[PATH_MAX];
1134        char    buf2[PATH_MAX];
1135        ssize_t len1;
1136        ssize_t len2;
1137
1138        len1 = readlink(path1, buf1, sizeof(buf1));
1139        len2 = readlink(path2, buf2, sizeof(buf2));
1140        if (len1 < 0 || len2 < 0) {
1141          perror("reading links");
1142          return;
1143        }
1144        buf1[len1] = '\0';
1145        buf2[len2] = '\0';
1146        if (len1 != len2 || strncmp(buf1, buf2, len1) != 0) {
1147          printf(
1148              "Symbolic links %s and %s "
1149              "differ\n",
1150              path1,
1151              path2
1152          );
1153          status |= 1;
1154        }
1155        return;
1156      }
1157      printf(
1158          "File %s is a %s while file %s is a %s\n",
1159          path1,
1160          S_ISLNK(stb1.st_mode)   ? "symbolic link"
1161          : S_ISDIR(stb1.st_mode) ? "directory"
1162                                  : "file",
1163          path2,
1164          S_ISLNK(stb2.st_mode)   ? "symbolic link"
1165          : S_ISDIR(stb2.st_mode) ? "directory"
1166                                  : "file"
1167      );
1168      status |= 1;
1169      return;
1170    }
1171  } else {
1172    if (stat(path1, &stb1) != 0) {
1173      if (!(Nflag || Pflag) || errno != ENOENT) {
1174        weprintf("%s", path1);
1175        return;
1176      }
1177      flags |= D_EMPTY1;
1178      memset(&stb1, 0, sizeof(stb1));
1179    }
1180
1181    if (stat(path2, &stb2) != 0) {
1182      if (!Nflag || errno != ENOENT) {
1183        weprintf("%s", path2);
1184        return;
1185      }
1186      flags |= D_EMPTY2;
1187      memset(&stb2, 0, sizeof(stb2));
1188      stb2.st_mode = stb1.st_mode;
1189    }
1190    if (stb1.st_mode == 0)
1191      stb1.st_mode = stb2.st_mode;
1192  }
1193  if (S_ISDIR(stb1.st_mode) && S_ISDIR(stb2.st_mode)) {
1194    if (rflag)
1195      diffdir(path1, path2, flags);
1196    else
1197      printf("Common subdirectories: %s and %s\n", path1, path2);
1198    return;
1199  }
1200  if (!S_ISREG(stb1.st_mode) && !S_ISDIR(stb1.st_mode))
1201    dp->d_status = D_SKIPPED1;
1202  else if (!S_ISREG(stb2.st_mode) && !S_ISDIR(stb2.st_mode))
1203    dp->d_status = D_SKIPPED2;
1204  else
1205    dp->d_status = diffreg(path1, path2, flags, 0);
1206  print_status(dp->d_status, path1, path2, "");
1207}
1208
1209static int
1210selectfile(const struct dirent *dp)
1211{
1212  struct Excludes *excl;
1213
1214  if (dp->d_fileno == 0)
1215    return 0;
1216
1217  if (dp->d_name[0] == '.'
1218      && (dp->d_name[1] == '\0' || (dp->d_name[1] == '.' && dp->d_name[2] == '\0')))
1219    return 0;
1220
1221  for (excl = excludes_list; excl != NULL; excl = excl->next)
1222    if (fnmatch(excl->pattern, dp->d_name, FNM_PATHNAME) == 0)
1223      return 0;
1224
1225  return 1;
1226}
1227
1228void
1229print_only(const char *path, size_t dirlen, const char *entry)
1230{
1231  if (dirlen > 1)
1232    dirlen--;
1233  printf("Only in %.*s: %s\n", (int)dirlen, path, entry);
1234}
1235
1236int
1237diffreg(char *file1, char *file2, int flags, int capsicum)
1238{
1239  FILE      *f1, *f2;
1240  int        i, rval;
1241  struct Pr *pr = NULL;
1242
1243  f1 = f2       = NULL;
1244  rval          = D_SAME;
1245  anychange     = 0;
1246  lastline      = 0;
1247  lastmatchline = 0;
1248
1249  if (diff_format == D_SIDEBYSIDE) {
1250    if (flags & D_EXPANDTABS) {
1251      if (width > 3)
1252        hw = (width - 3) / 2;
1253      else
1254        hw = 0;
1255    } else if (width <= 3 || width <= tabsize) {
1256      hw = 0;
1257    } else {
1258      hw = (width - 3) / 2;
1259      while (hw > 0 && roundup(hw + 3, tabsize) + hw > width)
1260        hw--;
1261      if (width - (roundup(hw + 3, tabsize) + hw) < tabsize)
1262        width = roundup(hw + 3, tabsize) + hw;
1263    }
1264    lpad = (width - hw * 2 - 1) / 2;
1265    rpad = (width - hw * 2 - 1) - lpad;
1266  }
1267
1268  if (flags & D_IGNORECASE)
1269    chrtran = cup2low;
1270  else
1271    chrtran = clow2low;
1272  if (S_ISDIR(stb1.st_mode) != S_ISDIR(stb2.st_mode))
1273    return S_ISDIR(stb1.st_mode) ? D_MISMATCH1 : D_MISMATCH2;
1274  if (strcmp(file1, "-") == 0 && strcmp(file2, "-") == 0)
1275    goto closem;
1276
1277  if (flags & D_EMPTY1)
1278    f1 = fopen(ARUU_PATH_DEVNULL, "r");
1279  else {
1280    if (!S_ISREG(stb1.st_mode)) {
1281      if ((f1 = opentemp(file1)) == NULL || fstat(fileno(f1), &stb1) == -1) {
1282        weprintf("%s", file1);
1283        rval = D_ERROR;
1284        status |= 2;
1285        goto closem;
1286      }
1287    } else if (strcmp(file1, "-") == 0)
1288      f1 = stdin;
1289    else
1290      f1 = fopen(file1, "r");
1291  }
1292  if (f1 == NULL) {
1293    weprintf("%s", file1);
1294    rval = D_ERROR;
1295    status |= 2;
1296    goto closem;
1297  }
1298
1299  if (flags & D_EMPTY2)
1300    f2 = fopen(ARUU_PATH_DEVNULL, "r");
1301  else {
1302    if (!S_ISREG(stb2.st_mode)) {
1303      if ((f2 = opentemp(file2)) == NULL || fstat(fileno(f2), &stb2) == -1) {
1304        weprintf("%s", file2);
1305        rval = D_ERROR;
1306        status |= 2;
1307        goto closem;
1308      }
1309    } else if (strcmp(file2, "-") == 0)
1310      f2 = stdin;
1311    else
1312      f2 = fopen(file2, "r");
1313  }
1314  if (f2 == NULL) {
1315    weprintf("%s", file2);
1316    rval = D_ERROR;
1317    status |= 2;
1318    goto closem;
1319  }
1320
1321  if (lflag)
1322    pr = start_pr(file1, file2);
1323
1324  (void)capsicum;
1325
1326  switch (files_differ(f1, f2, flags)) {
1327    case 0:
1328      goto closem;
1329    case 1:
1330      break;
1331    default:
1332      rval = D_ERROR;
1333      status |= 2;
1334      goto closem;
1335  }
1336
1337  if (diff_format == D_BRIEF && ignore_pats == NULL
1338      && (flags & (D_FOLDBLANKS | D_IGNOREBLANKS | D_IGNORECASE | D_SKIPBLANKLINES | D_STRIPCR))
1339             == 0) {
1340    rval = D_DIFFER;
1341    status |= 1;
1342    goto closem;
1343  }
1344  if ((flags & D_FORCEASCII) != 0) {
1345    prepare(0, f1, stb1.st_size, flags);
1346    prepare(1, f2, stb2.st_size, flags);
1347  } else if (
1348      !asciifile(f1) || !asciifile(f2) || !prepare(0, f1, stb1.st_size, flags)
1349      || !prepare(1, f2, stb2.st_size, flags)
1350  ) {
1351    rval = D_BINARY;
1352    status |= 1;
1353    goto closem;
1354  }
1355  if (len[0] > INT_MAX - 2)
1356    errno = EFBIG, enprintf(1, "%s", file1);
1357  if (len[1] > INT_MAX - 2)
1358    errno = EFBIG, enprintf(1, "%s", file2);
1359
1360  prune();
1361  sort(sfile[0], slen[0]);
1362  sort(sfile[1], slen[1]);
1363
1364  member = (int *)file[1];
1365  equiv(sfile[0], slen[0], sfile[1], slen[1], member);
1366  member = ereallocarray(member, slen[1] + 2, sizeof(*member));
1367
1368  class = (int *)file[0];
1369  unsort(sfile[0], slen[0], class);
1370  class = ereallocarray(class, slen[0] + 2, sizeof(*class));
1371
1372  klist    = ecalloc(slen[0] + 2, sizeof(*klist));
1373  clen     = 0;
1374  clistlen = 100;
1375  clist    = ecalloc(clistlen, sizeof(*clist));
1376  i        = stone(class, slen[0], member, klist, flags);
1377  free(member);
1378  free(class);
1379
1380  J = ereallocarray(J, len[0] + 2, sizeof(*J));
1381  unravel(klist[i]);
1382  free(clist);
1383  free(klist);
1384
1385  ixold = ereallocarray(ixold, len[0] + 2, sizeof(*ixold));
1386  ixnew = ereallocarray(ixnew, len[1] + 2, sizeof(*ixnew));
1387  check(f1, f2, flags);
1388  output(file1, f1, file2, f2, flags);
1389
1390closem:
1391  if (pr != NULL)
1392    stop_pr(pr);
1393  if (anychange) {
1394    status |= 1;
1395    if (rval == D_SAME)
1396      rval = D_DIFFER;
1397  }
1398  if (f1 != NULL && f1 != stdin)
1399    fclose(f1);
1400  if (f2 != NULL && f2 != stdin)
1401    fclose(f2);
1402
1403  return rval;
1404}
1405
1406static int
1407files_differ(FILE *f1, FILE *f2, int flags)
1408{
1409  char   buf1[BUFSIZ], buf2[BUFSIZ];
1410  size_t i, j;
1411
1412  if ((flags & (D_EMPTY1 | D_EMPTY2)) || stb1.st_size != stb2.st_size
1413      || (stb1.st_mode & S_IFMT) != (stb2.st_mode & S_IFMT))
1414    return 1;
1415
1416  if (stb1.st_dev == stb2.st_dev && stb1.st_ino == stb2.st_ino)
1417    return 0;
1418
1419  for (;;) {
1420    i = fread(buf1, 1, sizeof(buf1), f1);
1421    j = fread(buf2, 1, sizeof(buf2), f2);
1422    if ((!i && ferror(f1)) || (!j && ferror(f2)))
1423      return -1;
1424    if (i != j)
1425      return 1;
1426    if (i == 0)
1427      return 0;
1428    if (memcmp(buf1, buf2, i) != 0)
1429      return 1;
1430  }
1431}
1432
1433static FILE *
1434opentemp(const char *f)
1435{
1436  char    buf[BUFSIZ], tempfile[PATH_MAX];
1437  ssize_t nread;
1438  int     ifd, ofd;
1439
1440  if (strcmp(f, "-") == 0)
1441    ifd = STDIN_FILENO;
1442  else if ((ifd = open(f, O_RDONLY, 0644)) == -1)
1443    return NULL;
1444
1445  strlcpy(tempfile, ARUU_PATH_TMP "/diff.XXXXXXXX", sizeof(tempfile));
1446  if ((ofd = mkstemp(tempfile)) == -1) {
1447    close(ifd);
1448    return NULL;
1449  }
1450  unlink(tempfile);
1451  while ((nread = read(ifd, buf, BUFSIZ)) > 0) {
1452    if (write(ofd, buf, nread) != nread) {
1453      close(ifd);
1454      close(ofd);
1455      return NULL;
1456    }
1457  }
1458  close(ifd);
1459  lseek(ofd, (off_t)0, SEEK_SET);
1460  return fdopen(ofd, "r");
1461}
1462
1463static int
1464prepare(int i, FILE *fd, size_t filesize, int flags)
1465{
1466  struct Line  *p;
1467  unsigned      h;
1468  size_t        sz, j = 0;
1469  enum Readhash r;
1470
1471  rewind(fd);
1472  sz = filesize / 25;
1473  if (sz < 100)
1474    sz = 100;
1475
1476  p = ecalloc(sz + 3, sizeof(*p));
1477  while ((r = readhash(fd, flags, &h)) != RH_EOF) {
1478    if (r == RH_BINARY)
1479      return 0;
1480    if (j == SIZE_MAX)
1481      break;
1482    if (j == sz) {
1483      sz = sz * 3 / 2;
1484      p  = ereallocarray(p, sz + 3, sizeof(*p));
1485    }
1486    p[++j].value = h;
1487  }
1488  len[i]  = j;
1489  file[i] = p;
1490  return 1;
1491}
1492
1493static void
1494prune(void)
1495{
1496  size_t i, j;
1497
1498  for (pref = 0;
1499       pref < len[0] && pref < len[1] && file[0][pref + 1].value == file[1][pref + 1].value;
1500       pref++)
1501    ;
1502  for (suff = 0; suff < len[0] - pref && suff < len[1] - pref
1503                 && file[0][len[0] - suff].value == file[1][len[1] - suff].value;
1504       suff++)
1505    ;
1506  for (j = 0; j < 2; j++) {
1507    sfile[j] = file[j] + pref;
1508    slen[j]  = len[j] - pref - suff;
1509    for (i = 0; i <= slen[j]; i++)
1510      sfile[j][i].serial = i;
1511  }
1512}
1513
1514static void
1515equiv(struct Line *a, int n, struct Line *b, int m, int *c)
1516{
1517  int i, j;
1518
1519  i = j = 1;
1520  while (i <= n && j <= m) {
1521    if (a[i].value < b[j].value)
1522      a[i++].value = 0;
1523    else if (a[i].value == b[j].value)
1524      a[i++].value = j;
1525    else
1526      j++;
1527  }
1528  while (i <= n)
1529    a[i++].value = 0;
1530  b[m + 1].value = 0;
1531  j              = 0;
1532  while (++j <= m) {
1533    c[j] = -b[j].serial;
1534    while (b[j + 1].value == b[j].value) {
1535      j++;
1536      c[j] = b[j].serial;
1537    }
1538  }
1539  c[j] = -1;
1540}
1541
1542static int
1543stone(int *a, int n, int *b, int *c, int flags)
1544{
1545  int      i, k, y, j, l;
1546  int      oldc, tc, oldl, sq;
1547  unsigned numtries, bound;
1548
1549  if (flags & D_MINIMAL)
1550    bound = UINT_MAX;
1551  else {
1552    sq    = sqrt(n);
1553    bound = MAX(256, sq);
1554  }
1555
1556  k    = 0;
1557  c[0] = newcand(0, 0, 0);
1558  for (i = 1; i <= n; i++) {
1559    j = a[i];
1560    if (j == 0)
1561      continue;
1562    y        = -b[j];
1563    oldl     = 0;
1564    oldc     = c[0];
1565    numtries = 0;
1566    do {
1567      if (y <= clist[oldc].y)
1568        continue;
1569      l = search(c, k, y);
1570      if (l != oldl + 1)
1571        oldc = c[l - 1];
1572      if (l <= k) {
1573        if (clist[c[l]].y <= y)
1574          continue;
1575        tc   = c[l];
1576        c[l] = newcand(i, y, oldc);
1577        oldc = tc;
1578        oldl = l;
1579        numtries++;
1580      } else {
1581        c[l] = newcand(i, y, oldc);
1582        k++;
1583        break;
1584      }
1585    } while ((y = b[++j]) > 0 && numtries < bound);
1586  }
1587  return k;
1588}
1589
1590static int
1591newcand(int x, int y, int pred)
1592{
1593  struct Cand *q;
1594
1595  if (clen == clistlen) {
1596    clistlen = clistlen * 11 / 10;
1597    clist    = ereallocarray(clist, clistlen, sizeof(*clist));
1598  }
1599  q       = clist + clen;
1600  q->x    = x;
1601  q->y    = y;
1602  q->pred = pred;
1603  return clen++;
1604}
1605
1606static int
1607search(int *c, int k, int y)
1608{
1609  int i, j, l, t;
1610
1611  if (clist[c[k]].y < y)
1612    return k + 1;
1613  i = 0;
1614  j = k + 1;
1615  for (;;) {
1616    l = (i + j) / 2;
1617    if (l <= i)
1618      break;
1619    t = clist[c[l]].y;
1620    if (t > y)
1621      j = l;
1622    else if (t < y)
1623      i = l;
1624    else
1625      return l;
1626  }
1627  return l + 1;
1628}
1629
1630static void
1631unravel(int p)
1632{
1633  struct Cand *q;
1634  size_t       i;
1635
1636  for (i = 0; i <= len[0]; i++)
1637    J[i] = i <= pref ? i : i > len[0] - suff ? i + len[1] - len[0] : 0;
1638  for (q = clist + p; q->y != 0; q = clist + q->pred)
1639    J[q->x + pref] = q->y + pref;
1640}
1641
1642static void
1643check(FILE *f1, FILE *f2, int flags)
1644{
1645  int  i, j, c, d;
1646  long ctold, ctnew;
1647
1648  rewind(f1);
1649  rewind(f2);
1650  j        = 1;
1651  ixold[0] = ixnew[0] = 0;
1652  ctold = ctnew = 0;
1653  for (i = 1; i <= (int)len[0]; i++) {
1654    if (J[i] == 0) {
1655      ixold[i] = ctold += skipline(f1);
1656      continue;
1657    }
1658    while (j < J[i]) {
1659      ixnew[j] = ctnew += skipline(f2);
1660      j++;
1661    }
1662    if (flags & (D_FOLDBLANKS | D_IGNOREBLANKS | D_IGNORECASE | D_STRIPCR)) {
1663      for (;;) {
1664        c = getc(f1);
1665        d = getc(f2);
1666        if (flags & (D_FOLDBLANKS | D_IGNOREBLANKS)) {
1667          if (c == EOF && isspace(d)) {
1668            ctnew++;
1669            break;
1670          } else if (isspace(c) && d == EOF) {
1671            ctold++;
1672            break;
1673          }
1674        }
1675        ctold++;
1676        ctnew++;
1677        if (flags & D_STRIPCR && (c == '\r' || d == '\r')) {
1678          if (c == '\r') {
1679            if ((c = getc(f1)) == '\n')
1680              ctold++;
1681            else
1682              ungetc(c, f1);
1683          }
1684          if (d == '\r') {
1685            if ((d = getc(f2)) == '\n')
1686              ctnew++;
1687            else
1688              ungetc(d, f2);
1689          }
1690          break;
1691        }
1692        if ((flags & D_FOLDBLANKS) && isspace(c) && isspace(d)) {
1693          do {
1694            if (c == '\n')
1695              break;
1696            ctold++;
1697          } while (isspace(c = getc(f1)));
1698          do {
1699            if (d == '\n')
1700              break;
1701            ctnew++;
1702          } while (isspace(d = getc(f2)));
1703        } else if (flags & D_IGNOREBLANKS) {
1704          while (isspace(c) && c != '\n') {
1705            c = getc(f1);
1706            ctold++;
1707          }
1708          while (isspace(d) && d != '\n') {
1709            d = getc(f2);
1710            ctnew++;
1711          }
1712        }
1713        if (chrtran(c) != chrtran(d)) {
1714          J[i] = 0;
1715          if (c != '\n' && c != EOF)
1716            ctold += skipline(f1);
1717          if (d != '\n' && c != EOF)
1718            ctnew += skipline(f2);
1719          break;
1720        }
1721        if (c == '\n' || c == EOF)
1722          break;
1723      }
1724    } else {
1725      for (;;) {
1726        ctold++;
1727        ctnew++;
1728        if ((c = getc(f1)) != (d = getc(f2))) {
1729          J[i] = 0;
1730          if (c != '\n' && c != EOF)
1731            ctold += skipline(f1);
1732          if (d != '\n' && c != EOF)
1733            ctnew += skipline(f2);
1734          break;
1735        }
1736        if (c == '\n' || c == EOF)
1737          break;
1738      }
1739    }
1740    ixold[i] = ctold;
1741    ixnew[j] = ctnew;
1742    j++;
1743  }
1744  for (; j <= (int)len[1]; j++) {
1745    ixnew[j] = ctnew += skipline(f2);
1746  }
1747}
1748
1749static void
1750sort(struct Line *a, int n)
1751{
1752  struct Line *ai, *aim, w;
1753  int          j, m = 0, k;
1754
1755  if (n == 0)
1756    return;
1757  for (j = 1; j <= n; j *= 2)
1758    m = 2 * j - 1;
1759  for (m /= 2; m != 0; m /= 2) {
1760    k = n - m;
1761    for (j = 1; j <= k; j++) {
1762      for (ai = &a[j]; ai > a; ai -= m) {
1763        aim = &ai[m];
1764        if (aim < ai)
1765          break;
1766        if (aim->value > ai[0].value || (aim->value == ai[0].value && aim->serial > ai[0].serial))
1767          break;
1768        w.value      = ai[0].value;
1769        ai[0].value  = aim->value;
1770        aim->value   = w.value;
1771        w.serial     = ai[0].serial;
1772        ai[0].serial = aim->serial;
1773        aim->serial  = w.serial;
1774      }
1775    }
1776  }
1777}
1778
1779static void
1780unsort(struct Line *f, int l, int *b)
1781{
1782  int *a, i;
1783
1784  a = ecalloc(l + 1, sizeof(*a));
1785  for (i = 1; i <= l; i++)
1786    a[f[i].serial] = f[i].value;
1787  for (i = 1; i <= l; i++)
1788    b[i] = a[i];
1789  free(a);
1790}
1791
1792static int
1793skipline(FILE *f)
1794{
1795  int i, c;
1796
1797  for (i = 1; (c = getc(f)) != '\n' && c != EOF; i++)
1798    continue;
1799  return i;
1800}
1801
1802static void
1803output(char *file1, FILE *f1, char *file2, FILE *f2, int flags)
1804{
1805  int i, j, m, i0, i1, j0, j1, nc;
1806
1807  rewind(f1);
1808  rewind(f2);
1809  m        = len[0];
1810  J[0]     = 0;
1811  J[m + 1] = len[1] + 1;
1812  if (diff_format != D_EDIT) {
1813    for (i0 = 1; i0 <= m; i0 = i1 + 1) {
1814      while (i0 <= m && J[i0] == J[i0 - 1] + 1) {
1815        if (diff_format == D_SIDEBYSIDE && suppress_common != 1) {
1816          nc = fetch(ixold, i0, i0, f1, '\0', 1, flags);
1817          print_space(nc, hw - nc + lpad + 1 + rpad, flags);
1818          fetch(ixnew, J[i0], J[i0], f2, '\0', 0, flags);
1819          printf("\n");
1820        }
1821        i0++;
1822      }
1823      j0 = J[i0 - 1] + 1;
1824      i1 = i0 - 1;
1825      while (i1 < m && J[i1 + 1] == 0)
1826        i1++;
1827      j1    = J[i1 + 1] - 1;
1828      J[i1] = j1;
1829
1830      if (diff_format == D_SIDEBYSIDE) {
1831        for (i = i0, j = j0; i <= i1 && j <= j1; i++, j++)
1832          change(file1, f1, file2, f2, i, i, j, j, &flags);
1833        while (i <= i1) {
1834          change(file1, f1, file2, f2, i, i, j + 1, j, &flags);
1835          i++;
1836        }
1837        while (j <= j1) {
1838          change(file1, f1, file2, f2, i + 1, i, j, j, &flags);
1839          j++;
1840        }
1841      } else
1842        change(file1, f1, file2, f2, i0, i1, j0, j1, &flags);
1843    }
1844  } else {
1845    for (i0 = m; i0 >= 1; i0 = i1 - 1) {
1846      while (i0 >= 1 && J[i0] == J[i0 + 1] - 1 && J[i0] != 0)
1847        i0--;
1848      j0 = J[i0 + 1] - 1;
1849      i1 = i0 + 1;
1850      while (i1 > 1 && J[i1 - 1] == 0)
1851        i1--;
1852      j1    = J[i1 - 1] + 1;
1853      J[i1] = j1;
1854      change(file1, f1, file2, f2, i1, i0, j1, j0, &flags);
1855    }
1856  }
1857  if (m == 0)
1858    change(file1, f1, file2, f2, 1, 0, 1, len[1], &flags);
1859  if (diff_format == D_IFDEF || diff_format == D_GFORMAT) {
1860    for (;;) {
1861#define c i0
1862      if ((c = getc(f1)) == EOF)
1863        return;
1864      printf("%c", c);
1865    }
1866#undef c
1867  }
1868  if (anychange != 0) {
1869    if (diff_format == D_CONTEXT)
1870      dump_context_vec(f1, f2, flags);
1871    else if (diff_format == D_UNIFIED)
1872      dump_unified_vec(f1, f2, flags);
1873  }
1874}
1875
1876static void
1877range(int a, int b, const char *separator)
1878{
1879  printf("%d", a > b ? b : a);
1880  if (a < b)
1881    printf("%s%d", separator, b);
1882}
1883
1884static void
1885uni_range(int a, int b)
1886{
1887  if (a < b)
1888    printf("%d,%d", a, b - a + 1);
1889  else if (a == b)
1890    printf("%d", b);
1891  else
1892    printf("%d,0", b);
1893}
1894
1895static char *
1896preadline(int fd, size_t rlen, off_t off)
1897{
1898  char   *line;
1899  ssize_t nr;
1900
1901  line = emalloc(rlen + 1);
1902  if ((nr = pread(fd, line, rlen, off)) == -1)
1903    enprintf(2, "preadline");
1904  if (nr > 0 && line[nr - 1] == '\n')
1905    nr--;
1906  line[nr] = '\0';
1907  return line;
1908}
1909
1910static int
1911ignoreline_pattern(char *line)
1912{
1913  int ret;
1914
1915  ret = regexec(&ignore_re, line, 0, NULL, 0);
1916  return ret == 0;
1917}
1918
1919static int
1920ignoreline(char *line, int skip_blanks)
1921{
1922  if (skip_blanks && *line == '\0')
1923    return 1;
1924  if (ignore_pats != NULL && ignoreline_pattern(line))
1925    return 1;
1926  return 0;
1927}
1928
1929static void
1930change(char *file1, FILE *f1, char *file2, FILE *f2, int a, int b, int c, int d, int *pflags)
1931{
1932  static size_t max_context = 64;
1933  long          curpos;
1934  int           i, nc;
1935  const char   *walk;
1936  int           skip_blanks, ignore;
1937
1938  skip_blanks = (*pflags & D_SKIPBLANKLINES);
1939restart:
1940  if ((diff_format != D_IFDEF || diff_format == D_GFORMAT) && a > b && c > d)
1941    return;
1942  if (ignore_pats != NULL || skip_blanks) {
1943    char *line;
1944    if (a <= b) {
1945      for (i = a; i <= b; i++) {
1946        line   = preadline(fileno(f1), ixold[i] - ixold[i - 1], ixold[i - 1]);
1947        ignore = ignoreline(line, skip_blanks);
1948        free(line);
1949        if (!ignore)
1950          goto proceed;
1951      }
1952    }
1953    if (a > b || c <= d) {
1954      for (i = c; i <= d; i++) {
1955        line   = preadline(fileno(f2), ixnew[i] - ixnew[i - 1], ixnew[i - 1]);
1956        ignore = ignoreline(line, skip_blanks);
1957        free(line);
1958        if (!ignore)
1959          goto proceed;
1960      }
1961    }
1962    return;
1963  }
1964proceed:
1965  if (*pflags & D_HEADER && diff_format != D_BRIEF) {
1966    printf("%s %s %s\n", diffargs, file1, file2);
1967    *pflags &= ~D_HEADER;
1968  }
1969  if (diff_format == D_CONTEXT || diff_format == D_UNIFIED) {
1970    if (context_vec_start == NULL || context_vec_ptr == context_vec_end - 1) {
1971      ptrdiff_t offset = -1;
1972
1973      if (context_vec_start != NULL)
1974        offset = context_vec_ptr - context_vec_start;
1975      max_context <<= 1;
1976      context_vec_start = ereallocarray(context_vec_start, max_context, sizeof(*context_vec_start));
1977      context_vec_end   = context_vec_start + max_context;
1978      context_vec_ptr   = context_vec_start + offset;
1979    }
1980    if (anychange == 0) {
1981      print_header(file1, file2);
1982      anychange = 1;
1983    } else if (
1984        a > context_vec_ptr->b + (2 * diff_context) + 1
1985        && c > context_vec_ptr->d + (2 * diff_context) + 1
1986    ) {
1987      if (diff_format == D_CONTEXT)
1988        dump_context_vec(f1, f2, *pflags);
1989      else
1990        dump_unified_vec(f1, f2, *pflags);
1991    }
1992    context_vec_ptr++;
1993    context_vec_ptr->a = a;
1994    context_vec_ptr->b = b;
1995    context_vec_ptr->c = c;
1996    context_vec_ptr->d = d;
1997    return;
1998  }
1999  if (anychange == 0)
2000    anychange = 1;
2001  switch (diff_format) {
2002    case D_BRIEF:
2003      return;
2004    case D_NORMAL:
2005    case D_EDIT:
2006      range(a, b, ",");
2007      printf("%c", a > b ? 'a' : c > d ? 'd' : 'c');
2008      if (diff_format == D_NORMAL)
2009        range(c, d, ",");
2010      printf("\n");
2011      break;
2012    case D_REVERSE:
2013      printf("%c", a > b ? 'a' : c > d ? 'd' : 'c');
2014      range(a, b, " ");
2015      printf("\n");
2016      break;
2017    case D_NREVERSE:
2018      if (a > b)
2019        printf("a%d %d\n", b, d - c + 1);
2020      else {
2021        printf("d%d %d\n", a, b - a + 1);
2022        if (!(c > d))
2023          printf("a%d %d\n", b, d - c + 1);
2024      }
2025      break;
2026  }
2027  if (diff_format == D_GFORMAT) {
2028    curpos = ftell(f1);
2029    nc     = ixold[a > b ? b : a - 1] - curpos;
2030    for (i = 0; i < nc; i++)
2031      printf("%c", getc(f1));
2032    for (walk = group_format; *walk != '\0'; walk++) {
2033      if (*walk == '%') {
2034        walk++;
2035        switch (*walk) {
2036          case '<':
2037            fetch(ixold, a, b, f1, '<', 1, *pflags);
2038            break;
2039          case '>':
2040            fetch(ixnew, c, d, f2, '>', 0, *pflags);
2041            break;
2042          default:
2043            printf("%%%c", *walk);
2044            break;
2045        }
2046        continue;
2047      }
2048      printf("%c", *walk);
2049    }
2050  }
2051  if (diff_format == D_SIDEBYSIDE) {
2052    if (color && a > b)
2053      printf("\033[%sm", add_code);
2054    else if (color && c > d)
2055      printf("\033[%sm", del_code);
2056    if (a > b) {
2057      print_space(0, hw + lpad, *pflags);
2058    } else {
2059      nc = fetch(ixold, a, b, f1, '\0', 1, *pflags);
2060      print_space(nc, hw - nc + lpad, *pflags);
2061    }
2062    if (color && a > b)
2063      printf("\033[%sm", add_code);
2064    else if (color && c > d)
2065      printf("\033[%sm", del_code);
2066    printf("%c", (a > b) ? '>' : ((c > d) ? '<' : '|'));
2067    if (color && c > d)
2068      printf("\033[m");
2069    print_space(hw + lpad + 1, rpad, *pflags);
2070    fetch(ixnew, c, d, f2, '\0', 0, *pflags);
2071    printf("\n");
2072  }
2073  if (diff_format == D_NORMAL || diff_format == D_IFDEF) {
2074    fetch(ixold, a, b, f1, '<', 1, *pflags);
2075    if (a <= b && c <= d && diff_format == D_NORMAL)
2076      printf("---\n");
2077  }
2078  if (diff_format != D_GFORMAT && diff_format != D_SIDEBYSIDE)
2079    fetch(ixnew, c, d, f2, diff_format == D_NORMAL ? '>' : '\0', 0, *pflags);
2080  if (edoffset != 0 && diff_format == D_EDIT) {
2081    printf(".\n");
2082    printf("%ds/.//\n", a + edoffset - 1);
2083    b = a + edoffset - 1;
2084    a = b + 1;
2085    c += edoffset;
2086    goto restart;
2087  }
2088  if ((diff_format == D_EDIT || diff_format == D_REVERSE) && c <= d)
2089    printf(".\n");
2090  if (inifdef) {
2091    printf("#endif /* %s */\n", ifdefname);
2092    inifdef = 0;
2093  }
2094}
2095
2096static int
2097fetch(long *f, int a, int b, FILE *lb, int ch, int oldfile, int flags)
2098{
2099  int i, j, c, lastc, col, nc, newcol;
2100
2101  edoffset = 0;
2102  nc       = 0;
2103  col      = 0;
2104  if ((diff_format == D_IFDEF) && oldfile) {
2105    long curpos = ftell(lb);
2106    nc          = f[a > b ? b : a - 1] - curpos;
2107    for (i = 0; i < nc; i++)
2108      printf("%c", getc(lb));
2109  }
2110  if (a > b)
2111    return 0;
2112  if (diff_format == D_IFDEF) {
2113    if (inifdef) {
2114      printf("#else /* %s%s */\n", oldfile == 1 ? "!" : "", ifdefname);
2115    } else {
2116      if (oldfile)
2117        printf("#ifndef %s\n", ifdefname);
2118      else
2119        printf("#ifdef %s\n", ifdefname);
2120    }
2121    inifdef = 1 + oldfile;
2122  }
2123  for (i = a; i <= b; i++) {
2124    fseek(lb, f[i - 1], SEEK_SET);
2125    nc = f[i] - f[i - 1];
2126    if (diff_format == D_SIDEBYSIDE && hw < nc)
2127      nc = hw;
2128    if (diff_format != D_IFDEF && diff_format != D_GFORMAT && ch != '\0') {
2129      if (color && (ch == '>' || ch == '+'))
2130        printf("\033[%sm", add_code);
2131      else if (color && (ch == '<' || ch == '-'))
2132        printf("\033[%sm", del_code);
2133      printf("%c", ch);
2134      if (Tflag
2135          && (diff_format == D_NORMAL || diff_format == D_CONTEXT || diff_format == D_UNIFIED))
2136        printf("\t");
2137      else if (diff_format != D_UNIFIED)
2138        printf(" ");
2139    }
2140    col = j = 0;
2141    lastc   = '\0';
2142    while (j < nc && (hw == 0 || col < hw)) {
2143      c = getc(lb);
2144      if (flags & D_STRIPCR && c == '\r') {
2145        if ((c = getc(lb)) == '\n')
2146          j++;
2147        else {
2148          ungetc(c, lb);
2149          c = '\r';
2150        }
2151      }
2152      if (c == EOF) {
2153        if (diff_format == D_EDIT || diff_format == D_REVERSE || diff_format == D_NREVERSE)
2154          weprintf("No newline at end of file");
2155        else
2156          printf(
2157              "\n\\ No newline at end of "
2158              "file\n"
2159          );
2160        return col;
2161      }
2162      if (c == '\t') {
2163        newcol = roundup(col + 1, tabsize);
2164        if ((flags & D_EXPANDTABS) == 0) {
2165          if (hw > 0 && newcol >= hw)
2166            return col;
2167          printf("\t");
2168        } else {
2169          if (hw > 0 && newcol > hw)
2170            newcol = hw;
2171          printf("%*s", newcol - col, "");
2172        }
2173        col = newcol;
2174      } else {
2175        if (diff_format == D_EDIT && j == 1 && c == '\n' && lastc == '.') {
2176          printf(".\n");
2177          edoffset = i - a + 1;
2178          return edoffset;
2179        }
2180        if (diff_format != D_SIDEBYSIDE || c != '\n') {
2181          if (color && c == '\n')
2182            printf("\033[m%c", c);
2183          else
2184            printf("%c", c);
2185          col++;
2186        }
2187      }
2188      j++;
2189      lastc = c;
2190    }
2191  }
2192  if (color && diff_format == D_SIDEBYSIDE)
2193    printf("\033[m");
2194  return col;
2195}
2196
2197static enum Readhash
2198readhash(FILE *f, int flags, unsigned *hash)
2199{
2200  int      i, t, space;
2201  unsigned sum;
2202
2203  sum   = 1;
2204  space = 0;
2205  for (i = 0;;) {
2206    switch (t = getc(f)) {
2207      case '\0':
2208        if ((flags & D_FORCEASCII) == 0)
2209          return RH_BINARY;
2210        goto hashchar;
2211      case '\r':
2212        if (flags & D_STRIPCR) {
2213          t = getc(f);
2214          if (t == '\n')
2215            break;
2216          ungetc(t, f);
2217        }
2218        /* FALLTHROUGH */
2219      case '\t':
2220      case '\v':
2221      case '\f':
2222      case ' ':
2223        if ((flags & (D_FOLDBLANKS | D_IGNOREBLANKS)) != 0) {
2224          space++;
2225          continue;
2226        }
2227        /* FALLTHROUGH */
2228      default:
2229      hashchar:
2230        if (space && (flags & D_IGNOREBLANKS) == 0) {
2231          i++;
2232          space = 0;
2233        }
2234        sum = sum * 127 + chrtran(t);
2235        i++;
2236        continue;
2237      case EOF:
2238        if (i == 0)
2239          return RH_EOF;
2240        /* FALLTHROUGH */
2241      case '\n':
2242        break;
2243    }
2244    break;
2245  }
2246  *hash = sum;
2247  return RH_OK;
2248}
2249
2250static int
2251asciifile(FILE *f)
2252{
2253  unsigned char buf[BUFSIZ];
2254  size_t        cnt;
2255
2256  if (f == NULL)
2257    return 1;
2258
2259  rewind(f);
2260  cnt = fread(buf, 1, sizeof(buf), f);
2261  return memchr(buf, '\0', cnt) == NULL;
2262}
2263
2264#define begins_with(s, pre) (strncmp(s, pre, sizeof(pre) - 1) == 0)
2265
2266static char *
2267match_function(const long *f, int pos, FILE *fp)
2268{
2269  char        buf[FUNCTION_CONTEXT_SIZE];
2270  size_t      nc;
2271  int         last  = lastline;
2272  const char *state = NULL;
2273
2274  lastline = pos;
2275  for (; pos > last; pos--) {
2276    fseek(fp, f[pos - 1], SEEK_SET);
2277    nc = f[pos] - f[pos - 1];
2278    if (nc >= sizeof(buf))
2279      nc = sizeof(buf) - 1;
2280    nc = fread(buf, 1, nc, fp);
2281    if (nc == 0)
2282      continue;
2283    buf[nc]                 = '\0';
2284    buf[strcspn(buf, "\n")] = '\0';
2285    if (most_recent_pat != NULL) {
2286      int ret = regexec(&most_recent_re, buf, 0, NULL, 0);
2287      if (ret != 0)
2288        continue;
2289      strlcpy(lastbuf, buf, sizeof(lastbuf));
2290      lastmatchline = pos;
2291      return lastbuf;
2292    } else if (
2293        isalpha(buf[0]) || buf[0] == '_' || buf[0] == '$' || buf[0] == '-' || buf[0] == '+'
2294    ) {
2295      if (begins_with(buf, "private:")) {
2296        if (!state)
2297          state = " (private)";
2298      } else if (begins_with(buf, "protected:")) {
2299        if (!state)
2300          state = " (protected)";
2301      } else if (begins_with(buf, "public:")) {
2302        if (!state)
2303          state = " (public)";
2304      } else {
2305        strlcpy(lastbuf, buf, sizeof(lastbuf));
2306        if (state)
2307          strlcat(lastbuf, state, sizeof(lastbuf));
2308        lastmatchline = pos;
2309        return lastbuf;
2310      }
2311    }
2312  }
2313  return lastmatchline > 0 ? lastbuf : NULL;
2314}
2315
2316static void
2317dump_context_vec(FILE *f1, FILE *f2, int flags)
2318{
2319  struct ContextVec *cvp = context_vec_start;
2320  int                lowa, upb, lowc, upd, do_output;
2321  int                a, b, c, d;
2322  char               ch, *f;
2323
2324  if (context_vec_start > context_vec_ptr)
2325    return;
2326
2327  b = d = 0;
2328  lowa  = MAX(1, cvp->a - diff_context);
2329  upb   = MIN((int)len[0], context_vec_ptr->b + diff_context);
2330  lowc  = MAX(1, cvp->c - diff_context);
2331  upd   = MIN((int)len[1], context_vec_ptr->d + diff_context);
2332
2333  printf("***************");
2334  if (flags & (D_PROTOTYPE | D_MATCHLAST)) {
2335    f = match_function(ixold, cvp->a - 1, f1);
2336    if (f != NULL)
2337      printf(" %s", f);
2338  }
2339  printf("\n*** ");
2340  range(lowa, upb, ",");
2341  printf(" ****\n");
2342
2343  do_output = 0;
2344  for (; cvp <= context_vec_ptr; cvp++)
2345    if (cvp->a <= cvp->b) {
2346      cvp = context_vec_start;
2347      do_output++;
2348      break;
2349    }
2350  if (do_output) {
2351    while (cvp <= context_vec_ptr) {
2352      a = cvp->a;
2353      b = cvp->b;
2354      c = cvp->c;
2355      d = cvp->d;
2356
2357      if (a <= b && c <= d)
2358        ch = 'c';
2359      else
2360        ch = (a <= b) ? 'd' : 'a';
2361
2362      if (ch == 'a')
2363        fetch(ixold, lowa, b, f1, ' ', 0, flags);
2364      else {
2365        fetch(ixold, lowa, a - 1, f1, ' ', 0, flags);
2366        fetch(ixold, a, b, f1, ch == 'c' ? '!' : '-', 0, flags);
2367      }
2368      lowa = b + 1;
2369      cvp++;
2370    }
2371    fetch(ixold, b + 1, upb, f1, ' ', 0, flags);
2372  }
2373
2374  printf("--- ");
2375  range(lowc, upd, ",");
2376  printf(" ----\n");
2377
2378  do_output = 0;
2379  for (cvp = context_vec_start; cvp <= context_vec_ptr; cvp++)
2380    if (cvp->c <= cvp->d) {
2381      cvp = context_vec_start;
2382      do_output++;
2383      break;
2384    }
2385  if (do_output) {
2386    while (cvp <= context_vec_ptr) {
2387      a = cvp->a;
2388      b = cvp->b;
2389      c = cvp->c;
2390      d = cvp->d;
2391
2392      if (a <= b && c <= d)
2393        ch = 'c';
2394      else
2395        ch = (a <= b) ? 'd' : 'a';
2396
2397      if (ch == 'd')
2398        fetch(ixnew, lowc, d, f2, ' ', 0, flags);
2399      else {
2400        fetch(ixnew, lowc, c - 1, f2, ' ', 0, flags);
2401        fetch(ixnew, c, d, f2, ch == 'c' ? '!' : '+', 0, flags);
2402      }
2403      lowc = d + 1;
2404      cvp++;
2405    }
2406    fetch(ixnew, d + 1, upd, f2, ' ', 0, flags);
2407  }
2408  context_vec_ptr = context_vec_start - 1;
2409}
2410
2411static void
2412dump_unified_vec(FILE *f1, FILE *f2, int flags)
2413{
2414  struct ContextVec *cvp = context_vec_start;
2415  int                lowa, upb, lowc, upd;
2416  int                a, b, c, d;
2417  char               ch, *f;
2418
2419  if (context_vec_start > context_vec_ptr)
2420    return;
2421
2422  b = d = 0;
2423  lowa  = MAX(1, cvp->a - diff_context);
2424  upb   = MIN((int)len[0], context_vec_ptr->b + diff_context);
2425  lowc  = MAX(1, cvp->c - diff_context);
2426  upd   = MIN((int)len[1], context_vec_ptr->d + diff_context);
2427
2428  printf("@@ -");
2429  uni_range(lowa, upb);
2430  printf(" +");
2431  uni_range(lowc, upd);
2432  printf(" @@");
2433  if (flags & (D_PROTOTYPE | D_MATCHLAST)) {
2434    f = match_function(ixold, cvp->a - 1, f1);
2435    if (f != NULL)
2436      printf(" %s", f);
2437  }
2438  printf("\n");
2439
2440  for (; cvp <= context_vec_ptr; cvp++) {
2441    a = cvp->a;
2442    b = cvp->b;
2443    c = cvp->c;
2444    d = cvp->d;
2445
2446    if (a <= b && c <= d)
2447      ch = 'c';
2448    else
2449      ch = (a <= b) ? 'd' : 'a';
2450
2451    switch (ch) {
2452      case 'c':
2453        fetch(ixold, lowa, a - 1, f1, ' ', 0, flags);
2454        fetch(ixold, a, b, f1, '-', 0, flags);
2455        fetch(ixnew, c, d, f2, '+', 0, flags);
2456        break;
2457      case 'd':
2458        fetch(ixold, lowa, a - 1, f1, ' ', 0, flags);
2459        fetch(ixold, a, b, f1, '-', 0, flags);
2460        break;
2461      case 'a':
2462        fetch(ixnew, lowc, c - 1, f2, ' ', 0, flags);
2463        fetch(ixnew, c, d, f2, '+', 0, flags);
2464        break;
2465    }
2466    lowa = b + 1;
2467    lowc = d + 1;
2468  }
2469  fetch(ixnew, d + 1, upd, f2, ' ', 0, flags);
2470  context_vec_ptr = context_vec_start - 1;
2471}
2472
2473static void
2474print_header(const char *file1, const char *file2)
2475{
2476  const char *time_format;
2477  char        buf[256];
2478  struct tm   tm1, tm2, *tm_ptr1, *tm_ptr2;
2479  int         nsec1 = stb1.st_mtim.tv_nsec;
2480  int         nsec2 = stb2.st_mtim.tv_nsec;
2481
2482  time_format = "%Y-%m-%d %H:%M:%S";
2483  if (cflag)
2484    time_format = "%c";
2485  tm_ptr1 = localtime_r(&stb1.st_mtime, &tm1);
2486  tm_ptr2 = localtime_r(&stb2.st_mtime, &tm2);
2487  if (label[0] != NULL)
2488    printf("%s %s\n", diff_format == D_CONTEXT ? "***" : "---", label[0]);
2489  else {
2490    strftime(buf, sizeof(buf), time_format, tm_ptr1);
2491    printf("%s %s\t%s", diff_format == D_CONTEXT ? "***" : "---", file1, buf);
2492    if (!cflag) {
2493      strftime(buf, sizeof(buf), "%z", tm_ptr1);
2494      printf(".%.9d %s", nsec1, buf);
2495    }
2496    printf("\n");
2497  }
2498  if (label[1] != NULL)
2499    printf("%s %s\n", diff_format == D_CONTEXT ? "---" : "+++", label[1]);
2500  else {
2501    strftime(buf, sizeof(buf), time_format, tm_ptr2);
2502    printf("%s %s\t%s", diff_format == D_CONTEXT ? "---" : "+++", file2, buf);
2503    if (!cflag) {
2504      strftime(buf, sizeof(buf), "%z", tm_ptr2);
2505      printf(".%.9d %s", nsec2, buf);
2506    }
2507    printf("\n");
2508  }
2509}
2510
2511static void
2512print_space(int nc, int n, int flags)
2513{
2514  int col, newcol, tabstop;
2515
2516  col    = nc;
2517  newcol = nc + n;
2518  if ((flags & D_EXPANDTABS) == 0) {
2519    while ((tabstop = roundup(col + 1, tabsize)) <= newcol) {
2520      printf("\t");
2521      col = tabstop;
2522    }
2523  }
2524  printf("%*s", newcol - col, "");
2525}