Contributor: WILLIAM FLORAC

unit Drives;
{
	Drives Unit for:
		Getting and setting drive labels.
		Getting and setting drive serial number.
		Testing if a drive is ready.
		Determining the type of drive (hard/3.5/5.25...)
		Return last DOS error status.

  All procedures and functions are protected from DOS critical errors.

	Author: William R. Florac
  Company: FITCO, Verona, WI (wee little company from my house)
	Copyright 1996, FITCO.  All rights reserved.

  1) Users of Drives.pas must accept this disclaimer of warranty:
       This Unit is supplied as is.  The Fitco disclaims all
       warranties, expressed or implied, including, without limitation,
       the warranties of merchantability and of fitness for any purpose.
       Fitco assumes no liability for damages, direct or conse-
       quential, which may result from the use of this Unit."

  2) This Unit is donated to the public as public domain except as
     noted below.

  3) You must copy all Software without modification and must include
     all pages, if the Software is distributed without inclusion in your
     software product. If you are incorporating the Software in
     conjunction with and as a part of your software product which adds
     substantial value, you may modify and include portions of the
     Software.

  4) Fitco retains the copyright for this Unit.  You may not distribute
     the source code (PAS) or its compiled unit (DCU) for profit.

  5) If you do find this Unit handy and you feel guilty
     for using such a great product without paying someone,
     please feel free to send a few bucks ($25) to support further
     development.

  6) This file was formated with tabs set to 2.

	Please forward any comments or suggestions to Bill Florac at:
	 	email: flash@etcconnect.com
		www: http://sumac.etcconnect.com/~fitco/
		mail: FITCO
					209 Jenna Dr
					Verona, WI  53593

	Revision History
		2/28/96
    	1.0 released
}


interface

uses
	SysUtils, WinProcs, WinTypes;

type
  	TDriveStyle = (tUnknown, tNoDrive, t3Floppy, t5Floppy, tFixed, tRFixed,
		tNetwork, tCDROM, tTape);

		PDeviceParams = ^TDeviceParams;
		TDeviceParams = record
			bSpecFunc: 			byte;			{Special functions}
			bDevType: 			byte;			{Device type}
			wDevAttr: 			word;			{Device attributes}
			wCylinders: 		word;			{Number of cylinders}
			bMediaType: 		byte;			{Media type}
{                          Beginning of BIOS parameter block (BPB)}
			wBytesPerSec: 	word;			{Bytes per sector}
			bSecPerClust: 	byte;			{Sectors per cluster}
			wResSectors: 		word;			{Number of reserved sectors}
			bFATs: 					byte;			{Number of FATs}
			wRootDirEnts: 	word;			{Number of root-directory entries}
			wSectors: 			word;			{Total number of sectors}
			bMedia: 				byte;			{Media descriptor}
			wFATsecs: 			word;			{Number of sectors per FAT}
			wSecPerTrack: 	word;			{Number of sectors per track}
			wHeads: 				word;			{Number of heads}
			dwHiddenSecs: 	longInt;	{Number of hidden sectors}
			dwHugeSectors: 	longInt;	{Number of sectors if wSectors == 0}
			reserved: 			array[0..10] of char;
{                          End of BIOS parameter block (BPB)}
		end;

	{parameter block for getting serial number}
	PSerialNumberParams = ^TSerialNumberParams;
	TSerialNumberParams = record
		wInfoLevel: 				word;
		dwDiskSerialNumber: longint;
		caLabel: 						array[0..10] of char;
		baFileSystem: 			array[0..7] of char;
	  end;


	{parameter block to get extened error codes}
	PExtErrorParams = ^TExtErrorParams;
  TExtErrorParams = record
    eCode: 		word;
		eClass: 	word;
		eAction: 	word;
		eLocus: 	word;
		eVolume: 	String;
		end;

	{structure for FCB}
  TEFCB = record
    Flag: 			byte;
    Reserved: 	array [0..4] of char;
    Attribute: 	byte;
    Drive: 			byte;
    Name: 			array [0..7] of char;
    Extension: 	array [0..2] of char;
    Misc: 			array  [0..24] of char;
	end;

