/*
$VER: main:c 1.0 (13-Sep-95) Copyright © by Lars Düning
*/

/*---------------------------------------------------------------------------
** Main module of MkDepend.
**
** Copyright © 1995  Lars Düning  -  All rights reserved.
** Permission granted for non-commercial use.
**---------------------------------------------------------------------------
** The module implements the argument parsing and the control flow.
**
** Usage:
**   mkdepend {-i|INCLUDE <includepath>[::<symbol>]}
**            {-x|EXCEPT  <filepattern>}
**            {-s|SUFFIX  <src_ext>{,<src_ext>}[:<obj_ext>] | :<obj_ext>}
**            {-p|OBJPAT  <src_ext>{,<src_ext>}:<obj_pattern>
**            [-f|MAKE    <makefile>]
**            [-v|VERBOSE]
**            {<filepattern>}
**
** The <objpattern> recognizes as meta-symbols:
**   %s: the full sourcename (w/o suffix)
**   %p: the path part of the sourcename
**   %n: the base of the sourcename (w/o suffix)
**   %x: the character x for every x != s, p or n.
**
**---------------------------------------------------------------------------
** C: DICE 3.01
**---------------------------------------------------------------------------
** [lars] Lars Düning; Am Wendenwehr 25; D-38114-Braunschweig;
**                     Germany; Tel. 49-531-345692
**---------------------------------------------------------------------------
** 11-Sep-95 [lars]
** 13-Sep-95 [lars] current
**---------------------------------------------------------------------------
*/

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

#include <dos/dos.h>
#include <dos/rdargs.h>
#include <exec/types.h>
#include <exec/libraries.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>

#include "reader.h"
#include "nodes.h"

extern struct Library *SysBase;

/*-------------------------------------------------------------------------*/

/* Release version number, should match the version number in the Makefiles */

#define VERSION "1.0"

/* Dynamic string array */

typedef struct sarray
{
  int     size;   /* Number of used strings */
  int     alloc;  /* Number of allocated string pointers */
  char ** strs;   /* Array of string pointers */
} SArray;

/* Program arguments */

static short    bVerbose = FALSE;          /* TRUE: Verbose action */
static SArray   aFiles   = { 0, 0, NULL }; /* Source files */
static SArray   aAvoid   = { 0, 0, NULL }; /* Source files to avoid */
static SArray   aIncl    = { 0, 0, NULL }; /* Include paths */
static SArray   aSymbol  = { 0, 0, NULL }; /* Include path symbols */
static SArray   aSrcExt  = { 0, 0, NULL }; /* Source file extensions */
static SArray   aObjExt  = { 0, 0, NULL }; /* Associated object file extensions */
static SArray   aObjPat  = { 0, 0, NULL }; /* Associated object file patterns */
static char   * sObjExt  = NULL;           /* Default object file extension */
static char   * sMake    = NULL;           /* Name of the Makefile */

/* Misc Variables */

static char * aPgmName = NULL;  /* Name of the program executable */
char * aVersion = "$VER: MkDepend " VERSION " (" __DATE__ ")";

static struct stat aMakeStat;    /* Stat buffer for the Makefile */
static int         bMakeExists;  /* TRUE: Old Makefile exists */

static int         returncode = RETURN_OK;   /* Global return code */

/*-----------------------------------------------------------------------*/
static void
set_rc (int code, int bAbort)

/* Set the global returncode to <code> given that <code> is higher
 * than the current one.
 * If <bAbort>, exit immediately then.
 */

{
  if (code > returncode)
    returncode = code;
  if (bAbort)
    exit(returncode);
}

/*-----------------------------------------------------------------------*/
static int
chmod (char *file, long mode)

/* Set the access mode of a file.
 * For Amiga-OS, only the owner access can be set.
 */

