Milán Major

netscape-revival

1 branch
Code

nspr-/src/md_Mac/mdmac.c

#include <Types.h>
#include <Timer.h>
#include <Files.h>
#include <Errors.h>
#include <Folders.h>
#include <Events.h>
#include <Processes.h>
#include <TextUtils.h>
#include <MixedMode.h>

#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stat.h>
#include <stdarg.h>
#include <unix.h>

#include "prthread.h"
#include "mdint.h"
#include "swkern.h"
#include "mdmacmem.h"
#include "prfile.h"
#include "prlog.h"
#include "prprf.h"
#include "MacErrorHandling.h"
#include "prmacos.h"
#include "fastmem.h"
#include "macfeprefs.h"

#include <LowMem.h>

enum {
	kDefalutMacintoshTimeSlice			= 25,
	kTimeSlicesPerWNELoop				= 5,
	kGarbageCollectionEmergencyFundSize = 16 * 1024,
	kGarbageCollectionMemTightFundSize	= 48 * 1024
};

enum {
	// Why arenÕt these defined in Files.h?
	kFileLockedBit						= 0x01,
	kRsrcForkOpenBit					= 0x04,
	kDataForkOpenBit					= 0x08,
	kIsDirectoryBit						= 0x10,
	kEitherForkOpenBit					= 0x80
};

enum {
	uppExitToShellProcInfo 				= kPascalStackBased,
	uppStackSpaceProcInfo				= kRegisterBased 
										  | RESULT_SIZE(SIZE_CODE(sizeof(long)))
										  | REGISTER_RESULT_LOCATION(kRegisterD0)
		 								  | REGISTER_ROUTINE_PARAMETER(1, kRegisterD1, SIZE_CODE(sizeof(UInt16)))
};


#define UNIMPLEMENTED_ROUTINE			\
	debugstr("Not Implemented Yet");	\
	return 0;

// This should be in LowMem.h
#define LMSetStackLowPoint(value)		\
	*((UInt32 *)(0x110)) = (value)

static long 	gInTimerCriticalActive = 0;

//
// Local routines
//
pascal void TimerCallback(TMTaskPtr);
void ActivateTimer(void);
void DeactivateTimer(void);
OSErr GetFullPath(short , long , char **, int *);
void MacintoshInitializeTime(void);
void TruncateFileName(char *macPath);
OSErr ConvertUnixPathToMacPath(const char *, char **);
OSErr ConvertMacPathToUnixPath(const char *macPath, char **unixPath);
OSErr CreateMacPathFromUnixPath(const char *, char **);
void PStrFromCStr(const char *, Str255);
void CStrFromPStr(ConstStr255Param, char **);
int strcmpcore(const char *, const char *, int);
OSErr GetFullPath(short vRefNum, long dirID, char **fullPath, int *strSize);
void _MD_GetRegisters(prword_t *to);
pascal void BumpMainThreadPriorityForEvent(EventKind eventNum);
short GetVolumeRefNumFromName(const char *);

long gClockTimerExpired = false;

PRThread *gPrimaryThread = NULL;

pascal void ExitToShellPatch(void);

UniversalProcPtr	gExitToShellPatchCallThru = NULL;
#if GENERATINGCFM
RoutineDescriptor 	gExitToShellPatchRD = BUILD_ROUTINE_DESCRIPTOR(uppExitToShellProcInfo, &ExitToShellPatch);
#else
#define gExitToShellPatchRD ExitToShellPatch
#endif

UniversalProcPtr	gStackSpacePatchCallThru = NULL;
#if GENERATINGCFM
pascal long StackSpacePatch(UInt16);
RoutineDescriptor 	StackSpacePatchRD = BUILD_ROUTINE_DESCRIPTOR(uppStackSpaceProcInfo, &StackSpacePatch);
#else
pascal long StackSpacePatch();
asm pascal long StackSpacePatchGlue();
#endif



//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark CREATING MACINTOSH THREAD STACKS

#if defined(powerc) || defined(__powerc)
#define kDefaultMacintoshStackSize 	58 * 1024	
#define kIdleThreadStackSize		8 * 1024
#define kFinalizerStackSize 		16 * 1024
#else
#define kDefaultMacintoshStackSize 	58 * 1024
#define kIdleThreadStackSize		8 * 1024
#define kFinalizerStackSize 		16 * 1024
#endif

int _MD_NewStack(PRThread *thread, size_t stackSize)
{
	PRThreadStack*	stackInfo;
	
	if (!strcmp(thread->name, "finalizer")) {
		stackSize = kFinalizerStackSize;
	}
	
	else if (!strcmp(thread->name, "idle")) {
		stackSize = kIdleThreadStackSize;
	}

	if (stackSize == 0)
		stackSize = kDefaultMacintoshStackSize;

	stackInfo = (PRThreadStack*) malloc(sizeof(PRThreadStack));
	if (stackInfo == NULL) {
#if DEBUG
		DebugStr("\p_MD_NewStack failed.");
#endif
		return 0;
	}

	//	Take the actual memory for the stack out of our
	//	Figment heap.

	stackInfo->allocSize = stackSize;
	stackInfo->allocBase = (char *)NewPtr(stackSize);

	if (stackInfo->allocBase == NULL) {
#if DEBUG
		DebugStr("\p_MD_NewStack failed.");
#endif
		free(stackInfo);
		return 0;
	}

	stackInfo->stackBottom = stackInfo->allocBase;
	stackInfo->stackTop = stackInfo->stackBottom + stackSize;
	stackInfo->stackSize = stackSize;
	
#if DEBUG
	//	Put a cookie at the top of the stack so that we can find 
	//	it from Macsbug.
	
	memset(stackInfo->allocBase, 0xDC, stackSize);
	
	((UInt32 *)stackInfo->stackTop)[-1] = 0xBEEFCAFE;
	((UInt32 *)stackInfo->stackTop)[-3] = (UInt32)(thread);
	((UInt32 *)stackInfo->stackBottom)[0] = 0xCAFEBEEF;
#endif
	
	thread->stack = stackInfo;
	
	// Turn off the snack stiffer.  The NSPR stacks are allocated in the 
	// application's heap; this throws the stack sniffer for a tizzy.
	// Note that the sniffer does not run on machines running the thread manager.
	// Yes, we will blast the low-mem every time a new stack is created.  We can afford
	// a couple extra cycles.
	LMSetStackLowPoint(0);
	
	return 1;
}

