/*
	File:		alUtil.c

	Contains:	Sundry utility routines.

	Written by:	Prashant Patel

	Copyright:	© 1990, 1993 by Apple Computer, Inc., all rights reserved.

    This file is used in these builds: BigBang

	Change History (most recent first):

	   <SM4>	 10/6/93	SAM		Fixed Cyclone roll in <LW2>.  AL_GetMyServer was not taking the
									length byte of the server name into account when copying the
									string.
	   <SM3>	 6/14/93	kc		Roll in Ludwig.
	   <LW2>	  4/9/93	fau		In AL_GetMyServer, changed the BlockMove call's size parameter
									to use Length(..) instead of GetHandleSize(..), fixing a NetBoot
									problem.
	   <SM2>	 10/8/92	PN		Add 4meg Typhoon floppy drive for Cyclone
		<41>	 6/17/91	PP		ich, dba, Post GM, Fix System Heap trashing bug that was
									introduced in the AL_getCatInfo routine during last workaround.
		<40>	 4/12/91	PP		pm,#86781:Workaround a FileShare bug that prevents doing a
									GetCatInfo on a > 27 character volume name which appears upto 27
									characters in VCB.
		<39>	  3/7/91	PP		ich,#iWS b5-mtm-010:Call GetMyZone correctly with timeout and
									retry count set properly. Return a boolean correctly in
									AL_isAFPmedia routine.
		<38>	 2/27/91	PP		ich,#ich-AMgr-0095:Make AL_mountForeign external.
		<37>	 1/31/91	PP		ich,#b4q4 WhiteBoard:In AL_stripFileName, use Str255 name so it
									could work for bad input.
		<36>	 1/25/91	PP		sad,#80663:Detect driveNum and driver ref num correctly for
									ejected off-line volumes.  Make AL_getDriverName more secure.
		<35>	 1/21/91	PP		(ich) Detect AppleTalk active or inactive correctly.
		<34>	 1/10/91	PP		(ich) Do not bring up auto log-in dialog if AppleTalk is
									inactive. During change to aux info, clear out volume name
									before copying it. For ejectable volumes, return nsDrvErr
									correctly.
		<33>	  1/2/91	PP		(ich) StripFilename routine handles case where input and output
									are pointers to same string.
		<32>	12/20/90	PP		(ich) Added BlockInit routine. Move AL_filterFile and
									AL_updateAList to alExt.c to save on paramter passing.
		<31>	12/13/90	PP		(ich) On client side, do not match FileShare volumes who may
									have the same creation date (e.g. duplicate of a folder).
		<30>	12/13/90	PP		(ich) Do not set needsUpdate for Audio CD volumes since it is
									useless and later on it causes volume to be made fuzzy. Restrict
									returned names for GetVolName and GetFileName to 255 chars. In
									ValidateHandle, test for filename and volume name length is now
									correct. In GetCatINfo, set versNum to 0 to keep MFS happy.
		<29>	11/29/90	PP		(prp/ich) In Filename ÒEqualStringÓ comparisons, do not ignore
									diacritical marks in the name.  Get in sync with VolumeMount
									equates in Files.h
		<28>	11/14/90	PP		(PP/ich) In CheckIfAnyMatchesCriterion, the name check should be
									for entire length of volume name in the alias record.
		<27>	10/30/90	PP		If MountEjectable returns userCanceledError, do not attempt
									MountForeign and return the error to the caller.
		<26>	10/30/90	PP		In AL_mountAFP, if a switch to root volume occurs, also switch
									data in alias record back to original data so the data is valid
									for FindTarget. In AL_mountVolume, order for mounting is hard
									disk, ejectable volumes followed by foreign volumes. Relax type
									matching criterion when creation date for a volume matches and
									if the network info matches.
		<25>	10/21/90	gbm		Change hfileInfo to hFileInfo for the last time (plus one!).
	    <25>	10/10/90	JL		Change hfileInfo to hFileInfo for the last time.
		<24>	10/10/90	PP		For audio CDs, recognize any inserted disk as the correct target
									disk. Handle AppleShare alias time difference. Preflight already
									logged on server and Guest disabled. Bring up correct alert for
									zero length name and password for mount dialog. Get rid of
									xCallParam defs because new AppleTalk.h now has them. Do not
									force update of registered user alias used as a Guest alias.
		<23>	  9/7/90	PP		Optimize determination of CheckIfMounted for foreign volumes.
		<22>	  9/1/90	PP		Relax driver name matching for ejectable volumes.  Do not do
									VolTypeMatch check until other criterion such as name and/or
									creation date has matched.
		<21>	 8/17/90	PP		Relax volume type match for foreign volumes (either mounted or
									ones identified by alias record) when name and creation date
									matches.
		<20>	 8/10/90	PP		Relax volume type matching when name and creation date match and
									matching volume is a foreign volume.
		<19>	  8/6/90	PP		Support auto volume mount for foreign file systems other than
									AppleShare.
		<18>	 7/19/90	PP		Remove AlreadyConnected dialog.
		<17>	 7/10/90	dba		get rid of warning
		<16>	 6/28/90	PP		Convert CanonicalFileSpec to FSSpec.  Invalid username alert has
									username as part of the message.
		<15>	 6/15/90	PP		Special Traps to support dialog code are no longer traps.  Fix
									bug related to getting current zone name if there are no
									bridges.
		<14>	 5/31/90	PP		Recognize ejectable off-line volumes.
		<13>	 5/16/90	PP		On client side, super user with "see entire volume" privilege
									should try to auto mount SharePoint as well as real volume.
		<12>	  5/3/90	PP		Closer integration of FileShare and aliases. Incorporate Diet
									Clinic tips.
		<11>	 4/10/90	PP		Change once more hFileInfo to hfileInfo to stay consistent with
									Files.h
		<10>	 4/10/90	PP		Add support for kARMnoUI bit in rules mask.
		 <9>	  4/5/90	PP		Code size optimization.
		 <8>	 3/20/90	PP		Guard against current implemenation of VolumeMount.
		 <7>	  3/8/90	PP		Add support for minimal aliases.  Change "hfileInfo" to
									"hFileInfo'" to match Files.h change.
		 <6>	 2/27/90	PP		EjectableFilter recognizes a floppy of same name or same
									creation date. Same name floppies will be fuzzy for more
									stringent checks later on.  Fix bugs in getting the driver name
									for RAM based drivers.
		 <5>	  2/6/90	PP		Return nsvErr if neither empty nor occupied floppy drive is
									found for floppy aliases.
		 <4>	 1/22/90	PP		PromptForUser has different interface.

	To Do:

** 2.0	prp 12/15/1989 FindVolume uses different heuristics.
** 1.9	prp 11/30/1989 PromptForUser call forces to provide a bogus
**		STARTCALLBACK routine. Take this out when PromptForUser is fixed
**		correctly.
** 1.8	prp 11/27/1989 Add SwitchDisk dialog for Ejectable floppies.
** 1.7	prp 10/30/1989 ¥ Handle 'paramErr' returned by _VolumeMount differently
**		in AL_mountVol
** 1.6	prp 10/17/1989 Aliases did not work on 68000 machines. Fixed odd
**		addressing in variable length info structure.
** 1.5	prp 10/13/1989 Add routines 'AL_appendPString' and 'AL_displayAlert'.
**		Also, Appleshare volume auto mount uses 'PromptForUser' routine
**		instead of old code.
** 1.4	prp 10/02/1989 AL_filterFile routine has additional param that fixes
**		bug in filtering.
** 1.3	prp 09/18/1989 Added AL_filterFile routine to do client filtering of
**		alias matches.
** 1.2	prp 09/06/1989 Changes from CodeReview.
** 1.1	prp 08/07/1989 Add automatic volume mount support.
** 1.0	prp 06/12/1989 Initial release.
***/

/*********************************************************************
*
*	File:		alUtil.c
*	Project: 	Alias Manager
*	Contains:	utility routines
*	Written by:	Prashant Patel
*
*	Copyright 1989, 1992 by Apple Computer, Inc.
*	All Rights Reserved.
*
**********************************************************************/

/**********************************************************************
 ***************		Public Include files		*******************
 *********************************************************************/

 #include "Aliases.h"

/**********************************************************************
 ***************		 Private Include files		*******************
 *********************************************************************/

 #include "aliasPriv.h"
 #include "aliasDlogPriv.h"

/**********************************************************************
 ***************		External C Include files		***************
 *********************************************************************/

#ifndef __MEMORY__
#include <Memory.h>
#endif
#ifndef __ERRORS__
#include <Errors.h>
#endif
#ifndef __DIALOGS__
#include <Dialogs.h>
#endif
#ifndef __TOOLUTILS__
#include <ToolUtils.h>
#endif
#ifndef __RESOURCES__
#include <Resources.h>
#endif
#ifndef __SCRPIT__
#include <Script.h>
#endif
#ifndef __DISKS__
#include <Disks.h>
#endif
#ifndef __DEVICES__
#include <Devices.h>
#endif
#ifndef __PPCTOOLBOX__
#include <PPCToolbox.h>
#endif
#ifndef __GESTALTEQU__
#include <GestaltEqu.h>
#endif

/*********************************************************************
******** private type definitions  ***********************************
*********************************************************************/
typedef struct {
	 AliasPtrPriv aptr;			// pointer to locked AliasRecord
	 short vRefNum;				// volume ref num of mounted volume
	 Boolean needsUpdate;		// did any info about volume change?
} EjectableFilterParams;

typedef struct {
	 VolumeLocation *vinfoPtr;	// pointer to volume mount info data
	 OSErr errFromVolMount;		// error from volume mount
	 short vRefNum;				// volume ref num of mounted volume
} PromptFilterParams;


