/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* from OpenSolaris "bdiff.c 1.15 05/06/08 SMI" */ /* * Portions Copyright (c) 2005 Gunnar Ritter, Freiburg i. Br., Germany */ #if __GNUC__ >= 3 && __GNUC_MINOR__ >= 4 || __GNUC__ >= 4 #define USED __attribute__ ((used)) #elif defined __GNUC__ #define USED __attribute__ ((unused)) #else #define USED #endif static const char sccsid[] USED = "@(#)bdiff.c 1.8 (gritter) 7/2/05"; #include "fatal.h" #include #include #include #include #include #include #include #include #include #include "sigset.h" #define ONSIG 16 /* * This program segments two files into pieces of <= seglim lines * (which is passed as a third argument or defaulted to some number) * and then executes diff upon the pieces. The output of * 'diff' is then processed to make it look as if 'diff' had * processed the files whole. The reason for all this is that seglim * is a reasonable upper limit on the size of files that diff can * process. * NOTE -- by segmenting the files in this manner, it cannot be * guaranteed that the 'diffing' of the segments will generate * a minimal set of differences. * This process is most definitely not equivalent to 'diffing' * the files whole, assuming 'diff' could handle such large files. * * 'diff' is executed by a child process, generated by forking, * and communicates with this program through pipes. */ static char Error[128]; static int seglim; /* limit of size of file segment to be generated */ static const char diff[] = DIFF; static const char tempskel[] = "/tmp/bdXXXXXX"; static char tempfile[32]; static char otmp[32], ntmp[32]; static int fflags; static int fatal_num = 1; /* exit number for fatal exit */ static off_t linenum; static size_t obufsiz, nbufsiz, dbufsiz; static size_t obuflen, nbuflen, dbuflen; static char *readline(char **, size_t *, size_t *, FILE *); static void addgen(char **, size_t *, size_t *, FILE *); static void delgen(char **, size_t *, size_t *, FILE *); static void fixnum(const char *); static void fatal(const char *); static void setsig(void); static void setsig1(int); static char *satoi(const char *, off_t *); static FILE *maket(char *); #define smalloc(n) srealloc(NULL, n) static void *srealloc(void *, size_t); static char *prognam; int main(int argc, char *argv[]) { FILE *poldfile, *pnewfile; char *oline, *nline, *diffline; char *olp, *nlp, *dp; int otcnt, ntcnt; pid_t i; int pfd[2]; FILE *poldtemp, *pnewtemp, *pipeinp; int status; prognam = basename(argv[0]); /* * Set flags for 'fatal' so that it will clean up, * produce a message, and terminate. */ fflags = FTLMSG | FTLCLN | FTLEXIT; setsig(); if (argc < 3 || argc > 5) fatal("arg count"); if (strcmp(argv[1], "-") == 0 && strcmp(argv[2], "-") == 0) fatal("both files standard input"); if (strcmp(argv[1], "-") == 0) poldfile = stdin; else if ((poldfile = fopen(argv[1], "r")) == NULL) { snprintf(Error, sizeof (Error), "Can not open '%s'", argv[1]); fatal(Error); } if (strcmp(argv[2], "-") == 0) pnewfile = stdin; else if ((pnewfile = fopen(argv[2], "r")) == NULL) { snprintf(Error, sizeof (Error), "Can not open '%s'", argv[2]); fatal(Error); } seglim = 3500; if (argc > 3) { if (argv[3][0] == '-' && argv[3][1] == 's') fflags &= ~FTLMSG; else { if ((seglim = atoi(argv[3])) == 0) fatal("non-numeric limit"); if (argc == 5 && argv[4][0] == '-' && argv[4][1] == 's') fflags &= ~FTLMSG; } } linenum = 0; /* Allocate the buffers and initialize their lengths */ obufsiz = BUFSIZ; nbufsiz = BUFSIZ; dbufsiz = BUFSIZ; oline = smalloc(obufsiz); nline = smalloc(nbufsiz); diffline = smalloc(dbufsiz); /* * The following while-loop will prevent any lines * common to the beginning of both files from being * sent to 'diff'. Since the running time of 'diff' is * non-linear, this will help improve performance. * If, during this process, both files reach EOF, then * the files are equal and the program will terminate. * If either file reaches EOF before the other, the * program will generate the appropriate 'diff' output * itself, since this can be easily determined and will * avoid executing 'diff' completely. */ for (;;) { olp = readline(&oline, &obufsiz, &obuflen, poldfile); nlp = readline(&nline, &nbufsiz, &nbuflen, pnewfile); if (!olp && !nlp) /* EOF found on both: files equal */ return (0); if (!olp) { /* * The entire old file is a prefix of the * new file. Generate the appropriate "append" * 'diff'-like output, which is of the form: * nan, n * where 'n' represents a line-number. */ addgen(&nline, &nbufsiz, &nbuflen, pnewfile); } if (!nlp) { /* * The entire new file is a prefix of the * old file. Generate the appropriate "delete" * 'diff'-like output, which is of the form: * n, ndn * where 'n' represents a line-number. */ delgen(&oline, &obufsiz, &obuflen, poldfile); } if (obuflen == nbuflen && memcmp(olp, nlp, obuflen) == 0) linenum++; else break; } /* * Here, first 'linenum' lines are equal. * The following while-loop segments both files into * seglim segments, forks and executes 'diff' on the * segments, and processes the resulting output of * 'diff', which is read from a pipe. */ for (;;) { /* If both files are at EOF, everything is done. */ if (!olp && !nlp) /* finished */ return (0); if (!olp) { /* * Generate appropriate "append" * output without executing 'diff'. */ addgen(&nline, &nbufsiz, &nbuflen, pnewfile); } if (!nlp) { /* * Generate appropriate "delete" * output without executing 'diff'. */ delgen(&oline, &obufsiz, &obuflen, poldfile); } /* * Create a temporary file to hold a segment * from the old file, and write it. */ poldtemp = maket(otmp); otcnt = 0; while (olp && otcnt < seglim) { fwrite(oline, sizeof *oline, obuflen, poldtemp); if (ferror(poldtemp) != 0) { fflags |= FTLMSG; fatal("Can not write to temporary file"); } olp = readline(&oline, &obufsiz, &obuflen, poldfile); otcnt++; } fclose(poldtemp); /* * Create a temporary file to hold a segment * from the new file, and write it. */ pnewtemp = maket(ntmp); ntcnt = 0; while (nlp && ntcnt < seglim) { fwrite(nline, sizeof *nline, nbuflen, pnewtemp); if (ferror(pnewtemp) != 0) { fflags |= FTLMSG; fatal("Can not write to temporary file"); } nlp = readline(&nline, &nbufsiz, &nbuflen, pnewfile); ntcnt++; } fclose(pnewtemp); /* Create pipes and fork. */ if ((pipe(pfd)) == -1) fatal("Can not create pipe"); if ((i = fork()) < 0) { close(pfd[0]); close(pfd[1]); fatal("Can not fork, try again"); } else if (i == 0) { /* child process */ close(pfd[0]); close(1); dup(pfd[1]); close(pfd[1]); putenv("LC_ALL=C"); /* Execute 'diff' on the segment files. */ execlp(diff, diff, "-a", otmp, ntmp, NULL); /* * Exit code here must be > 1. * Parent process treats exit code of 1 from the child * as non-error because the child process "diff" exits * with a status of 1 when a difference is encountered. * The error here is a true error--the parent process * needs to detect it and exit with a non-zero status. */ close(1); snprintf(Error, sizeof (Error), "Can not execute '%s'", diff); fatal_num = 2; fatal(Error); } else { /* parent process */ close(pfd[1]); pipeinp = fdopen(pfd[0], "r"); /* Process 'diff' output. */ while ((dp = readline(&diffline, &dbufsiz, &dbuflen, pipeinp))) { if (isdigit(*dp)) fixnum(diffline); else fwrite(diffline, sizeof *diffline, dbuflen, stdout); } fclose(pipeinp); /* EOF on pipe. */ while (wait(&status) != i); if (status && (!WIFEXITED(status) || WEXITSTATUS(status) != 1)) { snprintf(Error, sizeof (Error), "'%s' failed", diff); fatal(Error); } } linenum += seglim; /* Remove temporary files. */ unlink(otmp); unlink(ntmp); } } /* Routine to save remainder of a file. */ static void saverest(char **linep, size_t *bufsizp, size_t *buflenp, FILE *iptr) { char *lp; FILE *temptr; temptr = maket(tempfile); lp = *linep; while (lp) { fwrite(*linep, sizeof **linep, *buflenp, temptr); linenum++; lp = readline(linep, bufsizp, buflenp, iptr); } fclose(temptr); } /* Routine to write out data saved by 'saverest' and to remove the file. */ static void putsave(char **linep, size_t *bufsizp, size_t *buflenp, char type) { FILE *temptr; if ((temptr = fopen(tempfile, "r")) == NULL) { snprintf(Error, sizeof (Error), "Can not open tempfile ('%s')", tempfile); fatal(Error); } while (readline(linep, bufsizp, buflenp, temptr)) { printf("%c ", type); fwrite(*linep, sizeof **linep, *buflenp, stdout); } fclose(temptr); unlink(tempfile); } static void fixnum(const char *lp) { off_t num; while (*lp) { switch (*lp) { case 'a': case 'c': case 'd': case ',': case '\n': printf("%c", *lp); lp++; break; default: lp = satoi(lp, &num); num += linenum; printf("%lld", (long long)num); } } } static void addgen(char **lpp, size_t *bufsizp, size_t *buflenp, FILE *fp) { off_t oldline; printf("%llda%lld", (long long)linenum, (long long)linenum+1); /* Save lines of new file. */ oldline = linenum + 1; saverest(lpp, bufsizp, buflenp, fp); if (oldline < linenum) printf(",%lld\n", (long long)linenum); else printf("\n"); /* Output saved lines, as 'diff' would. */ putsave(lpp, bufsizp, buflenp, '>'); exit(0); } static void delgen(char **lpp, size_t *bufsizp, size_t *buflenp, FILE *fp) { off_t savenum; printf("%lld", (long long)linenum+1); savenum = linenum; /* Save lines of old file. */ saverest(lpp, bufsizp, buflenp, fp); if (savenum +1 != linenum) printf(",%lldd%lld\n", (long long)linenum, (long long)savenum); else printf("d%lld\n", (long long)savenum); /* Output saved lines, as 'diff' would. */ putsave(lpp, bufsizp, buflenp, '<'); exit(0); } static void clean_up(void) { unlink(tempfile); unlink(otmp); unlink(ntmp); } static FILE * maket(char *file) { FILE *iop = NULL; int fd; strcpy(file, tempskel); if ((fd = mkstemp(file)) == -1 || (iop = fdopen(fd, "w+")) == NULL) { snprintf(Error, sizeof (Error), "Can not open/create temp file ('%s')", file); fatal(Error); } return (iop); } static void fatal(const char *msg) /* * General purpose error handler. * * The argument to fatal is a pointer to an error message string. * The action of this routine is driven completely from * the "fflags" global word (see ). * * The FTLMSG bit controls the writing of the error * message on file descriptor 2. A newline is written * after the user supplied message. * * If the FTLCLN bit is on, clean_up is called. */ { if (fflags & FTLMSG) fprintf(stderr, "%s: %s\n", prognam, msg); if (fflags & FTLCLN) clean_up(); if (fflags & FTLEXIT) exit(fatal_num); } static void setsig(void) /* * General-purpose signal setting routine. * All non-ignored, non-caught signals are caught. * If a signal other than hangup, interrupt, or quit is caught, * a "user-oriented" message is printed on file descriptor 2. * If hangup, interrupt or quit is caught, that signal * is set to ignore. * Termination is like that of "fatal", * via "clean_up()" */ { void (*act)(int); int j; for (j = 1; j < ONSIG; j++) { act = sigset(j, setsig1); if (act == SIG_ERR) continue; if (act == SIG_DFL) continue; sigset(j, act); } } /*ARGSUSED*/ static void setsig1(int sig) { clean_up(); exit(1); } static char * satoi(const char *p, off_t *ip) { off_t sum; sum = 0; while (isdigit(*p)) sum = sum * 10 + (*p++ - '0'); *ip = sum; return (char *)p; } /* * Read a line of data from a file. If the current buffer is not large enough * to contain the line, double the size of the buffer and continue reading. * Loop until either the entire line is read or until there is no more space * to be malloc'd. */ #if defined (__GLIBC__) && defined (_IO_getc_unlocked) #undef getc #define getc(f) _IO_getc_unlocked(f) #endif #define LSIZE 128 static char * readline(char **line, size_t *linesize, size_t *length, FILE *fp) { int c; size_t n = 0; if (*line == NULL || *linesize < LSIZE + n + 1) *line = srealloc(*line, *linesize = LSIZE + n + 1); for (;;) { if (n >= *linesize - LSIZE / 2) *line = srealloc(*line, *linesize += LSIZE); c = getc(fp); if (c != EOF) { (*line)[n++] = c; (*line)[n] = '\0'; if (c == '\n') break; } else { if (n > 0) break; else return NULL; } } *length = n; return *line; } static void * srealloc(void *p, size_t n) { if ((p = realloc(p, n)) == NULL) { write(2, "Out of memory\n", 14); _exit(077); } return p; }