/* ************************************************************************ *\
*                                                                          *
* File:        md.c                                                        *
*                                                                          *
*      Updates makefiles from the .n dependency files generated by the     *
*      -MD option to "cc" (and "cpp").                                     *
*                                                                          *
* Abstract:                                                                *
*                                                                          *
*      Basically, "md" does two things:                                    *
*      1) It processes the raw dependency files produced by the cpp -MD     *
*         option.  There is one line in the file for every #include        *
*         encountered, but there are repeats and patterns like             *
*         .../dir1/../dir2 appear which should reduce to .../dir2          *
*         Md canonicalizes and flushes repeats from the dependency         *
*         list.  It also sorts the file names and "fills" them to a 78     *
*         character line.                                                  *
*      2) Md also updates the makefile directly with the dependency        *
*         information, so the .d file can be thrown away (-- -d option)    *
*         This is done to save space.  Md assumes that dependency          *
*         information in the makefile is sorted by .o file name and it     *
*         procedes to merge in (add/or replace [as appropriate])  the new  *
*         dependency lines that it has generated.  For time effeciency,    *
*         Md assumes that any .d files it is given that were created       *
*         before the creation date of the "makefile" were processed        *
*         already.  It ignores them unless the force flag (-f) is given.   *
*                                                                          *
* Arguments:                                                               *
*                                                                          *
*      -d      delete the .d file after it is processed                    *
*      -f      force an update of the dependencies in the makefile         *
*              even though the makefile is more recent than the .n file    *
*              (This implies that md has been run already.)                *
*      -m      specify the makefile to be upgraded.  The defaults are      *
*              "makefile" and then "Makefile".                             *
*      -u      like -m above, but the file will be created if necessary    *
*      -o      specify an output file for the dependencies other than a    *
*              makefile                                                    *
*      -v      set the verbose flag                                        *
*      -x      expunge old dependency info from makefile                   *
*      -D      subswitch for debugging.  can be followed by any of         *
*              "c", "d", "m", "o", "t", "D" meaning:                       *
*              c       show file contents                                  *
*              d       show new dependency crunching                       *
*              m       show generation of makefile                         *
*              o       show files being opened                             *
*              t       show time comparisons                               *
*              D       show very low level debugging                       *
*                                                                          *
* Author:      Robert V. Baron                                             *
*              Copyright (c) 1986 by Robert V. Baron                       *
*                                                                          *
* HISTORY                                                                  *
* 29-Apr-87  Robert Baron (rvb) at Carnegie-Mellon University
*      If specified -u file does not exist, assume it is empty and
*      generate one.  As a sanity check, it must be possible to create
*      the output file.
*      Also, generalized fix below to handle any case of . as a
*      file name.
*
* 25-Mar-87  Mary Thompson (mrt) at Carnegie Mellon
*      Fixed up pathnamecanonicalization to recognize .// and
*      drop the second / as well. mmax cpp generates this form.
*
*  6-Jan-87  Robert Baron (rvb) at Carnegie-Mellon University
*      Fixed up pathname canonicalization to that ../../, etc would be
*      handled correctly.
*      Also made "force" on by default.
*
* 16-Mar-86  Robert Baron (rvb) at Carnegie-Mellon University
*              Created 4/16/86                                             *
*                                                                          *
\* ************************************************************************ */


#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define LINESIZE 65536  //  NeXT_MOD

#define OUTLINELEN 79
#define IObuffer 50000
#define SALUTATION "# Dependencies for File:"
#define SALUTATIONLEN (sizeof SALUTATION - 1)
#define OLDSALUTATION "# DO NOT DELETE THIS LINE"
#define OLDSALUTATIONLEN (sizeof OLDSALUTATION - 1)

char file_array[IObuffer];      /* read file and store crunched names */
char dep_line[LINESIZE];        /* line being processed */
char dot_o[LINESIZE];           /* <foo.o>: prefix */
char *path_component[100];      /* stores components for a path while being
                                  crunched */

struct dep {                    /* stores paths that a file depends on */
       int len;
       char *str;
} dep_files[1000];
int dep_file_index;