{
  BPTR lock;
  struct FileInfoBlock *info;
  int rc;

  /* Unix mode 'rwx??????' => DOS mode 'rwxw' aka 'rwxd'.
  ** Well, this should be done using the FIB* constants from dos.h
  ** but they will hardly change and 'knowing' them keeps this a one-liner.
  ** Note that the Amiga-OS bits disallow the operation when set.
  */
  mode = ~((mode & 0700) >> 5 | (mode & 0200) >> 7) & 017;
  info = (struct FileInfoBlock *) malloc(sizeof (struct FileInfoBlock));
  if (info == NULL) return -1;
  rc = -1;
  if ((lock = Lock(file, SHARED_LOCK)) == NULL) goto chmod_exit;
  if (Examine(lock, info) == DOSFALSE) goto chmod_exit;
  UnLock(lock);
  if (SetProtection(file, (info->fib_Protection & (~017)) | mode) != DOSFALSE)
    rc = 0;
chmod_exit:
  free (info);
  return rc;
}

/*-----------------------------------------------------------------------*/
static void
CheckStacksize (LONG try_size)

/* Check the stacksize of this process against try_size.
 * If the actual stacksize is smaller, print a message and exit.
 */

{
  struct Task *pThis;

  pThis = (struct Task *)FindTask(NULL);

  /* Sanity checks, shouldn't happen anyway */
  assert(pThis && pThis->tc_Node.ln_Type == NT_PROCESS);

  if ((LONG)((char *)pThis->tc_SPUpper - (char *)pThis->tc_SPLower) < try_size)
  {
    printf("%s: Needs at least %ld KByte stack.\n", aPgmName, (try_size+512) >> 10);
    exit(RETURN_ERROR);
  }
}

/*-------------------------------------------------------------------------*/
static void
check_os2 (void)

/* Check if OS2 is available. If not, print a message and exit.
 */

{
  char *sNeedsOS2 = "Fatal: Needs OS 2.0 or better.\n";

  if (SysBase->lib_Version < 36)
  {
    Write(Output(), sNeedsOS2, strlen(sNeedsOS2));
    set_rc(RETURN_FAIL, 1);
  }
}

/*-------------------------------------------------------------------------*/
static void
exit_doserr (int code)

/* Print a message according to IoErr(), then exit with <code>.
 */

{
  PrintFault(IoErr(), aPgmName);
  set_rc(code, 1);
}

/*-------------------------------------------------------------------------*/
static void
exit_nomem (int code)

/* Print a "Out of Memory" message and exit with <code>.
 */

{
  printf("%s: Out of memory.\n", aPgmName);
  set_rc(code, 1);
}

/*-------------------------------------------------------------------------*/
static int
array_addfile (SArray *pArray, char * pName)

/* Add a copy of <*pName> to the string array <pArray>.
 * Return 0 on success, non-0 on error.
 * If pName is NULL, the array is simply extended by a NULL pointer.
 */

{
  char ** pStrs;
  assert(pArray->size <= pArray->alloc);
  if (pArray->size+1 > pArray->alloc)
  {
    pStrs = (char **)realloc(pArray->strs, sizeof(char *)*(pArray->alloc+4));
    if (!pStrs)
      return 1;
    pArray->strs = pStrs;
    pArray->alloc += 4;
  }
  else
    pStrs = pArray->strs;
  if (pName)
  {
    pStrs[pArray->size] = strdup(pName);
    if (!pStrs[pArray->size])
      return 1;
  }
  else
    pStrs[pArray->size] = NULL;
  pArray->size++;
  return 0;
}

/*-------------------------------------------------------------------------*/
static int
array_addlist (SArray *pArray, int count, char ** pList)

/* Add the <count> string pointers from <pList> to string array <pArray>.
 * pList may be freed after return, the strings themselves are still
 * referenced (by pArray then).
 * Return 0 on success, non-0 else.
 */

{
  char ** pStrs;
  int     i;

  assert(pList);
  if (!count)
    return 0;
  assert(pArray->size <= pArray->alloc);
  if (pArray->size+count > pArray->alloc)
  {
    pStrs = (char **)realloc(pArray->strs, sizeof(char *)*(pArray->alloc+count));
    if (!pStrs)
      return 1;
    pArray->strs = pStrs;
    pArray->alloc += count;
  }
  else
    pStrs = pArray->strs;
  for (i = 0; i < count; i++)
    pStrs[pArray->size++] = pList[i];
  return 0;
}

/*-------------------------------------------------------------------------*/
static int
add_expfile (SArray *pArray, char * pName)

