/* io.c - input/output and error modules for linker */

/* Copyright (C) 1994 Bruce Evans */

#include "syshead.h"
#include "const.h"
#include "type.h"
#include "globvar.h"
#include "version.h"

#define DRELBUFSIZE	3072
#define ERR		(-1)
#define ERRBUFSIZE	1024
#define INBUFSIZE	1024
#define OUTBUFSIZE	2048
#define TRELBUFSIZE	1024

#ifdef REL_OUTPUT
PRIVATE char *drelbuf;		/* extra output buffer for data relocations */
PRIVATE char *drelbufptr;	/* data relocation output buffer ptr */
PRIVATE char *drelbuftop;	/* data relocation output buffer top */
#endif
PRIVATE char *errbuf;		/* error buffer (actually uses STDOUT) */
PRIVATE char *errbufptr;	/* error buffer ptr */
PRIVATE char *errbuftop;	/* error buffer top */
PRIVATE int  errfil = STDOUT_FILENO;
PRIVATE char *inbuf;		/* input buffer */
PRIVATE char *inbufend;		/* input buffer top */
PRIVATE char *inbufptr;		/* end of input in input buffer */
PRIVATE int infd;		/* input file descriptor */
PRIVATE char *inputname;	/* name of current input file */
PRIVATE char *outbuf;		/* output buffer */
PRIVATE char *outbufptr;	/* output buffer ptr */
PRIVATE char *outbuftop;	/* output buffer top */
PRIVATE int outfd;		/* output file descriptor */
PRIVATE mode_t outputperms;	/* permissions of output file */
PRIVATE char *outputname;	/* name of output file */
PRIVATE char *refname;		/* name of program for error reference */
#ifdef REL_OUTPUT
PRIVATE char *trelbuf;		/* extra output buffer for text relocations */
PRIVATE char *trelbufptr;	/* text relocation output buffer ptr */
PRIVATE char *trelbuftop;	/* text relocation output buffer top */
PRIVATE int trelfd;		/* text relocation output file descriptor */
#endif
PRIVATE unsigned warncount;	/* count of warnings */

#ifdef MSDOS
#define off_t	long		/* NOT a typedef */
#endif

FORWARD void errexit P((char *message));
FORWARD void flushout P((void));
#ifdef REL_OUTPUT
FORWARD void flushtrel P((void));
#endif
FORWARD void outhexdigs P((bin_off_t num));
FORWARD void outputerror P((char *message));
FORWARD void put04x P((unsigned num));
FORWARD void putstrn P((char *message));
FORWARD void refer P((void));
FORWARD void stderr_out P((void));

PUBLIC void ioinit(progname)
char *progname;
{
    infd = ERR;
    if (*progname)
	refname = progname;	/* name must be static (is argv[0]) */
    else
	refname = "link";
    for(progname=refname; *progname; progname++)
       if(*progname=='/')
          refname=progname+1;

#ifdef REL_OUTPUT
    drelbuf = malloc(DRELBUFSIZE);
    drelbuftop = drelbuf + DRELBUFSIZE;
#endif
    errbuf = malloc(ERRBUFSIZE);
    errbufptr = errbuf;
    errbuftop = errbuf + ERRBUFSIZE;
    inbuf = malloc(INBUFSIZE);
    outbuf = malloc(OUTBUFSIZE);/* outbuf invalid if this fails but then */
				/* will not be used - tableinit() aborts */
    outbuftop = outbuf + OUTBUFSIZE;
#ifdef REL_OUTPUT
    trelbuf = malloc(TRELBUFSIZE);
    trelbuftop = trelbuf + TRELBUFSIZE;
#endif
}

PUBLIC void closein()
{
    if (infd != ERR && close(infd) < 0)
	inputerror("cannot close");
    infd = ERR;
}