void _MD_FreeStack(PRThread *thread)
{
#if DEBUG
	//	Clear out our cookies. 
	
	memset(thread->stack->allocBase, 0xEF, thread->stack->stackSize);
	
	((UInt32 *)thread->stack->stackTop)[-1] = 0;
	((UInt32 *)thread->stack->stackTop)[-3] = 0;
	((UInt32 *)thread->stack->stackBottom)[0] = 0;
#endif

	if (thread->stack != NULL) {
		if (thread->stack->allocBase != NULL)
			DisposePtr((Ptr)(thread->stack->allocBase));
		free(thread->stack);	
	}
}

#if !GENERATINGCFM

asm long pascal StackSpacePatchCallThruGlue(long theAddress)
{
	move.l	4(sp), a0
	jsr		(a0)
	move.l	(sp)+, a0
	add		#0x8, sp 
	move.l	d0, -(sp)
	jmp		(a0)
}

asm pascal long StackSpacePatchGlue()
{

	//	Check out LocalA5.  If it is zero, then
	//	it is our first time through, and we should
	//	store away our A5.  If not, then we are
	//	a real time manager callback, so we should
	//	store away A5, set up our local a5, jsr
	//	to our callback, and then restore the
	//	previous A5.
	
	lea			LocalA5, a0
	move.l		(a0), d0
	cmpi.l		#0, d0
	bne			DoStackSpace
	
	move.l		a5, (a0)
	rts

DoStackSpace:
	
	//	Save A5, restore our local A5
	
	move.l		a5, -(sp)
	move.l		d0, a5 
	
	//	Jump to our C routine
	
	clr.l		-(sp)
	jsr 		StackSpacePatch
	move.l		(sp)+, d0
	
	//	Restore the previous A5
	
	move.l		(sp)+, a5

	rts

LocalA5:

	dc.l		0
	
}

#endif

#if GENERATINGCFM
pascal long StackSpacePatch(UInt16 trapNo)
#else
pascal long StackSpacePatch()
#endif
{
	char		tos;
	PRThread	*thisThread;
	
	thisThread = PR_CurrentThread();
	
	//	If we are the primary thread, then call through to the
	//	good ol' fashion stack space implementation.  Otherwise,
	//	compute it by hand.
	if ((thisThread == gPrimaryThread) || 	
		(&tos < thisThread->stack->stackBottom) || 
		(&tos > thisThread->stack->stackTop)) {
#if GENERATINGCFM
		return CallOSTrapUniversalProc(gStackSpacePatchCallThru, uppStackSpaceProcInfo, trapNo);
#else
		return StackSpacePatchCallThruGlue((long)gStackSpacePatchCallThru);
#endif
	}
	else {
		return &tos - thisThread->stack->stackBottom;
	}
}


//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark TIME MANAGER-BASED CLOCK

TMTask		gTimeManagerTaskElem;
Boolean		gTimerHasFired = false;
Boolean 	gShouldReprime = false;
long		gInTimerCallback = false;
long		gTimeSlicesOnNonPrimaryThread = 0;

pascal void TimerCallback(TMTaskPtr tmTaskPtr)
{
	gInTimerCallback = true;
	
	if (gInTimerCriticalActive == 0) {

	#pragma unused (tmTaskPtr)
		gClockTimerExpired = true;
		
		//	And tell nspr that a clock interrupt occured.
		
		_PR_ClockInterruptHandler();
		
		//	This is a good opportunity to make sure that the main
		//	mozilla thread actually gets some time.  If interrupts
		//	are on, then we know it is safe to check if the main
		//	thread is being starved.  If moz has not been scheduled
		//	for a long time, then then temporarily bump the fe priority 
		//	up so that it gets to run at least one. 
		
		gTimeSlicesOnNonPrimaryThread++;

		if (!_pr_intsOff) {

			_pr_intsOff = 1;
	 
			if (gTimeSlicesOnNonPrimaryThread >= 20) {
				_PR_SetThreadPriority(gPrimaryThread, 31);
				gTimeSlicesOnNonPrimaryThread = 0;
			}
			
			if (gPrimaryThread == PR_CurrentThread())
				gTimeSlicesOnNonPrimaryThread = 0;
		
			_pr_intsOff = 0;

		}

		//	Reset the clock timer so that we fire again.
		
		ResetTimer();

	}
	
	gInTimerCallback = false;
}

#if GENERATINGCFM

RoutineDescriptor 	gTimerCallbackRD = BUILD_ROUTINE_DESCRIPTOR(uppTimerProcInfo, &TimerCallback);

#else

asm void gTimerCallbackRD(void)
{
	//	Check out LocalA5.  If it is zero, then
	//	it is our first time through, and we should
	//	store away our A5.  If not, then we are
	//	a real time manager callback, so we should
	//	store away A5, set up our local a5, jsr
	//	to our callback, and then restore the
	//	previous A5.
	
	lea			LocalA5, a0
	move.l		(a0), d0
	cmpi.l		#0, d0
	bne			TimerProc
	
	move.l		a5, (a0)
	rts

TimerProc:
	
	//	Save A5, restore our local A5
	
	move.l		a5, -(sp)
	move.l		d0, a5 
	
	//	Jump to our C routine
	
	move.l 		a1, -(sp)
	jsr 		TimerCallback
	
	//	Restore the previous A5
	
	move.l		(sp)+, a5

	rts

LocalA5:

	dc.l		0
	
}

#endif

void ActivateTimer(void)
{
	gInTimerCriticalActive++;

	//	NOTE:  	Software interrupts must be off at this time
	//			for any of this cruft to work!

	gTimeManagerTaskElem.qLink = 0;

	//	Make sure that our time manager task is ready to go.
	InsTime((QElemPtr)&gTimeManagerTaskElem);
	
	PrimeTime((QElemPtr)&gTimeManagerTaskElem, 5); // Default clock tick is 5 ms

	gInTimerCriticalActive--;

}

void DeactivateTimer(void)
{
	gInTimerCriticalActive++;
	
	RmvTime((QElemPtr)&gTimeManagerTaskElem);
	gTimeManagerTaskElem.qLink = 0;
	
	gInTimerCriticalActive--;
}

void ResetTimer(void)
{
	gInTimerCriticalActive++;
	DeactivateTimer();
	ActivateTimer();
	gInTimerCriticalActive--;
}


