/*
    LIB - a librarian for compatible OBJ/LIB files
    Copyright (C) 1995,1996  Steffen Kaiser

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* $RCSfile: LIBBER.C $
   $Locker: ska $	$Name: v3_2 $	$State: Rel $

	Librarien for OBJ files, generates LIB file.

	Supported Compilers:
		- Micro-C v3.14 handling the bugs with buffered streams in R/W mode.
		- Borland C/C++ v2.0, v3.1, v4.0, v4.52

	Compiling Hints:
		- Never use a far data model! There has been implemented a storage
			manager for far memory that does not coexist with the normal
			management.
		- Check out the source file CONFIG.H to alter some internal
			behaviours, limits, and default values.

*/

#ifndef _MICROC_
#include <io.h>
#include <string.h>
#include <stdlib.h>
#include <dir.h>
#include <dos.h>
#else
#include <file.h>
#define ff_name FF_name
#endif
#include <stdio.h>
#include <portable.h>
#include <getopt.h>
#include "types.h"
#include "list.h"
#include "lib.h"
#include "tmplist.h"
#include "yerror.h"
#include "mlib.h"

#ifndef lint
static char const rcsid[] = 
	"$Id: LIBBER.C 3.3 1999/02/17 05:18:08 ska Rel ska $";
#endif

FLAG ovrrun = NUL;		/* library size overrun */
FLAG upCaseIt = !NUL;		/* uppercase the symbol names */
FLAG globExtern = !NUL;	/* glob names depending on external files */
FLAG libRenamed = NUL;	/* original library has been renamed */
FLAG dispDoing = !NUL;	/* display what is done on the modules */
FLAG replInternal = NUL;	/* internally globbed wildcards accept the ADD command */
FLAG removeBrk = NUL;	/* reset verbose ^Break check flag */
FLAG replWarn = !NUL;	/* warn if a replaced module "+-" was not found */
FLAG8 returnNumber = 0;
FLAG8 cmd = CMD_NONE;
unsigned newPageSize = 0;	/* 0 == no manually set page size */
jmp_buf restart;
byte ybuf[COPYBUF];		/* short-time temporary buffer. May be altered in any function */
char *origName = NULL;	/* file name of original .LIB name */
char *renName = NULL;	/* file name of renamed .LIB file */
char *xtractFnam = NULL;	/* file name of special extraction feature report file */
FILE *xtractFile = NULL;	/* special extraction feature report file */
unsigned xtractCnt = 0;		/* special extraction mode substitution counter */

extern void cmdToken(char *s);
#ifdef __TURBOC__
unsigned _stklen = 8 * 1024;
#endif

#ifdef _MICROC_
int access(char *fnam, int mode)
/* test, whether or not file fnam exists and have the specified mode.
	mode:
		0: file exists
		1: execution allowed (Extension .COM & .EXE)
		2: write permission
		4: read permission (always granted, for Unix compatibly)
		6: read & write permission (for Unix compatibly)
	Return:
		0: Yes
		-1: No
*/
{	struct FF_block ff;
	char *p;

	DB_ENTER("access");
	DB_PRINT("inf", ("fnam = %s", fnam));

	/* first, check if file exists */
	if(findfirst(fnam, ff, 0) || !findnext(ff))
		DB_RETURN( -1);		/* file does not exist -> all modes are impossible */

	if(mode & 1) {	/* check execution permission */
		if(!(p = strchr(ff.FF_name))
		 || (memcmp(++p, "BAT", 4) && memcmp(p, "COM", 4)
		     && memcmp(p, "EXE", 4)))
			DB_RETURN( -1);			/* not the correct extension */
	}

	if(mode & 2)	/* check for write permission */
		if(ff.FF_attrib & READONLY)
			DB_RETURN( -1);

	DB_RETURN( 0);		/* mode allowed */
}
#endif