/* Glob filename <*pName> and add the filenames to string array <pArray>.
 * Return 0 on success, non-0 else.
 *
 * The function uses DICE' supplied function expand_args() which does not
 * implement all possible wildcard patterns.
 */

{
  char  * argv[] = { NULL, pName, NULL };
  int     argc = 2;
  char ** xargv;
  int     xargc;
  int     rc;

  if (expand_args(argc, argv, &xargc, &xargv))
  {
    printf("%s: Error expanding %s\n", aPgmName, pName);
    set_rc(RETURN_ERROR, 1);
  }
  assert(xargc > 1);

  rc = 0;
  if (xargc > 1)
  {
    rc = array_addlist(pArray, xargc-1, xargv+1);
    if (rc)
    {
      while (--xargc > 0)
        if (xargv[xargc])
          free(xargv[xargc]);
    }
    free(xargv);
  }

  return rc;
}

/*-------------------------------------------------------------------------*/
static void
getargs (void)

/* Get the arguments from the commandline.
 * Unfortunately we have to emulate ReadArgs() as we have several /M values
 * in our template.
 */

{
  int  i;                 /* all purpose */

  #define BUFSIZE 1024
  char aBuffer[BUFSIZE];  /* Argument buffer */
  char aArgBuf[BUFSIZE];  /* Source buffer for separator less arguments */
  long item, arg;         /* Result from ReadItem(), FindArg() */
  struct CSource aArgSrc; /* For postponed arguments */

  enum {
    NoArg, QuesFound, OtherArg, QuesPending
  } eHelp;
  enum {
    Standard, ParseMake, ParseIncl, ParseExcept, ParseSuffix, ParseObjpat
  } eMulti;

  /* General command template */
  char * sTemplate
    = "-F=MAKE/K,-I=INCLUDE/K,-X=EXCEPT/K,-S=SUFFIX/K,-P=OBJPAT/K,-V=VERBOSE/S,FILES/M";
  #define TEMP_MAKE    0
  #define TEMP_INCLUDE 1
  #define TEMP_EXCEPT  2
  #define TEMP_SUFFIX  3
  #define TEMP_PATTERN 4
  #define TEMP_VERBOSE 5
  #define TEMP_FILES   6

  /* Options which allow for no separator */
  char * sAbbrev
    = "-F/K,-I/K,-X/K,-S/K,-P/K";
  #define ABBREV_MAKE    0
  #define ABBREV_INCLUDE 1
  #define ABBREV_EXCEPT  2
  #define ABBREV_SUFFIX  3
  #define ABBREV_PATTERN 4

  /* Translation table abbrev indices to template indices */
  int aA2T[] = { TEMP_MAKE, TEMP_INCLUDE, TEMP_EXCEPT, TEMP_SUFFIX, TEMP_PATTERN };

  eHelp = NoArg;
  eMulti = Standard;
  aArgSrc.CS_Buffer = NULL;
  while(1)
  {
    if (eHelp == QuesPending)
    {
      aBuffer[0] = '?';
      aBuffer[1] = '\0';
      item = ITEM_UNQUOTED;
      eHelp = OtherArg;
    }
    else
    {
      aBuffer[0] = '\0';
      item = ReadItem(aBuffer, BUFSIZE, aArgSrc.CS_Buffer ? &aArgSrc : NULL);
    }

    if (ITEM_EQUAL == item)
      item = ITEM_UNQUOTED;
    if (ITEM_ERROR == item)
      exit_doserr(RETURN_ERROR);

    if (ITEM_NOTHING == item)
    {
      if (!aArgSrc.CS_Buffer && eHelp != QuesFound)
        break; /* outer while(1) */
      if (aArgSrc.CS_Buffer)
        aArgSrc.CS_Buffer = NULL;
      else
      {
        FGetC(Input()); /* re-read newline */
        Write(Output(), sTemplate, strlen(sTemplate));
        Write(Output(), ": ", 2);
        eHelp = NoArg;
        eMulti = Standard;
      }
      continue;
    }

    if (eHelp == NoArg && eMulti == Standard && !strcmp(aBuffer, "?"))
    {
      eHelp = QuesFound;
      continue;
    }

    /* At this point, we have an arg beside '?' so mark the '?'
     * for reinsertion
     */
    if (eHelp == QuesFound)
    {
      eHelp = QuesPending;
    }

    /* Check if the argument is a keyword */
    arg = -1;
    if (ITEM_UNQUOTED == item && !aArgSrc.CS_Buffer)
    {
      /* Shortcuts need no separator */
      if (aBuffer[0] == '-' && aBuffer[1] != '\0' && aBuffer[2] != '\0')
      {
        char aTmp[3] = { '-', aBuffer[1], '\0' };
        arg = FindArg(sAbbrev, aTmp);
        if (arg != -1)
        {
          int len;
          arg = aA2T[arg];
          strcpy(aArgBuf, aBuffer+2);
          len = strlen(aArgBuf);
          aArgBuf[len] = '\n';   /* ReadItem() needs this as terminator */
          aArgBuf[len+1] = '\0';
          aBuffer[2] = '\0';
          aArgSrc.CS_Buffer = aArgBuf;
          aArgSrc.CS_Length = len+1;
          aArgSrc.CS_CurChr = 0;
        }
      }
      if (arg == -1)
        arg = FindArg(sTemplate, aBuffer);
    }
    /* arg == -1: aBuffer is an argument
     * arg != -1: bufindex == 0: aBuffer is keyword
     */

    /* Evaluate keyword if any */
    switch(arg)
    {
    case TEMP_MAKE:
      eMulti = ParseMake;
      break;
    case TEMP_INCLUDE:
      eMulti = ParseIncl;
      break;
    case TEMP_EXCEPT:
      eMulti = ParseExcept;
      break;
    case TEMP_SUFFIX:
      eMulti = ParseSuffix;
      break;
    case TEMP_PATTERN:
      eMulti = ParseObjpat;
      break;
    case TEMP_VERBOSE:
      bVerbose = 1;
      break;
    default:
      assert(arg == -1);
      break;
    }

    if (arg != -1)
      continue; /* of while(1) */

    /* Assign argument value (if any) */
    switch (eMulti)
    {
    case Standard:
      if (ITEM_QUOTED == item)
        i = array_addfile(&aFiles, aBuffer);
      else
        i = add_expfile(&aFiles, aBuffer);
      if (i)
        exit_nomem(RETURN_FAIL);
      break;

    case ParseMake:
      if (sMake)
        free(sMake);
      sMake = strdup(aBuffer);
      if (!sMake)
        exit_nomem(RETURN_FAIL);
      break;

    case ParseExcept:
      if (ITEM_QUOTED == item)
        i = array_addfile(&aAvoid, aBuffer);
      else
        i = add_expfile(&aAvoid, aBuffer);
      if (i)
        exit_nomem(RETURN_FAIL);
      break;

    case ParseIncl:
      {
        char * pSym, * pMark;

        /* Allow for <path>::<symbol> notation */
        pSym = NULL;
        pMark = aBuffer+strlen(aBuffer);
        if (pMark != aBuffer)
        {
          pMark--;
          while (!pSym && pMark >= aBuffer+1)
          {
            if (':' != *pMark)
            {
              pMark--;
              continue;
            }
            if (':' != *(pMark-1))
            {
              pMark -= 2;
              continue;
            }
            pSym = pMark+1;
            *(pMark-1) = '\0';
          }
        }
        if (pSym && !strlen(pSym))
          pSym = NULL;
        i = array_addfile(&aSymbol, pSym);
        if (i)
          exit_nomem(RETURN_FAIL);

        /* Make sure <path> ends in a ':' or '/' */
        pMark = aBuffer+strlen(aBuffer);
        if (pMark > aBuffer && ':' != *(pMark-1) && '/' != *(pMark-1))
        {
          *pMark = '/';
          *(pMark+1) = '\0';
        }
        i = array_addfile(&aIncl, aBuffer);
        if (i)
          exit_nomem(RETURN_FAIL);
      }
      break;

    case ParseSuffix:
    case ParseObjpat:
      {
        char * pMark, *pOsfix;

        /* Allow for <src_suffix>:<obj_suffix> notation */
        pOsfix = NULL;
        pMark = aBuffer+strlen(aBuffer);
        if (pMark != aBuffer)
        {
          pMark--;
          while (!pOsfix && pMark >= aBuffer)
          {
            if (':' != *pMark)
            {
              pMark--;
              continue;
            }
            pOsfix = pMark+1;
            *pMark = '\0';
          }
        }
        if (pOsfix && !strlen(pOsfix))
          pOsfix = NULL;

        if (ParseObjpat == eMulti && !pOsfix)
        {
          printf("%s: Object pattern missing in argument.\n", aPgmName);
          set_rc(RETURN_WARN, 0);
          break;
        }

        /* :<obj_suffix> alone defines default object suffix */
        if (pOsfix && !strlen(aBuffer))
        {
          if (sObjExt)
            free(sObjExt);
          sObjExt = strdup(pOsfix);
          if (!sObjExt)
            exit_nomem(RETURN_FAIL);
        }
        else
        {
          char * pSfix;

          /* Allow for <sfix1>,<sfix2>,...,<sfixn> for source suffixes */
          for ( pSfix = aBuffer
              ; pMark = strchr(pSfix, ',')
              ; pSfix = pMark+1
              )
          {
            *pMark = '\0';
            if (strlen(pSfix))
            {
              if (   array_addfile(&aSrcExt, pSfix)
                  || (ParseSuffix == eMulti ? array_addfile(&aObjExt, pOsfix)
                                            : array_addfile(&aObjPat, pOsfix)
                     )
                 )
                exit_nomem(RETURN_FAIL);
            }
          }
          if (strlen(pSfix))
          {
            if (   array_addfile(&aSrcExt, pSfix)
                || (ParseSuffix == eMulti ? array_addfile(&aObjExt, pOsfix)
                                          : array_addfile(&aObjPat, pOsfix)
                   )
               )
              exit_nomem(RETURN_FAIL);
          }
        }
      }
      break;

    default:
      assert(0);
    }

    eMulti = Standard;
  } /* while(1) */

  #undef BUFSIZE
  #undef TEMP_MAKE
  #undef TEMP_INCLUDE
  #undef TEMP_EXCEPT
  #undef TEMP_SUFFIX
  #undef TEMP_PATTERN
  #undef TEMP_VERBOSE
  #undef TEMP_FILES
  #undef ABBREV_MAKE
  #undef ABBREV_INCLUDE
  #undef ABBREV_EXCEPT
  #undef ABBREV_SUFFIX
  #undef ABBREV_PATTERN
}