qsort_strcmp(a, b)
struct dep *a, *b;
{
extern int strcmp();
       return strcmp(a->str, b->str);
}

char *outfile = (char *) 0;     /* generate dependency file */
FILE *out;

char *makefile = (char *) 0;    /* user supplied makefile name */
char *real_mak_name;            /* actual makefile name (if not supplied) */
char shadow_mak_name[LINESIZE]; /* changes done here then renamed */
FILE *mak;                      /* for reading makefile */
FILE *makout;                   /* for writing shadow */
char makbuf[LINESIZE];          /* one line buffer for makefile */
struct stat makstat;            /* stat of makefile for time comparisons */
int mak_eof = 0;                        /* eof seen on makefile */
FILE *find_mak(), *temp_mak();

int delete = 0;                 /* -d delete dependency file */
int debug = 0;
int     D_contents = 0;         /* print file contents */
int     D_depend = 0;           /* print dependency processing info */
int     D_make = 0;             /* print makefile processing info */
int     D_open = 0;             /* print after succesful open */
int     D_time = 0;             /* print time comparison info */
int force = 1;                  /* always update dependency info */
int update = 0;                 /* it's ok if the -m file does not exist */
int verbose = 0;                /* tell me something */
int expunge = 0;                /* first flush dependency stuff from makefile */


char *name;

static void scan_mak(FILE *, FILE *, char *);
static void finish_mak(FILE *, FILE *);

main(argc,argv)
register char **argv;
{
int size;

       name = *argv;
       {register char *cp =name;
               while (*cp) if (*cp++ == '/') name = cp;
       }

       for ( argv++ ; --argc ; argv++ ) { register char *token = *argv;
               if (*token++ != '-' || !*token)
                       break;
               else { register int flag;
                       for ( ; flag = *token++ ; ) {
                               switch (flag) {
                               case 'd':
                                       delete++;
                                       break;
                               case 'f':
                                       force++;
                                       break;
                               case 'u':
                                       update++;
                               case 'm':
                                       makefile = *++argv;
                                       if (--argc < 0) goto usage;
                                       break;
                               case 'o':
                                       outfile = *++argv;
                                       if (--argc < 0) goto usage;
                                       break;
                               case 'v':
                                       verbose++;
                                       break;
                               case 'x':
                                       expunge++;
                                       break;
                               case 'D':
                                       for ( ; flag = *token++ ; )
                                               switch (flag) {
                                               case 'c':
                                                       D_contents++;
                                                       break;
                                               case 'd':
                                                       D_depend++;
                                                       break;
                                               case 'm':
                                                       D_make++;
                                                       break;
                                               case 'o':
                                                       D_open++;
                                                       break;
                                               case 't':
                                                       D_time++;
                                                       break;
                                               case 'D':
                                                       debug++;
                                                       break;
                                               default:
                                                       goto letters;
                                               }
                                       goto newtoken;
                               default:
                                       goto usage;
                               }
letters: ;
                       }
               }
newtoken: ;
       }

       if (!expunge && argc < 1) goto usage;
       if ((int) outfile && (int) makefile)    /* not both */
               goto usage;

       if ((int) outfile) {
               /*
                * NeXT_MOD, For SGS stuff, in case still linked to master version
                */
               unlink(outfile);

               if ((out = fopen(outfile, "w")) == NULL) {
                       fprintf(stderr, "%s: outfile = \"%s\" ", name, outfile);
                       perror("fopen");
                       fflush(stdout), fflush(stderr);
                       exit(1);
               } else if (D_open)
                       printf("%s: opened outfile \"%s\"\n", name, outfile);
       } else if (mak = find_mak(makefile)) {
               makout = temp_mak();
               out = makout;
               if (expunge)
                       expunge_mak(mak, makout);
               else
                       skip_mak(mak, makout);
       } else if (mak_eof &&  /* non existent file == mt file */
                  (int)(makout = temp_mak())) { /* but we need to be able */
               out = makout;                    /* to write here */
       } else if (makefile) {
               fprintf(stderr, "%s: makefile \"%s\" can not be opened or stat'ed\n",
                       name, makefile);
               exit(2);
       }

       for (; argc--; argv++) {
               dep_file_index = 0;

               if (size = read_dep(*argv)) {

                       save_dot_o();
                       if (D_depend) printf("%s: dot_o = \"%s\"\n", name, dot_o);

                       parse_dep();
                       if (mak) scan_mak(mak, makout, dot_o);
                       if (out) output_dep(out);

                       if (delete)
                               unlink(*argv);
               }
       }

       if (mak) finish_mak(mak, makout);
       rename(shadow_mak_name, real_mak_name);
       exit(0);
usage:
       fprintf(stderr, "usage: md -f -Dcdmot -m makefile -o outputfile -v <file1> ... <filen>\n");
       exit(1);
}