PUBLIC void closeout()
{
#ifdef REL_OUTPUT
    unsigned nbytes;
#endif

    flushout();
#ifdef REL_OUTPUT
    flushtrel();
    nbytes = drelbufptr - drelbuf;
    if (write(trelfd, drelbuf, nbytes) != nbytes)
	outputerror("cannot write");
#endif
    if (close(outfd) == ERR)
	outputerror("cannot close");
#ifdef REL_OUTPUT
    if (close(trelfd) == ERR)
	outputerror("cannot close");
#endif
}

PUBLIC void errtrace(name, level)
char *name;
int level;
{
    while (level-- > 0)
	putbyte(' ');
    putstrn(name);
}

PUBLIC void executable()
{
    if (errcount)
        unlink(outputname);
#ifndef MSDOS
    else
	chmod(outputname, outputperms);
#endif
}

PUBLIC void flusherr()
{
    if( errbufptr != errbuf )
       write(errfil, errbuf, (unsigned) (errbufptr - errbuf));
    errbufptr = errbuf;
}

PRIVATE void stderr_out()
{
   if( errfil != STDERR_FILENO )
   {
      flusherr();
      errfil = STDERR_FILENO;
   }
}

PRIVATE void flushout()
{
    unsigned nbytes;

    nbytes = outbufptr - outbuf;
    if (write(outfd, outbuf, nbytes) != nbytes)
	outputerror("cannot write");
    outbufptr = outbuf;
}

#ifdef REL_OUTPUT
PRIVATE void flushtrel()
{
    unsigned nbytes;

    nbytes = trelbufptr - trelbuf;
    if (write(trelfd, trelbuf, nbytes) != nbytes)
	outputerror("cannot write");
    trelbufptr = trelbuf;
}
#endif

PUBLIC void openin(filename)
char *filename;
{
#if 0 /* XXX - this probably won't work with constructed lib names? */
    if (infd == ERR || strcmp(inputname, filename) != 0)
#endif
    {
	closein();
	inputname = filename;	/* this relies on filename being static */
#ifdef O_BINARY
	if ((infd = open(filename, O_BINARY|O_RDONLY)) < 0)
#else
	if ((infd = open(filename, O_RDONLY)) < 0)
#endif
	    inputerror("cannot open");
	inbufptr = inbufend = inbuf;
    }
}

PUBLIC void openout(filename)
char *filename;
{
    mode_t oldmask;

    outputname = filename;
#ifdef O_BINARY
    if ((outfd = open(filename, O_BINARY|O_WRONLY|O_CREAT|O_TRUNC, CREAT_PERMS)) == ERR)
#else
    if ((outfd = creat(filename, CREAT_PERMS)) == ERR)
#endif
	outputerror("cannot open");

#ifndef MSDOS
    /* Can't do this on MSDOS; it upsets share.exe */
    oldmask = umask(0); umask(oldmask);
    outputperms = ((CREAT_PERMS | EXEC_PERMS) & ~oldmask);
    chmod(filename, outputperms & ~EXEC_PERMS);
#endif

#ifdef REL_OUTPUT
    drelbufptr = drelbuf;
#endif
    outbufptr = outbuf;
#ifdef REL_OUTPUT
#ifdef O_BINARY
    if ((trelfd = open(filename, O_BINARY|O_WRONLY, CREAT_PERMS)) == ERR)
#else
    if ((trelfd = open(filename, O_WRONLY, CREAT_PERMS)) == ERR)
#endif
	outputerror("cannot reopen");
    trelbufptr = trelbuf;
#endif
}

PRIVATE void outhexdigs(num)
register bin_off_t num;
{
    if (num >= 0x10)
    {
	outhexdigs(num / 0x10);
	num %= 0x10;
    }
    putbyte(hexdigit[num]);
}

PRIVATE void put04x(num)
register unsigned num;
{
    putbyte(hexdigit[num / 0x1000]);
    putbyte(hexdigit[(num / 0x100) & 0x0F]);
    putbyte(hexdigit[(num / 0x10) & 0x0F]);
    putbyte(hexdigit[num & 0x0F]);
}