/*-------------------------------------------------------------------------*/
static int
readfiles (void)

/* Read and analyse all files.
 * If a file can't be read, print a message.
 * Return 0 if all went well, RETURN_WARN if one of the files could not be
 * found, RETURN_ERROR if one of the files could not be read properly.
 */

{
  int           rc;       /* Return value */
  struct stat   aStat;    /* buffer for stat() */
  Node        * pNode;    /* Node of the file under examination */
  int           srcRead;  /* Number of source files read */
  int           index;    /* index in the aIncl array */
  char          aName[FILENAME_MAX+1];
  int           i;
  const char  * pName;    /* Includefile name returned by reader */

  rc = 0;
  srcRead = 0;
  aName[FILENAME_MAX] = '\0';

  while (pNode = nodes_todo())
  {
    /* Search the correct directory to read from */
    index = -1;
    pNode->iInclude = 0;
    strcpy(aName, pNode->pName);
    i = stat(aName, &aStat);
    if (i && !(pNode->flags & NODE_SOURCE))
    {
      for (index = 0; i && index < aIncl.size; index++)
      {
        strcpy(aName, aIncl.strs[index]);
        strcat(aName, pNode->pName);
        assert(aName[FILENAME_MAX] == '\0');
        i = stat(aName, &aStat);
      }
      if (!i)
        pNode->iInclude = index;
    }
    if (i || !reader_open(aName))
    {
      if (!i)
        perror("mkdepend");
      if (rc < RETURN_ERROR)
        rc = RETURN_WARN;
      printf("%s: Warning: Cant't read '%s'.\n", aPgmName, aName);
      continue;
    }
    if (bVerbose)
    {
      printf(" reading %-65s\r", aName); fflush(stdout);
    }
    while (pName = reader_get())
    {
      if (nodes_depend(pNode, pName))
      {
        if (bVerbose)
          printf("%+-78s\r", "");
        reader_close();
        exit_nomem(RETURN_FAIL);
      }
    }
    if (bVerbose)
      printf("%+-78s\r", "");
    if (!reader_eof() || reader_close())
    {
      perror("mkdepend");
      printf("%s: Error reading '%s'\n", aPgmName, aName);
      rc = RETURN_ERROR;
    }
    else if (pNode->flags & NODE_SOURCE)
    {
      srcRead++;
    }
  } /* while (nodes_todo()) */
  if (!srcRead)
  {
    printf("%s: No source file read.\n", aPgmName);
    rc = RETURN_ERROR;
  }
  if (bVerbose)
    fflush(stdout);
  return rc;
}