DriveLabel = string[11];

  {my exception class}
	EDriveException = Class(Exception);

const
     {$I strings}  { can be found at the END of this module !}

{standard calls}
function DriveReady(wDrive: word): boolean;
{Tests to see if a drive is ready.  (floppy there and door closed)}

function GetDriveLabel(wDrive: word): string;
function SetDriveLabel(wDrive: word; s: string): boolean;
{Gets and sets drive label}

function GetDriveSerialNumber(wDrive: word): LongInt;
function SetDriveSerialNumber(wDrive: word; SerialNumber: LongInt): boolean;
{Gets and sets drive serial number}

function GetDefaultDrive: word;
{Returns current default drive}

function GetDriveStyle(wDrive: word): TDriveStyle;
{Returns the drive style (hard, 3-1/2, 5-1/4...)}

procedure GetExtendedErrorInfo(ep: PExtErrorParams);
{Gets the parameters for the last DOS error.  Useful after a DriveReady failure.}

{other calls}
function IsCDROMDrive(wDrive: word): boolean;
function WriteDriveSNParam(wDrive: word; psnp: PSerialNumberParams): boolean;
function ReadDriveSNParam(wDrive: word; psnp: PSerialNumberParams): boolean;
function GetDeviceParameters(wDrive: word; var dp: TDeviceParams): boolean;


implementation

{determins if the drive is ready w/o critical errors enabled}
function DriveReady(wDrive: word): boolean;
var
  OldErrorMode: Word;
begin
	{turn off errors}
  OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
	try
		if DiskSize(wDrive) = -1
		then result := false
		else result := true;
  finally
		{turn on errors}
		SetErrorMode(OldErrorMode);
	end;

end;

{get drive parameters w/o drive access}
function GetDeviceParameters(wDrive: word; var dp: TDeviceParams): boolean;
begin
	result := TRUE;      {Assume success}
   asm
			push ds
      mov  bx, wDrive
      mov  ch, 08h      {Device category--must be 08h}
      mov  cl, 60h      {MS-DOS IOCTL Get Device Parameters}
      lds  dx, dp
      mov  ax, 440Dh
      int  21h
      jnc  @gdp_done     {CF SET if error}
      mov  result, FALSE
   @gdp_done:
      pop  ds
	end;
end;

{gets last error message from DOS}
procedure GetExtendedErrorInfo(ep: PExtErrorParams);
var
	tCode: word;
	tClass: byte;
	tAction: byte;
	tLocus: byte;