read_dep(file)
register char *file;
{
register int fd;
register int size;
struct stat statbuf;

       if ((fd = open(file, 0)) < 0) {
               fprintf(stderr, "%s: file = \"%s\" ", name, file);
               perror("open");
               fflush(stdout), fflush(stderr);
               return 0;
       }
       if (D_open)
               printf("%s: opened dependency file \"%s\"\n", name, file);

       if (fstat(fd, &statbuf) < 0) {
               fprintf(stderr, "%s: file = \"%s\" ", name, file);
               perror("stat");
               fflush(stdout), fflush(stderr);
               goto out;
       }
       switch(statbuf.st_mode & S_IFMT) {
       case S_IFREG:
               if (D_time)
                       printf("%s: file time = %d\n", name, statbuf.st_mtime);

               if (statbuf.st_size > IObuffer) {
                       fprintf(stderr, "%s: file \"%s\" tooo big for IObuffer\n",
                               name, file);
                       goto out;
               } else if (force)
                       break;
               else if ((int) mak && statbuf.st_mtime < makstat.st_mtime) {
                       if (verbose || D_time)
                               fprintf(stderr, "%s: skipping \"%s\" %d < %d \"%s\"\n",
                                       name, file, statbuf.st_mtime, makstat.st_mtime,
                                       real_mak_name);
                       goto out;
               } else /* >=   =>ok */
                       break;
       case S_IFDIR:
       case S_IFLNK:
       case S_IFCHR:
       case S_IFBLK:
       case S_IFSOCK:
       default:
               fprintf(stderr, "%s: bad mode: 0%o on \"%s\"\n",
                       name, statbuf.st_mode, file);
               fflush(stdout), fflush(stderr);
               goto out;
       }

       if ((size = read(fd, file_array, sizeof (file_array))) < 0) {
               fprintf(stderr, "%s: file = \"%s\" ", name, file);
               perror("read");
               fflush(stdout), fflush(stderr);
               goto out;
       }
       file_array[size] = 0;

       if (close(fd) < 0) {
               fprintf(stderr, "%s: file = \"%s\" ", name, file);
               perror("close");
               fflush(stdout), fflush(stderr);
               return 0;
       }

       if (D_depend && D_contents)
               printf("file_array: \"%s\"\n", file_array);
       return size;
out: ;
       close(fd);
       return 0;
}

save_dot_o()
{
register char *cp = file_array;
register char *svp = dot_o;
register int c;

       while ((*svp++ = (c = *cp++)) && c != ':');
       *svp = 0;
}