/*-------------------------------------------------------------------------*/
static void
make_objname ( char *pBuf, const char *pName, int slen
             , const char * pObjExt, const char *pObjPat
             )

/* Construct the name of the dependency target.
 *
 *   pBuf   : Buffer to construct the name in.
 *   pName  : Name of the sourcefile.
 *   slen   : Length of the sourcefile suffix.
 *   pObjExt: Object extensions for this sourcefile, may be NULL.
 *   pObjPat: Object pattern for this sourcefile, may be NULL.
 *
 * If pObjPat is not NULL, the pattern is used to construct the name.
 * If pObjPat is NULL, the pObjExt string is appended to the sourcefile
 * name (minus its source suffix) to construct the name. If pObjExt is
 * NULL, the default object extension is used.
 */

{
  int nlen, plen;
  char * pBasename;
  char * pSrc, *pDst, ch;

  nlen = strlen(pName)-slen;
  if (!pObjPat)
  {
    strcpy(pBuf, pName);
    if (pObjExt)
      strcpy(pBuf+nlen, pObjExt);
    else
      strcpy(pBuf+nlen, sObjExt);
    return;
  }
  pBasename = FilePart(pName);
  plen = pBasename-pName;
  pSrc = pObjPat;
  pDst = pBuf;
  while ('\0' != (ch = *pSrc++))
  {
    if (ch != '%')
    {
      *pDst++ = ch;
      continue;
    }
    ch = *pSrc++;
    switch(ch)
    {
    case 's':
      strncpy(pDst, pName, nlen);
      pDst += nlen;
      break;

    case 'p':
      if (pName != pBasename)
      {
        strncpy(pDst, pName, plen);
        pDst += plen;
      }
      break;

    case 'n':
      strncpy(pDst, pBasename, nlen-plen);
      pDst += nlen-plen;
      break;

    case '%':
      *pDst++ = '%';
      break;

    default:
      pSrc--; /* to read it again */
      break;
    }
  }
  *pDst = '\0';
}