#ifdef LONG_OFFSETS

PUBLIC void put08lx(num)
register bin_off_t num;
{
    put04x(num / 0x10000);
    put04x(num % 0x10000);
}

#else /* not LONG_OFFSETS */

PUBLIC void put08x(num)
register bin_off_t num;
{
    putstr("0000");
    put04x(num);
}

#endif /* not LONG_OFFSETS */

PUBLIC void putbstr(width, str)
unsigned width;
char *str;
{
    unsigned length;
    
    for (length = strlen(str); length < width; ++length)
	putbyte(' ');
    putstr(str);
}

PUBLIC void putbyte(ch)
int ch;
{
    register char *ebuf;

    ebuf = errbufptr;
    if (ebuf >= errbuftop)
    {
	flusherr();
	ebuf = errbufptr;
    }
    *ebuf++ = ch;
    errbufptr = ebuf;
}

PUBLIC void putstr(message)
char *message;
{
    while (*message != 0)
	putbyte(*message++);
}

PRIVATE void putstrn(message)
char *message;
{
    putstr(message);
    putbyte('\n');
    flusherr(); errfil = STDOUT_FILENO;
}

PUBLIC int readchar()
{
    int ch;
	
    register char *ibuf;
    int nread;

    ibuf = inbufptr;
    if (ibuf >= inbufend)
    {
	ibuf = inbufptr = inbuf;
	nread = read(infd, ibuf, INBUFSIZE);
	if (nread <= 0)
	{
	    inbufend = ibuf;
	    return ERR;
	}
	inbufend = ibuf + nread;
    }
    ch = (unsigned char) *ibuf++;
    inbufptr = ibuf;
    return ch;
}

PUBLIC void readin(buf, count)
char *buf;
unsigned count;
{
    int ch;
    
    while (count--)
    {
	if ((ch = readchar()) < 0)
	    prematureeof();
	*buf++ = ch;
    }
}

PUBLIC bool_pt readineofok(buf, count)
char *buf;
unsigned count;
{
    int ch;
    
    while (count--)
    {
	if ((ch = readchar()) < 0)
	    return TRUE;
	*buf++ = ch;
    }
    return FALSE;
}

PUBLIC void seekin(offset)
unsigned long offset;
{
    inbufptr = inbufend = inbuf;
    if (lseek(infd, (off_t) offset, SEEK_SET) != offset)
	prematureeof();
}

PUBLIC void seekout(offset)
unsigned long offset;
{
    flushout();
    if (lseek(outfd, (off_t) offset, SEEK_SET) != offset)
	outputerror("cannot seek in");
}

#ifdef REL_OUTPUT
PUBLIC void seektrel(offset)
unsigned long offset;
{
    flushtrel();
    if (lseek(trelfd, (off_t) offset, SEEK_SET) != offset)
	outputerror("cannot seek in");
}
#endif

PUBLIC void writechar(ch)
int ch;
{
    register char *obuf;

    obuf = outbufptr;
    if (obuf >= outbuftop)
    {
	flushout();
	obuf = outbufptr;
    }
    *obuf++ = ch;
    outbufptr = obuf;
}

#ifdef REL_OUTPUT
PUBLIC void writedrel(buf, count)
register char *buf;
unsigned count;
{
    register char *rbuf;

    rbuf = drelbufptr;
    while (count--)
    {
	if (rbuf >= drelbuftop)
	    inputerror("data relocation buffer full while processing");
	*rbuf++ = *buf++;
    }
    drelbufptr = rbuf;
}
#endif

PUBLIC void writeout(buf, count)
register char *buf;
unsigned count;
{
    register char *obuf;

    obuf = outbufptr;
    while (count--)
    {
	if (obuf >= outbuftop)
	{
	    outbufptr = obuf;
	    flushout();
	    obuf = outbufptr;
	}
	*obuf++ = *buf++;
    }
    outbufptr = obuf;
}

