/* DiskMisc.c - miscellaneous disk support routines.
 * Mark Rinfret (et al), 1987
 *
 * History:		(most recent change first)
 *
 * 12/15/87 -MRR- Added two new routines, FindDevice and GetVolumeName.
 *                These are based largely on the "Info" program by
 *                Chuck McManis.
 *
 * 11/24/87 -MRR- The routine "DiskBlocks" was changed to "DiskBlocksLeft".
 *	              A new routine, "TotalDiskBlocks", returns the capacity,
 *				  in disk blocks, of the drive associated with a pathname.
 *				  A new routine, "GetDiskInfo", returns InfoData on a given
 *                pathname.
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <exec/io.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/filehandler.h>
#include <functions.h>
#include <ctype.h>
#include <stdio.h>
#include <clib/macros.h>
#include ":src/lib/DiskMisc.h"

extern LONG 	sendpkt();
static int errCode;


extern struct DosLibrary *DOSBase;

/* These are some macros that help in dealing with BCPL pointers and strings
 * the first is macro converts a BPTR to a C pointer of type struct DeviceList *
 * The second two provide the length of a BSTR * and a pointer to it's text.
 */

/* Convert BCPL DeviceList pointer to C pointer. */
#define DLPTR(x)	((struct DeviceList *)BADDR(x))

/* Get the length of a BCPL string. */
#define LENGTH(x)	(unsigned) (*(UBYTE *)BADDR(x))

/* Get address of BCPL string, adjusted for leading length byte. */
#define STRING(x)	(((char *)BADDR(x))+1)

^L
/* 
 * Find the first device node for a specified device type.
 * Called with:
 *		dList:		device list 
 *		dType:		device type mnemonic
 * Returns:
 *		pointer to device node or NULL
 *
 * Note: by passing the dl_Next field on successive calls, you may scan
 *	     the entire device list.
 */

struct DeviceList *
FindDevice(dList, dType)

struct DeviceList *dList;		/* Pointer to a device list structure */
long dType;						/* A device type as defined in dos.h  */

{
struct DeviceList	*t; 		/* A temporary pointer */

for (t = dList; ((t != NULL) && (t->dl_Type != dType)); 
	   t = DLPTR(t->dl_Next));
return(t);
}

char *
GetVolumeName(deviceName, volumeName)
	char *deviceName, *volumeName;
{
	struct RootNode 	*rootNode;
	struct DeviceList 	*devList, *t, *t2;
	struct DeviceNode 	*devNode;
	struct DosInfo		*dosInfo;

	char dName[31];
	int i, nameLength;
	struct InfoData	*info;
	struct Process	*myProcess;
	APTR			savedWindow;
	struct Lock *l;
	char *vName = NULL;			/* set non-null on success */

	*volumeName = '\0';			/* initialize name to null */

	/* Make a copy of the device name string. */
	strncpy(dName, deviceName, sizeof(dName)-1);

	/* Make sure that the device name is not colon-terminated.   Also
	 * insure that the device name is all upper case. 
	 */
	nameLength = strlen(dName);
	for (i = 0; i < nameLength; ++i) {
		if (dName[i] == ':') {
			dName[i] = '\0';
			break;
		}
		else
			dName[i] = toupper(dName[i]);
	}
	nameLength = strlen(dName);		/* Get device name length. */

	if (! (info = (struct InfoData *) 
		AllocMem((long) sizeof(struct InfoData), MEMF_PUBLIC|MEMF_CLEAR))) {
		return vName;				/* Will be NULL if unsuccessful. */
	}

	/* Get the DOS root node. */

	rootNode = (struct RootNode *)DOSBase->dl_Root;

	/* Get the DOS info node. */

	dosInfo = (struct DosInfo *)BADDR(rootNode->rn_Info);

	/* devList becomes the anchor point that we always start from */

	devList = (struct DeviceList *)BADDR(dosInfo->di_DevInfo);  

	/* Disable requesters if no disk present */
	  myProcess = (struct Process *) FindTask(NULL);
	  savedWindow = myProcess->pr_WindowPtr;
	  myProcess->pr_WindowPtr = (APTR) -1L;

	  for (t = FindDevice(DLPTR(dosInfo->di_DevInfo), DLT_DEVICE); 
	  	   t != NULL;
	       t = FindDevice(DLPTR(t->dl_Next), DLT_DEVICE)) {
		devNode = (struct DeviceNode *) t;

	    /* A non-null task pointer indicates a disk device. */

	    if (devNode->dn_Task) { 
#ifdef DEBUG
			char tempString[31];
			strncpy(tempString, STRING(devNode->dn_Name),
					LENGTH(devNode->dn_Name) );
			printf("Testing %s\n", tempString);
#endif
			if (!strncmp(dName, STRING(devNode->dn_Name),
			      		 MAX(LENGTH(devNode->dn_Name),nameLength) ) ) {

				/* We've found a match!  Now, add a terminating colon to the
				 * device name so we can pass it to Lock. 
				 */
				dName[nameLength++] = ':';
				dName[nameLength] = '\0';

#ifdef DEBUG
				printf("Matched %s\n", deviceName);
#endif
				l = (struct Lock *) Lock(dName, ACCESS_READ);
				if (l) {						/* disk inserted? */
					Info(l, info);
					t2 = DLPTR(info->id_VolumeNode);
					if (t2 != NULL)  {
						strncpy(volumeName, 
								STRING(t2->dl_Name), 
								LENGTH(t2->dl_Name) );
						volumeName[LENGTH(t2->dl_Name)] = '\0';
					}
#ifdef DEBUG
					else
						DebugWrite("Null volume name!\n");
#endif
					UnLock(l);
				}
#ifdef DEBUG
				else
					printf("Unable to lock %s!\n", dName);
#endif
				vName = volumeName;				/* set 'success' */
				break;
			}
	    } 										/* end of disk device test */
	} 										/* end for */
	myProcess->pr_WindowPtr = savedWindow;	/* restore window pointer */
cleanup:
	if (info) FreeMem(info, (long) sizeof(struct InfoData));
	return vName;
}

