/*
 * link.c -- linker main program that controls the linking process.
 */

#include "link.h"
#include "tproto.h"
#include "tglobals.h"
#include "../h/header.h"

#include <sys/types.h>
#include <sys/stat.h>

#ifdef BinaryHeader
   #include "hdr.h"
#endif					/* BinaryHeader */

static	void	setexe	(char *fname);

FILE *infile;				/* input file (.u1 or .u2) */
FILE *outfile;				/* interpreter code output file */

#ifdef DeBugLinker
   FILE *dbgfile;			/* debug file */
   static char dbgname[MaxPath];	/* debug file name */
#endif					/* DeBugLinker */

struct lfile *llfiles = NULL;		/* List of files to link */

char inname[MaxPath];			/* input file name */

char icnname[MaxPath];			/* current icon source file name */
int colmno = 0;				/* current source column number */
int lineno = 0;				/* current source 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);
      infile = fopen(inname, "r");
      if (infile == NULL)
         quitf("cannot open %s",inname);
      readglob();
      fclose(infile);
      }

   /* Phase II (optional): scan code and suppress unreferenced procs. */
   if (!strinv)
      scanrefs();

   /* Phase III: resolve undeclared variables and generate code. */

   /*
    * Open the output file.
    */
   outfile = fopen(outname, "wb");

   if (outfile == NULL) {		/* may exist, but can't open for "w" */
      ofile = NULL;			/* so don't delete if it's there */
      quitf("cannot create %s",outname);
      }

   /*
    * Write the bootstrap header to the output file.
    */
   #ifdef BinaryHeader
      /*
       *  With BinaryHeader defined, always write MaxHdr bytes.
       */
      fwrite(iconxhdr, sizeof(char), MaxHdr, outfile);
      hdrsize = MaxHdr;

   #else				/* BinaryHeader */
      /*
       *  Write a short shell header terminated by \n\f\n\0.
       *  Use magic "#!/bin/sh" to ensure that $0 is set when run via $PATH.
       *  Pad header to a multiple of 8 characters.
       *
       *  The shell header searches for iconx in this order:
       *     a.  location specified by ICONX environment variable
       *         (if specified, this MUST work, else the script exits)
       *     b.  iconx in same directory as executing binary
       *     c.  location specified in script
       *         (as generated by icont or as patched later)
       *     d.  iconx in $PATH
       *
       *  The ugly ${1+"$@"} is a workaround for non-POSIX handling
       *  of "$@" by some shells in the absence of any arguments.
       *  Thanks to the Unix-haters handbook for this trick.
       */
      {
      char script[2 * MaxPath + 300];
      sprintf(script, "%s\n%s%-72s\n%s\n\n%s\n%s\n%s\n%s\n\n%s\n",
         "#!/bin/sh",
         "IXBIN=", iconxloc,
         "IXLCL=`echo $0 | sed 's=[^/]*$=iconx='`",
         "[ -n \"$ICONX\" ] && exec \"$ICONX\" $0 ${1+\"$@\"}",
         "[ -x \"$IXLCL\" ] && exec \"$IXLCL\" $0 ${1+\"$@\"}",
         "[ -x \"$IXBIN\" ] && exec \"$IXBIN\" $0 ${1+\"$@\"}",
         "exec iconx $0 ${1+\"$@\"}",
         "[executable Icon binary follows]");
      strcat(script, "        \n\f\n" + ((int)(strlen(script) + 4) % 8));
      hdrsize = strlen(script) + 1;	/* length includes \0 at end */
      fwrite(script, hdrsize, 1, outfile);	/* write header */
      }

   #endif				/* BinaryHeader */

   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, "w");
         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)) != 0) {
      filename = lf->lf_name;
      makename(inname, SourceDir, filename, U1Suffix);
      makename(icnname, TargetDir, filename, SourceSuffix);
      infile = fopen(inname, "r");
      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.
 */
void 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.
 */

void lfatal(s1, s2)
char *s1, *s2;
   {
   fatals++;
   fprintf(stderr, "%s: ", icnname);
   if (lineno)
      fprintf(stderr, "Line %d # : ", lineno);
   fprintf(stderr, "\"%s\": %s\n", s1, s2);
   }

/*
 * setexe - mark the output file as executable
 */

static void setexe(fname)
char *fname;
   {
   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 */
      }
   }