void rspFile(char *fnam)
{	char *p;
	static int rec = 0;	/* allow no response files witihin a response
						   file. That will deny recursivity. */
	FILE *fp;

	DB_ENTER("rspFile");
	DB_PRINT("arg", ("response file = %s", fnam));

	if(rec)
		fatal(E_recRspFile);
	rec = 1;
	if((fp = fopen(fnam, "r")) != NULL) {
		while(fgets(ybuf, sizeof(ybuf), fp))
			if((p = strtok(ybuf, " \t\n\r")) != NULL)
				do cmdToken(p);
				while((p = strtok(NULL, " \t\n\r")) != NULL);
	}
	else error(E_openFile, fnam);
	fclose(fp);
	rec = 0;
	chkHeap()
	DB_EXIT
}

void cmdToken(char *s)
{	int cmd1;
	char *name, *dr, *path, *ext, *fname;
#ifdef _MICROC_
	struct FF_block ff;
#else
	struct ffblk ff;
#endif

	DB_ENTER("cmdToken");
	DB_PRINT("arg", ("token = %s", s));

	for(cmd1 = CMD_NONE; *s; ++s)
		switch(*s) {
			case '-': cmd1 |= CMD_SUB; break;
			case '+': cmd1 |= CMD_ADD; break;
			case '*': cmd1 |= CMD_EXT;
			case '&': break;	/* for compatibly with old LIB */
			default: goto breakFor;
	}
breakFor:
	if(cmd1) {	/* cmd changed */
		cmd = cmd1;
	}

	if(*s == '@' && s[1]) /* response file */
		rspFile(s + 1);
	else  if(*s) {	/* grab the file name */
		if(cmd == CMD_NONE)
			error(E_cmdModeMissing);

		chkHeap()
			/* subtract & extract cannot contain drive|path|extension */
		if(!fsplit(s, &dr, &path, &name, &ext, 0))
			fatal(E_noMem);
		if(!name) name = dupstr("*");
		else if(!*name) error(E_emptyModName, s);
		if(!ext) ext = dupstr("OBJ");

		if(path && *path == '.') {		/* path starts with "." or "./"? */
			if(path[1] == '\\') {			/* cut ".\\" */
				fname = dupstr(path + 2);
				U_free(path);
				path = fname;
			}
			else if(!path[1]) {
				U_free(path);
				path = NULL;
			}
		}

		chkHeap()
		if((fname = fmerge(NULL, dr, path, name, ext)) == NULL)
			fatal(E_noMem);

		if(globExtern || (!replInternal && (cmd & CMD_ADD))) {	/* glob on external files */
			/* allow "+-" to update the stored & new modules */
			if(findfirst(fname, aS(ff), 0)) {	/* match no hidden or system */
				if(wildcarded(fname) || (cmd & CMD_ADD))
					warning(W_matchFiles, fname);
				else
					appList(cmd, fname, 2);	/* file names are to upper-case */
			}
			else do {
				U_free(fname);
				chkHeap()
				if((fname = fmerge(NULL, dr, path, ff.ff_name, NULL)) == NULL)
					fatal(E_noMem);
				appList(cmd, fname, 2);	/* file names are to upper-case */
			} while(!findnext(aS(ff)));
		}
		else {		/* glob on int‘rnal module names */
			/* allow "+-" to refresh the stored modules */
			if((cmd & CMD_ADD) && !(cmd & CMD_SUB) && !access(fname, 4))
				warning(W_matchFiles, fname);
			else appList(cmd, fname, 2);	/* file names are to upper-case */
		}
		chkHeap()

		U_free(fname);
		U_free(dr);
		U_free(path);
		U_free(name);
		U_free(ext);
		chkHeap()
	}
	DB_EXIT
}

int chkCmdFlags(struct cmdList *p)
{	if((p->fl_flags & (CMD_ADD | CMD_EXT)) == (CMD_ADD | CMD_EXT))
		error(E_cmdMode, p->fl_name);
	return 0;		/* don't delete this entry */
}