parse_dep()
{
register char *lp = file_array;
register int c;

       while (*lp) {register char *tlp = lp;
                    register char *cp = dep_line;
                    register int i = 0;
                    int abspath = 0;
                    char oldc;
                    char *oldcp;

                       /* get a line to process */
               while ((c = *lp++) && c != '\n')
                 {
                   if (c == '\\')
                     lp++;             /* skip backslash newline */
                   else
                     *cp++ = c;
                 }
                 if (!c)
                   break;
               *cp = 0;
               cp = dep_line;
               lp[-1] = 0;
                       /* skip .o file name */
               while ((c = *cp++) && c != ':'); if (!c) continue;
next_filename:
               i = 0;
               abspath = 0;
               while ((c = *cp) && (c == ' ' || c == '\t')) cp++; if (!c) continue;

                       /* canonicalization processing */

                                       /* initial / is remembered */
               if (c == '/')
                       abspath++;

               while (c && c != ' ' && c != '\t') {
                       if (D_depend) printf("i = %d going \"%s\"\n", i, cp);
                                       /* kill \'s */
                       while ((c = *cp) && c == '/') cp++; if (!c) break;
                       path_component[i] = cp;
                                       /* swallow chars till next / or null */
                       while ((c = *cp++) && c != '/' && c != ' ' && c != '\t');
                       if (c) cp[-1]=0;/* end component C style */

                                       /* ignore . */;
                       if (!strcmp(path_component[i], "."))
                               ;       /* if "component" != .. */
                       else            /* don't reduce /component/.. to nothing */
                               i++;    /* there could be symbolic links! */
               }
                       /* reassemble components */
               oldc = c;               /* save c */
               oldcp = cp;             /* save cp */
               cp = tlp;               /* overwrite line in buffer */
               if (abspath)
                       *cp++ = '/';
               for (c=0; c<i; c++) {register char *ccp = path_component[c];
                       while (*cp++ = *ccp++);
                       *--cp = '/';
                       cp++;
               }
               *--cp = 0;

               c=dep_file_index++;
               dep_files[c].str = tlp;
               dep_files[c].len = cp - tlp;
               if (D_depend)
                       printf("%s: dep_file[%d] = \"%s\" Len %d\n",
                               name, dep_file_index - 1, tlp, cp - tlp);
               tlp = cp + 1;
               if (oldc)
                 {
                    cp = oldcp;
                    goto next_filename;
                 }
       }
}

output_dep(out)
FILE *out;
{
register int j;
register int size = 1000;
register int dot_o_len = strlen(dot_o);
register struct dep *dp = dep_files;
int written = 0;

       if (D_depend && debug)
               for(j = 0; j < dep_file_index; j++) {
                       printf("dep_files[%d] = %s\n", j, dep_files[j].str);
               }

       qsort(dep_files, dep_file_index, sizeof (struct dep), qsort_strcmp);

       if (D_depend && debug)
               for(j = 0; j < dep_file_index; j++) {
                       printf("dep_files[%d] = %s\n", j, dep_files[j].str);
               }

       fprintf(out, "%s %s", SALUTATION, dot_o);
       for(j = 0; j < dep_file_index; j++, dp++)
                                       {register int len = dp->len;
                                        register char *str = dp->str;
               if (j && len == (dp-1)->len && !strcmp(str, (dp-1)->str))
                       continue;
               written++;
               if (size + len + 1 > OUTLINELEN) {
                       fprintf(out, "\n%s %s", dot_o, str);
                       size = dot_o_len + len + 1;
               } else {
                       fprintf(out, " %s", str);
                       size += len + 1;
               }
       }
       fprintf(out, "\n");
       if (verbose)
               fprintf(stdout, "%s: \"%s\" %d => %d\n", name, dot_o, dep_file_index, written);
}

               /* process makefile */
FILE *
find_mak(file)
char *file;
{
FILE *mak;

       if ((int) file) {
               if ((mak = fopen(file, "r")) != NULL) {
                       real_mak_name = file;
               } else if (update) {
                       mak_eof = 1;
                       real_mak_name = file;
                       return NULL;
               } else {
                       fprintf(stderr, "%s: file = \"%s\" ", name, file);
                       perror("fopen");
                       fflush(stdout), fflush(stderr);
                       return NULL;
               }
       } else {
               if ((mak = fopen("makefile", "r")) != NULL) {
                       real_mak_name = "makefile";
               } else if ((mak = fopen("Makefile", "r")) != NULL) {
                       real_mak_name = "Makefile";
               } else return NULL;
       }

       if (fstat(fileno(mak), &makstat) < 0) {
               fprintf(stderr, "%s: file = \"%s\" ", name, real_mak_name);
               perror("stat");
               fflush(stdout), fflush(stderr);
               return NULL;
       }
       if (D_open)
               printf("%s: opened makefile \"%s\"\n", name, real_mak_name);
       if (D_time)
               printf("%s: makefile time = %d\n", name, makstat.st_mtime);

       return mak;
}