void _MD_StartClock(void)
{

	//	If we are not generating CFM-happy code, then make sure that 
	//	we call our callback wrapper once so that we can
	//	save away our A5.

#if !GENERATINGCFM
	gTimerCallbackRD();
#endif

	//	Fill in the Time Manager queue element
	
	gTimeManagerTaskElem.tmAddr = (TimerUPP)&gTimerCallbackRD;
	gTimeManagerTaskElem.tmWakeUp = 0;
	gTimeManagerTaskElem.tmReserved = 0;

	TimerCallback(NULL);
}


void _PR_Pause(void)
{	
	PR_Yield();
}


//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark TIME

UnsignedWide			dstLocalBaseMicroseconds;
unsigned long			gJanuaryFirst1970Seconds;

void MacintoshInitializeTime(void)
{
	UnsignedWide			upTime;
	unsigned long			currentLocalTimeSeconds,
							startupTimeSeconds;
	uint64					startupTimeMicroSeconds;
	uint32					upTimeSeconds;	
	uint64					oneMillion, upTimeSecondsLong, microSecondsToSeconds;
	DateTimeRec				firstSecondOfUnixTime;
	
	//	Figure out in local time what time the machine 
	//	started up.  This information can be added to 
	//	upTime to figure out the current local time 
	//	as well as GMT.

	Microseconds(&upTime);
	
	GetDateTime(&currentLocalTimeSeconds);
	
	LL_I2L(microSecondsToSeconds, PR_USEC_PER_SEC);
	LL_DIV(upTimeSecondsLong,  *((uint64 *)&upTime), microSecondsToSeconds);
	LL_L2I(upTimeSeconds, upTimeSecondsLong);
	
	startupTimeSeconds = currentLocalTimeSeconds - upTimeSeconds;
	
	//	Make sure that we normalize the macintosh base seconds
	//	to the unix base of January 1, 1970.
	
	firstSecondOfUnixTime.year = 1970;
	firstSecondOfUnixTime.month = 1;
	firstSecondOfUnixTime.day = 1;
	firstSecondOfUnixTime.hour = 0;
	firstSecondOfUnixTime.minute = 0;
	firstSecondOfUnixTime.second = 0;
	firstSecondOfUnixTime.dayOfWeek = 0;
	
	DateToSeconds(&firstSecondOfUnixTime, &gJanuaryFirst1970Seconds);
	
	startupTimeSeconds -= gJanuaryFirst1970Seconds;
	
	//	Now convert the startup time into a wide so that we
	//	can figure out GMT and DST.
	
	LL_I2L(startupTimeMicroSeconds, startupTimeSeconds);
	LL_I2L(oneMillion, PR_USEC_PER_SEC);
	LL_MUL(dstLocalBaseMicroseconds, oneMillion, startupTimeMicroSeconds);
}



//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark STDCLIB

void abort(void)
{
	exit(-1);
}

void *memcpy(void *to, const void *from, size_t size)
{
	if (size != 0) {
#if DEBUG
		if ((UInt32)to < 0x1000)
			DebugStr("\pmemcpy has illegal to argument");
		if ((UInt32)from < 0x1000)
			DebugStr("\pmemcpy has illegal from argument");
#endif
		BlockMoveData(from, to, size);
	}
	return to;
}


//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark MISCELLANEOUS

void debugstr(const char *debuggerMsg)
{
	Str255		pStr;
	
	PStrFromCStr(debuggerMsg, pStr);
	DebugStr(pStr);
}


char *strdup(const char *source)
{
	char 	*newAllocation;
	size_t	stringLength;

#ifdef DEBUG
	PR_ASSERT(source);
#endif
	
	stringLength = strlen(source) + 1;
	
	newAllocation = (char *)malloc(stringLength);
	if (newAllocation == NULL)
		return NULL;
	BlockMoveData(source, newAllocation, stringLength);
	return newAllocation;
}

prword_t *_MD_HomeGCRegisters(PRThread *t, int isCurrent, int *np)
{
    if (isCurrent) {
	(void) setjmp(t->context.registers);
    }
    *np = sizeof(t->context) / sizeof(prword_t);
    return (prword_t*) (t->context.registers);
}

void _MD_GetRegisters(prword_t *to)
{
  (void) setjmp((void*) to);
}

typedef struct GCBlockHeader GCBlockHeader;

enum {
	kGCBlockNormalType				= 1,
	kGCBlockTempMemoryType,
	kGCBlockEmergencyFundType
};

struct GCBlockHeader {
	UInt32		blockType;
	Handle		blockHandle;
};

void *_MD_GrowGCHeap(size_t *sizep)
{
	static void		*gEmergencyFund = NULL;
	void			*heapPtr = NULL;
	size_t			sizeToAllocate = *sizep;
	Handle			newTempMemoryHandle;
	OSErr			resultCode;
	
	if (gEmergencyFund == NULL) {
		gEmergencyFund = NewPtr(kGarbageCollectionEmergencyFundSize + sizeof(GCBlockHeader));
		if (gEmergencyFund != NULL)	{
			((GCBlockHeader *)gEmergencyFund)->blockType = kGCBlockEmergencyFundType;
		}
	}
	
	//	I know this is evil, but try out of the temp 
	//	memory heap before the app.  This helps us run 
	//	better on systems with more memory.  It also
	//	makes us look less greedy.
	
	newTempMemoryHandle = TempNewHandle(sizeToAllocate + sizeof(GCBlockHeader), &resultCode);
	if (newTempMemoryHandle != NULL) {
		GCBlockHeader	*blockHeader = (GCBlockHeader *)(*newTempMemoryHandle);
		blockHeader->blockType = kGCBlockTempMemoryType;
		blockHeader->blockHandle = newTempMemoryHandle;
		MoveHHi(newTempMemoryHandle);
		HLock(newTempMemoryHandle);
		heapPtr = (void *)*newTempMemoryHandle;
	}
	
	else {	
		heapPtr = NewPtr(sizeToAllocate);
	}
	
	//	If we can't get a full segment, try a smaller one
	
	if (heapPtr == NULL) {
		heapPtr = NewPtr(kGarbageCollectionMemTightFundSize + sizeof(GCBlockHeader));
		if (heapPtr != NULL) {
			((GCBlockHeader *)heapPtr)->blockType = kGCBlockNormalType;
		}
		*sizep = kGarbageCollectionMemTightFundSize;
	}
	
	//	If even this fails, then use our emergency 
	//	(preallocated) fund.
	
	if (heapPtr == NULL) {
		heapPtr = gEmergencyFund;
		if (heapPtr == NULL)
			*sizep = 0;
		else	
			*sizep = kGarbageCollectionEmergencyFundSize;
	}
	
	if (heapPtr == NULL) {
		return NULL;
	} else
		return (void *)((char *)heapPtr + sizeof(sizeof(GCBlockHeader))); 

}