int chkXtrctFlags(struct cmdList *p)
{	if(p->fl_flags & (CMD_ADD | CMD_SUB))
		error(E_xtrctMode, p->fl_name);
	return 0;		/* don't delete this entry */
}

int notUsed(struct cmdList *p)	/* module not subtracted or extracted */
{	int i, f;

	if((i = ((f = p->fl_flags) & (CMD_SUB | CMD_EXT))) != 0
	 && ((f >> CMD_DONE) & i) != i
	 && (replWarn || (f & (CMD_SUB | CMD_ADD)) != (CMD_SUB | CMD_ADD))) {
		warning(E_noExtSubMod, p->fl_name);
		returnNumber = msgErrorNumber(E_noExtSubMod);
	}
	/* preserve all original flags if the creation fails */
	if(replInternal && wildcarded(p->fl_name))	/* ADD flag was globbed */
		p->fl_flags = f & ~CMD_ALL | ((f & CMD_ALL) << CMD_DONE);
	else
		p->fl_flags = f & ~(CMD_SUB | CMD_EXT) | (i << CMD_DONE);
	return 0;		/* don't delete this entry */
}

int reEnable(struct cmdList *p)
{	int f;

	/* re-enable the DONE flags */
	f = p->fl_flags;
	p->fl_flags = (f & ~((CMD_ALL << CMD_DONE) | CMD_ERROR))
	 | ((f >> CMD_DONE) & CMD_ALL);

	return f & CMD_TWICE;		/* this was a temporary entry */
}


void dispVersion(void)
{	dword length;
	word segm, pages;
	MSGID lenUnit;

	/* count available far memory block */
	longclr(length);
	while((segm = Falloc(0xffff)) != 0)		/* allocate all free memory */
		longaddu(length, peekw(segm, 2));/* cumulate all paragraphe amounts */
	clrList();					/* free the allocated far memory blocks */
	if(DW_HI(length) & 0xf000) {	/* cannot be transformed into bytes */
		lenUnit = M_kilobytes;
		longshrn(length, 6);		/* paragraphes -> KB */
	}
	else {
		lenUnit = M_bytes;
		longshln(length, 4);		/* paragraphes -> bytes */
	}
#ifdef _MICROC_
	ltoa(length, ybuf, 10);
#else
	sprintf(ybuf, "%ld", length);
#endif
	beautify(ybuf);

	pages = 1;
	while((segm = nextPrime(pages + 1)) != 0)
		pages = segm;

	message(stdout, E_version, 
		"- LLIB - Copyright 1996,1997,1999 Steffen Kaiser",
		"$Date: 1999/02/17 05:18:08 $ $State: Rel $ $Name: v3_2 $",
#ifdef NDEBUG
		"",
#else
		msgLock(M_dbgAvail),
#endif
		MINPAGESIZE, pages, STD_PAGEINFO, ybuf, msgLock(lenUnit));
	msgUnlock(lenUnit);
#ifndef NDEBUG
	msgUnlock(M_dbgAvail);
#endif
}

FILE *restartOutFile;
int cmdArgSwapout(struct cmdList *p)
{	int len;

	assert(restartOutFile);

	len = sizeof(struct cmdList) + strlen(p->fl_name);
	if(fwrite(&len, sizeof(len), 1, restartOutFile) != 1
	 || fwrite(p, len, 1, restartOutFile) != 1)
	 	error(E_writeFile, origName);

	return 0;
}
void cmdArgSwapin(void)
{	struct cmdList *p;
	int len;

	for(;;)
		switch(fread(&len, 1, sizeof(len), restartOutFile)) {
		case 0:		return;		/* all scanned */
		case sizeof(len):
			if(fread(p = getmem(len), len, 1, restartOutFile) == 1) {
				Fappend(p, len);
				U_free(p);
				break;
			}
		default:
			error(E_readFile, origName);
		}
}