/* Reply buffer for ASPGetStatus (mapped to FPGetServerInfo) call as described in
AFP Engineering Tech Notes. */

typedef struct {
	short machineTypeOffset;
	short AFPversOffset;
	short UAMStringsOffset;
	short iconMaskOffset;
	short flags;
	Str31 serverName;
	Str15 machineType;
	char  variableData[128+256];
} ASPGetStatusReplyBuf;


 /*********************************************************************
 ***************		Is it a full pathname? ************************
 *********************************************************************/

pascal Boolean ALI_isFullpath(const unsigned char *path, // I pointer to a path name
							register short len)		// I length of the pathname
// If the first character of a pathname is a colon, or if the pathname contains
// no colons, it must be a partial pathname; otherwise it's a full pathname.
{
	register short i = 0;		// for loop index
	register unsigned char *s = path; // pointer to input full or partial filename

	if (*s == kChrSeparator)	// it is a partial pathname
		return (FALSE);

	else {

		while (++i <= len) {		// any colon in the pathname?

			if (*s++ == kChrSeparator)
				return (TRUE);

		}
	}

	return (FALSE);

} // end of AL_isFullpath routine

/*********************************************************************
 ***************		parse volumename  ****************************
 *********************************************************************/

pascal void AL_getVolname(const unsigned char *fname, //I pointer to a file specification
				register short len,		//I length of the pathname
				StringPtr vname)		//O string pointer to returned volume name

{
	register short i = 0;		// for loop index
	register unsigned char *s = fname;		// pointer to input file specification
	register unsigned char *d = vname+1;	// pointer to output volumename

	if (len > (sizeof(Str255)-1)) // maximum length of returned volumename can't exceed!
		len = sizeof(Str255) - 1;

	while (++i <= len)			// locate volume name separator char
		if ((*d++ = *s++) == kChrSeparator) // copy all chars including separator
			break;

	Length(vname) = i-1;		// length of the volume name minus the separator char

} // end of AL_getVolname routine

/*********************************************************************
 ***************		parse filename  ******************************
 *********************************************************************/

pascal void ALI_getFilename(const unsigned char *path, //I pointer to a path specification
				register short len,		//I length of the pathname
				StringPtr fname)		//O string pointer to returned filename

{
	// given a full pathname or a partial pathname, return the filename
	// component.
	register short i = 0;		// loop index
	register unsigned char *s = path;	// pointer to input full or partial filename

	s += len;				// point beyond the last character
	while (i < len) {

		if (*--s == kChrSeparator) {

			len = i;		// don't count it in filename
			++s;			// do not copy the separator character
			break;

		}

	i++;

	}

	if (len > (sizeof(Str255)-1)) // maximum length of returned filename can't exceed!
		len = sizeof(Str255) - 1;
	Length(fname) = len;				// length of returned filename
	BlockMove (s, fname+1, len); // store resultant string

} // end of AL_getFilename routine

/*********************************************************************
 ***************		strip filename  ******************************
 *********************************************************************/

pascal void ALI_stripFilename(const unsigned char *path, //I pointer to a path specification
				register short len,		//I length of the pathname
				StringPtr fname)		//O string pointer to returned stripped pathname

{
	Str255 name;

	// given a full pathname or a partial pathname, strip the filename
	// component.
	AL_getFilename (path, len, name);
	Length(fname) = len - Length(name);
	BlockMove ((Ptr)path, fname+1, Length(fname)); // store resultant string

} // end of AL_stripFilename routine

 /*********************************************************************
 ***************		copy pascal string  ***************************
 *********************************************************************/

pascal void ALI_copyPString(const StringPtr source,	//I string pointer to source string
				StringPtr dest)			//O string pointer to destination string

{
	BlockMove ((Ptr)source, (Ptr)dest, Length(source)+1); // store resultant string

} // end of AL_copyPString routine

 /*********************************************************************
 ***************		append pascal string  *************************
 *********************************************************************/

pascal void ALI_appendPString(const StringPtr source,//I string pointer to source string
				StringPtr dest)			//O string pointer to destination string

{
	BlockMove ((Ptr)(source+1), (Ptr)(dest + (Length(dest)+1)),
					Length(source)); // store resultant string
	Length(dest) += Length(source);

} // end of AL_appendPString routine

 /*********************************************************************
 ***************		comapare  FileSystem  fileName string   *******
 *********************************************************************/

pascal Boolean ALI_FSEqualString(const Str255 str1,	//I string pointer to string one
				const Str255 str2)	//I string pointer to string two
{
	return (EqualString (str1, str2, FALSE, TRUE));

} // end of AL_FSEqualString routine

 /*********************************************************************
 ***************		comapare  FileSystem  string data by length  **
 *********************************************************************/

pascal Boolean AL_FSEqualStringByLength(const StringPtr s1,//I pointer to string one
				const StringPtr s2,					//I pointer to string two
				register short len)					//I length of name data block

{
	Str255 str1;			// store data 1 in string 1
	Str255 str2;			// store data 2 in string 2

	if (len > (sizeof(Str255)-1)) 	// is it something we can pass to EqualString?
		return (FALSE);				// we can't, just be safe and say not equal
	else if (len == 0) 				// empty strings are equal
		return (TRUE);

	Length(str1) = Length(str2) = len;
	BlockMove ((Ptr)s1, (Ptr)(str1+1), len);
	BlockMove ((Ptr)s2, (Ptr)(str2+1), len);

	return (ALI_FSEqualString (str1, str2));

} // end of AL_FSEqualStringByLength routine


 /*********************************************************************
 ***************		init a block with given char  *****************
 *********************************************************************/

pascal void ALI_BlockInit (const Ptr source, 		//I pointer to source block
						const char withThisCh,		//I init it with this char
						register int len)			//I length of block

{
	register Ptr s = source;
	while (len--)
		*s++ = withThisCh;
		
} // end of AL_BlockInit routine


 /*********************************************************************
 ***************		comapare two blocks   *************************
 *********************************************************************/

pascal Boolean ALI_BlockCompare(const Ptr block1, //I pointer to block one
				const Ptr block2, 	//I pointer to block two
				register int len)		//I length of blocks
{
	register Ptr s1 = block1;
	register Ptr s2 = block2;

	while (len--)
		if (*s1++ != *s2++)
			return (FALSE);

	return (TRUE);

} // end of AL_BlockCompare routine


/*********************************************************************
***************	Display an alert message *****************************
*********************************************************************/
pascal void AL_displayAlert(short strIdx)	//I	index into STR# resource
{
	Str255 localStr;						// local string to be displayed in alert

	GetIndString (localStr, kAliasResID, strIdx); // get the display string
	ParamText (localStr, '', '', '');		// display our message
	(void) StopAlert (kAlAlertID, NULL);	// use our alert box

} // end of AL_displayAlert routine

/*********************************************************************
 ************** Do GetCatInfo from given parameters  *****************
 *********************************************************************/

 pascal OSErr AL_getCatInfo(short vref,				//I
 					long dir,						//I
					const StringPtr nam,			//I
					CInfoPBPtr thisCPBPtr)			//I/O
{
	OSErr error;

	/* It is assumed that the ioFDirIndex field is set appropriately before calling
	this routine.  That field controls whether the info is obtained by name mode,
	for a particular directory or by directory index. */

	thisCPBPtr->hFileInfo.ioVRefNum = vref; // volume ref #
	thisCPBPtr->hFileInfo.ioFVersNum = 0;	// keep MFS happy!!!!

	/* Because of a FileShare bug, whenever doing a GetCatInfo by name for a volume
	which could be greater than 27 characters long, always do it instead by dirID and hence 
	longer than 27 character volume names work correctly.  */
	
	/* Be really paranoid about the bug fix because it already caused System heap trash in certain
	circumstances.  When a relative path was being looked in dirID 1, the name pointer points to
	partial path in the alias record and the GetCatInfo will return the name of the volume when
	lookup is changed to by dirID mode.  This could cause data to be overwritten in heap.  
	Make this test really tight so that it is only for the particular bug fix.  */
	
	if (thisCPBPtr->hFileInfo.ioFDirIndex == 0 && dir == kVolRootDirID &&
		nam != NULL && (Length(nam) >= (sizeof(Str27)-1)) && (*(nam+1) != kChrSeparator)) {

		thisCPBPtr->hFileInfo.ioNamePtr = NULL;			// donÕt use name pointer at all
		thisCPBPtr->hFileInfo.ioDirID = kRootDirID;		// look for the root dirID
		thisCPBPtr->hFileInfo.ioFDirIndex = -1;			// -1 index indicates dirID mode

		error = PBGetCatInfoSync(thisCPBPtr);

		thisCPBPtr->hFileInfo.ioNamePtr = nam;			// set up PB as if we did by name
		thisCPBPtr->hFileInfo.ioDirID = kVolRootDirID;	// the directory we were told
		thisCPBPtr->hFileInfo.ioFDirIndex = 0;			// 0 index indicates by name mode

	}
	else {

		thisCPBPtr->hFileInfo.ioNamePtr = nam;	// passed name pointer
		thisCPBPtr->hFileInfo.ioDirID = dir;	// look in this directory

		error = PBGetCatInfoSync(thisCPBPtr);

	}

	return error;

} // end of AL_getCatInfo routine

/*********************************************************************
 ***************		Validate an alias record handle  *************
 *********************************************************************/

OSErr AL_validateHandle(const AliasHandlePriv alias)