void _MD_FreeGCSegment(void *base, int32 len)
{
	GCBlockHeader	*blockHeader = (GCBlockHeader *)((char *)base - sizeof(GCBlockHeader));
	
	switch (blockHeader->blockType) {
	
		case kGCBlockNormalType:
			DisposePtr((Ptr)blockHeader);
			break;
		
		case kGCBlockTempMemoryType:
			DisposeHandle(blockHeader->blockHandle);
			break;

		case kGCBlockEmergencyFundType:
			DisposePtr((Ptr)blockHeader);
			break;
	
	}

}


void _MD_InitOS(int when)
{
	gPrimaryThread = PR_CurrentThread();

	if (when == _MD_INIT_AT_START) {
	
		Handle		environmentVariables;

		MacintoshInitializeMemory();
		MacintoshInitializeTime();
		
		//	Install resource-controlled environment variables.
		
		environmentVariables = GetResource('Envi', 128);
		if (environmentVariables != NULL) {
		
			Size 	resourceSize;
			char	*currentPutEnvString = (char *)*environmentVariables,
					*currentScanChar = currentPutEnvString;
					
			resourceSize = GetHandleSize(environmentVariables);			
			DetachResource(environmentVariables);
			HLock(environmentVariables);
			
			while (resourceSize--) {
			
				if ((*currentScanChar == '\n') || (*currentScanChar == '\r')) {
					*currentScanChar = 0;
					putenv(currentPutEnvString);
					currentPutEnvString = currentScanChar + 1;
				}
			
				currentScanChar++;
			
			}
			
			DisposeHandle(environmentVariables);

		}

#if GENERATINGCFM
		gStackSpacePatchCallThru = GetOSTrapAddress(0x0065);
		SetOSTrapAddress((UniversalProcPtr)&StackSpacePatchRD, 0x0065);
		{
			long foo;
			foo = StackSpace();
		}
#else
		gStackSpacePatchCallThru = GetOSTrapAddress(0x0065);
		SetOSTrapAddress((UniversalProcPtr)&StackSpacePatchGlue, 0x0065);
		StackSpace();
#endif

		//	THIS IS VERY IMPORTANT.  Install our ExitToShell patch.  
		//	This allows us to deactivate our Time Mananger task even
		//	if we are not totally gracefully exited.  If this is not
		//	done then we will randomly crash at later times when the
		//	task is called after the app heap is gone.
		
		gExitToShellPatchCallThru = GetToolboxTrapAddress(0x01F4);
		SetToolboxTrapAddress((UniversalProcPtr)&gExitToShellPatchRD, 0x01F4);

	}
	
}

PRThread *PR_GetPrimaryThread()
{
	return gPrimaryThread;
}

void _PR_InitIO(void)
{

}

void PR_InitMemory(void) {
	//	Needed for Mac browsers without Java.  We donÕt want them calling PR_INIT, since it
	//	brings in all of the thread support.  But we do need to allow them to initialize
	//	the NSPR memory package.
	//	This should go away when all clients of the NSPR want threads AND memory.
	MacintoshInitializeMemory();
}

//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark TERMINATION

typedef pascal void (* ExitToShellProc)(void);  

//	THIS IS *** VERY *** IMPORTANT... our ExitToShell patch.
//	This allows us to deactivate our Time Mananger task even
//	if we are not totally gracefully exited.  If this is not
//	done then we will randomly crash at later times when the
//	task is called after the app heap is gone.

pascal void ExitToShellPatch(void)
{
	gInTimerCriticalActive++;
	DeactivateTimer();
	
#if DEBUG_MAC_MEMORY || STATS_MAC_MEMORY
	DumpAllocHeap();
#endif
	
#if GENERATINGCFM
	CallUniversalProc(gExitToShellPatchCallThru, uppExitToShellProcInfo);
#else 
	{
		ExitToShellProc	*exitProc = (ExitToShellProc *)&gExitToShellPatchCallThru;
		(*exitProc)();
	}
#endif

}


//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark STDC STYLE IO


OSErr GetFullPath(short vRefNum, long dirID, char **fullPath, int *strSize)
{
	Str255			pascalDirName;
	char			cDirName[256];
	char			*tmpPath = NULL;						// needed since sprintf isnÕt safe
	CInfoPBRec		myPB;
	OSErr			err = noErr;
	
	
	// get the full path of the temp folder.
	*strSize = 256;
	*fullPath = NULL;
	*fullPath = malloc(*strSize);	// How big should this thing be?
	require_action (*fullPath != NULL, errorExit, err = memFullErr;);
		
	tmpPath = malloc(*strSize);
	require_action (tmpPath != NULL, errorExit, err = memFullErr;);

	strcpy(*fullPath, "");				// Clear C result
	strcpy(tmpPath, "");
	pascalDirName[0] = 0;				// Clear Pascal intermediate string
	
	myPB.dirInfo.ioNamePtr = &pascalDirName[0];
	myPB.dirInfo.ioVRefNum = vRefNum;
	myPB.dirInfo.ioDrParID = dirID;
	myPB.dirInfo.ioFDirIndex = -1;				// Getting info about
	
	do {
		myPB.dirInfo.ioDrDirID = myPB.dirInfo.ioDrParID;

		err = PBGetCatInfoSync(&myPB);
		require(err == noErr, errorExit);
			
		// Move the name into C domain
		memcpy(&cDirName, &pascalDirName, 256);
		p2cstr((unsigned char *)&cDirName);							// Changes in place!
		
		if ((strlen(cDirName) + strlen(*fullPath)) > *strSize) {
			// We need to grow the string, do it in 256 byte chunks
			(*strSize) += 256;										
			*fullPath = realloc(*fullPath, *strSize);
			require_action (*fullPath != NULL, errorExit, err = memFullErr;);

			tmpPath = realloc(tmpPath, *strSize);
			require_action (tmpPath != NULL, errorExit, err = memFullErr;);
		}
		sprintf(tmpPath, "%s:%s", cDirName, *fullPath);
		strcpy(*fullPath, tmpPath);
	} while (myPB.dirInfo.ioDrDirID != fsRtDirID);
	
	free(tmpPath);
	
	return noErr;
	
	
errorExit:
	free(*fullPath);
	free(tmpPath);
	
	return err;

}



