/*
 * link.c -- linker main program that controls the linking process.
 */

#include "..\h\config.h"
#include "general.h"
#include "tproto.h"
#include "globals.h"
#include "link.h"
#include "..\h\paths.h"
#include "..\h\header.h"

#ifdef Header
#include "hdr.h"

#ifndef MaxHeader
#define MaxHeader MaxHdr
#endif					/* MaxHeader */

#endif					/* Header */

/*
 * Prototype.
 */

hidden	novalue	setexe	Params((char *fname));


/*
 * The following code is operating-system dependent [@link.01].  Include
 *  system-dependent files and declarations.
 */

#if PORT
   /* nothing to do */
Deliberate Syntax Error
#endif					/* PORT */

#if AMIGA || ATARI_ST || HIGHC_386 || MACINTOSH || VM || VMS
   /* nothing to do */
#endif					/* AMIGA || ATARI_ST || HIGHC_386 ... */

#if MSDOS
#if MICROSOFT || TURBO
#include <fcntl.h>
#endif					/* MICROSOFT || TURBO */
#endif					/* MSDOS */

#if MVS
char *routname;				/* real output file name */
#endif					/* MVS */

#if OS2
#if MICROSOFT
#include <fcntl.h>
#endif					/* MICROSOFT */
#endif					/* OS2 */

#if UNIX
#ifdef CRAY
#define word word_fubar
#include <sys\types.h>
#include <sys\stat.h>
#undef word
#else					/* CRAY */
#include <sys\types.h>
#include <sys\stat.h>
#endif					/* CRAY */
#endif					/* UNIX */

/*
 * End of operating-system specific code.
 */

FILE *infile;				/* input file (.u1 or .u2) */
FILE *outfile;				/* interpreter code output file */

#ifdef DeBugLinker
FILE *dbgfile;				/* debug file */
static char dbgname[MaxFileName];	/* debug file name */
#endif					/* DeBugLinker */

char inname[MaxFileName];		/* input file name */
static char icnname[MaxFileName];	/* icon source file name */

struct lfile *llfiles = NULL;		/* List of files to link */

#ifdef EvalTrace
int colmno = 0;				/* current source program colm number */
#endif					/* EvalTrace */

int lineno = 0;				/* current source program line number */
int fatals = 0;				/* number of errors encountered */

/*
 *  ilink - link a number of files, returning error count
 */
int ilink(ifiles,outname)
char **ifiles;
char *outname;
   {

   int i;
   struct lfile *lf,*lfls;
   char *filename;			/* name of current input file */

   linit();				/* initialize memory structures */
   while (*ifiles)
      alsolink(*ifiles++);		/* make initial list of files */

   /*
    * Phase I: load global information contained in .u2 files into
    *  data structures.
    *
    * The list of files to link is maintained as a queue with llfiles
    *  as the base.  lf moves along the list.  Each file is processed
    *  in turn by forming .u2 and .icn names from each file name, each
    *  of which ends in .u1.  The .u2 file is opened and globals is called
    *  to process it.  When the end of the list is reached, lf becomes
    *  NULL and the loop is terminated, completing phase I.  Note that
    *  link instructions in the .u2 file cause files to be added to list
    *  of files to link.
    */
   for (lf = llfiles; lf != NULL; lf = lf->lf_link) {
      filename = lf->lf_name;
      makename(inname, SourceDir, filename, U2Suffix);
      makename(icnname, TargetDir, filename, SourceSuffix);

#if MVS || VM
/*
 * Even though the ucode data is all reasonable text characters, use
 *  of text I/O may cause problems if a line is larger than LRECL.
 *  This is likely to be true with any compiler, though the precise
 *  disaster which results may vary.
 */
      infile = fopen(inname, ReadBinary);
#else
      infile = fopen(inname, ReadText);
#endif					/* MVS || VM */

      if (infile == NULL)
         quitf("cannot open %s",inname);
      readglob();
      fclose(infile);
      }

   /* Phase II: resolve undeclared variables and generate code. */

   /*
    * Open the output file.
    */

#ifdef WATERLOO_C_V3_0
   strcat(outname," (BIN");
#endif					/* WATERLOO_C_V3_0 */

#if MVS
   routname = outname;
   outfile = tmpfile();         /* write icode to temporary file to
                                   avoid fseek-PDS limitations */
#else					/* MVS */
   outfile = fopen(outname, WriteBinary);
#endif					/* MVS */

/*
 * The following code is operating-system dependent [@link.02].  Set
 *  untranslated mode if necessary.
 */

#if PORT
   /* probably nothing */
Deliberate Syntax Error
#endif					/* PORT */

#if AMIGA || ATARI_ST || HIGHC_386 || MACINTOSH || MVS || UNIX || VM || VMS
   /* nothing to do */
#endif					/* AMIGA || ATARI_ST || ... */

#if MSDOS
#if LATTICE
   fmode(outfile,1);			/* set for untranslated mode */
#endif					/* LATTICE */
#if MICROSOFT || TURBO
   setmode(fileno(outfile),O_BINARY);	/* set for untranslated mode */
#endif					/* MICROSOFT || TURBO */
#endif					/* MSDOS */

#if OS2
#if MICROSOFT
   setmode(fileno(outfile),O_BINARY);
#endif					/* MICROSOFT */
#endif					/* OS2 */

/*
 * End of operating-system specific code.
 */
 
   if (outfile == NULL) 
      quitf("cannot create %s",outname);

#ifdef Header
   /*
    * Open Header, which contains the start-up program and copy it to the
    *  output file.  Then, write out null bytes to past the end of the
    *  start-up program.
    */
   {
   int hsize;
   char hname[MaxFileName];
   char hdrdat[MaxHeader+1];

#if MACINTOSH
#if MPW
      {  /* Look for header in same directory as the linker */
      char *p, *rindex();

      p = (char *)getenv("Command");
      if (p) {
         strcpy(hname,p);
         p = rindex(hname,':') + 1;
         }
      else
         p = hname;
      strcpy(p,HeaderPath);
      fprintf(stderr,HeaderPath);
      fflush(stderr);
      }
#endif					/* MPW */
#endif					/* MACINTOSH */

   fwrite(iconxhdr, sizeof(char), MaxHeader, outfile);
   }
#endif					/* Header */

   for (i = sizeof(struct header); i--;)
      putc(0, outfile);
   fflush(outfile);
   if (ferror(outfile) != 0)
      quit("unable to write to icode file");

#ifdef DeBugLinker
   /*
    * Open the .ux file if debugging is on.
    */
   if (Dflag) {
      makename(dbgname, TargetDir, llfiles->lf_name, ".ux");
      dbgfile = fopen(dbgname, WriteText);
      if (dbgfile == NULL)
         quitf("cannot create %s", dbgname);
      }
#endif					/* DeBugLinker */

   /*
    * Loop through input files and generate code for each.
    */
   lfls = llfiles;
   while (lf = getlfile(&lfls)) {
      filename = lf->lf_name;
      makename(inname, SourceDir, filename, U1Suffix);
      makename(icnname, TargetDir, filename, SourceSuffix);

#if MVS || VM
      infile = fopen(inname, ReadBinary);
#else					/* MVS || VM */
      infile = fopen(inname, ReadText);
#endif					/* MVS || VM */

      if (infile == NULL)
         quitf("cannot open %s", inname);
      gencode();
      fclose(infile);
      }
   gentables();		/* Generate record, field, global, global names,
			   static, and identifier tables. */
   fclose(outfile);
   lmfree();
   if (fatals > 0)
      return fatals;
   setexe(outname);
   return 0;
   }