FILE *
temp_mak()
{
FILE *mak;

       strcpy(shadow_mak_name, real_mak_name);
       strcat(shadow_mak_name, ".md");

       /*
        * For SGS stuff, in case still linked to master version
        */
       unlink(shadow_mak_name);
       if ((mak = fopen(shadow_mak_name, "w")) == NULL) {
               fprintf(stderr, "%s: file = \"%s\" ", name, shadow_mak_name);
               perror("fopen");
               fflush(stdout), fflush(stderr);
               return NULL;
       }
       if (D_open)
               printf("%s: opened makefile.md \"%s\"\n", name, shadow_mak_name);

       return mak;
}

skip_mak(makin, makout)
register FILE *makin, *makout;
{
register int len = SALUTATIONLEN;

       if (D_make)
               printf("skipping in \"%s\"  ", real_mak_name);

       while (fgets(makbuf, LINESIZE, makin) != NULL) {
               if (D_make && D_contents)
                       printf("%s: \"%s\"\n", real_mak_name, makbuf);
               if (strncmp(makbuf, SALUTATION, len)) {
                       fputs(makbuf, makout);
               } else
                       break;
       }
       mak_eof = feof(makin);
       if (mak_eof)
               fclose(makin);
       if (D_make)
               printf("eof = %d str = \"%s\"", mak_eof, makbuf);
}

expunge_mak(makin, makout)
register FILE *makin, *makout;
{
register int len = SALUTATIONLEN;
register int oldlen = OLDSALUTATIONLEN;

       if (D_make)
               printf("expunging in \"%s\"  ", real_mak_name);

       while (fgets(makbuf, LINESIZE, makin) != NULL) {
               if (D_make && D_contents)
                       printf("%s: \"%s\"\n", real_mak_name, makbuf);
               if (! strncmp(makbuf, SALUTATION, len) ||
                   ! strncmp(makbuf, OLDSALUTATION, oldlen))
                       break;
               else
                       fputs(makbuf, makout);
       }
       mak_eof = 1;
       if (mak_eof)
               fclose(makin);
       if (D_make)
               printf("eof = %d str = \"%s\"", mak_eof, makbuf);
}

static void
scan_mak(FILE *makin, FILE *makout, char *file)
{
register char *cp = &makbuf[SALUTATIONLEN+1];
register int len = strlen(file);
register int ret;

       if (D_make)
               printf("scanning in \"%s\" for \"%s\"\n", real_mak_name, file);

       do {
               if (mak_eof)            /* don't scan any more */
                       return;

               ret = strncmp(cp, file, len);
               if (D_make)
                       printf("saw \"%s\" ret = %d\n", cp, ret);

               if (ret < 0) {          /* skip forward till match or greater */
                       fputs(makbuf, makout);          /* line we're looking at */
                       while (fgets(makbuf, LINESIZE, makin) != NULL) {
                               if (strncmp(makbuf, SALUTATION, SALUTATIONLEN)) {
                                       fputs(makbuf, makout);
                               } else
                                       break;
                       }
                       mak_eof = feof(makin);
                       if (mak_eof)
                               fclose(makin);
                       continue;
               } else if (ret == 0) {  /* flush match */
                       while (fgets(makbuf, LINESIZE, makin) != NULL) {
                               if (strncmp(makbuf, SALUTATION, SALUTATIONLEN)) {
                                       ;       /* flush old stuff */
                               } else
                                       break;
                       }
                       mak_eof = feof(makin);
                       if (mak_eof)
                               fclose(makin);
                       break;
               } else {                /* no luck this time */
                       break;
               }
       } while (1);
}

static void
finish_mak(FILE *makin, FILE *makout)
{
       if (mak_eof)            /* don't scan any more */
               return;

       if (D_make)
               printf("finishing in \"%s\"\n", real_mak_name);

       fputs(makbuf, makout);          /* line we're looking at */
       while (fgets(makbuf, LINESIZE, makin) != NULL) {
               fputs(makbuf, makout);
       }
}