OSErr CreateMacPathFromUnixPath(const char *unixPath, char **macPath)
{
	// Given a Unix style path with '/' directory separators, this allocates 
	// a path with Mac style directory separators in the path.
	//
	// It does not do any special directory translation; use ConvertUnixPathToMacPath
	// for that.
	
	char		*src;
	char		*tgt;
	OSErr		err = noErr;

	*macPath = malloc(strlen(unixPath) * 2);	// Will be enough extra space.
	require_action (*macPath != NULL, exit, err = memFullErr;);

	strcpy(*macPath, "");						// Clear the Mac path
	
	(const char *)src = unixPath;
	tgt = *macPath;
	
	if (strchr(src, DIRECTORY_SEPARATOR) == src)				// If weÕre dealing with an absolute
		src++;													// path, skip the separator
	else
		*(tgt++) = MAC_PATH_SEPARATOR;	
		
	if (strstr(src, UNIX_THIS_DIRECTORY_STR) == src)			// If it starts with /
		src += 2;												// skip it.
		
	while (*src) 
	{				// deal with the rest of the path
		if (strstr(src, UNIX_PARENT_DIRECTORY_STR) == src) {	// Going up?
			*(tgt++) = MAC_PATH_SEPARATOR;						// simply add an extra colon.
			src +=3;
		}
		else if (*src == DIRECTORY_SEPARATOR) {					// Change the separator
			*(tgt++) = MAC_PATH_SEPARATOR;
			src++;
		}
		else
			*(tgt++) = *(src++);
	}
	
	*tgt = NULL;							// make sure itÕs null terminated.

exit:
	return err;
}

OSErr ConvertUnixPathToMacPath(const char *unixPath, char **macPath)
{	
		OSErr		err = noErr;
		

	//	******** HACK ALERT ********
	//
	//	Java really wants long file names (>31 chars).  We truncate file names 
	//	greater than 31 characters long.  Truncation is from the middle.
	//
	//	Convert UNIX style path names (with . and / separators) into a Macintosh
	//	style path (with :).
	//
	// There are also a couple of special paths that need to be dealt with
	// by translating them to the appropriate Mac special folders.  These include:
	//
	//			/usr/tmp/file  =>  {TempFolder}file
	//
	// The file conversions we need to do are as follows:
	//
	//			file			=>		file
	//			dir/file		=>		:dir:file
	//			./file			=>		file
	//			../file			=>		::file
	//			../dir/file		=>		::dir:file
	//			/file			=>		::BootDrive:file
	//			/dir/file		=>		::BootDrive:dir:file
	
	
	
	if (*unixPath != DIRECTORY_SEPARATOR) {				// Not root relative, just convert it.
		err = CreateMacPathFromUnixPath(unixPath, macPath);
	}
	
	else {
		// WeÕre root-relative.  This is either a special Unix directory, or a 
		// full path (which weÕll support on the Mac since they might be generated).
		// This is not condoning the use of full-paths on the Macintosh for file 
		// specification.
		
		short		foundVRefNum;
		long		foundDirID;
		int			pathBufferSize;
		char		*temp;
		char		isNetscapeDir = false;

		// Are we dealing with the temp folder?
		if ((strncmp(unixPath, "/usr/tmp", strlen("/usr/tmp")) == 0) || 
			((strncmp(unixPath, "/tmp", strlen("/tmp")) == 0))) {
			unixPath = strrchr(unixPath, DIRECTORY_SEPARATOR);						// Find last back-slash
			unixPath++;																// Skip the slash
			err = FindFolder(kOnSystemDisk, kTemporaryFolderType, kCreateFolder,	// Create if needed
								&foundVRefNum, &foundDirID);
		}
		
		else if (strncmp(unixPath, "/bin", strlen("/bin")) == 0) {
			dprintf("Unable to translate Unix file path %s to Mac path\n", unixPath);
			err = -1;
			goto Exit_ConvertUnixPathToMacPath;
		}
		
		else if (strncmp(unixPath, "/dev", strlen("/dev")) == 0) {
			dprintf("Unable to translate Unix file path %s to Mac path\n", unixPath);
			err = -1;
			goto Exit_ConvertUnixPathToMacPath;
		}
	
		else if (strncmp(unixPath, "/etc", strlen("/etc")) == 0) {
			dprintf("Unable to translate Unix file path %s to Mac path\n", unixPath);
			err = -1;
			goto Exit_ConvertUnixPathToMacPath;
		}
	
		else if (strncmp(unixPath, "/usr", strlen("/usr")) == 0) {
		
			int		usrNetscapePathLen;
			
			usrNetscapePathLen = strlen("/usr/local/netscape/");
		
			if (strncmp(unixPath, "/usr/local/netscape/", usrNetscapePathLen) == 0) {
				unixPath += usrNetscapePathLen;				
				err = FindPreferencesFolder(&foundVRefNum, &foundDirID);
				isNetscapeDir = true;
			}
			
			else {
				dprintf("Unable to translate Unix file path %s to Mac path\n", unixPath);
				err = -1;
				goto Exit_ConvertUnixPathToMacPath;
			}

		}
		
		else {
			// This is a root relative directory, weÕll just convert the whole thing.
			err = CreateMacPathFromUnixPath(unixPath, macPath);
			goto Exit_ConvertUnixPathToMacPath;
		}
	
		// WeÕre dealing with a special folder
		if (err == noErr)
			// Get the path to the root-relative directory
			err = GetFullPath(foundVRefNum, foundDirID, macPath, &pathBufferSize);		// mallocs macPath
		
		// Insert bad O.J. joke here
		if (err == noErr){
			
			// copy over the remaining file name, converting
			if (pathBufferSize < (strlen(*macPath) + strlen(unixPath))) {
				// need to grow string
				*macPath = realloc(*macPath, (strlen(*macPath) + strlen(unixPath) + 
					(isNetscapeDir ? strlen("Netscape Ä:") : 0)));
				err = (*macPath == NULL ? memFullErr : noErr);
			}
			
			if (isNetscapeDir)
				strcat(*macPath, "Netscape Ä:");
		
			if (err == noErr)
				strcat(*macPath, unixPath);
			
			//	Make sure that all of the /Õs are :Õs in the final pathname
				
			for (temp = *macPath + strlen(*macPath) - strlen(unixPath); *temp != '\0'; temp++) {
				if (*temp == DIRECTORY_SEPARATOR)
					*temp = MAC_PATH_SEPARATOR;
			}

		}
	}
	
	
Exit_ConvertUnixPathToMacPath:

	if (err == noErr) {
		// Fix file names which are too long
		// *** HACK TO BE REMOVED BEFORE SHIPPING ***
		char	*fileName;
		
		fileName = strrchr(*macPath, MAC_PATH_SEPARATOR);		// Find last colon
		if (fileName == NULL)									// No colon, 
			fileName = *macPath;								// so use file name
		else													// otherwise
			fileName++;											// skip the colon.
		MapFullToPartialMacFile(fileName);
	}
	return err;
}