#ifdef REL_OUTPUT
PUBLIC void writetrel(buf, count)
register char *buf;
unsigned count;
{
    register char *rbuf;

    rbuf = trelbufptr;
    while (count--)
    {
	if (rbuf >= trelbuftop)
	{
	    trelbufptr = rbuf;
	    flushtrel();
	    rbuf = trelbufptr;
	}
	*rbuf++ = *buf++;
    }
    trelbufptr = rbuf;
}
#endif

/* error module */

PRIVATE void errexit(message)
char *message;
{
    putstrn(message);
    exit(2);
}

PUBLIC void fatalerror(message)
char *message;
{
    refer();
    errexit(message);
}

PUBLIC void inputerror(message)
char *message;
{
    refer();
    putstr(message);
    putstr(" input file ");
    errexit(inputname);
}

PUBLIC void input1error(message)
char *message;
{
    refer();
    putstr(inputname);
    errexit(message);
}

PRIVATE void outputerror(message)
char *message;
{
    refer();
    putstr(message);
    putstr(" output file ");
    errexit(outputname);
}

PUBLIC void outofmemory()
{
    inputerror("out of memory while processing");
}

PUBLIC void prematureeof()
{
    inputerror("premature end of");
}

PUBLIC void redefined(name, message, archentry, deffilename, defarchentry)
char *name;
char *message;
char *archentry;
char *deffilename;
char *defarchentry;
{
    ++warncount;
    refer();
    putstr("warning: ");
    putstr(name);
    putstr(" redefined");
    putstr(message);
    putstr(" in file ");
    putstr(inputname);
    if (archentry != NUL_PTR)
    {
	putbyte('(');
	putstr(archentry);
	putbyte(')');
    }
    putstr("; using definition in ");
    putstr(deffilename);
    if (defarchentry != NUL_PTR)
    {
	putbyte('(');
	putstr(defarchentry);
	putbyte(')');
    }
    putstrn("");
}

PUBLIC void interseg(fname, aname, name)
char *fname, *aname, *name;
{
    ++errcount;
    refer();
    putstr("error in ");
    putstr(fname);
    if( aname ) 
    {
       putstr("(");
       putstr(aname);
       putstr(")");
    }
    putstr(" intersegment jump to ");
    if( name ) putstr(name);
    else       putstr("same file");

    putstrn("");
}

PRIVATE void refer()
{
    stderr_out();
    putstr(refname);
    putstr(": ");
}

PUBLIC void reserved(name)
char *name;
{
    ++errcount;
    stderr_out();
    putstr("incorrect use of reserved symbol: ");
    putstrn(name);
}

PUBLIC void size_error(seg, count, size)
int seg;
bin_off_t count;
bin_off_t size;
{
    refer();
    putstr("seg ");
    outhexdigs((bin_off_t) seg);
    putstr(" has wrong size ");
    outhexdigs(count);
    putstr(", supposed to be ");
    outhexdigs(size);
    errexit("");
}

PUBLIC void undefined(name)
char *name;
{
    ++errcount;
    stderr_out();
    putstr("undefined symbol: ");
    putstrn(name);
}

PUBLIC void usage()
{
    stderr_out();
    putstr("usage: ");
    putstr(refname);
#ifdef REL_OUTPUT
    errexit("\
 [-03NMdimrstz[-]] [-llib_extension] [-o outfile] [-Ccrtfile]\n\
       [-Llibdir] [-Olibfile] [-Ttextaddr] [-Ddataaddr] [-Hheapsize] infile...");
#else
    errexit("\
 [-03NMdimstz[-]] [-llib_extension] [-o outfile] [-Ccrtfile]\n\
       [-Llibdir] [-Olibfile] [-Ttextaddr] [-Ddataaddr] [-Hheapsize] infile...");
#endif
}

PUBLIC void version_msg()
{
    stderr_out();
    putstr("ld86 version: ");
    errexit(VERSION);
}

PUBLIC void use_error(message)
char *message;
{
    refer();
    putstrn(message);
    usage();
}