{
	register AliasPtrPriv aptr;

	if (alias == NULL)
		return (paramErr);

	aptr = *alias;

	if ( (aptr->aliasSize < sizeof(AliasRecordPriv)) || // minimum number of bytes in the record?

		 (aptr->aliasVersion < kOldestSupportedAliasVersion) || 
		 
		 (aptr->thisAliasKind < kFileAlias || aptr->thisAliasKind > kDirAlias) || // correct alias kind?

		 (aptr->volumeType < kInvalidValue || aptr->volumeType > kVolOtherEjectable) || // correct volume type?

		 (Length(aptr->volumeName) == 0 || Length(aptr->volumeName) > (sizeof(Str27)-1)) || // volume name reasonable length?

		 (Length(aptr->fileName) > (sizeof(Str63)-1)) // file name reasonable length?

	   )

	  	return (paramErr);		// Is corrupt handle error better?

	else

		return (noErr);

} // end of routine AL_validateHandle

 /*********************************************************************
 ** Get pointer and length for various variable info in alias record **
 *********************************************************************/

pascal char *AL_getVarPtr (const AliasPtrPriv aptr,	//I alias record pointer
						short which,			//I which variable info
						short *len)				//O length of data
{
	register varInfo *vptr;		// pointer in variable length structure
	register short i;			// for loop index

	vptr = (varInfo *) &aptr->vdata;		// start at the beginning of variable info

	for (i=kMaxMark-kFolderNm-1; i>=0; --i) {

		if (vptr->what == which || vptr->what == kEndMark) // found a match or reached end, return data pointer
			break;
		else 					// point to the next variable entry
			vptr = (varInfo *)((char *)vptr + ((vptr->len+1) & ~1) + sizeof(varInfo));

	}

	*len = vptr->len;			// return length of data

	return (vptr->data); 		// if not found, return end pointer

} // end of AL_getVarPtr routine

 /*********************************************************************
 **** 	Add an entry of variable length info of alias record **********
 *********************************************************************/

pascal char *AL_addVarEntry (const AliasPtrPriv aptr,	//I alias record pointer
						short which,			//I which variable info
						short len,				//I length of this varInfo entry
						Ptr data)				//I ptr to data to be stuffed, if any
{
	register varInfo *vptr;		// pointer in variable length structure
	char *returnedDataPtr = NULL; // pointer to data area of added entry

	/* this routine adds an entry at the end of other varInfo entries. If an endMarker is
	   being added, it means wiping out all entries. */

	vptr = (varInfo *) &aptr->vdata; // start at the beginning of variable info

	if (which != kEndMark) {	// adding an endMarker means wipe out all entries

		while (vptr->what != kEndMark) // look until the end marker is reached
			vptr = (varInfo *)((char *)vptr + ((vptr->len+1) & ~1) + sizeof(varInfo)); // point to the next variable entry

		// vptr now points to the place where a new entry should be added.
		vptr->what = which;		// added this kind of entry
		vptr->len = len;		// this is its length

		if (data != NULL)		// move data if it was given
			BlockMove (data, vptr->data, len);

		returnedDataPtr = vptr->data;

		vptr = (varInfo *)((char *)vptr + ((vptr->len+1) & ~1) + sizeof(varInfo)); // point to the next variable entry

	}

	vptr->what = kEndMark;
	vptr->len = 0;				// no datalength for endMarker

	return (returnedDataPtr);	// return a pointer to the added entry's data area, null for end mark

} // end of AL_addVarEntry routine

 /*********************************************************************
 ***************  Return a Drive Q Entry for a given drive num ********
 *********************************************************************/

static DrvQElPtr GetDrvQForDrive(short driveNum)	//I drive number
{
	register DrvQElPtr	drvQEntry;		// drive queue entry

	drvQEntry = (DrvQElPtr) GetDrvQHdr()->qHead; // get the header of the queue

	while (drvQEntry != NULL) {			// look at all drive queue entries

		if (drvQEntry->dQDrive == driveNum) // driver entry we are looking for
			break;
		drvQEntry = (DrvQElPtr) drvQEntry->qLink; // next entry in the drive queue

	}

	return (drvQEntry);

} // end of GetDrvQForDrive routine


 /*********************************************************************
 ***************  Does given drive support ejectable media? ***********
 *********************************************************************/

static OSErr IsEjectable(short driveNum,	//I drive number
						Boolean *ejectable)	//O supports ejectable media or not?
{
	register DrvQElPtr	drvQEntry;		// drive queue entry
	register long fourBytesFlag;		// four bytes of flags preceding each drive queue entry

	*ejectable = TRUE;					// assume its ejectable

	drvQEntry = GetDrvQForDrive(driveNum); // get the drive queue entry for this drive

	if (drvQEntry != NULL) {

		// four bytes of flags preceding each drive queue entry
		fourBytesFlag = (*((long *)drvQEntry -1));

		/* value 8 in byte 1 means nonejetctable disk in drive.
		   value $FC-$FF in byte 1 means disk was ejected within last 1.5 seconds. */
		if ((fourBytesFlag & kNonEjectable) && (!(fourBytesFlag & kDiskWasEjected)))
			*ejectable = FALSE;

		return (noErr);

	}

	return (nsDrvErr);

} // end of IsEjectable routine

 /*********************************************************************
 *******  Return real drive number and driver ref num for a volume ****
 *********************************************************************/

pascal void AL_getDriveInfo(register HVolumeParam *vpbPtr, //I mounted volume's param block
						short *dRefNum,	 //O driver reference number
						short *driveNum) //O drive number
{
	DrvQElPtr		drvQEntry;	// drive queue entry for this drive

	*dRefNum = vpbPtr->ioVDRefNum;
	*driveNum = vpbPtr->ioVDrvInfo;

	/* Detect ejectable off-line volumes correctly.  For ejectable off-line volumes,
	ioVDrvInfo is 0 indicating an off-line volume and ioVDRefNum is the real drive number.
	*/

	if (*driveNum == 0) {

		*driveNum = vpbPtr->ioVDRefNum;
		drvQEntry = GetDrvQForDrive(*driveNum);
		if (drvQEntry != NULL)
			*dRefNum = drvQEntry->dQRefNum;

	}

} // end of AL_getDriveInfo routine


 /*********************************************************************
 ***************  Return driver name for a given driver ref num  ******
 *********************************************************************/

pascal void AL_getDriverName(	short DRefNum,	//I driver reference number
						StringPtr drvrName) //O driver name
{
	register  DCtlPtr DCtlEntryPtr;	// device control entry pointer
	register  Ptr driverPtr = NULL;	// pointer to ROM driver or handle to RAM driver
	DCtlHandle DCtlHandle;			// device control entry handle

	DCtlHandle = GetDCtlEntry (DRefNum);

	if (DCtlHandle) {
	
		DCtlEntryPtr = *DCtlHandle;
		driverPtr = DCtlEntryPtr->dCtlDriver;
	
		if (driverPtr && (DCtlEntryPtr->dCtlFlags & kRAMbasedDriver)) // driver is RAM-based
			driverPtr = *((Ptr *)driverPtr);
	}

	if (driverPtr)
		AL_copyPString((char *) (driverPtr+18), drvrName);
	else
		Length(drvrName)=0;

} // end of AL_getDriverName routine


 /*********************************************************************
 ****  Convert numeric values into spaces of a given string ***********
 *********************************************************************/

static void ConvertNumericToSpace(register StringPtr s, //I/O string converted in place
							register short slen)   //I length of string
{
	register short i=0;					// loop index
	short whichByte;					// which byte of the character

	while (++i <= slen) {

		whichByte = CharByte(s, 0);
		if (whichByte == smSingleByte || whichByte == smLastByte) {

			if ((CharType(s, 0) & (smcTypeMask | smcClassMask)) == (smCharPunct + smPunctNumber))
				*s = ' ';	// convert it to a blank

		}

		++s;

	}
	
} // end of ConvertNumericToSpace routine

 /*********************************************************************
 ****  Do driver names match for alias record and given DRefNum *******
 *********************************************************************/

static Boolean DoDriversMatch(const AliasPtrPriv aptr,	//I alias record pointer,
								short DRefNum)			//I driver reference number
{
	Str255		drefDrvrName;			// driver name from DRefNum
	Str255		aliasDrvrName;			// driver name from alias record
	register short		dlen;			// length of driver name from DRefNum
	short		len;					// length of driver name from alias record
	char		*s;						// pointer to driver name in alias record

	/* While comparing driver names, convert all numeric digits to spaces.  This is to
	correctly compare drivers that change their names dynamically depending upon 
	scsiID (e.g. DataPakDriver4 to DataPakDriver4 for Mass Micro Systems).  Also,
	even after conversion, just compare first 8 bytes of names.  In all cases, this
	should be sufficient to locate the correct driver in cases where Driver version
	numbers may be appended at the end.
	*/
	
	AL_getDriverName (DRefNum, drefDrvrName);
	dlen = Length(drefDrvrName);
	ConvertNumericToSpace (drefDrvrName+1, dlen);

	s = AL_getVarPtr(aptr, kDrvrNm, &len);
	Length(aliasDrvrName) = len;
	BlockMove (s, aliasDrvrName+1, len);	
	ConvertNumericToSpace (aliasDrvrName+1, len);

	if (len > dlen)
		len = dlen;

	return (AL_FSEqualStringByLength (drefDrvrName+1, aliasDrvrName+1, len <= 8 ? len : 8));

} // end of DoDriversMatch routine

 /*********************************************************************
 ****  Is It an alias for a AFP media volume? *************************
 *********************************************************************/

Boolean AL_isAFPmedia(const AliasPtrPriv aptr)	//I alias record pointer
{

	return ((aptr->volumeAttributes & kVolAFPmedia) ? TRUE : FALSE);

} // end of AL_isAFPmedia routine

 /*********************************************************************
 ****  Is Mounted volume an AppleShare volume ? ************************
 *********************************************************************/