// Hey! Before you delete this "hack" you should look at how it's being
// used by sun-java/netscape/applet/appletStubs.c.
OSErr ConvertMacPathToUnixPath(const char *macPath, char **unixPath) 
{
	// *** HACK ***
	// Get minimal version working
	
	char		*unixPathPtr;
	
	*unixPath = malloc(strlen(macPath) + 2);	// Add one for the front slash, one for null
	if (*unixPath == NULL)
		return (memFullErr);
		
	unixPathPtr = *unixPath;
	
	*unixPathPtr++ = DIRECTORY_SEPARATOR;
	
	do {
		// Translate all colons to slashes
		if (*macPath == MAC_PATH_SEPARATOR)
			*unixPathPtr = DIRECTORY_SEPARATOR;
		else
			*unixPathPtr = *macPath;

		unixPathPtr++;
		macPath++;
	} while (*macPath != NULL);
	
	// Terminate the string
	*unixPathPtr = '\0';
	
	return (noErr);
}

OSErr ConvertUnixPathToFSSpec(const char *unixPath, FSSpec *fileSpec)
{
	char			*macPath,
					*macPathCopy,
					*scanChar;
	CInfoPBRec		pb;
	Str255			pascalMacPath;
	short			volume;
	short			fileNameLength;
	OSErr			convertError;
	
	convertError = ConvertUnixPathToMacPath(unixPath, &macPath);
	if (convertError != noErr)
		return convertError;
		
	macPathCopy = strdup(macPath);
	if (macPathCopy == NULL) {
		free(macPath);
		return memFullErr;
	}
	
	scanChar = macPathCopy;
	while ((*scanChar != ':') && (*scanChar != '\0'))
		scanChar++;
	*scanChar = '\0';
		
	volume = GetVolumeRefNumFromName(macPathCopy);
	
	free(macPathCopy);

	// Get info about the object.
	PStrFromCStr(macPath, pascalMacPath);
	
	pb.dirInfo.ioNamePtr = pascalMacPath;
	pb.dirInfo.ioVRefNum = volume;
	pb.dirInfo.ioDrDirID = 0;
	pb.dirInfo.ioFDirIndex = 0;
	
	if (PBGetCatInfoSync(&pb) != noErr) {
		free(macPath);
		return paramErr;
	}
	
	scanChar = macPath + strlen(macPath);
	while ((*scanChar != ':') && (scanChar != macPath))
		scanChar--;
	
	if (scanChar == macPath)
		return paramErr;
	
	scanChar++;
		
	fileNameLength = strlen(scanChar);
	if (fileNameLength > 255)
		fileNameLength = 255;
		
	BlockMove(scanChar, fileSpec->name + 1, fileNameLength);
	fileSpec->name[0] = fileNameLength;
	fileSpec->parID = pb.dirInfo.ioDrParID;
	fileSpec->vRefNum = volume;
	
	free(macPath);

	return noErr;

}

int PR_OpenFile(const char *path, int oflag, int mode)
{
	// Macintosh doesnÕt really have mode bits, just drop them
	#pragma unused (mode)

    int 	result;
    char	*macPath = NULL;
    OSErr	err = noErr;
        
    
    err = ConvertUnixPathToMacPath(path, &macPath);
    	    
    // Use the standard C library open, using the newPath if we created one
    if (err == noErr) {
		result = open(((macPath == NULL) ? path : (const char *)macPath), oflag);
	    result = (result < 0 ? errno : result);
	}
	else
		result = err;
    
	if (macPath != NULL)
    	free(macPath);
    	
    return result;
}

int PR_Stat(const char *path, struct stat *buf)
{
	OSErr	err = noErr;
	char	*macFileName = NULL;
	int		result;	
	
    err = ConvertUnixPathToMacPath(path, &macFileName);
	
	if (err == noErr)
		result = stat(macFileName, buf);
	else {
		result = -1;
	}
		
	free(macFileName);
	
	return result;
}

FILE *_OS_FOPEN(const char *filename, const char *mode) 
{
	OSErr	err = noErr;
	char	*macFileName = NULL;
	FILE	*result;
	
	
    err = ConvertUnixPathToMacPath(filename, &macFileName);
	
	if (err == noErr)
		result = fopen(macFileName, mode);
	else {
		result = NULL;
		errno = err;
	}
		
	free(macFileName);
	
	return result;
}



int PR_Mkdir(char *unixPath, int mode)
{
	HFileParam		fpb;
	Str255			pMacPath = "\p";
	char			*cMacPath = NULL;
	OSErr			err = -1;

	#pragma unused (mode)					// Mode is ignored on the Mac
	if (unixPath) {
    	err = ConvertUnixPathToMacPath(unixPath, &cMacPath);

    	if (err == noErr) {
	    	PStrFromCStr(cMacPath, pMacPath);
			fpb.ioNamePtr = pMacPath;
			fpb.ioVRefNum = 0;
			fpb.ioDirID = 0L;
			err = PBDirCreateSync((HParmBlkPtr)&fpb);
		}
	}

	free(cMacPath);
	
	if (err != noErr)
		errno = err;
	return (err == noErr ? 0 : -1);
}



int PR_AccessFile(char *path, int amode)
{
	//
	// Emulate the Unix access routine
	//
	
	CInfoPBRec		pb;
	OSErr			err;
	char			*cMacPath = NULL;
	Str255			pacalMacPath;
	int				result = noErr;
	struct stat		info;
	
	
	// Convert to a Mac style path
	err = ConvertUnixPathToMacPath(path, &cMacPath);
	require_action (err == noErr, finish, {result = -1; errno = ENOENT;});
	
	err = stat(cMacPath, &info);
	require_action (err == noErr, finish, {result = -1; errno = ENOENT;});
	
	// If all weÕre doing is checking for the existence of the file, weÕre out of here.
	// On the Mac, if a file exists, you can read from it.
	// This doesnÕt handle remote AppleShare volumes.  Does it need to?
	if ((amode == PR_AF_EXISTS) || (amode == PR_AF_READ_OK)) {
		result = 0;
		goto finish;
	}
	
	PStrFromCStr(cMacPath, pacalMacPath);
	
	pb.hFileInfo.ioNamePtr = pacalMacPath;
	pb.hFileInfo.ioVRefNum = info.st_dev;
	pb.hFileInfo.ioDirID = 0;
	pb.hFileInfo.ioFDirIndex = 0;
	
	err = PBGetCatInfoSync(&pb);
	require_action (err == noErr, finish, {result = -1; errno = ENOENT;});
	
	// Check out all the access permissions.
	
	if (amode == PR_AF_WRITE_OK) {
		// Make sure the file isnÕt open by someone else.  Is this correct?
		if (pb.hFileInfo.ioFlAttrib & kEitherForkOpenBit) {
			errno = EACCES;
			result = -1;
		}
	}
	
	finish:
		free(cMacPath);
		return result;
}