/*
 *	The dispPageInfo() function uses the same memory cache the
 *	command line arguments are stored in. Usually this is no
 *	problem, but in the restart case the dispPageInfo() function
 *	must be called in order to gain knowledge about a suitable
 *	pagesize and the scanned command line arguments must be preserved.
 *
 *	Solution: The command line arguments are swapped out into a file.
 *	Because the library file itself is created immediately after
 *	this function terminates, the library file itself is used to
 *	temporarily hold the command line arguments.
 */
void restartLIB(void)
{
	DB_ENTER("restartLIB");

	ovrrun = NUL;

	informative(M_restart);
	if(libIn) {			/* input library/module exists */
		Close(libIn);
		U_free(libNameOld);
		libIn = 0;
	}
	Close(libOut);
	forEachList(aF(reEnable));	/* restore the list to its initial value */
	if((restartOutFile = fopen(origName, "wb")) == NULL)
		error(E_createFile, origName);
	forEachList(aF(cmdArgSwapout));
	fclose(restartOutFile);
	dispPageInfo(1);	/* create the new page size fromout the modules
							processed til now. */
	Sclose();			/* close the temp file, it must be fully rebuilt */
	informative(M_newPageSize, newPageSize = pageSizeNew);

	/* Read back the command line */
	if((restartOutFile = fopen(origName, "rb")) == NULL)
		error(E_openFile, origName);
	cmdArgSwapin();
	fclose(restartOutFile);
	unlink(origName);

	DB_EXIT
}

void stdRet(void)
{	DB_ENTER("stdRet");

	if(removeBrk)
		cbreak(0);				/* disable verbose ^Break checking */

	Sclose();
	chkHeap()

	if(xtractFile) {
		fclose(xtractFile);
		xtractFile = NULL;
	}
	if(xtractFnam) {
		U_free(xtractFnam);
		xtractFnam = NULL;
	}

	DB_EXIT
}

void errRet(void) /* clean up in case of error */
{	DB_ENTER("errRet");

	stdRet();
/* re-install library */
	if(libOut)
		Close(libOut);

	if(origName) {
		unlink(origName);
		if(renName)
			rename(renName, origName);
	}

/* remove temporary list file */
	Sclose();

	DB_EXIT
}

int cBreak()
{	putc('\n', stderr);	/* flush error stream */
	fatal(E_UserBreak);
	return 1;		/* security purpose: Break if fatal() fails */
}

char *opt1st = "ACDGHIMQSVWX?";
char *opt2nd = "IPW@";
char *opt3rd = "#";			/* Debug flags */
char *optSWCHAR = "/";		/* The '-' cannot allowed, because it's a command */
char *optBool = "";			/* boolean state flags */