static Boolean IsAppleShare(short FSID) //I from given FSID
{
	/* 
	Is the following the right way to do this?  DTS Tech note #66 of June 1990 says so.
	Actually, better way may be doing GetVolMountInfo on the volume and comparing the 
	media type to be 'afpm'.  But this is expensive.
	*/
	
	return (FSID >= 0x0001 && FSID <= 0x0016);

} // end of IsAppleShare routine

/*********************************************************************
 **** 	Compute volume type from given vpb ****************************
 *********************************************************************/

AliasVolType AL_getVolType (register HVolumeParam *vpbPtr) //I mounted volume's param block
{
	register AliasVolType vpbVolType; // volume type derived from vpb
	long 			diskSize;	// disk size in # of bytes
	OSErr			err;		// return code
	Boolean 		ejectable;	// Does drive support ejectable media or not?
	short			dRefNum;	// driver reference number
	short			driveNum;	// drive number

	vpbVolType = kVolHD;		// assume local hard disk

	AL_getDriveInfo(vpbPtr, &dRefNum, &driveNum);

	/**** the following code is not the best way to determine if a volume is floppy
		or not.  It should be replaced with a precise way of determining this. ****/

	if (dRefNum == kSONYRefNum && vpbPtr->ioVFSID == kFileManagerID) {

		diskSize = vpbPtr->ioVNmAlBlks * vpbPtr->ioVAlBlkSiz; // compute size in bytes

		if (diskSize <= k400K)
			vpbVolType = kVolFloppy400K; // <= 400K of storage, assume a 400K floppy

		else if (diskSize <= k800K)
			vpbVolType = kVolFloppy800K; // <= 800K of storage, assume a 800K floppy

		else if (diskSize < kMinHDSize)
			vpbVolType = kVolFloppy1400K; // less than 2 Mb of storage, assume a 1.4Mb floppy

	} else {						// Is it Apple CDRom drive or other ejectable non-floppy?

		err = IsEjectable(driveNum, &ejectable);
		if (err == noErr && ejectable)   // it is ejectable, but not a floppy, CDROM or HD
			vpbVolType = kVolOtherEjectable;

		else if (vpbPtr->ioVFSID != kFileManagerID)	// handled by an external file system
			vpbVolType = kVolForeign;

	}

	return (vpbVolType);

} // end of AL_getVolType routine

 /*********************************************************************
 **** 	Return correct data pointer from AFP volume mount info 	*******
 *********************************************************************/

static char *GetAFPdataPointer (VolumeLocation *vloc,	//I volume location pointer
					  		short kind) // what kind of data? zoneName, serverName, userName etc.
{
	AFPVolMountInfo *afp;				// afp vol mount info block pointer
	short offset=0;

	afp = (AFPVolMountInfo *) vloc;
	
	switch (kind) {
	
	case asiZoneName:
		offset = afp->zoneNameOffset;
		break;
		
	case asiServerName:
		offset = afp->serverNameOffset;
		break;
		
	case asiVolumeName:
		offset = afp->volNameOffset;
		break;
		
	case asiUserName:
		offset = afp->userNameOffset;
		break;
		
	case asiUserPswd:
		offset = afp->userPasswordOffset;
		break;
		
	case asiVolumePswd:
		offset = afp->volPasswordOffset;
		break;
		
	}
	
	return (((char *) vloc) + offset);
	
}  // end of GetAFPdataPointer routine


 /*********************************************************************
 **** 	Return zone/server/user name from alias record 	***************
 *********************************************************************/

char *AL_getAFPinfo (const AliasPtrPriv aptr,	//I alias record pointer
					  short kind) // what kind of info? zoneName, serverName or userName
{
	short 		len;					// length of variable info data

	return (GetAFPdataPointer((VolumeLocation *) AL_getVarPtr (aptr, kVolMntInfo, &len), kind)); 

}  // end of AL_getAFPinfo routine

/*********************************************************************
 ***************	Get my zone name	******************************
 *********************************************************************/

/* .XPP driver call for getting my current zone name using GetMyZone as described
in tech note #250.
*/

void AL_GetMyZonePhs2 (StringPtr zoneName) //I zone name pointer

{
	XCallParam xpb;

	xpb.ioRefNum = xppRefNum;
	xpb.csCode = xCall;
	xpb.xppSubCode = zipGetMyZone;
	xpb.xppTimeout = 3;		// timeout period for .XPP
	xpb.xppRetry = 4;		// retry count

	xpb.zipBuffPtr = (Ptr) zoneName;
	xpb.zipInfoField[0] = 0; /* always 0 */
	xpb.zipInfoField[1] = 0; /* always 0 */

	if ((!GetBridgeAddress()) || (PBControl((ParmBlkPtr)&xpb, FALSE) != noErr)) {

		Length(zoneName) = 1; // always return local zone named '*' if zone name can't be got
		zoneName[1] = '*';	// local zone

	}

} // end of AL_GetMyZonePhs2 routine

/*********************************************************************
 ***************	Get my server name	******************************
 *********************************************************************/

void AL_GetMyServer (StringPtr serverName) //I server name pointer

{
	StringHandle strH;	// server name string handle

	Length(serverName) = 0; // return null string if server name can't be gotten

	strH = GetString(kServerNameID);
	if (strH != NULL)
		BlockMove ((*strH), serverName, Length(*strH)+1);	//<SM4>

} // end of AL_GetMyServer routine

/*********************************************************************
 ***************	Change alias record to auxiliary info ************
 *********************************************************************/

void AL_changeToAuxInfo (register AliasPtrPriv aptr, //I pointer to locked AliasRecord
							register AuxAliasInfo *auxPtr) //I pointer to auxiliary data

{
	AL_BlockInit ((Ptr)aptr->volumeName, '\0', sizeof(Str27));
	AL_copyPString (auxPtr->auxVolName, aptr->volumeName);
	aptr->volumeCrDate = auxPtr->auxVolCrDate;
	aptr->volumeType = auxPtr->auxVolType;
	aptr->parDirID = auxPtr->auxDirIDs.auxParDirID;
	aptr->fileNum = auxPtr->auxDirIDs.auxFileNum;

} // end of ChangeToAuxInfo routine

/**********************************************************************
 ***************		Get volume mount info	***********************
 *********************************************************************/

pascal OSErr AL_getVolMountInfo(short vRefNum, 			//I volume reference number
						VolumeLocationHandle *vloc)		//I/O volume location record handle 
{
	OSErr err; 		// result code, assume failure
	short vmntInfoSize;			// volume mount info size
	
	err = GetVolMountInfoSize(vRefNum, &vmntInfoSize);
	if (err == noErr && vmntInfoSize > 0) {
	
		*vloc = (VolumeLocationHandle) NewHandleClear(vmntInfoSize);
		if (*vloc == NULL) 		// no heap space
			return (MemError());
		
		HLock((Handle)(*vloc));	// lock the newly created volume location record
		
		err = GetVolMountInfo (vRefNum, (**vloc));
		
		HUnlock((Handle)(*vloc));	// lock the newly created volume location record

		if (err != noErr) {
			DisposHandle((Handle)(*vloc));	// fatal error, return NULL
			*vloc = NULL;
		}
	}
	
	return (err);
		
} // end of AL_getVolMountInfo routine

 /*********************************************************************
 **** 	Does volume mount info match to the one stored in alias ? *****
 *********************************************************************/

static Boolean ZoneAndServerMatches (const AliasPtrPriv aptr,	//I alias record pointer
								short vRefNum)	//I volume reference number
{
   	VolumeLocationHandle vloc=NULL;	// volume location data handle
	Boolean answer = TRUE; 		// assume they match

	if (aptr->volumeAttributes & kVolMntExists) {
	
		if (AL_getVolMountInfo (vRefNum, &vloc) == noErr &&
				(*vloc)->media == kAFPmedia)

			answer =  (FSEqualString(AL_getAFPinfo (aptr, asiZoneName), 
								GetAFPdataPointer((*vloc), asiZoneName)) &&

					   FSEqualString(AL_getAFPinfo (aptr, asiServerName), 
								GetAFPdataPointer((*vloc), asiServerName)));

		else 
			answer = FALSE;

	} // if no volume mount info was stored, assume that it matches

	if (vloc != NULL)
		DisposHandle((Handle)vloc);	

	return (answer); 

}  // end of ZoneAndServerMatches routine

 /*********************************************************************
 **** 	Find a VCB entry that matches a drive number  	***************
 *********************************************************************/

static VCB* FindVCB(short drvNum)
{
	register VCB* vcbp;

	for (vcbp = (VCB*)(GetVCBQHdr()->qHead);
		 vcbp && vcbp->vcbDrvNum != drvNum;
		 vcbp = (VCB*)vcbp->qLink)
		;

	return vcbp;
}

 /*********************************************************************
 **** 	Get AppleShare time difference for the mounted volume 	*******
 *********************************************************************/

static long AL_getAppleShareTimeDiff (VCB* vcbp)	//I	mounted volume's param block

{
	return (vcbp == nil ? 0 :
		*((long *) (((Ptr)vcbp) + kVCBsrvrTimeOffset)) - kSrvrTimeBase);

} // end of AL_getAppleShareTimeOffset routine

 /*********************************************************************
 **** 	Fix a creation date if it comes from AppleSgare volume 	*******
 ****   and the alias is for an AppleShare volume.				*******
 *********************************************************************/

unsigned long AL_fixedCreationDate (HVolumeParam *vpbPtr, //I mounted volume's param block (if nil, use passed vRefNum)
						short vref,				//I volume reference number/WD ref
						unsigned long dateToFix) //I date needing adjustment

