Contributor: RAY BERNARD program FREE; { show free disk space and other info } {$M 32000,0,100000} {$IFDEF Windows} This program is intended for DOS real mode only! {$ELSE} {$IFDEF DPMI} Compile for real mode so program does not need RTM.EXE and DPMI16BI.OVL! {$ENDIF} {$ENDIF} {-------------------------------------------------------------------------} { } { FREE.PAS } { ======== } { Display drive information and handle phantom floppy drives so } { that you don't get the annoying 'insert disk in drive' message. } { That means when a non-active phantom floppy drive letter is } { listed on the command line, list it's mapped drive and show the } { info for the mapped drive, like this: } { } { Free A: ----------- - same drive as B } { Free B: 0 - drive not ready } { } { See the program Usage display (FREE /?) for more information. } { } {-------------------------------------------------------------------------} { This program uses the Object Professional library by } { TurboPower Software. } {-------------------------------------------------------------------------} { (c) 1995 by Ray Bernard Consulting and Design. All Rights Reserved. } { Portions (c) 1995 by TurboPower Software. All Rights Reserved. } {-------------------------------------------------------------------------} {-------------------------------------------------------------------------} { ABOUT THIS PROGRAM } { ================== } { We developed this program to show drive information because it seemed } { like we were always adding new drives to our computers, then changing } { around the drive contents to optimize their usage. Most of the 1 gig } { drives are partitioned into 4 drives for maximum utilization (to make } { 16K sectors instead of 32K sectors). Whenever we would rearrange } { drive contents, it was helpful to have a utility that would identify } { all of a computers local and networked drives, and display the drive } { volume label and type of drive. } { } { Now that most of the kinks are worked out of this program, it seemed } { fitting to upload the source code to TurboPower's BBS and CIS forum, } { in case others would find it handy. } { } { This program is provided "AS-IS" without any warranty. Although it is } { copyrighted, free use may be made of the source code by any registered } { TurboPower software customers. Free use may be made of the compiled } { version regardless of TurboPower customer status. } { } { -Ray Bernard } { Ray Bernard Consulting and Design CIS: [73354,3325] } { June 17, 995 } {-------------------------------------------------------------------------} Uses DOS, OpDOS, OpCrt, OpString, OpInline; type TPhantomRec = record DrvLtr, MapDrvLtr : Char; end; var DriveList : String[26]; { drive letters extracted from command line } HighDriveSpace, LowDriveSpace : LongInt; { to hold LOW and HIGH options space values } ReportOptions : Word; { set per command line options } PhantomList : array['A'..'Z'] of TPhantomRec; { phantom drive global list } const { Report Options for ShowFreeSpaceAllDrives } ShowAtOrAbove = $00000001; { /H } ShowAtOrBelow = $00000002; { /L } ShowAndPause = $00000004; { /P } ShowAllDrives = $00000008; { /A } ShowSize = $00000010; { /C } ShowType = $00000020; { /T } ShowVolumeLabel = $00000040; { /V } ShowFloppies = $00000080; { /F } MsPerSec = 1000; Kbyte = 1024; Megabyte = 1024*Kbyte; Gigabyte = 1024*Megabyte; FreeSpacePad = 11; { pad free space number string to 11 spaces } { global declarations } DriveLetterSet : set of Char = ['A'..'Z']; { for parameter checking } CommandDelimiters : set of char = ['-','/']; { for parameter checking } PauseDelaySecs : Word = 5; { default seconds to pause after display } Debug : Boolean = True; DosVer300 = $0300; {for DOS 3.00} DosVer320 = $0314; {for DOS 3.20, $14 = 20} procedure ShowUsageAndHalt; {-Show the usage information for this program } begin HighVideo; Writeln('FREE.EXE - Display drive free space and other information.'); LowVideo; Writeln('(c) 1995 by Ray Bernard Consulting and Design, CompuServe: 73354,3325.'); Writeln('Portions (c) 1987-1995 by TurboPower Software. All Rights Reserved.'); Writeln('FREE.EXE is "freeware", distributed "as is" and without warranty.'); Writeln; HighVideo; Write('Usage: '); LowVideo; Writeln('FREE [Drives] [Options]'); Writeln; HighVideo; Write('Drives: '); LowVideo; Writeln('One or more drive letters, with or without spaces'); Writeln('separating them. If no drives are listed the default drive is assumed.'); Writeln; HighVideo; Write('Options: '); LowVideo; Writeln('One or more options, separated by spaces. You may'); Writeln('also use the first letter of the option, such as /A for /ALL.'); Writeln('/ALL show all drives (A and B)'); Writeln('/FLOPPY show all drives including A and B'); Writeln('/HIGH n all drives with n or more free space'); Writeln('/LOW n all drives with n or less free space'); Writeln('/PAUSE pause 5 seconds after display or until key pressed'); Writeln('/PAUSE n pause n seconds after the display or until key pressed'); Writeln('/SIZE drive size (formatted capacity)'); Writeln('/TYPE type of drive'); Writeln('/VOLUME show volume label'); Writeln; Write('Press a key to continue . . .'); if ReadKey <> #0 then {do nothing} ; ClrScr; Writeln('FREE.EXE - Usage information, page 2.'); Writeln; Writeln('High and Low free space amounts can be specified in bytes, kbytes, '); Writeln('megabytes or gigabytes using (K, M or G), with or without commas to separate'); Writeln('thousands. Example: 52,428,800 or 52428800 or 50M for fifty megabytes.'); Writeln; Writeln('EXAMPLE PROGRAM OUTPUT:'); Writeln; Writeln('C:>FREE /f /t /v /s'); Writeln('Free A: 0 (Size 0.0MB) - drive not ready'); Writeln('Free B: 0 (Size 0.0MB) - drive not ready'); Writeln('Free C: 48,283,648 (Size 515MB) - local hard drive IDE#1_V1'); Writeln('Free D: 47,218,688 (Size 251MB) - local hard drive IDE#2_V1'); Writeln('Free E: 162,906,112 (Size 519MB) - local hard drive SCSI#1_V1'); Writeln('Free F: 72,351,744 (Size 257MB) - local hard drive IDE#1_V3'); Writeln('Free G: 34,570,240 (Size 257MB) - local hard drive IDE#1_V4'); Writeln('Free H: 35,274,752 (Size 251MB) - local hard drive IDE#2_V2'); Writeln('Free K: 203,807,616 (Size 2GB) - remote or network drive AT_SERV_C'); Writeln('Free O: ----------- - same drive as B'); Writeln('Free N: 34,570,240 (Size 257MB) - logical SUBST or ASSIGN on G IDE#1_V4'); Writeln('Free R: 0 (Size 127MB) - CD-ROM Drive MFCDISC'); Writeln; Halt; end; function IsDriveLocal(Drive : Byte) : Boolean; { -Returns TRUE if drive is local. Drive is the drive number (0 for default, 1 for A:, 2 for B: ...). NOTE: If DOS 3 or greater is not loaded, it is assumed that the Drive is local (since DOS has no standard way of knowing under DOS 2). IsDriveLocal is taken from SHARE.PAS, a unit supplied with TurboPower's B-Tree Filer. } var Regs : Registers; begin if OpDos.DosVersion >= DosVer300 then {DOS 3 or greater required} begin with Regs do begin AX := $4409; BL := Drive; MsDos(Regs); if Odd(Flags) then IsDriveLocal := True {if error, then assume drive is local} else {the drive is local if bit 12 of DX is clear} IsDriveLocal := not FlagIsSet(DX,$1000); end; end else IsDriveLocal := True; {assume it's local for DOS < 3} end; function DrvNumToLtr(DriveNum : Byte) : Char; {-Return Drive Letter for 1-based Drive Number (A=1, B=2, etc.)} begin DrvNumToLtr := Chr(DriveNum+64); end; function DrvLtrToNum(DriveLetter : Char) : Byte; {-Return 1-based Drive Number (A=1, B=2, etc.) for DriveLetter} begin DrvLtrToNum := Ord(Upcase(DriveLetter))-64; end; function IsDrivePhantom(DriveLetter : Char; var MappedTo : Char) : Boolean; {-Return True if Phantom drive and pass back original drive mapped to. } { Will recognize floppy drives drives reassigned using DRIVER.SYS. } var Regs : Registers; IsPhantom : Boolean; DriveNum : Byte; begin IsPhantom := False; DriveNum := DrvLtrToNum(DriveLetter); if OpDos.DosVersion > DosVer320 then begin {DOS 3.2 or greater required} with Regs do begin AX := $440E; BL := DriveNum; MsDos(Regs); if Odd(Flags) then begin IsPhantom := False; {if error, then assume drive not phantom } end else begin if AL = 0 then {the drive is not phantom drive if AL = 0} IsPhantom := False else begin IsPhantom := (AL <> DriveNum); if IsPhantom then MappedTo := DrvNumToLtr(AL); end; end; end; end; IsDrivePhantom := IsPhantom; end; { IsDrivePhantom } function IsListedAsPhantom(DriveLetter : Char) : Boolean; {-Refer to this program's global PhantomList variable } begin IsListedAsPhantom := PhantomList[DriveLetter].DrvLtr = DriveLetter; end; procedure RestoreOutputRedirection; {-Return Write and Writeln to Standard I/O to allow } { redirection of program output to file or printer } begin { undo OpCrt's assignment of Output to CRT } Close(Output); Assign(Output,''); Rewrite(Output); end; function NumFloppies : Byte; {-Return the number of floppy drives installed per DOS "List of Lists" } var Equipment : Byte; begin Equipment := mem[$0040:$0010]; if (equipment and $0001) = 1 then NumFloppies := ((Equipment shr 6) and $0003)+1 else NumFloppies := 0; end; procedure HaltWithMessage(Message : String); {-Display Message and halt } begin Writeln(Message); Halt; end; function InsertCommas(S : String) : String; {-Add commas to string for thousands. Work from the end } { of the string exit on the first space encountered, to } { be able to handle strings like: 'TOTAL: 1,000'. } var I : Word; Len : Word; begin Len := Length(S); I := Len; while I > 1 do begin if (Len+1-I) mod 3 = 0 then if S[I-1] = ' ' then Break else insert(',', S, I); dec(I); end; InsertCommas := S; end; procedure GetAndSavePhantomInfo(DriveLetter : Char); {-If DriveLetter is a Phantom drive, set data in PhantomList } var MapCh : Char; begin if IsDrivePhantom(DriveLetter,MapCh) then with PhantomList[DriveLetter] do begin DrvLtr := DriveLetter; MapDrvLtr := MapCh; end; end; function DiskTypeString(DriveLetter : Char) : String; {-Return the disk class information as a string with leading space & dash. } { Checks the global variable PhantomList for phantom drive info, and exit } { for phantom drive without calling GetDiskClass to avoid disk hit. } var DiskType : DiskClass; DriveNum : Byte; SubDriveCh : Char; DTStr : String; begin DriveNum := DrvLtrToNum(DriveLetter); if IsListedAsPhantom(DriveLetter) then begin DiskTypeString := ' - same drive as '+PhantomList[DriveLetter].MapDrvLtr; Exit; end; DiskType := GetDiskClass(DriveLetter,SubDriveCh); case DiskType of Floppy360 : DTStr := ' - 360KB, 5.25" diskette'; Floppy720 : DTStr := ' - 720KB, 3.50" diskette'; Floppy12 : DTStr := ' - 1.2MB, 5.25" diskette'; Floppy144 : DTStr := ' - 1.44MB, 3.50" diskette'; OtherFloppy : DTStr := ' - unlised diskette type'; Bernoulli : DTStr := ' - Bernouli drive'; HardDisk : DTStr := ' - local hard drive'; RamDisk : DTStr := ' - RAM drive'; SubstDrive : DTStr := ' - logical SUBST or ASSIGN on '+ SubDriveCh; UnknownDisk : DTStr := ' - unlisted disk type'; InvalidDrive : if not IsDriveLocal(DriveNum) then DTStr := ' - remote or network drive' else DTStr := ' - drive not ready'; NovellDrive : DTStr := ' - Novelle network drive'; CDRomDisk : DTStr := ' - CD-ROM drive'; else { Trap for GetDiskClass update to include new types} { before this routine is revised, and ensure that } { a function result is always assigned. } DTStr := 'unrecognized disk type'; end; {case} DiskTypeString := DTStr; end; function DiskCapacity(DriveLetter : char) : longint; {-Return the disk capacity in number of bytes for the specified drive. } var DriveNum : byte; BytesPerCluster, AvailClusters, TotalClusters, SectorsPerCluster, BytesPerSector : word; TotalBytes : longint; begin DiskCapacity := 0; if IsListedAsPhantom(DriveLetter) then Exit; DriveNum := DrvLtrToNum(DriveLetter); if GetDiskInfo(DriveNum,AvailClusters,TotalClusters, BytesPerSector,SectorsPerCluster) then begin BytesPerCluster := LongInt(SectorsPerCluster) * BytesPerSector; DiskCapacity := LongInt(TotalClusters) * BytesPerCluster; end; end; { DiskCapacity } function DiskCapacityString(DriveLetter : Char) : String; {-Return disk capacity info in megabytes or gigabytes formatted with commas } var DiskCap : LongInt; TempStr : String; begin DiskCap := DiskCapacity(DriveLetter); if DiskCap >= Gigabyte then TempStr := Long2Str(DiskCap div Gigabyte)+'GB' else if DiskCap > 9*Megabyte then TempStr := InsertCommas(Long2Str(DiskCap div Megabyte))+'MB' else TempStr := Long2Str(DiskCap div Megabyte)+'.'+ copy(Long2Str(DiskCap mod Megabyte),1,1)+'MB'; DiskCapacityString := TempStr; end; function AvailableDriveSpace(DriveLetter : char) : longint; {-Return the number of bytes free on specified drive} var DriveNum : byte; BytesPerCluster, AvailClusters, TotalClusters, SectorsPerCluster, BytesPerSector : word; TotalBytes : longint; begin AvailableDriveSpace := 0; if IsListedAsPhantom(DriveLetter) then Exit; DriveNum := DrvLtrToNum(DriveLetter); if GetDiskInfo(DriveNum,AvailClusters,TotalClusters, BytesPerSector,SectorsPerCluster) then begin BytesPerCluster := LongInt(SectorsPerCluster) * BytesPerSector; AvailableDriveSpace := LongInt(AvailClusters) * BytesPerCluster; end; end; { AvailableDriveSpace } function DriveInfoString(DriveLetter : Char) : String; {-Return the formatted drive information for DriveLetter. } { Use global variable ReportOptions to determine what data } { should be inlcuded in returned string. Return a nul } { string if the drive's infor should not be displayed } { per high or low options. } var N : Byte; InfoStr : String; ProcResult : Word; VolumeStr : VolumeNameStr; FreeSpace : LongInt; ShowThisDrive : Boolean; const SpacePadWidth = 13; begin InfoStr := 'Free '+DriveLetter+': '; if not IsListedAsPhantom(DriveLetter) then begin FreeSpace := AvailableDriveSpace(DriveLetter); InfoStr := InfoStr + LeftPad(InsertCommas(Long2Str(FreeSpace)),SpacePadWidth); end else begin InfoStr := InfoStr + ' -----------'; FreeSpace := 0; end; if FlagIsSet(ReportOptions,ShowSize) then if not IsListedAsPhantom(DriveLetter) then InfoStr := InfoStr + ' (Size '+ DiskCapacityString(DriveLetter) + ')' else InfoStr := InfoStr + ' (Size -----)'; if FlagIsSet(ReportOptions,ShowType) then InfoStr := InfoStr + DiskTypeString(DriveLetter); if FlagIsSet(ReportOptions,ShowVolumeLabel) then if not IsListedAsPhantom(DriveLetter) then begin ProcResult := GetVolumeLabel(DriveLetter,VolumeStr); if ProcResult = 0 then if Length(VolumeStr) > 0 then InfoStr := InfoStr + ' '+VolumeStr else InfoStr := InfoStr + ' no label'; end; { handle high and low disk space options } if FlagIsSet(ReportOptions,ShowAtOrAbove) or FlagIsSet(ReportOptions,ShowAtOrBelow) then begin { allow both settings combined } ShowThisDrive := ( (FlagIsSet(ReportOptions,ShowAtOrAbove) and (FreeSpace >= HighDriveSpace)) or (FlagIsSet(ReportOptions,ShowAtOrBelow) and (FreeSpace <= LowDriveSpace)) ); end else ShowThisDrive := True; if not ShowThisDrive then DriveInfoString := '' else DriveInfoString := InfoStr; end; procedure DisplayDriveInfoString(DriveLetter : Char); {-Writeln DriveInfoString's result for DriveLetter if not a nul string } var DisplayStr : String; begin DisplayStr := DriveInfoString(DriveLetter); if Length(DisplayStr) > 0 then Writeln(DisplayStr); end; procedure DisplayDrivesInfo; {-Display the info for all drives in DriveList, checking ReportOptions } { for display settings. } { Before calling DriveInfoString, make all calls to GetPhantomInfo. } { DriveInfoString's call to GetDiskInfo will cause a disk in each drive, } { making any phantom drive current and thus undetectable by our methods. } { If a single drive was specified on the command line, and it turns out } { to be a phantom drive, show the drive info for the mapped drive, too. } var N : Byte; begin { build global list of phantom drive maps } for N := 1 to Length(DriveList) do GetAndSavePhantomInfo(DriveList[N]); { now get and display drive information } if (Length(DriveList) = 1) then begin { hangle single specified phantom drive } DisplayDriveInfoString(DriveList[1]); if IsListedAsPhantom(DriveList[1]) then DisplayDriveInfoString(PhantomList[DriveList[1]].MapDrvLtr); end else { show drive info for all listed drives } for N := 1 to Length(DriveList) do DisplayDriveInfoString(DriveList[N]); { pause if option enabled } if FlagIsSet(ReportOptions,ShowAndPause) then Delay(PauseDelaySecs*MsPerSec); end; function AllValidDrivesList : String; {-Return a string containing drive letters of all valid drives} var DriveLetter : Char; TempDrivesList : String; FirstDrive : Char; const LastDrive = 'Z'; begin TempDrivesList := ''; if FlagIsSet(ReportOptions,ShowFloppies) then FirstDrive := 'A' else FirstDrive := 'C'; for DriveLetter := FirstDrive to LastDrive do if ValidDrive(DriveLetter) then TempDrivesList := TempDrivesList + DriveLetter else { primary floppies show invalid when last access was via the the Phantom } { drive, so check DOS equipment list for floppies by calling NumFloppies } if (DriveLetter in ['A','B']) and (NumFloppies > 0) then TempDrivesList := TempDrivesList + DriveLetter; AllValidDrivesList := TempDrivesList; end; function StripCommas(S : String) : String; {-Remove any commas from S and return as result } var LenS : Byte absolute S; P : Byte; begin for P := LenS downto 1 do if S[P] = ',' then Delete(S,P,1); StripCommas := S; end; procedure ParseCommandLine; {-Parse command line to get drive list and options } { First, check to see if a parameter is a drive letter or list of them. } { If so, put the letter or letters in the drive list. } { If not a drive letter, the parameter should be a command option. } { Process the command option, or ignore if not a valid option (could be } { an option parameter or an invalid option). } var ChPos,Count : Byte; TempDelaySecs : Word; Param,Param2 : String; ParamLen : Byte absolute Param; Param2Len : Byte absolute Param2; HLChar : Char; { /HIGH, /LOW: K = Kbytes, M = Megabytes, G = Gig } const HLCharSet : set of Char = ['G','K','M']; begin { initialize variables } FillChar(PhantomList, SizeOf(PhantomList), #0); ReportOptions := 0; HighDriveSpace := 0; LowDriveSpace := 0; for Count := 1 to ParamCount do begin Param := StUpCase(ParamStr(Count)); if Param[1] in DriveLetterSet then begin for ChPos := 1 to ParamLen do { for drives listed w/no spacing } if Pos(Param[ChPos],DriveList) = 0 then { correct user error } if ValidDrive(Param[ChPos]) then DriveList := DriveList + Param[ChPos] else { primary floppies show invalid when last access was via the the Phantom } { drive, so check DOS equipment list for floppies by calling NumFloppies } if (Param[ChPos] in ['A','B']) and (NumFloppies > 0) then DriveList := DriveList + Param[ChPos]; end else { 1st char should be command delimiter } { 2nd char should indicate option } if Param[1] in CommandDelimiters then begin case Param[2] of 'A' : SetFlag(ReportOptions,ShowAllDrives); 'F' : SetFlag(ReportOptions,ShowFloppies); 'H' : begin HLChar := #255; { to check if set } if ParamCount <= Count then HaltWithMessage('specify drive space with /HIGH option'); { check for G, K or M } Param2 := StUpCase(ParamStr(Count+1)); if Param2[Param2Len] in HLCharSet then begin HLChar := Param2[Param2Len]; Delete(Param2,Param2Len,1); end; { validate free space number } if not Str2Long(StripCommas(Param2),HighDriveSpace) then HaltWithMessage('specify amount of drive space with /HIGH option') else { handle G, K or M } if HLChar <> #255 then begin case HLChar of 'G' : HighDriveSpace := HighDriveSpace * Gigabyte; 'K' : HighDriveSpace := HighDriveSpace * Kbyte; 'M' : HighDriveSpace := HighDriveSpace * Megabyte; end; {case} end; SetFlag(ReportOptions,ShowAtOrAbove); SetFlag(ReportOptions,ShowAllDrives); end; 'L' : begin HLChar := #255; { to check if set } if ParamCount <= Count then HaltWithMessage('specify drive space with /LOW option'); { check for G, K or M } Param2 := StUpCase(ParamStr(Count+1)); if Param2[Param2Len] in HLCharSet then begin HLChar := Param2[Param2Len]; Delete(Param2,Param2Len,1); end; { validate free space number } if not Str2Long(StripCommas(Param2),LowDriveSpace) then HaltWithMessage('specify amount of drive space with /LOW option') else { handle G, K or M } if HLChar <> #255 then begin case HLChar of 'G' : LowDriveSpace := LowDriveSpace * Gigabyte; 'K' : LowDriveSpace := LowDriveSpace * Kbyte; 'M' : LowDriveSpace := LowDriveSpace * Megabyte; end; {case} end; SetFlag(ReportOptions,ShowAtOrBelow); SetFlag(ReportOptions,ShowAllDrives); end; 'P' : begin SetFlag(ReportOptions,ShowAndPause); if Count < ParamCount then { next param could be delay } if Str2Word(ParamStr(Count+1),TempDelaySecs) then PauseDelaySecs := TempDelaySecs; end; 'S' : SetFlag(ReportOptions,ShowSize); 'T' : SetFlag(ReportOptions,ShowType); 'V' : SetFlag(ReportOptions,ShowVolumeLabel); '?' : ShowUsageAndHalt; end; { case } end; { if Param[1] in CommandDelimiters } end; { for Count := 1 to ParamCount } if FlagIsSet(ReportOptions,ShowAllDrives) or FlagIsSet(ReportOptions,ShowFloppies) then DriveList := AllValidDrivesList; end; { ParseCommandLine } begin { FREE } RestoreOutputRedirection; if ParamCount > 0 then ParseCommandLine; if Length(DriveList) = 0 then { if no drives on command line } DriveList := DefaultDrive; { default to current the drive } DisplayDrivesInfo; end. { FREE }