/*
** libadd.c
**
** Libraries and Devices Manager: MountLibDev()
**
** $Id: libadd.c,v 3.0 94/08/09 17:26:27 GF Exp Locker: GF $
**
**************************************************************************
**
** Copyright © 1994 by Gabriele Falcioni. All Rights Reserved.
**
** 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.
**
** For inquiries, comments, donations, bug reports, write to:
**
** Gabriele Falcioni
** via E. Cialdini, 50
** I-60122 Ancona
** ITALY
**
*/

#include <string.h>

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/resident.h>
#include <exec/libraries.h>
#include <exec/execbase.h>
#include <dos/dos.h>
#include <dos/dosasl.h>

#include <proto/dos.h>
#include <proto/exec.h>

#include <usrh/filefinder.h>
#include "libman.h"

/* --- My favourite macros -------------------------------------------- */

#define STDMEM (MEMF_PUBLIC|MEMF_CLEAR)

#define ConStruct(type)		((type *)AllocMem(sizeof(type),STDMEM))
#define DiStruct(ptr)		FreeMem((ptr),sizeof(*(ptr)))
#define ClearStruct(ptr)	memset((ptr),0,sizeof(*(ptr)))

#ifndef FOREVER
#define FOREVER for (;;)
#endif

#ifndef STREQ
#define STREQ(s1,s2) (!strcmp((s1),(s2)))
#endif

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

/*
 * NAME
 * find_romtag -- finds a RomTag
 *
 * FUNCTION
 * It finds a RomTag in a given SegList and returns a pointer to it.
 *
 * INPUTS
 * seglist - a BCPL pointer to the SegList to scan.
 *
 * RESULT
 * a pointer to the RomTag or NULL if not found.
 */

static struct Resident *find_romtag(BPTR seglist)
{
	ULONG *segptr;

	segptr = BADDR(seglist);

	{
		register UWORD *scanptr = (UWORD *)(segptr+1);
		register ULONG  scanlen = *(segptr-1) >> 1;	/* in WORDs */

		do {
			if (*scanptr++ == RTC_MATCHWORD)
				if (((UWORD *)*((ULONG *)scanptr)) == (scanptr-1))
					return (struct Resident *)(scanptr-1);
		} while (--scanlen);
	}

	return NULL;
}

/*
 * NAME
 * mount_romtag -- mounts a given romtag
 *
 * FUNCTION
 * It attempts to mount a given romtag. The name and type of the romtag
 * are extracted. The required system list is checked for a node with
 * the same name. If it is found and the force parameter is TRUE the the old
 * node is removed. The new node is initialized and mounted.
 *
 * INPUTS
 * seglist - a pointer to the lib/dev node seglist.
 * romtag  - a pointer to the lib/dev node romtag.
 * force   - if TRUE, the nodes with the same name are removed.
 *
 * RESULT
 * level of failure:
 * RETURN_OK		all libraries/devices succesfully loaded,
 * RETURN_WARN		some libraries/devices substituted to the old ones,
 * RETURN_ERROR	some libraries/devices refused to mount,
 * RETURN_FAIL		total failure.
 * Error messages will be printed (if required) to stdout.
 */