/* This routine returns the number of disk blocks remaining on the
 * drive specified by 'name'.  Though 'name' would typically be the
 * drive name or volume name, it can also be the name of any file
 * on the disk drive.
 * Called with:
 *		name:		disk device or volume name
 * Returns:
 *		> 0 => number of blocks available
 *		< 0 => error status
 */

LONG
DiskBlocksLeft(name)
	char *name;
{
	LONG blocks = -1L;
	struct InfoData *info = NULL;

	if ( !( info = GetDiskInfo(name) ) ) 
		return -errCode;

	blocks = info->id_NumBlocks - info->id_NumBlocksUsed;

	FreeMem(info, (long) sizeof(struct InfoData));
	return blocks;					/* bad status indicator */
}

/* Get disk info, given a pathname.
 * Called with:
 *		name:	file pathname
 * Returns:
 *		a pointer to an InfoData structure
 * Note:
 *		It is the user application's responsibility to free the memory
 *		allocated for the InfoData structure.
 */

struct InfoData *
GetDiskInfo( name )
	char *name;
{
	struct InfoData *info = NULL;
	struct FileLock *lock = NULL;

	errCode = 0;

	if (lock = (struct FileLock *) Lock(name, ACCESS_READ)) {
		if (info = AllocMem((long)sizeof(struct InfoData),MEMF_PUBLIC)) {
			if ( ! Info(lock,info))  errCode = IoErr();
		}
		else 
			errCode = ERROR_NO_FREE_STORE;
		UnLock(lock);
	}
	else 
		errCode = IoErr();

	if (errCode) {
		if (info) FreeMem( info, (long) sizeof( struct InfoData ) );
		info = NULL;
	}
	return info;
}

/* Disk ACTION_INHIBIT support routine.
 * Author:		Mark R. Rinfret
 * Date:		06/29/87
 * 
 * This routine provides support for user-written disk formatting, copy
 * operations which benefit from suppressing/restoring disk validation.
 */

int
Inhibit(drivename, code)
	char *drivename; int code;
{
	struct MsgPort     *task;
	LONG 	arg[2];
	LONG 	rc;

	if (!(task=(struct MsgPort *) DeviceProc(drivename))) 
		return 1;						/* fail, darn it! */

	arg[0] = code;

	/* Now, cross all your fingers and toes... */

	return ( !sendpkt(task,ACTION_INHIBIT,arg,1));
}

/* This routine returns the total number of disk blocks on the
 * drive specified by 'name'.  Though 'name' would typically be the
 * drive name or volume name, it can also be the name of any file
 * on the disk drive.
 * Called with:
 *		name:		disk device, volume or path name
 * Returns:
 *		> 0 => total number of blocks on drive
 *		< 0 => error status
 */

LONG
TotalDiskBlocks(name)
	char *name;
{
	struct InfoData *info = NULL;
	long int blocks = -1L;

	if ( ! ( info = GetDiskInfo(name) ) ) 
		return -errCode;

	blocks = info->id_NumBlocks;

	FreeMem(info, (long) sizeof(struct InfoData));
	return blocks;					/* bad status indicator */
}

#ifdef DEBUG
main()
{
	long blocks;

	char deviceName[81], volumeName[81];

	if ((blocks = TotalDiskBlocks("df0:")) < 0) {
		printf("Bad status from TotalDiskBlocks() => %ld\n", -blocks);
		exit();
	}
	else
		printf("Total disk blocks on DF0: => %ld\n", blocks);

	if ( (blocks = DiskBlocksLeft("df0:") ) < 0) 
		printf("Bad status from DiskBlocks() => %ld\n", -blocks);
	else
		printf("Disk blocks left on df0: => %ld\n",blocks);

	for (;;) {
		puts("Enter a disk DEVICE name (dh0, df0, etc.).");
		puts("Just hit RETURN to quit.");
		gets(deviceName);
		if (*deviceName == '\0') break;
		if (!GetVolumeName(deviceName, volumeName)) 
			puts("Sorry - I couldn't find that device in my system.");
		else
			printf("The volume name is %s\n",volumeName);
	}
}
#endif