begin
  asm
		push ds
		push bp
  	mov  bx, 0
  	mov  ah, 59h
    int  21h
		mov	 tCode, ax
		mov	 tClass, bh
    mov	 tAction, bl
		mov	 tLocus, ch
		pop  bp
		pop  ds
	end;
  ep^.eCode := tCode;
	ep^.eClass := tClass;
	ep^.eAction := tAction;
	ep^.eLocus := tLocus;
	ep^.eVolume := '?'; {don't support this for now}
end;

{get volume serial number for a drive:  0=default, 1=A...}
{returns -1 if unable to read}
function GetDriveSerialNumber(wDrive: word): LongInt;
var
	snp: TSerialNumberParams;
begin
	snp.dwDiskSerialNumber := 0;
	if ReadDriveSNParam(wDrive, @snp)
	then Result := snp.dwDiskSerialNumber
	else Result := -1;
end;


{set volume serial number for a drive:  0=default, 1=A... }
{returns true if it was sucessful}
function SetDriveSerialNumber(wDrive: word; SerialNumber: LongInt): boolean;
var
	snp: TSerialNumberParams;
begin
	result := false;
	{get current parameters}
	if ReadDriveSNParam(wDrive, @snp) then begin
		{change serial number}
		snp.dwDiskSerialNumber := SerialNumber;
		{and write back out}
		if WriteDriveSNParam(wDrive, @snp) then result := true;
	end;
end;

{Write Drive parameters: 0=default, 1=A...}
{Note: wDrive and psnp are treate as var with assembler directive}
{This interupt does NOT generate a critical error!}
function WriteDriveSNParam(wDrive: word; psnp: PSerialNumberParams): boolean; assembler;
asm
	push ds           {ds might get changed so save it}
	mov  bx, wDrive
  mov  al, 01h
	mov  ah, 69h
  lds  dx, psnp
  int  21h
  jnc  @no_error    {CF SET if error}
	xor	 ax,ax				{set false}
  jmp	 @exit
@no_error:
  mov	ax, 1					{set true}
@exit:
	pop	ds						{restore ds}
end;

{Read Drive parameters: 0=default, 1=A...}
{Note: wDrive and psnp are treate as var with assembler directive}
{This interupt does NOT generate a critical error!}
function ReadDriveSNParam(wDrive: word; psnp: PSerialNumberParams): boolean; assembler;
asm
	push ds
	mov  bx, wDrive
  mov  al, 00h
	mov  ah, 69h
  lds  dx, psnp
  int  21h
  jnc  @no_error    	{CF SET if error}
	xor	 ax,ax		{set false}
  jmp	 @exit
@no_error:
  mov	ax, 1			{set true}
@exit:
	pop	ds
end;

{sets the label of the drive specified: wDrive: 0=default 1=A...}
{returns true if it was sucessful}
function SetDriveLabel(wDrive: word; s: string): boolean;
const
		EFCB: TEFCB = (
	    Flag: $FF;                          	{ Extended FCB Flag }
      Reserved: (#0,#0,#0,#0,#0);           { Reserved}
      Attribute: $08;                       { Volume Label Attribute}
      Drive: 2;                             { Drive Identifier}
      Name: '????????';  										{ File Name}
      Extension: '???';                     { File Extension}
      Misc: (#0, #0, #0, #0, #0,            { Misc. Info filled by DOS}
        ' ',' ',' ',' ',' ',' ',' ',' ',  	{ Misc. Info filled by DOS}
        ' ',' ',' ',                      	{ Misc. Info filled by DOS}
        #0, #0, #0, #0, #0, #0, #0, #0, #0  { Misc. Info filled by DOS}
				)
      );
var
	Ps: pchar;
	err: integer;
	x: integer;

begin
	{abort if drive not ready}
	if not DriveReady(wDrive) then begin
		result := false;
		exit;
	end;
	{assume ok}
	result := true;

	{default things that change in constant varaiable}
	EFCB.Name := '????????';
	EFCB.Extension := '???';
	EFCB.Drive := wDrive;

	{See if it exist using a FCB}
	asm
		{Check to see;  if the volume label exists}
		{point DTA to ourself}
		mov	 dx,offset EFCB
		mov	 ah,1Ah
		int  21h
		{point to default FCB}
   	mov  dx, offset EFCB
    mov  ah, 11h
    int  21h
		{Exit if label is not present}
    cmp  al, 0
    jne  @exit
		{Else delete the volume label}
    mov  dx, offset EFCB
    mov  ah, 013h
    int  21h
    or	 al,al
		jz	@exit
		mov	result, 0
  @exit:
	end;

	if not result then exit;

	{if string is empty, then just erase}
	if length(s) = 0 then exit;
	{format string}
	for x := length(s) + 1 to 11  do s[x] := ' ';
	s[0] := char(11);
	{add drive letter!}
  if wdrive = 0
	then s := '\' + s + #0
	else s := chr(64+wdrive) + ':\' + s + #0;
	ps := @s[1];

	{on now make new one it!}
	asm
		push ds
    lds  dx, ps
    mov  cx, faVolumeID
    mov  ah,3Ch
    int  21h
    {CF set if error}
    jnc  @noerror
    mov  result, FALSE
		jmp	 @exit
  @noerror:
		{close file ax = handle}
		mov  bx,ax
		mov	 ah,3Eh
		int	 21h
  @exit:
  	pop  ds
	end
end;

{Get label from drive.  0=default, 1=A...}
{return string of 11 character or "NO NAME" if not found}
function GetDriveLabel(wDrive: word): string;
const
	pattern: string[6] = 'c:\*.*';
var
	sr: TsearchRec;
  OldErrorMode: Word;
  DotPos: Byte;
begin
	{get default drive}
	if wDrive = 0
	then wDrive := GetDefaultDrive
	else dec(wDrive);

	{switch out drive letter}
	pattern[1] := char(65 + wDrive);

	{stop errors and try}
  OldErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
	try
		if FindFirst(Pattern, faVolumeID, sr) = 0 then begin
      Result := sr.Name;
      DotPos := Pos('.', Result);
      if DotPos <> 0 then Delete(Result, DotPos, 1);
	  end
		else result := 'NO NAME'
  finally
		{restore errorsa}
		SetErrorMode(OldErrorMode);
  end;
end;

function GetDefaultDrive: word; assembler;
asm
	mov	ah, 19h 			{convert default to real}
	int	21h
	xor	ah, ah				{clear hi byte}
end;

{Determine id drive is a CDROM, 0=default, 1=A ...}
function IsCDROMDrive(wDrive: word): boolean; assembler;
var
	wTempDrive: word;
asm
	mov	ax, wDrive
	or  ax, ax
	jnz	@not_default
	mov	ah, 19h 			{convert default to drive}
	int	21h
	xor	ah, ah
	mov wTempDrive, ax
	jmp	@test_it
@not_default: 			{zero base it}
	dec	ax
	mov wTempDrive, ax
@test_it:
	mov ax, 1500h     {first test for presence of MSCDEX}
  xor bx, bx
  int 2fh
  mov ax, bx        {MSCDEX is not there if bx is zero}
	or  ax, ax        {so return FALSE}
	jz  @no_mscdex
	mov ax, 150bh     {MSCDEX driver check API}
	mov cx, wTempDrive    {...cx is drive index}
	int 2fh
	or 	ax, ax
@no_mscdex:
end;

{returns drive type}
{read BOIS not drive so floppy does not have to be in drive}
{I don't have all types of drive so not all could be tested}
function GetDriveStyle(wDrive: word): TDriveStyle;
var
	x: word;
	wTempDrive: word;
	dp: TDeviceParams;
begin
	{convert default to drive}
	if wDrive = 0
	then wTempDrive := GetDefaultDrive
	else wTempDrive := wDrive - 1;
	x := GetDriveType(wTempDrive);

	{get types}
	case x of
	drive_Removable: begin
		dp.bSpecFunc := 0; {need to clear this}
		if GetDeviceParameters(wDrive,dp) then begin
			case dp.bDevType of
			0,1: result := t5floppy;		{320K/360K/1.2M}
			2,7,9: result := t3floppy;	{720K/1.44M/2.88M}
			5: result := tRFixed;     	{yes a removable fixed drive!}
			6: result := tTape;         {tape}
      else result := tUnknown;
			end;
		end
		else result := tUnknown;
	end;
  drive_Fixed:
		if IsCDROMDrive(wDrive)
		then result := tCDROM
		else result := tFixed;
 	drive_Remote:
		if IsCDROMDrive(wDrive) {I think this is possible on a network!}
		then result := tCDROM
		else result := tNetWork;
	else result := tUnknown;
	end;
end;

end. {of unit}

{ ----------------   STRINGS.PAS  ---------------------------------}
{ CUT }

{string constants for drives.pas}

{The error class may be one of the following}
 	eClassStr: array[0..$0D] of string = (
{OK											}'OK',
{ERRCLASS_OUTRES (01h)	}'Out of resource, such as storage.',
{ERRCLASS_TEMPSIT (02h)	}'Not an error, temporary situation (file or record lock)',
{ERRCLASS_AUTH (03h)		}'Authorization problem.',
{ERRCLASS_INTRN (04h)		}'Internal error in system.',
{ERRCLASS_HRDFAIL (05h)	}'Hardware failure.',
{ERRCLASS_SYSFAIL (06h)	}'System software failure (missing or incorrect configuration files).',
{ERRCLASS_APPERR (07h)	}'Application error.',
{ERRCLASS_NOTFND (08h)  }'File or item not found.',
{ERRCLASS_BADFMT (09h)  }'File or item with an invalid format or type.',
{ERRCLASS_LOCKED (0Ah)  }'Interlocked file or item.',
{ERRCLASS_MEDIA (0Bh)   }'Wrong disk in drive, bad spot on disk, or other storage-medium problem.',
{ERRCLASS_ALREADY (0Ch) }'Existing file or item.',
{ERRCLASS_UNK (0Dh)	 	  }'Unknown.');

{*The suggested action may be one of the following:}
 	eActionStr: array[0..$07] of string = (
{OK											}'OK',
{ERRACT_RETRY (01h)			}'Retry immediately.',
{ERRACT_DLYRET (02h)		}'Delay and retry.',
{ERRACT_USER (03h)			}'Bad user input, get new values.',
{ERRACT_ABORT (04h)			}'Terminate in an orderly manner.',
{ERRACT_PANIC (05h)			}'Terminate immediately.',
{ERRACT_IGNORE (06h)		}'Ignore the error.',
{ERRACT_INTRET (07h)		}'Remove the cause of the error (to change disks, for example) and then retry.');

{The error location may be one of the following:}
eLocusStr: array[0..$05] of string = (
{OK											}'OK',
{ERRLOC_UNK (01h)				}'Unknown',
{ERRLOC_DISK (02h)			}'Random-access device, such as a disk drive',
{ERRLOC_NET (03h)				}'Network',
{ERRLOC_SERDEV (04h)		}'Serial device',
{ERRLOC_MEM (05h)				}'Memory');

{MS DOS error codes}
eDosErrorStr: array[0..$5A] of string = (
{0000h  non error} 'OK',
{0001h}	'ERROR_INVALID_FUNCTION',
{0002h}	'ERROR_FILE_NOT_FOUND',
{0003h}	'ERROR_PATH_NOT_FOUND',
{0004h}	'ERROR_TOO_MANY_OPEN_FILES',
{0005h}	'ERROR_ACCESS_DENIED',
{0006h}	'ERROR_INVALID_HANDLE',
{0007h}	'ERROR_ARENA_TRASHED',
{0008h}	'ERROR_NOT_ENOUGH_MEMORY',
{0009h}	'ERROR_INVALID_BLOCK',
{000Ah}	'ERROR_BAD_ENVIRONMENT',
{000Bh}	'ERROR_BAD_FORMAT',
{000Ch}	'ERROR_INVALID_ACCESS',
{000Dh}	'ERROR_INVALID_DATA',
{000Eh} 'Reserved',
{000Fh}	'ERROR_INVALID_DRIVE',
{0010h}	'ERROR_CURRENT_DIRECTORY',
{0011h}	'ERROR_NOT_SAME_DEVICE',
{0012h}	'ERROR_NO_MORE_FILES',
{0013h}	'ERROR_WRITE_PROTECT',
{0014h}	'ERROR_BAD_UNIT',
{0015h}	'ERROR_NOT_READY',
{0016h}	'ERROR_BAD_COMMAND',
{0017h}	'ERROR_CRC',
{0018h}	'ERROR_BAD_LENGTH',
{0019h}	'ERROR_SEEK',
{001Ah}	'ERROR_NOT_DOS_DISK',
{001Bh}	'ERROR_SECTOR_NOT_FOUND',
{001Ch}	'ERROR_OUT_OF_PAPER',
{001Dh}	'ERROR_WRITE_FAULT',
{001Eh}	'ERROR_READ_FAULT',
{001Fh}	'ERROR_GEN_FAILURE',
{0020h}	'ERROR_SHARING_VIOLATION',
{0021h}	'ERROR_LOCK_VIOLATION',
{0022h}	'ERROR_WRONG_DISK',
{0023h}	'ERROR_FCB_UNAVAILABLE',
{0024h}	'ERROR_SHARING_BUFFER_EXCEEDED',
{0025h}	'ERROR_CODE_PAGE_MISMATCHED',
{0026h}	'ERROR_HANDLE_EOF',
{0027h}	'ERROR_HANDLE_DISK_FULL',
{0028h} 'Reserved',
{0029h} 'Reserved',
{002Ah} 'Reserved',
{002Bh} 'Reserved',
{002Ch} 'Reserved',
{002Dh} 'Reserved',
{002Eh} 'Reserved',
{002Fh} 'Reserved',
{0030h} 'Reserved',
{0031h} 'Reserved',
{0032h}	'ERROR_NOT_SUPPORTED',
{0033h}	'ERROR_REM_NOT_LIST',
{0034h}	'ERROR_DUP_NAME',
{0035h}	'ERROR_BAD_NETPATH',
{0036h}	'ERROR_NETWORK_BUSY',
{0037h}	'ERROR_DEV_NOT_EXIST',
{0038h}	'ERROR_TOO_MANY_CMDS',
{0039h}	'ERROR_ADAP_HDW_ERR',
{003Ah}	'ERROR_BAD_NET_RESP',
{003Bh}	'ERROR_UNEXP_NET_ERR',
{003Ch}	'ERROR_BAD_REM_ADAP',
{003Dh}	'ERROR_PRINTQ _FULL',
{003Eh}	'ERROR_NO_SPOOL_SPACE',
{003Fh}	'ERROR_PRINT_CANCELLED',
{0040h}	'ERROR_NETNAME_DELETED',
{0041h}	'ERROR_NETWORK_ACCESS_DENIED',
{0042h}	'ERROR_BAD_DEV_TYPE',
{0043h}	'ERROR_BAD_NET_NAME',
{0044h}	'ERROR_TOO_MANY_NAMES',
{0045h}	'ERROR_TOO_MANY_SESS',
{0046h}	'ERROR_SHARING_PAUSED',
{0047h}	'ERROR_ERROR_REQ _NOT_ACCEP',
{0048h}	'ERROR_REDIR_PAUSED',
{0049h} 'Reserved',
{004Ah} 'Reserved',
{004Bh} 'Reserved',
{004Ch} 'Reserved',
{004Dh} 'Reserved',
{004Eh} 'Reserved',
{004Fh} 'Reserved',
{0050h}	'ERROR_FILE_EXISTS',
{0051h}	'ERROR_DUP_FCB',
{0052h}	'ERROR_CANNOT_MAKE',
{0053h}	'ERROR_FAIL_I24',
{0054h}	'ERROR_OUT_OF_STRUCTURES',
{0055h}	'ERROR_ALREADY_ASSIGNED',
{0056h}	'ERROR_INVALID_PASSWORD',
{0057h}	'ERROR_INVALID_PARAMETER',
{0058h}	'ERROR_NET_WRITE_FAULT',
{0059h}	'Function not supported on Network',
{005Ah}	'ERROR_SYS_COMP_NOT_LOADED');

cDriveStr: array[0..8] of string = (
			'Unknown',
			'NoDrive',
			'3-1/2" floppy',
			'5-1/4" floppy',
		 	'hard',
			'removable hard',
			'network',
			'CD ROM',
      'tape');