static LONG mount_romtag(
	BPTR seglist,
	register struct Resident *romtag,
	BOOL force)
{
	LONG rc;
	UBYTE type,*name;
	struct Node *node;
	struct List *list;

	rc = RETURN_OK;

	type = romtag->rt_Type;
	name = romtag->rt_Name;

	switch (type) {
		case NT_LIBRARY:
			list = &SysBase->LibList;
			break;

		case NT_DEVICE:
			list = &SysBase->DeviceList;
			break;

		default:
			Printf("Unknown RomTag Type: 0x%02lx\n",type);
			return RETURN_ERROR;
	}

	Forbid();

	/*
	 * check if already mounted and remove it
	 * if requested.
	 */

	if (force && (node = FindName(list,name))) {
		if (type == NT_LIBRARY)
			RemLibrary((struct Library *)node);
		else
			RemDevice((struct Device *)node);
		rc = RETURN_WARN;
	}

	/*
	 * if no more duplicates mount the library/device.
	 */

	if (!FindName(list,name)) {
		if (romtag->rt_Flags & RTF_AUTOINIT) {

			/*
			 * mount an RTF_AUTOINIT library/device.
			 */

			ULONG *autoinit;
			struct Library *ldnode;

			autoinit = (ULONG *)romtag->rt_Init;
			ldnode = MakeLibrary(
				(APTR)autoinit[1],
				(struct InitStruct *)autoinit[2],
				(APTR)autoinit[3],
				(ULONG)autoinit[0],
				seglist);

			if (ldnode) {
				if (type == NT_LIBRARY)
					AddLibrary(ldnode);
				else
					AddDevice((struct Device *)ldnode);
			} else rc = RETURN_ERROR;

		} else {

			/*
			 * self mounting library device.
			 */

			void (* __asm RTInitializer)(
				register __d0 LONG cleared,
				register __a0 BPTR seg,
				register __a6 struct ExecBase *sysbase);

#pragma msg 147 ignore push

			/* lazy programmers hate writing complicated cast */
			RTInitializer = romtag->rt_Init;

#pragma msg 147 pop

			(*RTInitializer)(NULL,seglist,SysBase);
		}

		/*
		 * NOTE
		 *
		 * Printf() from ahead this point is safe
		 * because system list are not accessed anymore.
		 */

		if (rc < RETURN_ERROR)
			Printf("%s %s %s\n",
				(type == NT_LIBRARY ? "Library" : "Device"),
				name,
				(rc == RETURN_WARN ? "replaced" : "added"));

	} else {
		Printf("%s %s already loaded%s",
			(type == NT_LIBRARY ? "Library" : "Device"),
			name,
			(force ? " - failed to remove\n" : "\n"));
		rc = RETURN_ERROR;
	}

	Permit();

	return rc;
}

/*
 * NAME
 * MountLibDev -- mounts a list of libraries and/or devices
 *
 * SYNOPSIS
 * rc = MountLibDev(from,force);
 *
 * LONG MountLibDev(UBYTE **,BOOL);
 *
 * FUNCTION
 * It loads and initializes a list of libraries and/or devices. This
 * function accepts standard AmigaDOS patterns. Every matching file is
 * LoadSeg()ed, an embedded RomTag is searched and the node name and type
 * are extracted. The system list is searched for already mounted nodes with
 * the same name. If one exists the new node is not mounted or the old one
 * can be removed (user option). Then the new node is mounted.
 *
 * INPUTS
 * from  - a pointer to an array of pointers to lib/dev pathnames. Standard
 *         AmigaDOS patterns accepted. The array must be NULL terminated.
 * force - if it's TRUE, duplicates of freshly loaded nodes are removed
 *         before mounting new ones.
 *
 * RESULTS
 * rc - level of failure:
 *      RETURN_OK		all libraries/devices succesfully loaded,
 *      RETURN_WARN	some libraries/devices substituted to old ones,
 *      RETURN_ERROR	some libraries/devices refused to mount,
 *      RETURN_FAIL	total failure.
 *      Error messages will be printed (if required) to stdout.
 */

#define SAFE_SET_RC(rc,level) if ((rc) < (level)) (rc) = (level)

LONG MountLibDev(UBYTE **from,BOOL force)
{
	LONG rc;
	struct FileFinder *ff;

	if (!from) {
		Printf("no libraries or devices to load.\n");
		SetIoErr(ERROR_REQUIRED_ARG_MISSING);
		return RETURN_FAIL;
	}

	if (ff = CreateFileFinder(FILEFT_Argv,from,TAG_END)) {

		while (FindFiles(ff)) {
			BPTR olddir;
			BPTR seglist;
			register struct AnchorPath *ap;

			ap = GetAnchorPath(ff);

			olddir = CurrentDir(ap->ap_Current->an_Lock);

			if (!(seglist = LoadSeg(ap->ap_Info.fib_FileName))) {
				Printf("LoadSeg() failed on: %s - ",ap->ap_Buf);
				PrintFault(IoErr(),NULL);
				rc = RETURN_ERROR;
			}

			(VOID)CurrentDir(olddir);

			if (seglist) {
				struct Resident *romtag;
				LONG new_rc;

				if (romtag = find_romtag(seglist)) {
					if (new_rc = mount_romtag(seglist,romtag,force)) {
						SAFE_SET_RC(rc,new_rc);
					}
				} else {
					Printf("RomTag not found in: %s\n",ap->ap_Buf);
					rc = RETURN_ERROR;
				}

				if (new_rc > RETURN_WARN) UnLoadSeg(seglist);
			}
		}

		if (IoErr()) {
			PrintFault(IoErr(),NULL);
			rc = RETURN_ERROR;
		}

		DeleteFileFinder(ff);
	} else {
		PrintFault(ERROR_NO_FREE_STORE,NULL);
		rc = RETURN_FAIL;
	}

	return rc;
}