/*-------------------------------------------------------------------------*/
static int
output (void)

/* Output the collected dependencies into the Makefile.
 * Return 0 on success, RETURN_WARN on a mild error, RETURN_ERROR on a
 * severe error.
 */

{
  int       rc;
  char    * pBackup;  /* Name of the Makefilebackup */
  Node    * pNode;

  pBackup = NULL;

  /* Rename old Makefile if necessary */
  if (bMakeExists)
  {
    pBackup = (char *)malloc(strlen(sMake)+4);
    strcpy(pBackup, sMake);
    strcat(pBackup, ".bak");
    remove(pBackup);
    if (rename(sMake, pBackup))
    {
      perror("mkdepend");
      printf("%s: Can't rename '%s' to '%s'.\n", aPgmName, sMake, pBackup);
      free(pBackup);
      return RETURN_ERROR;
    }
  }

  rc = RETURN_OK;
  do
  {
    /* Open files */
    if (reader_openrw(pBackup, sMake))
    {
      perror("mkdepend");
      printf("%s: Can't write '%s'\n", aPgmName, sMake);
      rc = RETURN_ERROR;
      break;
    }

    if (bVerbose)
    {
      printf(" %s '%s'\r", bMakeExists ? "updating" : "creating", sMake); fflush(stdout);
    }

    /* Copy the Makefile up to the tagline */
    if (reader_copymake("# --- DO NOT MODIFY THIS LINE -- AUTO-DEPENDS FOLLOW ---\n"))
    {
      int i = errno;
      if (bVerbose)
        printf("%+-78s\r", "");
      errno = i;
      perror("mkdepend");
      printf("%s: Error copying '%s' to '%s'.\n", aPgmName, pBackup, sMake);
      rc = RETURN_ERROR;
      break;
    }

    /* Walk and output the dependencies */
    nodes_initwalk();
    while (rc != RETURN_ERROR && (pNode = nodes_inorder()))
    {
      if ((pNode->flags & (NODE_SOURCE|NODE_AVOID)) == NODE_SOURCE)
      {
        Node    * pRNode;
        NodeRef * pList, * pRef;
        int       suffix;         /* Suffix index */
        int       slen;           /* Linelen so far */
        int       len;
        char      aObjname[FILENAME_MAX+1];

        aObjname[FILENAME_MAX] = '\0';

        pList = nodes_deplist(pNode);
        assert(pList);
        pRef = pList;

        /* Check for a given suffix.
         * Search backwards in case later definitions overwrote
         * earlier ones.
         */
        slen = strlen(pNode->pName);
        for (suffix = aSrcExt.size-1; suffix >= 0; suffix--)
        {
          len = strlen(aSrcExt.strs[suffix]);
          if (!strcmp(pNode->pName+slen-len, aSrcExt.strs[suffix]))
            break;
        }

        /* Construct the name of the dependency target and write it */
        if (suffix >= 0)
        {
          make_objname( aObjname, pNode->pName, len
                      , aObjExt.size ? aObjExt.strs[suffix] : NULL
                      , aObjPat.size ? aObjPat.strs[suffix] : NULL
                      );
          assert(aObjname[FILENAME_MAX] == '\0');
          if (reader_write(aObjname))
          {
            rc = RETURN_ERROR;
            break; /* outer while */
          }
          slen = strlen(aObjname);
        }
        else
        {
          if (reader_write(pNode->pName))
          {
            rc = RETURN_ERROR;
            break; /* outer while */
          }
          pRef = pRef->pNext;
        }

        if (reader_write(" : "))
        {
          rc = RETURN_ERROR;
          break; /* outer while */
        }
        slen += 3;

        for ( ; rc != RETURN_ERROR && pRef; pRef = pRef->pNext)
        {
          pRNode = pRef->pNode;
          /* Construct the name of the dependee */
          aObjname[0] = ' ';
          aObjname[1] = '\0';
          if (pRNode->iInclude)
          {
            if (aSymbol.strs[pRNode->iInclude-1])
              strcat(aObjname, aSymbol.strs[pRNode->iInclude-1]);
            else
              strcat(aObjname, aIncl.strs[pRNode->iInclude-1]);
            strcat(aObjname, pRNode->pName);
          }
          else
          {
            strcat(aObjname, pRNode->pName);
          }
          assert(aObjname[FILENAME_MAX] == '\0');
          len = strlen(aObjname);

          /* Fit it into the line */
          if (slen > 0)
          {
            if (slen+len > 75)
            {
              if (reader_writen(" \\\n", 3))
              {
                rc = RETURN_ERROR;
                break; /* inner while */
              }
              slen = 0;
            }
          }
          if (!slen)
          {
            if (reader_writen("   ", 3))
            {
              rc = RETURN_ERROR;
              break; /* inner while */
            }
            slen = 3;
          }
          if (reader_writen(aObjname, len))
          {
            rc = RETURN_ERROR;
            break; /* inner while */
          }
          slen += len;
        } /* for () */
        nodes_freelist(pList);
        if (slen && reader_writen("\n\n", 2))
        {
          rc = RETURN_ERROR;
          break; /* outer while */
        }
      } /* if (source node) */
    }  /* while (treewalk */

    if (rc == RETURN_ERROR)
    {
      int i = errno;
      if (bVerbose)
        printf("%+-78s\r", "");
      errno = i;
      perror("mkdepend");
      printf("%s: Error writing '%s'.\n", aPgmName, sMake);
      rc = RETURN_ERROR;
      break;
    }

    /* Finish up */
    if (bVerbose)
      printf("%+-78s\r", "");

    if (reader_writeflush() || reader_close())
    {
      perror("mkdepend");
      printf("%s: Error writing '%s'.\n", aPgmName, sMake);
      rc = RETURN_ERROR;
      break;
    }

    if (bMakeExists && chmod(sMake, aMakeStat.st_mode))
    {
      /* perror("mkdepend"); */
      printf("%s: Warning: Can't update mode of '%s'.\n", aPgmName, sMake);
      rc = RETURN_WARN;
    }

    if (pBackup && remove(pBackup))
    {
      perror("mkdepend");
      printf("%s: Warning: Can't remove backup '%s'.\n", aPgmName, pBackup);
      rc = RETURN_WARN;
      break;
    }

  } while(0);

  /* Error cleanup */
  if (rc == RETURN_ERROR)
  {
    reader_close();
    remove(sMake);
    if (pBackup && rename(pBackup, sMake))
    {
      perror("mkdepend");
      printf("%s: Can't restore '%s' from backup '%s'\n", aPgmName, sMake, pBackup);
    }
  }

  if (bVerbose && rc != RETURN_ERROR)
    printf("%+-78s\r", "");

  if (pBackup)
    free(pBackup);

  return rc;
}