{
	HVolumeParam vpb; //I mounted volume's param block
	unsigned long answer = dateToFix;

	// Is the volume an AppleShare Volume?
	// In such a case, adjust the creation date so that is the real creation date on server.
	
	if (vpbPtr == nil && AL_findVolByVRefNum (vref, &vpb) == noErr) 
		vpbPtr = &vpb;
			
	if (vpbPtr != nil && IsAppleShare(vpbPtr->ioVFSID)) 
		answer = (long)dateToFix + AL_getAppleShareTimeDiff (FindVCB(vpbPtr->ioVDrvInfo));

	return (answer);
			
} // end of AL_fixedCreationDate routine

 /*********************************************************************
 **** 	Check if given volume type matches given vpb derived volType **
 *********************************************************************/

static Boolean AL_volTypeMatches (register const AliasPtrPriv aptr,	//I alias record pointer
						HVolumeParam *vpbPtr, //I mounted volume's param block
						register AliasVolType *vpbVolTypePtr)
{

	short			dRefNum;			// driver reference number
	short			driveNum;			// drive number

	*vpbVolTypePtr = AL_getVolType(vpbPtr); // get derived volume type from given vpb

	if (aptr->volumeType == (*vpbVolTypePtr)) { // type matches, continue matching other criterion

		if ((*vpbVolTypePtr) == kVolForeign) { // for HFS File System ID, no other criterion

			if ((!AL_isAFPmedia(aptr)) || ZoneAndServerMatches(aptr, vpbPtr->ioVRefNum)) // zone and server matches
				return (TRUE);

		}

		else if (aptr->volumeType == kVolOtherEjectable) { // make sure the drivers match

			AL_getDriveInfo(vpbPtr, &dRefNum, &driveNum);
			return (DoDriversMatch(aptr, dRefNum));

			}

		else
			return (TRUE);
	} 

	return (FALSE);

} // end of AL_volTypeMatches routine

 /*********************************************************************
 **** 	Check if given volume's real dirID matches ********************
 *********************************************************************/

static Boolean SharePointCopy (register const AliasPtrPriv aptr,	//I alias record pointer
						HVolumeParam *vpbPtr) //I mounted volume's param block
{
	short auxLen;				// length of auxiliary real parent dirID
	AuxAliasDirIDs *realDirPtr;	// pointer to real directory ID info
	FIDParam 		hpb;			// H parameter block for fileIDs

	if (AL_isAFPmedia(aptr) && aptr->volumeType == kVolForeign &&
			(aptr->volumeAttributes & kAuxRealDirID)) { // real dirID info exists

		realDirPtr = (AuxAliasDirIDs *) AL_getVarPtr(aptr, kAuxRealDirID, &auxLen);
		if (realDirPtr->auxRealRootDirID != kInvalidValue &&
			auxLen == sizeof(AuxAliasDirIDs)) { // catch old version incompatibility

			hpb.ioNamePtr = NULL; // get the real parent dirID for this shared volume
			hpb.ioVRefNum = vpbPtr->ioVRefNum; // setup fileID call parameters
			hpb.ioSrcDirID = kRootDirID;
			if (PBCreateFileIDRef ((HParmBlkPtr)&hpb, FALSE) == noErr &&
				realDirPtr->auxRealRootDirID != hpb.ioFileID) // does not match with value in alias
				return (TRUE); // Not the real thing, a duplicate of original Shared folder

		}
	}
	
	return (FALSE);

} // end of SharePointCopy routine

 /*********************************************************************
 **** 	Check if any mounted volume matches given criterion 	*******
 *********************************************************************/

static pascal OSErr AL_checkIfAnyMatchesCriterion (const AliasPtrPriv aptr, //I alias record pointer
						short matchMask,	// match criterion
						HVolumeParam *vpbPtr)	//I/O mounted volume's param block if matches criterion

{
	OSErr 		err;				// result code
	Str27 localName;				// temporary local volume name
	AliasVolType vpbVolType;		// vpb derived volume type
	Boolean needsUpdate;			// to satisfy the call, it is ignored
	Boolean volTypeMatches;			// Do volume types match?

	vpbPtr->ioVolIndex = 1;
	vpbPtr->ioNamePtr = localName;	// return name

	while ((err = PBHGetVInfo ((HParmBlkPtr)vpbPtr, FALSE)) == noErr) {

		++(vpbPtr->ioVolIndex);	// next mounted volume

		/* Audio CDs have dynamically assigned names and creation dates.  Hence, recognize 
		any audio CD that is mounted as the correct one.
		*/
		if (vpbPtr->ioVFSID == kAudioFSID && aptr->volumeFSID == kAudioFSID)
			break;

		if ((matchMask & kMatchCrDate) && (aptr->volumeCrDate != AL_fixedCreationDate(vpbPtr, vpbPtr->ioVRefNum, vpbPtr->ioVCrDate)))
			continue;

		if ((matchMask & kMatchName) && 
			(!FSEqualString (localName, aptr->volumeName)))
				continue;

		volTypeMatches = AL_volTypeMatches (aptr, vpbPtr, &vpbVolType); // Is it the right type?
		if (volTypeMatches && (!SharePointCopy (aptr, vpbPtr))) // Make sure it is not a FileShare share point copy
			break;

		/* For FileShare aliases, relax type match failure if creation date and Network
		info matches.
		*/
		
		if ((matchMask & kMatchCrDate) && AL_isAFPmedia(aptr) && (!volTypeMatches)) {

			/* On local server, resoving an alias created from client. */
			if (aptr->volumeType == kVolForeign && AL_isItSelf(aptr, &needsUpdate))
				break;

			/* On client, resoving an alias created on local server. */
			if (aptr->volumeType != kVolForeign && vpbPtr->ioVFSID != kFileManagerID &&
				ZoneAndServerMatches(aptr, vpbPtr->ioVRefNum))
				break;
				
		}
		
	}

	return (err);

} // end of AL_checkIfAnyMatchesCriterion routine

 /*********************************************************************
 **** 	Check if a particular volume is mounted or not 	***************
 *********************************************************************/

OSErr AL_checkIfMounted (const AliasPtrPriv aptr,	//I alias record pointer
						short *vref,		  //O mounted volume's vref number
						Boolean *needsUpdate) //O does VolMount info need updating? Set onlyif needs updating

{
	OSErr 		err;				// result code
	HVolumeParam vpb;				// volume parameter block
	AliasVolType vpbVolType;		// vpb derived volume type
	Boolean volTypeMatch=false;		// Does the volume type match with alias record?

	err = AL_findVolByName (aptr->volumeName, &vpb);
	// for fullpath minimal aliases, either a volume by same name found or no such volume
	if ((err != noErr && err != nsvErr) || aptr->volumeType == kInvalidValue)
		goto EXIT;

	/* If volume types do not match but creation date and name matches and if the derived volume 
	type is foreign, assume it is a FileShare case where the owner is accessing the alias from
	a remote machine and hence set it to true. 
	*/

	/* If volume types do not match but creation date and name matches and if the original volume 
	type is foreign, assume it is a FileShare case where the remote auxiliary info switch has occurred
	assuming that it is not the same node.  The server name has probably changed from the
	FileShare by the user.  So, eventhough the alias is for a foreign volume and current matching
	disk is non-foreign, assume a match.
	*/
	
	if (err == noErr) {
	
		volTypeMatch = AL_volTypeMatches(aptr, &vpb, &vpbVolType);
		vpb.ioVCrDate = AL_fixedCreationDate(&vpb, vpb.ioVRefNum, vpb.ioVCrDate); // fix vol CrDate for AppleShare volumes

		if (vpb.ioVCrDate == aptr->volumeCrDate && 
				(aptr->volumeType == kVolForeign || vpbVolType == kVolForeign)) {

			*needsUpdate = (!volTypeMatch);
			volTypeMatch = TRUE;

		}
	}

	/* If the creation date and type matches, found the volume we are looking for. */
	if (err != noErr || (!volTypeMatch) || vpb.ioVCrDate != aptr->volumeCrDate) {

		/* Try to find another by same name and matching creation date and type. */
		err = AL_checkIfAnyMatchesCriterion (aptr, kMatchCrDate + kMatchName, &vpb);
		if (err == nsvErr) {

			/* Try to find another by same creation date and matching type. */
			err = AL_checkIfAnyMatchesCriterion (aptr, kMatchCrDate, &vpb);
			if (err == nsvErr)
				/* Try to find another by same name and matching type. */
				err = AL_checkIfAnyMatchesCriterion (aptr, kMatchName, &vpb);

			if (err == noErr) // yes, we found it, but name or creation date are different
				*needsUpdate = TRUE;	// set the update needed flag

		}
	}

EXIT:

	if (err == noErr)
		*vref = vpb.ioVRefNum;

	return (err);

} // end of AL_checkIfMounted routine

 /*********************************************************************
 **** 	Callback routine for PromptForUser call  	*******************
 *********************************************************************/