/*
 * lwarn - issue a linker warning message.
 */
novalue lwarn(s1, s2, s3)
char *s1, *s2, *s3;
   {
   fprintf(stderr, "%s: ", icnname);
   if (lineno)
      fprintf(stderr, "Line %d # :", lineno);
   fprintf(stderr, "\"%s\": %s%s\n", s1, s2, s3);
   fflush(stderr);
   }

/*
 * lfatal - issue a fatal linker error message.
 */

novalue lfatal(s1, s2)
char *s1, *s2;
   {

   fprintf(stderr, "%s: ", icnname);
   if (lineno)
      fprintf(stderr, "Line %d # : ", lineno);
   fprintf(stderr, "\"%s\": %s\n", s1, s2);
   fatals++;
   }

/*
 * setexe - mark the output file as executable
 */

static novalue setexe(fname)
char *fname;
   {

/*
 * The following code is operating-system dependent [@link.03].  It changes the
 *  mode of executable file so that it can be executed directly.
 */

#if PORT
   /* something is needed */
Deliberate Syntax Error
#endif					/* PORT */

#if AMIGA
   /* not necessary */
#endif					/* AMIGA */

#if ATARI_ST || MSDOS || MVS || OS2 || VM || VMS 
   /* can't be made executable */
   /* note: VMS files can't be made executable, but see "iexe.com" under VMS. */
#endif					/* ATARI_ST || MSDOS || VMS */

#if HIGHC_386
   /* not implemented yet. */
#endif					/* HIGHC_386 */

#if MACINTOSH
#if MPW
   /* Nothing to do here -- file is set to type TEXT
      (so it can be executed as a script) in tmain.c.
   */
/* #pragma unused(fname) */
#endif					/* MPW */
#endif					/* MACINTOSH */

#if MSDOS
#if LATTICE || MICROSOFT || TURBO
   chmod(fname,0755);	/* probably could be smarter... */
#endif					/* LATTICE || MICROSOFT || TURBO */
#if MWC
   /* can't handle */
#endif					/* MWC */
#endif					/* MSDOS */

#if UNIX
      {
      struct stat stbuf;
      int u, r, m;
      /*
       * Set each of the three execute bits (owner,group,other) if allowed by
       *  the current umask and if the corresponding read bit is set; do not
       *  clear any bits already set.
       */
      umask(u = umask(0));		/* get and restore umask */
      if (stat(fname,&stbuf) == 0)  {	/* must first read existing mode */
         r = (stbuf.st_mode & 0444) >> 2;	/* get & position read bits */
         m = stbuf.st_mode | (r & ~u);		/* set execute bits */
         chmod(fname,m);		 /* change file mode */
         }
      }
#endif					/* UNIX */

/*
 * End of operating-system specific code.
 */
   }