long PR_Seek(int fd, long off, int whence) {
	
	return lseek(fd, off, whence);
}



//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark DIRECTORY OPERATIONS

struct PRDirStr {
	char		*currentFileName;
	short		ioVRefNum;
	long		ioDirID;
	short		ioFDirIndex;
};
typedef struct PRDirStr PRDirStr;



short GetVolumeRefNumFromName(const char *cTgtVolName)
{
	Str32				pVolName;
	char				*cVolName = NULL;
	OSErr				err;
	HParamBlockRec		hPB;
	short				refNum = 0;
	
	hPB.volumeParam.ioVolIndex = 0;
	hPB.volumeParam.ioNamePtr = pVolName;
	do {
		hPB.volumeParam.ioVolIndex++;
		err = PBHGetVInfoSync(&hPB);
		CStrFromPStr(pVolName, &cVolName);
		if (strcmp(cTgtVolName, cVolName) == 0) {
			refNum =  hPB.volumeParam.ioVRefNum;
			break;
		}
	} while (err == noErr);
	
	return refNum;
}



PRDir *PR_OpenDirectory(char *cUnixPath)
{
	// Emulate the Unix opendir() routine.

	PRDirStr		*dir = NULL;
	char			*cMacPath = NULL;
	char			*position = NULL;
	char			cVolName[32];
	CInfoPBRec		pb;
	Str255			pascalMacPath;
	
	
	// Allocate the directory structure
	dir = malloc(sizeof(PRDir));
	require (dir != NULL, exitRelease);
		
	// Get the Macintosh path
	require((ConvertUnixPathToMacPath(cUnixPath, &cMacPath) == noErr), exitRelease);
	
	// Get the vRefNum
	position = strchr(cMacPath, MAC_PATH_SEPARATOR);
	if ((position == cMacPath) || (position == NULL))
		dir->ioVRefNum = 0;										// Use application relative searching
	else {
		memset(cVolName, 0, sizeof(cVolName));
		strncpy(cVolName, cMacPath, position-cMacPath);
		dir->ioVRefNum = GetVolumeRefNumFromName(cVolName);
	}

	// Get info about the object.
	PStrFromCStr(cMacPath, pascalMacPath);
	
	pb.dirInfo.ioNamePtr = pascalMacPath;
	pb.dirInfo.ioVRefNum = dir->ioVRefNum;
	pb.dirInfo.ioDrDirID = 0;
	pb.dirInfo.ioFDirIndex = 0;
	require(PBGetCatInfoSync(&pb) == noErr, exitRelease);
	
	// Are we dealing with a directory?
	require(((pb.dirInfo.ioFlAttrib & ioDirFlg) == 0), exitRelease);
	
	// This is a directory, store away the pertinent information
	dir->ioDirID = pb.dirInfo.ioDrDirID;
	dir->currentFileName = NULL;
	dir->ioFDirIndex = 1;						// We post increment.  I.e. index is always the
												// nth. item we should get on the next call.
	goto exit;
	
	exitRelease:
		free(dir);
		dir = NULL;
		
	exit:
		free(cMacPath);
		return dir;
}


PRDirEntry *PR_ReadDirectory(PRDir *dir, int flags)
{
	// Emulate the Unix readdir() routine.
	#pragma unused (flags)			// Mac doesnÕt have the concept of . & ..
	
	CInfoPBRec					pb;
	char						*tempStr;
	char						*mappedFile;
	Str255						pascalName = "\p";
	
	if (dir == NULL)
		return NULL;

	// Release the last name read.
	free(dir->currentFileName);
	dir->currentFileName = NULL;
		
	// WeÕve got all the info we need, just get info about this guy.
	pb.hFileInfo.ioNamePtr = pascalName;
	pb.hFileInfo.ioVRefNum = dir->ioVRefNum;
	pb.hFileInfo.ioFDirIndex = dir->ioFDirIndex;
	pb.hFileInfo.ioDirID = dir->ioDirID;
	if (PBGetCatInfoSync(&pb) != noErr)
		return NULL;
	
	// Convert the Pascal string to a C string.
	CStrFromPStr(pascalName, &tempStr);
	
	// Map the file if we need to
	mappedFile = MapPartialToFullMacFile(tempStr);
	if (mappedFile == NULL)
		dir->currentFileName = tempStr;
	else {
		dir->currentFileName = mappedFile;
		free(tempStr);
	}
	
	dir->ioFDirIndex++;
	
	return (PRDirEntry *)dir;
}



void PR_CloseDirectory(PRDir *dir)
{
	// Emulate the Unix closedir() routine
	free(dir->currentFileName);
	free(dir);
}


//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark LINK/UNLINK OPERATIONS

int PR_Unlink(const char *path) {UNIMPLEMENTED_ROUTINE}



//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark DLL OPERATIONS


char * PR_GetDLLSearchPath(void)
{
	// Return the colon separated list of paths containing the CFM search path.
	
	//  *** HACK *** 
	//	For now, just return the applicationÕs directory.
	
	OSErr					err;
	ProcessInfoRec			processInfo;
	FSSpec					processFSSpec;
	char					*fullPath = NULL;
	char					*unixFullPath = NULL;
	int						sizeOfFullPath = 0;
	ProcessSerialNumber		thisAppsPSN = {0, kCurrentProcess};
	
	processInfo.processAppSpec = &processFSSpec;
	processInfo.processName = NULL;
	processInfo.processInfoLength = sizeof(ProcessInfoRec);
	
	err = GetProcessInformation(&thisAppsPSN, &processInfo);
	require (err == noErr, exit);
		
	err = GetFullPath(processFSSpec.vRefNum, processFSSpec.parID, &fullPath, &sizeOfFullPath);
	require (err == noErr, exit);
	
	err = ConvertMacPathToUnixPath(fullPath, &unixFullPath);
	free(fullPath);	
	
exit:
	return (err == noErr ? unixFullPath : NULL);
}