static OSErr PromptForUserFilter (StringPtr password, //I
 							StringPtr name,		//I
							short method,		//I
							Ptr yourDataPtr)	//I
{
	OSErr 		err=noErr;		 			// result code
	PromptFilterParams *paramRecPtr;		// parameters passed to prompt filter
	Str127 localStr;						// local string to be displayed in alert
	Str31 localStr2;						// local string to be displayed in alert

	/* Validate the input. */
	if (method == kScrambledPswdLogin && Length(name) == 0) // zero length name for a registered user is invalid
		err = afpParmErr;
	
	if (err == noErr) {
	
		paramRecPtr = (PromptFilterParams *) yourDataPtr;
	
		AL_copyPString (name, GetAFPdataPointer(paramRecPtr->vinfoPtr, asiUserName));
		AL_copyPString (password, GetAFPdataPointer(paramRecPtr->vinfoPtr, asiUserPswd));
	
		paramRecPtr->errFromVolMount = err = VolumeMount(paramRecPtr->vinfoPtr, &(paramRecPtr->vRefNum));
		
	}

	if (err == afpUserNotAuth) // ask for the password again
		AL_displayAlert(kBadPswdIdx);

	else if (err == afpParmErr)  { // unknown user

		GetIndString (localStr, kAliasResID, kUnknownUsrIdx); // get the display string
		AL_appendPString (name, localStr);	// append the typed user name
		GetIndString (localStr2, kAliasResID, kUnknownUsrIdx2); // get trailing part of display message
		AL_appendPString (localStr2, localStr);
		ParamText (localStr, '', '', '');		// display our message
		(void) StopAlert (kAlAlertID, NULL);	// use our alert box

		}

	else  // any other fatal error or no err, get out of prompt dialog and return the error
		err = noErr;

	return (err == afpUserNotAuth ? kSelectPswdItem :
		err == afpParmErr ? kSelectUserNameItem : err);

}  // end of PromptForUserFilter routine

 /*********************************************************************
 **** 	Lookup the given server and return its address 	***************
 *********************************************************************/

static OSErr LookupServer (const AliasPtrPriv aptr, //I alias record pointer
							AddrBlock *entityAddr)   //O entity address	
{
	MPPParamBlock checkNBP;			// use NBP to confirm the name
	EntityName 	entity;				// packed entity name, (server name, "AFPServer", zone name)
	char rbuf[128];					// return buffer (at least sizeof (EntityName) + sizeof (AddrBlock) + 4)	

	GetIndString (&rbuf[0], kAliasResID, kAFPServerIdx); // entityType is "AFPServer"
	NBPSetEntity ((Ptr) &entity, AL_getAFPinfo (aptr, asiServerName), // zone and server name from alias record 
				rbuf, AL_getAFPinfo (aptr, asiZoneName));

	checkNBP.NBPentityPtr = (Ptr) &entity;

	checkNBP.NBPretBuffSize = sizeof (EntityName) + sizeof (AddrBlock) + 4; // as dictated by NBP
	checkNBP.NBPretBuffPtr = &rbuf;
	checkNBP.NBPmaxToGet = 1; 	// just this particular server

	checkNBP.NBPinterval = 1; 	// allow 8 ticks before looking up again
	checkNBP.NBPcount = 2; 		// allow 2 max retries

	if (PLookupName ((MPPPBPtr) &checkNBP, FALSE) == noErr && checkNBP.NBPnumGotten == 1) {
	
		(void) NBPExtract (rbuf, 1, 1, &entity, entityAddr);
		return (noErr);
		
	} else

		return (afpNoServer);

}  // end of LookupServer routine

 /*********************************************************************
 **** 	Return info about server 	***********************************
 *********************************************************************/

static void GetServerInfo (const AliasPtrPriv aptr, 	//I alias record pointer
									Boolean *guestLoginAllowed, //O Does it allow guest login?
									Boolean *IsItSelf)	//O Is it ourself?
{
	XPPPrmBlk getStatus;		// param block for ASPGetStatus call
	AddrBlock entityAddr;		// entity address	
    short myNode;				// caller's node ID
    short myNet;				// caller's network #
	ASPGetStatusReplyBuf rbuf;	// reply buffer for GetStatus call
	Ptr UAMStrPtr;				// pointer into UAM strings
	unsigned char cntOfUAMStrs;	// count of UAM strings
	unsigned char index;		// loop index
	Str15	NoUserAuthent;		// string represting "No User Authent"

	*IsItSelf = FALSE;			// assume it is not self
	*guestLoginAllowed = TRUE;	// assume guest login is allowed
	
	if (LookupServer (aptr, &entityAddr) == noErr) {
	
		(void) GetNodeAddress (&myNode, &myNet);
		if (myNet == entityAddr.aNet && myNode == entityAddr.aNode) {
		
			*IsItSelf = TRUE;
			return;
			
		}

		getStatus.ioRefNum = xppRefNum;

		getStatus.aspTimeout = 1; 	// allow 8 ticks before getting server info again
		getStatus.aspRetry = 2; 	// allow 2 max retries
		*(AddrBlock *)(&getStatus.cbSize) = entityAddr; // lookup info for this server
		getStatus.rbSize = sizeof(ASPGetStatusReplyBuf);
		getStatus.rbPtr = (Ptr) &rbuf;
		
		if (ASPGetStatus((XPPParmBlkPtr) &getStatus, FALSE) == noErr) {
		
			UAMStrPtr = ((Ptr) &rbuf) + rbuf.UAMStringsOffset;
			cntOfUAMStrs = *UAMStrPtr++;

			GetIndString (NoUserAuthent, kAliasResID, kNoUserAuthentIdx); // "No User Authent" string
			
			for (index=1; index<=cntOfUAMStrs; ++index)
				if (FSEqualString(NoUserAuthent, UAMStrPtr))
					break;
				else
					UAMStrPtr += (Length(UAMStrPtr) +1); // point to the next UAM string
					
			if (index > cntOfUAMStrs)	// guest login is disallowed
				*guestLoginAllowed = FALSE;	
		}
	}
	
}  // end of GetServerInfo routine

 /*********************************************************************
 **** 	Is any user currently logged on to server? 	*******************
 *********************************************************************/

static Boolean AlreadyLoggedOnServer (const AliasPtrPriv aptr)	//I alias record pointer

{
	register VCB* vcbp;		// volume control block pointer

	vcbp = (VCB*)(GetVCBQHdr()->qHead);

	while (vcbp) { 

		if (IsAppleShare(vcbp->vcbFSID) && ZoneAndServerMatches(aptr, vcbp->vcbVRefNum)) 
			return(TRUE);

		vcbp = (VCB*)vcbp->qLink;
	}

	return(FALSE);
	
}  // end of AlreadyLoggedOnServer routine


 /********************************************************************
 **** 	Is AppleTalk active or not ? 	******************************
 *********************************************************************/

static Boolean AppleTalkActive ()

{
	OSErr 		err;						// result code
	long 		appletalk;					// gestalt result

	// Check if appletalk is active (gestalt returns 0 if it isn't)
	err = Gestalt(gestaltAppleTalkVersion, &appletalk);
	return (err == noErr && appletalk != 0);

}  // end of AppleTalkActive routine


 /*********************************************************************
 *** Get copied volume location data from alias record       **********
 *********************************************************************/

static OSErr GetVolumeLocation (const AliasPtrPriv aptr, //I alias record pointer
						VolumeLocationHandle *vloc)		//I/O volume location record handle 
{
	VolumeLocation *vlocInAlias;			// volume location data pointer in alias record
	short 		vlocSize;					// volume location info size


	vlocInAlias = (VolumeLocation *) AL_getVarPtr (aptr, kVolMntInfo, &vlocSize);

	return (PtrToHand((Ptr)vlocInAlias, (Handle *)vloc, vlocSize));

}  // end of GetVolumeLocation routine

 /*********************************************************************
 *** Auto mount a volume that supports AFP media (e.g. APPleShare)*****
 *********************************************************************/

static OSErr AL_mountAFP (const AliasPtrPriv aptr,	//I alias record pointer
						short *vref,		//O mounted volume's vref number
						Boolean *needsUpdate, //O does VolMount info need updating?
						Boolean UIallowed, 	//I Is user interface allowed or not?
						AuxAliasInfo *auxInfo) //I auxiliary alias record info