int main(int argc, char **argv)
{	char *drive, *path, *name, *ext, *p, *pn;
	int c;
#ifndef NO_DEBUG
	DB_ENTER_INIT();

	DB_PROCESS(argv[0]);
	if((DB_FILE = fopen("lib.err", "w+t")) == NULL)
		fatal("Cannot open debug error file lib.err");
	DB_PUSH("d:F:L:n:t");
	DB_ENTER_DO("main");
#endif

	msgInit();
	msgErrFct(errRet);
	ctrlbrk(aF(cBreak));
	removeBrk = !cbreak(-1);	/* get current ^Break state */
	cbreak(1);					/* set verbose ^Break checking */

	p = (char*)I_locMsgFile;
	name = NULL;		/* indicate: no library name */
	libIn = libOut = NULL;	/* neither library nor module opened */
	pageSizeOld = 0;	/* input library not opened */
	libNameNew = libNameOld = NULL;

	/* read switches */
quaffOptions:
	while((c = getoptG(argc, argv)) != EOF) 
		switch(c) {
			case 'C': upCaseIt = NUL; break;	/* case sensitive */
			case 'D': dblMod = NUL; break;	/* don't check for multiple added module */
			case 'A': replInternal = !NUL; break;	/* accept "+" symbol with internal globbed wildcards */
			case 'G': globExtern = NUL; break;	/* glob not depends on external files */
			case 'M': modifyTHEADR = NUL; break;	/* don't modify module names */
			case 'X': alignDir = NUL; break;	/* don't align library directory */
			case 'S': dispDoing = NUL; break;	/* silent, do not display what is done on the modules */
			case 'Q': setNoiseLevel(ENoise_informative, NOISE_DENY); break;
			case 'W': replWarn = NUL; break;	/* don't warn if on "-+" module is not in lib */
			case '@': /* special extraction mode */
				if(!optarg || !*optarg)
					error(E_argMissing, '@');
				xtractFnam = dupstr(optarg);
				break;
			case 'I': 	/* display page size information */
				if(optarg) {
					if(*optarg) {
						dispInfo = strtol(optarg, &optarg, 0);
						if(optarg && *optarg)
							error(E_option, argv[optind]);
						if(!dispInfo)
							dispInfo = 0xffff;
					}
					else dispInfo = STD_PAGEINFO;
				}
				else dispInfo = 1;
				break;
			case 'V':	/* version retrievement */
				dispVersion();
				exit(msgErrorNumber(E_version));
			case 'P': 
				newPageSize = strtol(optarg, &optarg, 0);
				if(optarg && *optarg)
					error(E_option, argv[optind]);
				break;
			case 'H':	/* additional help */
				message(stdout, M_2hlpScreen, appName());
				exit(msgErrorNumber(E_hlpScreen));
			case '#':	/* change debug flags */
#ifndef NO_DEBUG
				DB_POP();
				DB_PUSH(optarg);
				informative(M_chgDbgFlags, optarg);
#else
				warning(W_dbgIgn);
#endif
				break;
			default:
				hlpScreen();
		}

	if(name == NULL) {		/* this argument is the library name */
		if(optind >= argc)	/* no library name at all */
			hlpScreen();
		chkHeap()

		if((p = strchr(pn = argv[optind], ','))	!= NULL) /* check for the list file */
			*p = 0;		/* set the file name terminator */
		else	++optind;	/* argument fully used */

		if(!fsplit(pn, &drive, &path, &name, &ext, 0))
			fatal(E_noMem);
		if(name == NULL)
			hlpScreen();
		if(ext == NULL)
			ext = dupstr("LIB");
		else if(!memicmp(ext, "BAK", 4))
			error(E_noHandleBAK);
		chkHeap()
		if(!p)		/* the list file is not appended */
			goto quaffOptions;		/* possibly more options follow */
		/* alter the argument to contain the ',' portion */
		*(argv[optind] = p) = ',';
	}

	if(xtractFnam)
		globExtern = NUL;	/* internally globbing */
	else if(globExtern)
		replInternal = NUL;

#ifndef NDEBUG
		informative(M_scanCmds);
#endif
/* read commands */
	chkHeap()
	while(optind < argc)
		if((p = strchr(argv[optind], ',')) != NULL) { /* end command list here */
			*p = 0;
			cmdToken(argv[optind]);
			if(p[1]) /* cmd,lst */
				argv[optind] = p + 1;
			else ++optind;
			break;
		}
		else cmdToken(argv[optind++]);
/* if there is one argument left, that's the List file; more than one
   is an error */
	if(argv[optind] && argv[optind + 1])
		error(E_cmdLine);
	chkHeap()

	if(xtractFnam) {		/* enter special extraction mode */
		forEachList(aF(chkXtrctFlags));
		if((xtractFile = fopen(xtractFnam, "wb")) == NULL)
			error(E_openFile, xtractFnam);
		informative(M_xtract, xtractFnam);
		cmd = CMD_NONE;		/* library won't be modified */
	}
	else {
		forEachList(aF(chkCmdFlags));		/* check for +* combination */

	/* Check, if the library will be altered */
		cmd = CMD_NONE;			/* on default: no altering of the lib */
		if(newPageSize)
			cmd = CMD_ADD;		/* library will be changed */
		else if(inList(CMD_ADD | CMD_SUB, "*", 2))
			cmd = CMD_SUB;
	}

/* Lopen() & Lcreate() need a malloc'ed string */
/* p is the library name to read, pn the one to write.
	If the library remains unchanged, the file to read is the original
	file name and the file to write is nul.
	Otherwise, the file to read is the .BAK file, and the file to
	write the original one. */

	if((p = fmerge(NULL, drive, path, name, ext)) == NULL
	 || (pn = fmerge(NULL, drive, path, name, "BAK")) == NULL)
		fatal(E_noMem);
	unlink(pn);	/* don't check if it's OK */

	if(access(p, 4)) { /* no input library */
		/* initialize output lib with standard values */
		if(cmd == CMD_NONE || !inList(CMD_ADD, "*", 2))
			error(E_openLib, p);
		U_free(pn);
		pn = NULL;	/* indicate: No input library for the restart code */
		pageSizeNew = MINPAGESIZE;
		origName = dupstr(p);	/* delete newly created library */
	}
	else {	/* file exists ==> it must be openable */
		if(cmd == CMD_NONE) {	/* no library backup needed */
			U_free(pn);			/* this name is not valid in this case */
			pn = p;
			p = dupstr("nul");	/* nul prevents from lib copy */
		}
		else {
			if(rename(libNameNew = p, libNameOld = pn))
				error(E_renameToBAK, p);
			renName = dupstr(pn);
			origName = dupstr(p);
		}
	}

	DB_PRINT("opt", ("upCase = %d", upCaseIt));
	DB_PRINT("opt", ("dblMod = %d", dblMod));
	DB_PRINT("opt", ("replInternal = %d", replInternal));
	DB_PRINT("opt", ("globExtern = %d", globExtern));
	DB_PRINT("opt", ("modifyTHEADR = %d", modifyTHEADR));
	DB_PRINT("opt", ("alignDir = %d", alignDir));
	DB_PRINT("opt", ("newPageSize = %u", newPageSize));
	DB_PRINT("opt", ("dispInfo = %u", dispInfo));

	chkHeap()
	if(DB_SETJMP(restart))	/* restart library copy */
		restartLIB();
	chkHeap()

	if(pn) {
		if(Lopen(dupstr(pn), libOld)) error(E_openLib, pn);
		pageSizeNew = pageSizeOld;
		dirPagesNew = dirPagesOld;
	}
	if(newPageSize)		/* manually set page size (or by restart code) */
		pageSizeNew = newPageSize;

	if(Lcreate(p, libNew))
		fatal(E_createFile, p);

	chkHeap()
	Sopen();

/* Copy the library, extract & subtract modules */
	chkHeap()
	if(libIn) {
		Rewind(libIn, libNameOld);	/* Lcopy() needs the LIBHEAD record */
		Lcopy();		/* the currently opened module is the source LIB file */
	}

/* Check for not-handled modules */
	if(!ovrrun)
		forEachList(aF(notUsed));
	chkHeap()

/* Append new modules */
	skipModule = dblMod = NUL;
	forEachList(aF(LaddMod));
	if(ovrrun)
		DB_LONGJMP(restart, 1);
	clrList();
	chkHeap()

/* close up library */
	if(cmd != CMD_NONE) {	/* for a non-altered library this may fail completely */
		writeLibEnd();		/* write the LIBEND module */
		Ldir(libNew);
	}

	clrList();
	chkHeap()

	if(argv[optind]) /* create list file */
		mkLstFile(argv[optind]);

	chkHeap()
	if(dispInfo)
		dispPageInfo(0);	/* don't update the page size */

	chkHeap()
	Lclose(libNew);
	Lcloseup(libOld);
	chkHeap()
	stdRet();
	chkHeap()
	DB_RETURN( returnNumber);
}