//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark STRING OPERATIONS

#if 0
OSErr PStrFromCStr(const char *cString, Str255 pString)
{
	unsigned int	len; 

	len = strlen(cString);
	if (len > 255)
		return (-1);

	pString[0] = len;
	BlockMoveData(cString, &pString[1], len);

	return (0);
}
#endif

//	PStrFromCStr converts the source C string to a destination
//	pascal string as it copies. The dest string will
//	be truncated to fit into an Str255 if necessary.
//  If the C String pointer is NULL, the pascal string's length is set to zero
//
void PStrFromCStr(const char* src, Str255 dst)
{
	short 	length  = 0;
	
	// handle case of overlapping strings
	if ( (void*)src == (void*)dst )
	{
		unsigned char*		curdst = &dst[1];
		unsigned char		thisChar;
				
		thisChar = *(const unsigned char*)src++;
		while ( thisChar != '\0' ) 
		{
			unsigned char	nextChar;
			
			// use nextChar so we don't overwrite what we are about to read
			nextChar = *(const unsigned char*)src++;
			*curdst++ = thisChar;
			thisChar = nextChar;
			
			if ( ++length >= 255 )
				break;
		}
	}
	else if ( src != NULL )
	{
		unsigned char*		curdst = &dst[1];
		short 				overflow = 255;		// count down so test it loop is faster
		register char		temp;
	
		// Can't do the K&R C thing of Òwhile (*s++ = *t++)Ó because it will copy trailing zero
		// which might overrun pascal buffer.  Instead we use a temp variable.
		while ( (temp = *src++) != 0 ) 
		{
			*(char*)curdst++ = temp;
				
			if ( --overflow <= 0 )
				break;
		}
		length = 255 - overflow;
	}
	dst[0] = length;
}


void CStrFromPStr(ConstStr255Param pString, char **cString)
{
	// Allocates a cString and copies a Pascal string into it.
	unsigned int	len;
	
	len = pString[0];
	*cString = malloc(len+1);
	
	if (*cString != NULL) {
		strncpy(*cString, (char *)&pString[1], len);
		(*cString)[len] = NULL;
	}
}


size_t strlen(const char *source)
{
	size_t currentLength = 0;
	
	if (source == NULL)
		return currentLength;
			
	while (*source++ != '\0')
		currentLength++;
		
	return currentLength;
}

int strcmpcore(const char *str1, const char *str2, int caseSensitive)
{
	char 	currentChar1, currentChar2;

	while (1) {
	
		currentChar1 = *str1;
		currentChar2 = *str2;
		
		if (!caseSensitive) {
			
			if ((currentChar1 >= 'a') && (currentChar1 <= 'z'))
				currentChar1 += ('A' - 'a');
		
			if ((currentChar2 >= 'a') && (currentChar2 <= 'z'))
				currentChar2 += ('A' - 'a');
		
		}
	
		if (currentChar1 == '\0')
			break;
	
		if (currentChar1 != currentChar2)
			return currentChar1 - currentChar2;
			
		str1++;
		str2++;
	
	}
	
	return currentChar1 - currentChar2;
}

int strcmp(const char *str1, const char *str2)
{
	return strcmpcore(str1, str2, true);
}

int strcasecmp(const char *str1, const char *str2)
{
	return strcmpcore(str1, str2, false);
}

#if GENERATING68K
asm void *memset(void *target, int pattern, size_t length)										// Legal asm qualifier
{
				MOVEA.L		4(SP),A0			// target -> A0
				MOVE.W		10(SP),D0			// pattern -> D0, length -> D1
				MOVE.L		12(SP),D1
				CMPI.L		#30,D1
				BLT			end
				
				// Fill D0 with the pattern
				MOVEQ		#0,D2				// Clear D2, weÕll use it as scratch
				MOVE.B		D0,D2				// Fill the bottom byte
				LSL.W		#8,D0				//  
				OR.W		D0,D2
				MOVE.L		D2,D0
				SWAP		D2
				OR.L		D2,D0
				
				// Are we odd aligned?
				MOVE.L		A0,D2				// Copy target address into scratch
				LSR.B		#1,D2				// Sets C bit
				BCC.S		checkAlign2			// If even, check for 16-byte alignment
				MOVE.B		D0,(A0)+			// Take care of odd byte
				SUBQ.L		#1,D1				// Update length
				
				// Are we odd 16-byte word alligned?
checkAlign2:	LSR.B		#1,D2				// Still set from last check
				BCC			totallyAligned
				MOVE.W		D0,(A0)+
				SUBQ.L		#2,D1
			
totallyAligned:	MOVE.L		D1,D2
				LSR.L		#4,D2
				SUBQ.L		#1,D2
copyHunk:		MOVE.L		D0,(A0)+
				MOVE.L		D0,(A0)+
				MOVE.L		D0,(A0)+
				MOVE.L		D0,(A0)+
				SUBQ.L		#1,D2
				BCC			copyHunk
				ANDI.W		#15,D1				// Check done?
				BRA			end
dribble:		MOVE.B		D0,(A0)+
end:			DBF			D1,dribble
				MOVE.L		4(SP),D0			// Return the target
				RTS								
}
#endif

void dprintf(const char *format, ...)
{
    va_list ap;
	char	*buffer;
	
	va_start(ap, format);
	buffer = PR_vsmprintf(format, ap);
	va_end(ap);
	
	debugstr(buffer);
	free(buffer);
}

void
exit(int result)
{
#pragma unused (result)

	if (gPrimaryThread != PR_CurrentThread())
		PR_Exit();
	else
		ExitToShell();
}


//##############################################################################
//##############################################################################
#pragma mark -
#pragma mark MISCELLANEOUS


//
//		***** HACK  FIX THESE ****
//
extern long _MD_GetOSName(char *buf, long count)
{
	long	len;
	
	len = PR_snprintf(buf, count, "Mac OS");
	
	return 0;
}

extern long _MD_GetOSVersion(char *buf, long count)
{
	long	len;
	
	len = PR_snprintf(buf, count, "7.5");

	return 0;
}

extern long _MD_GetArchitecture(char *buf, long count)
{
	long	len;
	
	len = PR_snprintf(buf, count, "PowerPC");

	return 0;
}