{
	OSErr 		err, err2;		 			// result code
	char 		*userPtr;					// user name pointer
	char 		*volNamePtr;				// volume name pointer
	Str127 		titleStr;					// title string for PromptForUser dialog
	Str31		titleStr2;					// a portion of the title string (also used to save user name)
	short 		loginMethod;				// authentication method used to 'login'
	Handle 		iconHndl;					// handle to displayed icon
	PromptFilterParams paramRec;			// parameters passed to prompt filter
	VolumeLocationHandle vlocH=NULL;		// copied volume location data handle
	VolumeLocation *vloc;					// volume location data pointer in copied handle
	Boolean 	wasAlreadyMounted;			// was volume already mounted?
	Boolean 	guestLoginAllowed;			// Guest login allowed on this server?
	Boolean 	isItSelf;					// Is the caller's server same as the one in alias record?

	if (GetVolumeLocation(aptr, &vlocH) != noErr)
		return (memFullErr);

	HLock((Handle)(vlocH));	// lock the newly created volume location record 
	vloc = *vlocH;	// get the volume location record pointer

	/* Put possibly switched volume name(e.g. shared folder name vs. the real volume name) 
	in the volume location record for the VolumeMount call.
	*/
	volNamePtr = GetAFPdataPointer(vloc, asiVolumeName);
	AL_copyPString (aptr->volumeName, volNamePtr);

	userPtr = GetAFPdataPointer(vloc, asiUserName);

	if (Length(userPtr) > 0 && (!AlreadyLoggedOnServer(aptr))) { // bring up the PromptForUser dialog

		char 		*userPswdPtr;		// user password pointer

		if (AppleTalkActive())
			GetServerInfo (aptr, &guestLoginAllowed, &isItSelf);
		else 
			isItSelf = TRUE; // force a failure if AppleTalk inactive

		/* can't do mounting of yourself OR if no user interface allowed. */
		if (isItSelf || (!UIallowed) || AL_isItSelf(aptr, needsUpdate)) {	

			err = nsvErr;
			goto EXIT;
		}

		GetIndString (titleStr, kAliasResID, kMountVolIdx);
		AL_appendPString (volNamePtr, titleStr);
		GetIndString (titleStr2, kAliasResID, kMountVolAsIdx);
		AL_appendPString (titleStr2, titleStr);

		iconHndl = GetResource ('ICON', kAppleShareICONID);
		loginMethod = kScrambledPswdLogin; // scrambled password

		AL_copyPString (userPtr, titleStr2); // save original user name
		userPswdPtr = GetAFPdataPointer(vloc, asiUserPswd);
		Length(userPswdPtr) = 0;		// no input password

		paramRec.vinfoPtr = vloc;		// set up the parameter block for promptFilter

		err2 = PromptForUser(titleStr, iconHndl, userPtr, userPswdPtr,
			kSelectPswdItem, guestLoginAllowed, &loginMethod, 
			(ProcPtr)PromptForUserFilter, (Ptr)&paramRec);

		if (err2 == noErr)  { // user supplied a password and volume mount was successful

			err = paramRec.errFromVolMount; // error from volume mount
			if (err == noErr) {

				*vref = paramRec.vRefNum; // volume ref number from volume mount

				/* Do not update change from registered user to a guest. */
				if (Length(userPtr) != 0 && (!FSEqualString (titleStr2, userPtr)))
					*needsUpdate = TRUE;
			}

		} else { // user canceled out of PromptForUser dialog

			err = userCanceledErr;
			goto EXIT;
			
		}

	}
	else // It was a Guest Login alias
		err = VolumeMount(vloc, vref);

	/* For FileShare, if we are on the client side as a Super User who can see "entire
	volume", we need to try to mount the real volume also.  Just copy the real volume
	name in the VolMountInfo record, keep the same password and do VolumeMount.
	*/

	if (err == paramErr && auxInfo != NULL) {

		AL_copyPString (auxInfo->auxVolName, volNamePtr);
		AL_changeToAuxInfo(aptr, auxInfo); // from now on, resolve from real data that was in alias record
		err = VolumeMount(vloc, vref);

	}

	/* If try to login as Guest wehere there is no Guest login is allowed, VolumeMount returns
	paramErr and the user name length is zero.  Map the error to afpAccessDenied.
	*/
	
	if (err == paramErr && Length(userPtr) == 0)
		err = afpAccessDenied;
	
	wasAlreadyMounted = (err == afpAlreadyMounted);
	if (wasAlreadyMounted) { // volume was already mounted (VolumeMount says so)

		HVolumeParam vpb;						// volume parameter block

		err = AL_findVolByName (volNamePtr, &vpb); // VolumeMount did not return the VRefNum
		*vref = vpb.ioVRefNum;

	}

	if (err == noErr) {

		CInfoPBRec   cpb;	// catalog info parameter block

		/*
		Has user's privileges been revoked since the alias was last used?  If so, unmount 
		the volume and return afpAccessDenied error. 
		*/

		cpb.hFileInfo.ioFDirIndex = 0;	// find by name mode
		err = AL_getCatInfo (*vref, kVolRootDirID, volNamePtr, &cpb);

		if (err == noErr &&
				/* this is bad.  Files.h does not have a nice way to access this field (byte #31) */
				(cpb.dirInfo.filler2 /*ioACUser*/ & kServerVolNoPrivs) == kServerVolNoPrivs) { // no privileges for that volume

			if (!wasAlreadyMounted) // we mounted it, unmount it.
				(void) UnmountVol (NULL, *vref); // no privileges for that volume
			err = afpAccessDenied;
			*vref = 0;	// do not return a valid volume ref num in such a case

		}

	}

	/*
		If the auto mounted volume's creation date has changed,
		set the needsUpdate flag to get the alias record updated.
	*/
	if (err == noErr) {

		HVolumeParam vpb;		// volume parameter block

		err = AL_findVolByVRefNum (*vref, &vpb);			
		if (err == noErr && aptr->volumeCrDate != AL_fixedCreationDate(&vpb, vpb.ioVRefNum, vpb.ioVCrDate))
			*needsUpdate = TRUE;  // the creation date needs updating

	}

EXIT:

	if (vlocH != NULL) { // get rid of volume location data handle

		HUnlock((Handle)vlocH);
		DisposHandle((Handle)vlocH);	
	}

	// some other serious error occurred during VolumeMount.  Currently, paramErr
	// is returned by VolumeMount for the case when Server is not reachable.

	return ((err == paramErr) ? nsvErr : err);

} // end of AL_mountAFP routine

 /*********************************************************************
 *********************** 	Auto mount a foreign volume ***************
 *********************************************************************/

OSErr AL_mountForeign (const AliasPtrPriv aptr,	//I alias record pointer
						short *vref,		//O mounted volume's vref number
						Boolean *needsUpdate, //O does VolMount info need updating?
						Boolean UIallowed, 	//I Is user interface allowed or not?
						AuxAliasInfo *auxInfo) //I auxiliary alias record info

{
	OSErr 		err=nsvErr;		 			// result code
	short 		len;						// length of variable vol mount info

	/* If the alias record says that there is no volume mount info available, return nsvErr. */
	if (aptr->volumeAttributes & kVolMntExists) {

		if (AL_isAFPmedia(aptr))
			err = AL_mountAFP(aptr, vref, needsUpdate, UIallowed, auxInfo);
		else 
			err = AliasVolumeMount((VolumeLocation *) AL_getVarPtr (aptr, kVolMntInfo, &len), 
					UIallowed, vref, needsUpdate);

	}
	
	return (err);

} // end of AL_mountForeign routine

 /*********************************************************************
 **** 	Auto mount a hard disk volume that is dragged to trash 	*******
 *********************************************************************/

static OSErr AL_mountHD (const AliasPtrPriv aptr,	//I alias record pointer
						short *vref,		//O mounted volume's vref number
						Boolean *needsUpdate) //O does VolMount info need updating?

{
	OSErr 		err = nsvErr;		 	// result code
	VolumeParam vpb;					// volume parameter block
	register DrvQElPtr	drvQEntry;		// drive queue entry
	register long fourBytesFlag;		// four bytes of flags preceding each drive queue entry

	drvQEntry = (DrvQElPtr) GetDrvQHdr()->qHead; // get the header of the queue

	while (drvQEntry != NULL) {			// look at all drive queue entries

		// byte 1 of four bytes of flags preceding each drive queue entry
		fourBytesFlag = (*((long *)drvQEntry -1));

		if ((fourBytesFlag & kNonEjectable) && (!(fourBytesFlag & kDiskWasEjected))) {

			if (!(FindVCB(drvQEntry->dQDrive))) { // it is also unmounted

				vpb.ioVRefNum = drvQEntry->dQDrive; // mount the volume in this drive

				err = PBMountVol((ParmBlkPtr)&vpb);
				if (err == noErr) {

					err = AL_checkIfMounted(aptr, vref, needsUpdate);
					if (err == noErr)
						break;
					else
						(void) UnmountVol (NULL, vpb.ioVRefNum); // unmount and eject it

				}
			}
		}

		drvQEntry = (DrvQElPtr) drvQEntry->qLink; // next entry in the drive queue
	}

	return (err);

} // end of AL_mountHD routine

/**********************************************************************
 ***************	Is the volume type a floppy?  *********************
 **********************************************************************/

Boolean AL_isItAFloppy (AliasVolType thisVol) //I for this volume type
{
	return ((thisVol == kVolFloppy400K || thisVol == kVolFloppy800K ||
			thisVol == kVolFloppy1400K)
		? TRUE : FALSE);

} // end of AL_isItAFloppy routine

/**********************************************************************
 ***************	Is size of the disk compatible?  ******************
 **********************************************************************/

static Boolean SizeCompatible (AliasVolType thisVol, //I for this volume type
						DrvQElPtr drvQEntry)	 //I try to match against this drive Q entry
{
	CntrlParam cpb;						// control parameter block
	short drvType;						// drive type
	DrvSts status;						// drive status (if control code 23 is not implemented)

	if (thisVol == kVolOtherEjectable)		// for CDROM and other ejectable non-floppy disks, assume compatible size
		return (TRUE);

	cpb.ioVRefNum = drvQEntry->dQDrive; // drive number
	cpb.ioCRefNum = drvQEntry->dQRefNum;// driver reference number
	cpb.csCode = 23; 					// control code, hard coded because can relate to the manual

	if (PBControl((ParmBlkPtr) &cpb, FALSE) == noErr) {

		drvType = cpb.csParam[1] & kDriveTypeMask;

		if ((drvType == 5) ||				// 4-Meg Typhoon
			(drvType == 4) ||				// means super drive, all floppies will fit
		    (drvType == 3 && thisVol != kVolFloppy1400K) || // 800K drive, only 1.4Mb won't fit
		    (drvType == 2 && thisVol == kVolFloppy400K)) // 400K drive, only 400K will fit
			return (TRUE);

	} else { // try getting device status

		if (DriveStatus(drvQEntry->dQDrive, &status) == noErr) {

			if (status.sides & kDoubleSided) { // double-sided drive, assume 800K drive

				if (thisVol != kVolFloppy1400K)
					return (TRUE);		// will support 400K and 800K volumes

			} else {					// single-sided drive, assume 400K drive

				if (thisVol == kVolFloppy400K)
					return (TRUE);		// will support 400K volume only

			}
		}
	}

	return (FALSE);						// size is not compatible

} // end of SizeCompatible routine

/**********************************************************************
 *************** Is there a free drive for a given ejectable media?  **
 **********************************************************************/