/*-------------------------------------------------------------------------*/
int main (int argc, char *argv[])

{
  int i;

  check_os2();

  /* Determine the program executables name */
  aPgmName = strdup(FilePart(argv[0]));
  if (!aPgmName)
    aPgmName = "mkdepend";

  CheckStacksize(10240);

  reader_init();

  /* Get arguments, set up defaults */
  getargs();
  if (!aFiles.size)
  {
    if (add_expfile(&aFiles, "#?.c"))
      exit_nomem(RETURN_FAIL);
  }
  if (!sObjExt)
    sObjExt = ".o";

  if (!aSrcExt.size)
  {
    if (array_addfile(&aSrcExt, ".c") || array_addfile(&aObjExt, NULL))
      exit_nomem(RETURN_FAIL);
  }

  if (bVerbose)
  {
    printf("MkDepend %s (%s) -- Make Dependency Generator\n", VERSION, __DATE__);
    puts("Copyright © 1995 Lars Düning.");
    putchar('\n');
  }

  /* Look for the Makefile to modify */
  if (sMake)
  {
    bMakeExists = !stat(sMake, &aMakeStat);
  }
  else
  {
    sMake = "Makefile";
    bMakeExists = !stat(sMake, &aMakeStat);
    if (!bMakeExists)
    {
      sMake = "Makefile.mk";
      bMakeExists = !stat(sMake, &aMakeStat);
    }
    if (!bMakeExists)
    {
      sMake = "DMakefile";
      bMakeExists = !stat(sMake, &aMakeStat);
    }
    if (!bMakeExists)
    {
      sMake = "SMakefile";
      bMakeExists = !stat(sMake, &aMakeStat);
    }
    if (!bMakeExists)
      sMake = "Makefile";
  }

  /* Add the source files to the tree */
  if (!aFiles.size)
  {
    printf("%s: No files given.\n", aPgmName);
    set_rc(RETURN_WARN, 1);
  }
  for (i = 0; i < aFiles.size; i++)
  {
    if (nodes_addsource(aFiles.strs[i], 0))
      exit_nomem(RETURN_FAIL);
  }

  /* Mark the exceptional files */
  for (i = 0; i < aAvoid.size; i++)
  {
    if (nodes_addsource(aAvoid.strs[i], 1))
      exit_nomem(RETURN_FAIL);
  }

  /* Read and analyse all those files */
  set_rc(readfiles(), 0);

  if (returncode < RETURN_ERROR)
    set_rc(output(), 0);

  if (returncode < RETURN_ERROR && bVerbose)
    printf("%s '%s'.\n", bMakeExists ? "Updated" : "Created", sMake);

  return returncode;
}

/***************************************************************************/