static Boolean DriveAvailable (const AliasPtrPriv aptr,	//I alias record pointer
						Boolean searchFlag,	 //I find an empty or occupied drive?
						short *drvNum)		 //O drive number of the available drive
{
	register DrvQElPtr	drvQEntry;		// drive queue entry
	register long fourBytesFlag;		// four bytes of flags preceding each drive queue entry

	drvQEntry = (DrvQElPtr) GetDrvQHdr()->qHead; // get the header of the queue

	while (drvQEntry != NULL) {			// look at all drive queue entries

		if (((drvQEntry->dQRefNum == kSONYRefNum) && (AL_isItAFloppy(aptr->volumeType))) ||
			(aptr->volumeType == kVolOtherEjectable)) { // disk driver we are looking for

			// byte 1 of four bytes of flags preceding each drive queue entry
			fourBytesFlag = (*((long *)drvQEntry -1));

			/* value 8 in byte 1 means nonejetctable disk in drive.
			   value $FC-$FF in byte 1 means disk was ejected within last 1.5 seconds. */
			if ((!(fourBytesFlag & kNonEjectable)) || (fourBytesFlag & kDiskWasEjected)) {

				if (AL_isItAFloppy(aptr->volumeType) || DoDriversMatch(aptr, drvQEntry->dQRefNum)) {

					/* Are we looking for an empty drive or an occupied drive?  Test the input parameter
					flag against the diskInDriveByte value. */
					if (((searchFlag == kFindEmptyDrive) && ((fourBytesFlag & kDiskInDriveByteMask) == 0)) ||
						((searchFlag != kFindEmptyDrive) && (fourBytesFlag & kDiskInDriveMask))) {

						if (SizeCompatible (aptr->volumeType, drvQEntry)) { // Is size compatible with this drive?

							*drvNum	= drvQEntry->dQDrive;
							return (TRUE);

						}
					}
				}
			}
		}

		drvQEntry = (DrvQElPtr) drvQEntry->qLink; // next entry in the drive queue

	}

	return (FALSE);						// no matching drive available

} // end of DriveAvailable routine

/**********************************************************************
 ***************	Filter for SwitchDisk ModalDialog call  ***********
 **********************************************************************/

static pascal Boolean EjectableFilter (DialogPtr theDialog,	//I dialog record pointer
						EventRecord * /*theEvent*/,			//I event record pointer
						short *itemHit)						//O item hit
{
	EventRecord diskEvent;				// disk-inserted event
	OSErr result;						// result from disk-inserted volume mount
	short drvNum;						// drive number from disk-inserted volume mount
	EjectableFilterParams *paramRecPtr;	// pointer to param rec passed as RefCon
	AliasPtrPriv aptr;					// alias record pointer from RefCon
	HVolumeParam vpb;					// volume parameter block
	Str27 volName;						// disk-inserted volume name
	AliasVolType vpbVolType;			// vpb derived volume type
	Boolean nameMatches;				// inserted volume name matches alias record volume name
	Boolean volTypeMatches;				// inserted volume type matches alias record volume name
	Boolean volTypeAudioCD;				// both inserted volume and alias record volumes are Audio CD

	if (GetNextEvent (diskMask, &diskEvent)) {

		result = (diskEvent.message  & kResultCodeMask) >> 16;
		drvNum = diskEvent.message  & kDrvNumMask;
		*itemHit = kI_BadEjectable;		// assume right one is not mounted

		if (result == noErr) {			// volume was mounted successfully

			vpb.ioNamePtr = volName;	// return volume name
			vpb.ioVolIndex = 0;			// by refNum only mode
			vpb.ioVRefNum = drvNum;		// pass the drive number

			result = PBHGetVInfo ((HParmBlkPtr) &vpb, FALSE);

			if (result == noErr) {

				paramRecPtr = (EjectableFilterParams *) (GetWRefCon (theDialog)); // get our param record pointer
				aptr = paramRecPtr->aptr; // get our alias record pointer

				volTypeMatches = AL_volTypeMatches (aptr, &vpb, &vpbVolType);

				if (volTypeMatches || (vpbVolType != kVolOtherEjectable && aptr->volumeType != kVolOtherEjectable)) { // found the right kind of volume
				
					/* Ignore colon at end of name in alias record if there is one and match volume name */
					nameMatches = AL_FSEqualStringByLength (volName+1, aptr->volumeName+1, Length(volName));

					/* Audio CDs have dynamically assigned names and creation dates.  Hence, recognize 
					any audio CD that is inserted as the correct one. Do not set needsUpdate for such
					volumes because it is useless and may lead volume to become fuzzy. */
					volTypeAudioCD = ((vpb.ioVFSID == kAudioFSID) && (aptr->volumeFSID == kAudioFSID));

					if (volTypeMatches && ((nameMatches) || (vpb.ioVCrDate == aptr->volumeCrDate) || // either the name or creation date should match
							(volTypeAudioCD))) { // assume it is our volume

						*itemHit = kI_MountedEjectable; // our ejectable volume was mounted
						paramRecPtr->vRefNum = vpb.ioVRefNum; // returned mounted volume's refrence number
						paramRecPtr->needsUpdate = ((!volTypeAudioCD) && ((!nameMatches) || (vpb.ioVCrDate != aptr->volumeCrDate)));

					} else {	// not the volume we are looking for

						(void) UnmountVol (NULL, vpb.ioVRefNum); // unmount and eject it
						(void) Eject (NULL, drvNum);

					}
				}

			} else
				(void) Eject (NULL, drvNum);

		} else
			(void) Eject (NULL, drvNum);

		return (TRUE);					// we handled the event
	}

	return (FALSE);						// Let ModalDialog handle the event

} // end of EjectableFilter routine

 /*********************************************************************
 **** 	Bring up switch disk dialog to mount an ejectable volume 	***
 *********************************************************************/

static OSErr AL_mountEjectable (const AliasPtrPriv aptr,	//I alias record pointer
						short *vref,		//O mounted volume's vref number
						Boolean *needsUpdate, //O does VolMount info need updating?
						Boolean UIallowed) 		//I Is user interface allowed or not?

{
	OSErr 		err = nsvErr;		// result code
	DialogPtr 	switchDiskDialog;  	// Pointer to SwitchDisk dialog
	short    	itemHit;   			// Get selection from ModalDialog
	short		drvNum=1;			// drive number where disk could be inserted
	Boolean   	ExitDialog;  		// Flag used to exit the Dialog
	EjectableFilterParams paramRec; // local param rec on stack passed to filter routine via RefCon
	GrafPtr		savePort;			// save current port for restoring later

	if (!UIallowed)
		return (err);

	/* Try to find an empty compatible drive where the disk could be inserted.  If such drive is
	available, there is no need to auto eject any volume.  If no such drive is available, find a
	compatible drive and auto eject the volume. */
	if (! DriveAvailable (aptr, kFindEmptyDrive, &drvNum)) {
		if (DriveAvailable (aptr, (!kFindEmptyDrive), &drvNum))
			(void) Eject (NULL, drvNum);
		else
			return (nsDrvErr);
	}

	GetPort (&savePort);			// save the current port
	switchDiskDialog = GetNewDialog(kAlSwitchDiskID, NULL, (WindowPtr)-1);// Bring in the dialog resource
	paramRec.aptr = aptr;			// pass alias record pointer in param record
	SetWRefCon (switchDiskDialog, (long)&paramRec); // store address of our local param storage
	AL_setArrowCursor();			// arrow cursor
	SetPort(switchDiskDialog);

	// display the name of the ejectable volume.
	ParamText (aptr->volumeName,'','','');

	do {							// Start of dialog handle loop
		ModalDialog(EjectableFilter, &itemHit); // Wait until an item is hit

		ExitDialog = TRUE;			// assume that correct action was taken

		switch (itemHit) {

		case kI_CancelSwitchDisk:	// Canceling out of switch disk dialog
			err = userCanceledErr;
			break;

		case kI_MountedEjectable: 	// mounted a correct ejectable volume. Did any info change?
			err = noErr;
			*vref = paramRec.vRefNum; // from filter routine
			if (paramRec.needsUpdate) // from filter routine, if info changed
				*needsUpdate = TRUE;
			break;

		case kI_BadEjectable:		// disk-inserted volume mount was unsuccessful or wrong volume
			ExitDialog = FALSE;		// keep the dialog box up and ask for disk again
			break;

		default:					// what was it?
			break;

		} // end of switch statement

	} while (! ExitDialog);    		// Handle dialog items until exit selected

	// release resources, dispose of handles and dialogs.
	if (switchDiskDialog != NULL) {
		DisposDialog(switchDiskDialog); // dispose of our dialog
		SetPort(savePort);				// restore the original port
	}

	return (err);

} // end of AL_mountEjectable routine

 /*********************************************************************
 ***************		Mount a volume ********************************
 *********************************************************************/

OSErr AL_mountVolume (const AliasPtrPriv aptr,	//I alias record pointer
 						short *vref,			//O volume reference number
						Boolean *needsUpdate, 	//O did anything about volume change?
						Boolean UIallowed, 		//I Is user interface allowed or not?
						AuxAliasInfo *auxInfo) //I auxiliary alias record info

{
	OSErr err = nsvErr;		// result code

	if (aptr->volumeType == kVolHD) // auto-mount of hard disks
		/* if non-ejectable hard disk, then scan driveQ for unmounted volumes.  Mount it and
		call AL_checkIfMounted.  If the right one found, OK else unmount it. */
		err = AL_mountHD(aptr, vref, needsUpdate);

	else if (aptr->volumeAttributes & kVolEjectable) // handle ejectable volumes here
		err = AL_mountEjectable (aptr, vref, needsUpdate, UIallowed); // bring up switch disk dialog

	if (err != noErr && err != userCanceledErr && err != nsDrvErr) // try mounting as foreign volume if the alias record has vol mount info
		err = AL_mountForeign(aptr, vref, needsUpdate, UIallowed, auxInfo);

	return (err);

} // end of AL_mountVolume routine

/*********************** end of alUtil.c file ******************************/
