home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
xwphescr.zip
/
XWPH0208.ZIP
/
src
/
helpers
/
dosh.c
< prev
next >
Wrap
C/C++ Source or Header
|
2002-08-11
|
140KB
|
4,247 lines
/*
*@@sourcefile dosh.c:
* dosh.c contains Control Program helper functions.
*
* This file has miscellaneous system functions,
* drive helpers, file helpers, and partition functions.
*
* Usage: All OS/2 programs.
*
* Function prefixes (new with V0.81):
* -- dosh* Dos (Control Program) helper functions
*
* These funcs are forward-declared in dosh.h, which
* must be #include'd first.
*
* The resulting dosh.obj object file can be linked
* against any application object file. As opposed to
* the code in dosh2.c, it does not require any other
* code from the helpers.
*
* dosh.obj can also be used with the VAC subsystem
* library (/rn compiler option).
*
* Note: Version numbering in this file relates to XWorkplace version
* numbering.
*
*@@header "helpers\dosh.h"
*/
/*
* This file Copyright (C) 1997-2000 Ulrich Möller.
* This file is part of the "XWorkplace helpers" source package.
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, in version 2 as it comes in the
* "COPYING" file of the XWorkplace main distribution.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define OS2EMX_PLAIN_CHAR
// this is needed for "os2emx.h"; if this is defined,
// emx will define PSZ as _signed_ char, otherwise
// as unsigned char
#define INCL_DOSMODULEMGR
#define INCL_DOSPROCESS
#define INCL_DOSEXCEPTIONS
#define INCL_DOSSESMGR
#define INCL_DOSQUEUES
#define INCL_DOSSEMAPHORES
#define INCL_DOSMISC
#define INCL_DOSDEVICES
#define INCL_DOSDEVIOCTL
#define INCL_DOSERRORS
#define INCL_KBD
#include <os2.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include "setup.h" // code generation and debugging options
#include "helpers\dosh.h"
#include "helpers\standards.h"
#pragma hdrstop
// static const CHAR G_acDriveLetters[28] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/*
*@@category: Helpers\Control program helpers\Wrappers
*/
/* ******************************************************************
*
* Wrappers
*
********************************************************************/
#ifdef DOSH_STANDARDWRAPPERS
/*
*@@ doshSleep:
*
*@@added V0.9.16 (2002-01-26) [umoeller]
*/
APIRET doshSleep(ULONG msec)
{
// put the call in brackets so the macro won't apply here
return (DosSleep)(msec);
}
/*
*@@ doshCreateMutexSem:
*
*@@added V0.9.16 (2002-01-26) [umoeller]
*/
APIRET doshCreateMutexSem(PSZ pszName,
PHMTX phmtx,
ULONG flAttr,
BOOL32 fState)
{
// put the call in brackets so the macro won't apply here
return (DosCreateMutexSem)(pszName, phmtx, flAttr, fState);
}
/*
*@@ doshRequestMutexSem:
*
*@@added V0.9.16 (2002-01-26) [umoeller]
*/
APIRET doshRequestMutexSem(HMTX hmtx, ULONG ulTimeout)
{
return (DosRequestMutexSem)(hmtx, ulTimeout);
}
/*
*@@ doshReleaseMutexSem:
*
*@@added V0.9.16 (2002-01-26) [umoeller]
*/
APIRET doshReleaseMutexSem(HMTX hmtx)
{
return (DosReleaseMutexSem)(hmtx);
}
/*
*@@ doshSetExceptionHandler:
*
*@@added V0.9.16 (2002-01-26) [umoeller]
*/
APIRET doshSetExceptionHandler(PEXCEPTIONREGISTRATIONRECORD pERegRec)
{
// put the call in brackets so the macro won't apply here
return (DosSetExceptionHandler)(pERegRec);
}
/*
*@@ doshUnsetExceptionHandler:
*
*@@added V0.9.16 (2002-01-26) [umoeller]
*/
APIRET doshUnsetExceptionHandler(PEXCEPTIONREGISTRATIONRECORD pERegRec)
{
// put the call in brackets so the macro won't apply here
return (DosUnsetExceptionHandler)(pERegRec);
}
#endif
/*
*@@category: Helpers\Control program helpers\Miscellaneous
* Miscellaneous helpers in dosh.c that didn't fit into any other
* category.
*/
/* ******************************************************************
*
* Miscellaneous
*
********************************************************************/
/*
*@@ doshGetChar:
* reads a single character from the keyboard.
* Useful for VIO sessions, since there's great
* confusion between the various C dialects about
* how to use getc(), and getc() doesn't work
* with the VAC subsystem library.
*
*@@added V0.9.4 (2000-07-27) [umoeller]
*/
CHAR doshGetChar(VOID)
{
// CHAR c;
// ULONG ulRead = 0;
KBDKEYINFO kki;
KbdCharIn(&kki,
0, // wait
0);
return (kki.chChar);
}
/*
*@@ doshQueryShiftState:
* returns TRUE if any of the SHIFT keys are
* currently pressed. Useful for checks during
* WM_COMMAND messages from menus.
*
*@@changed V0.9.5 (2000-09-27) [umoeller]: added error checking
*/
BOOL doshQueryShiftState(VOID)
{
BOOL brc = FALSE;
APIRET arc = NO_ERROR;
HFILE hfKbd;
ULONG ulAction;
if (!(arc = DosOpen("KBD$", &hfKbd, &ulAction, 0,
FILE_NORMAL,
FILE_OPEN,
OPEN_ACCESS_READONLY | OPEN_SHARE_DENYWRITE,
(PEAOP2)NULL)))
{
SHIFTSTATE ShiftState;
ULONG cbDataLen = sizeof(ShiftState);
if (!(arc = DosDevIOCtl(hfKbd, IOCTL_KEYBOARD, KBD_GETSHIFTSTATE,
NULL, 0, NULL, // no parameters
&ShiftState, cbDataLen, &cbDataLen)))
brc = ((ShiftState.fsState & 3) != 0);
DosClose(hfKbd);
}
return brc;
}
/*
*@@ doshIsWarp4:
* checks the OS/2 system version number.
*
* Returns:
*
* -- 0 (FALSE): OS/2 2.x or Warp 3 is running.
*
* -- 1: Warp 4.0 is running.
*
* -- 2: Warp 4.5 is running (WSeB or Warp 4 FP 13+ or eCS
* or ACP/MCP), or even something newer.
*
*@@changed V0.9.2 (2000-03-05) [umoeller]: reported TRUE on Warp 3 also; fixed
*@@changed V0.9.6 (2000-10-16) [umoeller]: patched for speed
*@@changed V0.9.9 (2001-04-04) [umoeller]: now returning 2 for Warp 4.5 and above
*/
ULONG doshIsWarp4(VOID)
{
static BOOL s_fQueried = FALSE;
static ULONG s_ulrc = 0;
if (!s_fQueried)
{
// first call:
ULONG aulBuf[3];
DosQuerySysInfo(QSV_VERSION_MAJOR, // 11
QSV_VERSION_MINOR, // 12
&aulBuf, sizeof(aulBuf));
// Warp 3 is reported as 20.30
// Warp 4 is reported as 20.40
// Aurora is reported as 20.45 (regardless of convenience packs)
if ( (aulBuf[0] > 20) // major > 20; not the case with Warp 3, 4, 5
|| ( (aulBuf[0] == 20) // major == 20 and minor >= 45
&& (aulBuf[1] >= 45)
)
)
// Warp 4.5 or newer:
s_ulrc = 2;
else if ( (aulBuf[0] == 20) // major == 20 and minor == 40
&& (aulBuf[1] == 40)
)
// Warp 4:
s_ulrc = 1;
s_fQueried = TRUE;
}
return (s_ulrc);
}
/*
*@@ doshQuerySysErrorMsg:
* this retrieves the error message for a system error
* (APIRET) from the system error message file (OSO001.MSG).
* This file better be on the DPATH (it normally is).
*
* This returns the string in the "SYSxxx: blahblah" style,
* which is normally displayed on the command line when
* errors occur.
*
* The error message is returned in a newly allocated
* buffer, which should be free()'d afterwards.
*
* Returns NULL upon errors.
*/
PSZ doshQuerySysErrorMsg(APIRET arc) // in: DOS error code
{
PSZ pszReturn = 0;
CHAR szDosError[1000];
ULONG cbDosError = 0;
DosGetMessage(NULL, 0, // no string replacements
szDosError, sizeof(szDosError),
arc,
"OSO001.MSG", // default OS/2 message file
&cbDosError);
if (cbDosError > 2)
{
szDosError[cbDosError - 2] = 0;
pszReturn = strdup(szDosError);
}
return (pszReturn);
}
/*
*@@ doshQuerySysUptime:
* returns the system uptime in milliseconds.
* This can be used for time comparisons.
*
*@@added V0.9.12 (2001-05-18) [umoeller]
*/
ULONG doshQuerySysUptime(VOID)
{
ULONG ulms;
DosQuerySysInfo(QSV_MS_COUNT,
QSV_MS_COUNT,
&ulms,
sizeof(ulms));
return (ulms);
}
/*
*@@ doshDevIOCtl:
*
* Works with those IOCtls where the buffer
* size parameters are always the same anyway,
* which applies to all IOCtls I have seen
* so far.
*
*@@added V0.9.13 (2001-06-14) [umoeller]
*/
APIRET doshDevIOCtl(HFILE hf,
ULONG ulCategory,
ULONG ulFunction,
PVOID pvParams,
ULONG cbParams,
PVOID pvData,
ULONG cbData)
{
return (DosDevIOCtl(hf,
ulCategory,
ulFunction,
pvParams, cbParams, &cbParams,
pvData, cbData, &cbData));
}
/*
*@@category: Helpers\Control program helpers\Shared memory management
* helpers for allocating and requesting shared memory.
*/
/* ******************************************************************
*
* Memory helpers
*
********************************************************************/
/*
*@@ doshMalloc:
* wrapper around malloc() which automatically
* sets ERROR_NOT_ENOUGH_MEMORY.
*
*@@added V0.9.16 (2001-10-19) [umoeller]
*/
PVOID doshMalloc(ULONG cb,
APIRET *parc)
{
PVOID pv;
*parc = NO_ERROR;
if (!(pv = malloc(cb)))
*parc = ERROR_NOT_ENOUGH_MEMORY;
return (pv);
}
/*
*@@ doshAllocArray:
* allocates c * cbArrayItem bytes.
* Similar to calloc(), but returns
* error codes:
*
* -- NO_ERROR: *ppv and *pcbAllocated were set.
*
* -- ERROR_NO_DATA: either c or cbArrayItem are
* zero.
*
* -- ERROR_NOT_ENOUGH_MEMORY: malloc() failed.
*
*@@added V0.9.16 (2001-12-08) [umoeller]
*/
APIRET doshAllocArray(ULONG c, // in: array item count
ULONG cbArrayItem, // in: size of one array item
PBYTE *ppv, // out: memory ptr if NO_ERROR is returned
PULONG pcbAllocated) // out: # of bytes allocated
{
if (!c || !cbArrayItem)
return ERROR_NO_DATA;
*pcbAllocated = c * cbArrayItem;
if (!(*ppv = (PBYTE)malloc(*pcbAllocated)))
return ERROR_NOT_ENOUGH_MEMORY;
return NO_ERROR;
}
/*
*@@ doshAllocSharedMem:
* wrapper for DosAllocSharedMem which has
* a malloc()-like syntax. Just due to my
* lazyness.
*
* Note that ulSize is always rounded up to the
* next 4KB value, so don't use this hundreds of times.
*
* Returns NULL upon errors. Possible errors include
* that a memory block calle pcszName has already been
* allocated.
*
* Use DosFreeMem(pvrc) to free the memory. The memory
* will only be freed if no other process has requested
* access.
*
*@@added V0.9.3 (2000-04-18) [umoeller]
*/
PVOID doshAllocSharedMem(ULONG ulSize, // in: requested mem block size (rounded up to 4KB)
const char* pcszName) // in: name of block ("\\SHAREMEM\\xxx") or NULL
{
PVOID pvrc = NULL;
APIRET arc = DosAllocSharedMem((PVOID*)&pvrc,
(PSZ)pcszName,
ulSize,
PAG_COMMIT | PAG_READ | PAG_WRITE);
if (arc == NO_ERROR)
return (pvrc);
return NULL;
}
/*
*@@ doshRequestSharedMem:
* requests access to a block of named shared memory
* allocated by doshAllocSharedMem.
*
* Returns NULL upon errors.
*
* Use DosFreeMem(pvrc) to free the memory. The memory
* will only be freed if no other process has requested
* access.
*
*@@added V0.9.3 (2000-04-19) [umoeller]
*/
PVOID doshRequestSharedMem(PCSZ pcszName)
{
PVOID pvrc = NULL;
APIRET arc = DosGetNamedSharedMem((PVOID*)pvrc,
(PSZ)pcszName,
PAG_READ | PAG_WRITE);
if (arc == NO_ERROR)
return (pvrc);
return NULL;
}
/*
*@@category: Helpers\Control program helpers\Drive management
* functions for managing drives... enumerating, testing,
* querying etc.
*/
/* ******************************************************************
*
* Drive helpers
*
********************************************************************/
/*
*@@ doshIsFixedDisk:
* checks whether a disk is fixed or removeable.
* ulLogicalDrive must be 1 for drive A:, 2 for B:, ...
* The result is stored in *pfFixed.
* Returns DOS error code.
*
* From my testing, this function does _not_ provoke
* "drive not ready" popups, even if the disk is not
* ready.
*
* Warning: This uses DosDevIOCtl, which has proved
* to cause problems with some device drivers for
* removeable disks.
*
* Returns:
*
* -- NO_ERROR: *pfFixed was set.
*
* -- ERROR_INVALID_DRIVE: drive letter invalid
*
* -- ERROR_NOT_SUPPORTED (50): for network drives.
*
*@@changed V0.9.14 (2001-08-03) [umoeller]: added extra fix for A: and B:
*/
APIRET doshIsFixedDisk(ULONG ulLogicalDrive, // in: 1 for A:, 2 for B:, 3 for C:, ...
PBOOL pfFixed) // out: TRUE for fixed disks
{
APIRET arc = ERROR_INVALID_DRIVE;
if ( (ulLogicalDrive == 1)
|| (ulLogicalDrive == 2)
)
{
// drive A: and B: can never be fixed V0.9.14 (2001-08-03) [umoeller]
*pfFixed = FALSE;
return NO_ERROR;
}
if (ulLogicalDrive)
{
// parameter packet
#pragma pack(1)
struct {
UCHAR command,
drive;
} parms;
#pragma pack()
// data packet
UCHAR ucNonRemoveable;
parms.drive = (UCHAR)(ulLogicalDrive - 1);
if (!(arc = doshDevIOCtl((HFILE)-1,
IOCTL_DISK, // 0x08
DSK_BLOCKREMOVABLE, // 0x20
&parms, sizeof(parms),
&ucNonRemoveable, sizeof(ucNonRemoveable))))
*pfFixed = (BOOL)ucNonRemoveable;
}
return arc;
}
/*
*@@ doshQueryDiskParams:
* this retrieves more information about a given drive,
* which is stored in the specified BIOSPARAMETERBLOCK
* structure.
*
* BIOSPARAMETERBLOCK is defined in the Toolkit headers,
* and from my testing, it's the same with the Toolkits
* 3 and 4.5.
*
* If NO_ERROR is returned, the bDeviceType field can
* be one of the following (according to CPREF):
*
* -- 0: 48 TPI low-density diskette drive
* -- 1: 96 TPI high-density diskette drive
* -- 2: 3.5-inch 720KB diskette drive
* -- 3: 8-Inch single-density diskette drive
* -- 4: 8-Inch double-density diskette drive
* -- 5: Fixed disk
* -- 6: Tape drive
* -- 7: Other (includes 1.44MB 3.5-inch diskette drive)
* -- 8: R/W optical disk
* -- 9: 3.5-inch 4.0MB diskette drive (2.88MB formatted)
*
* From my testing, this function does _not_ provoke
* "drive not ready" popups, even if the disk is not
* ready.
*
* Warning: This uses DosDevIOCtl, which has proved
* to cause problems with some device drivers for
* removeable disks.
*
* This returns the DOS error code of DosDevIOCtl.
* This will be:
*
* -- NO_ERROR for all local disks;
*
* -- ERROR_NOT_SUPPORTED (50) for network drives.
*
*@@added V0.9.0 [umoeller]
*@@changed V0.9.13 (2001-06-14) [umoeller]: changed prototype to use BIOSPARAMETERBLOCK directly
*@@changed V0.9.13 (2001-06-14) [umoeller]: now querying standard media, no redetermine
*/
APIRET doshQueryDiskParams(ULONG ulLogicalDrive, // in: 1 for A:, 2 for B:, 3 for C:, ...
PBIOSPARAMETERBLOCK pdp) // out: drive parameters
{
APIRET arc = ERROR_INVALID_DRIVE;
if (ulLogicalDrive)
{
#pragma pack(1)
// parameter packet
struct {
UCHAR ucCommand,
ucDrive;
} parms;
#pragma pack()
parms.ucCommand = 0; // 0 = return standard media,
// 1 = read currently inserted media
// (1 doesn't work any more, returns arc 87
// V0.9.13 (2001-06-14) [umoeller])
parms.ucDrive=(UCHAR)(ulLogicalDrive-1);
// zero the structure V0.9.13 (2001-06-14) [umoeller]
memset(pdp, 0, sizeof(BIOSPARAMETERBLOCK));
arc = doshDevIOCtl((HFILE)-1,
IOCTL_DISK, // 0x08
DSK_GETDEVICEPARAMS, // 0x63
&parms, sizeof(parms),
pdp, sizeof(BIOSPARAMETERBLOCK));
/* if (!arc)
{
_Pmpf((" bDeviceType: %d", pdp->bDeviceType));
_Pmpf((" bytes per sector: %d", pdp->usBytesPerSector));
_Pmpf((" sectors per track: %d", pdp->usSectorsPerTrack));
} */
}
return arc;
}
/*
*@@ doshQueryDriveType:
* tests the specified BIOSPARAMETERBLOCK
* for whether it represents a CD-ROM or
* some other removeable drive type.
*
* Returns one of:
*
* -- DRVTYPE_HARDDISK (0)
*
* -- DRVTYPE_PARTITIONABLEREMOVEABLE
*
* -- DRVTYPE_CDROM
*
* -- DRVTYPE_TAPE
*
* -- DRVTYPE_VDISK
*
* -- DRVTYPE_FLOPPY
*
* -- DRVTYPE_UNKNOWN (255)
*
* The BIOSPARAMETERBLOCK must be filled
* first using doshQueryDiskParams.
*
*@@added V0.9.16 (2002-01-13) [umoeller]
*/
BYTE doshQueryDriveType(ULONG ulLogicalDrive,
PBIOSPARAMETERBLOCK pdp,
BOOL fFixed)
{
if (pdp)
{
if (pdp->fsDeviceAttr & DEVATTR_PARTITIONALREMOVEABLE) // 0x08
return DRVTYPE_PARTITIONABLEREMOVEABLE;
else if (fFixed)
return DRVTYPE_HARDDISK;
else if ( (pdp->bDeviceType == 7) // "other"
&& (pdp->usBytesPerSector == 2048)
&& (pdp->usSectorsPerTrack == (USHORT)-1)
)
return DRVTYPE_CDROM;
else switch (pdp->bDeviceType)
{
case DEVTYPE_TAPE: // 6
return DRVTYPE_TAPE;
case DEVTYPE_48TPI: // 0, 360k 5.25" floppy
case DEVTYPE_96TPI: // 1, 1.2M 5.25" floppy
case DEVTYPE_35: // 2, 720k 3.5" floppy
case DEVTYPE_OTHER: // 7, 1.44 3.5" floppy
// 1.84M 3.5" floppy
case DEVTYPE_35_288MB:
if ( (ulLogicalDrive == 1)
|| (ulLogicalDrive == 2)
)
return DRVTYPE_FLOPPY;
else
return DRVTYPE_VDISK;
case DEVTYPE_RWOPTICAL: // 8, what is this?!?
return DRVTYPE_FLOPPY;
}
}
return (DRVTYPE_UNKNOWN);
}
/*
*@@ doshHasAudioCD:
* sets *pfAudio to whether ulLogicalDrive
* currently has an audio CD inserted.
*
* Better call this only if you're sure that
* ulLogicalDrive is a CD-ROM drive. Use
* doshQueryRemoveableType to check.
*
*@@added V0.9.14 (2001-08-01) [umoeller]
*/
APIRET doshHasAudioCD(ULONG ulLogicalDrive,
HFILE hfDrive, // in: DASD open
BOOL fMixedModeCD,
PBOOL pfAudio)
{
APIRET arc = NO_ERROR;
ULONG ulAudioTracks = 0,
ulDataTracks = 0;
CHAR cds1[4] = { 'C', 'D', '0', '1' };
CHAR cds2[4];
*pfAudio = FALSE;
// check for proper driver signature
if (!(arc = doshDevIOCtl(hfDrive,
IOCTL_CDROMDISK,
CDROMDISK_GETDRIVER,
&cds1, sizeof(cds1),
&cds2, sizeof(cds2))))
{
if (memcmp(&cds1, &cds2, 4))
// this is not a CD-ROM then:
arc = NO_ERROR;
else
{
struct {
UCHAR ucFirstTrack,
ucLastTrack;
ULONG ulLeadOut;
} cdat;
// get track count
if (!(arc = doshDevIOCtl(hfDrive,
IOCTL_CDROMAUDIO,
CDROMAUDIO_GETAUDIODISK,
&cds1, sizeof(cds1),
&cdat, sizeof(cdat))))
{
// still no error: build the audio TOC
ULONG i;
for (i = cdat.ucFirstTrack;
i <= cdat.ucLastTrack;
i++)
{
BYTE cdtp[5] =
{ 'C', 'D', '0', '1', (UCHAR)i };
struct {
ULONG ulTrackAddress;
BYTE bFlags;
} trackdata;
if (!(arc = doshDevIOCtl(hfDrive,
IOCTL_CDROMAUDIO,
CDROMAUDIO_GETAUDIOTRACK,
&cdtp, sizeof(cdtp),
&trackdata, sizeof(trackdata))))
{
if (trackdata.bFlags & 64)
ulDataTracks++;
else
{
ulAudioTracks++;
if (!fMixedModeCD)
{
// caller doesn't want mixed mode:
// stop here
ulDataTracks = 0;
break;
}
}
}
}
// _Pmpf((" got %d audio, %d data tracks",
// ulAudioTracks, ulDataTracks));
if (!ulDataTracks)
*pfAudio = TRUE;
}
else
{
// not audio disk:
// go on then
// _Pmpf((" CDROMAUDIO_GETAUDIODISK returned %d", arc));
arc = NO_ERROR;
}
}
}
else
{
// not CD-ROM: go on then
// _Pmpf((" CDROMDISK_GETDRIVER returned %d", arc));
arc = NO_ERROR;
}
return arc;
}
/*
*@@ doshEnumDrives:
* this function enumerates all valid drive letters on
* the system by composing a string of drive letters
* in the buffer pointed to by pszBuffer, which should
* be 27 characters in size to hold information for
* all drives. The buffer will be null-terminated.
*
* If (pcszFileSystem != NULL), only drives matching
* the specified file system type (e.g. "HPFS") will
* be enumerated. If (pcszFileSystem == NULL), all
* drives will be enumerated.
*
* If (fSkipRemovables == TRUE), removeable drives will
* be skipped. This applies to floppy, CD-ROM, and
* virtual floppy drives. This will start the search
* at drive letter C: so that drives A: and B: will
* never be checked (to avoid the hardware bumps).
*
* Otherwise, the search starts at drive A:. Still,
* removeable drives will only be added if valid media
* is inserted.
*
*@@changed V0.9.4 (2000-07-03) [umoeller]: this stopped at the first invalid drive letter; fixed
*@@changed V0.9.4 (2000-07-03) [umoeller]: added fSkipRemoveables
*/
VOID doshEnumDrives(PSZ pszBuffer, // out: drive letters
PCSZ pcszFileSystem, // in: FS's to match or NULL
BOOL fSkipRemoveables) // in: if TRUE, only non-removeable disks will be returned
{
CHAR szName[5] = "";
ULONG ulLogicalDrive = 1, // start with drive A:
ulFound = 0; // found drives count
APIRET arc = NO_ERROR; // return code
if (fSkipRemoveables)
// start with drive C:
ulLogicalDrive = 3;
// go thru the drives, start with C: (== 3), stop after Z: (== 26)
while (ulLogicalDrive <= 26)
{
#pragma pack(1)
struct
{
UCHAR dummy,drive;
} parms;
#pragma pack()
// data packet
UCHAR nonRemovable=0;
parms.drive=(UCHAR)(ulLogicalDrive-1);
arc = doshDevIOCtl((HFILE)-1,
IOCTL_DISK,
DSK_BLOCKREMOVABLE,
&parms, sizeof(parms),
&nonRemovable, sizeof(nonRemovable));
if ( // fixed disk and non-removeable
((arc == NO_ERROR) && (nonRemovable))
// or network drive:
|| (arc == ERROR_NOT_SUPPORTED)
)
{
ULONG ulOrdinal = 0; // ordinal of entry in name list
BYTE fsqBuffer[sizeof(FSQBUFFER2) + (3 * CCHMAXPATH)] = {0};
ULONG cbBuffer = sizeof(fsqBuffer); // Buffer length)
PFSQBUFFER2 pfsqBuffer = (PFSQBUFFER2)fsqBuffer;
szName[0] = ulLogicalDrive + 'A' - 1;
szName[1] = ':';
szName[2] = '\0';
arc = DosQueryFSAttach(szName, // logical drive of attached FS
ulOrdinal, // ignored for FSAIL_QUERYNAME
FSAIL_QUERYNAME, // return data for a Drive or Device
pfsqBuffer, // returned data
&cbBuffer); // returned data length
if (arc == NO_ERROR)
{
// The data for the last three fields in the FSQBUFFER2
// structure are stored at the offset of fsqBuffer.szName.
// Each data field following fsqBuffer.szName begins
// immediately after the previous item.
CHAR* pszFSDName = (PSZ)&(pfsqBuffer->szName) + (pfsqBuffer->cbName) + 1;
if (pcszFileSystem == NULL)
{
// enum-all mode: always copy
pszBuffer[ulFound] = szName[0]; // drive letter
ulFound++;
}
else if (strcmp(pszFSDName, pcszFileSystem) == 0)
{
pszBuffer[ulFound] = szName[0]; // drive letter
ulFound++;
}
}
}
ulLogicalDrive++;
} // end while (G_acDriveLetters[ulLogicalDrive] <= 'Z')
pszBuffer[ulFound] = '\0';
}
/*
*@@ doshQueryBootDrive:
* returns the letter of the boot drive as a
* single (capital) character, which is useful for
* constructing file names using sprintf and such.
*
*@@changed V0.9.16 (2002-01-13) [umoeller]: optimized
*/
CHAR doshQueryBootDrive(VOID)
{
// this can never change, so query this only once
// V0.9.16 (2002-01-13) [umoeller]
static CHAR cBootDrive = '\0';
if (!cBootDrive)
{
ULONG ulBootDrive;
DosQuerySysInfo(QSV_BOOT_DRIVE, QSV_BOOT_DRIVE,
&ulBootDrive,
sizeof(ulBootDrive));
cBootDrive = (CHAR)ulBootDrive + 'A' - 1;
}
return (cBootDrive);
}
/*
*@@ doshQueryMedia:
* determines whether the given drive currently
* has media inserted.
*
* Call this only for non-fixed (removable) disks.
* Use doshIsFixedDisk to find out.
*
*@@added V0.9.16 (2002-01-13) [umoeller]
*/
APIRET doshQueryMedia(ULONG ulLogicalDrive,
BOOL fCDROM, // in: is drive CD-ROM?
ULONG fl) // in: DRVFL_* flags
{
APIRET arc;
HFILE hf = NULLHANDLE;
ULONG dummy;
CHAR szDrive[3] = "C:";
szDrive[0] = 'A' + ulLogicalDrive - 1;
arc = DosOpen(szDrive, // "C:", "D:", ...
&hf,
&dummy,
0,
FILE_NORMAL,
OPEN_ACTION_FAIL_IF_NEW
| OPEN_ACTION_OPEN_IF_EXISTS,
OPEN_FLAGS_DASD
| OPEN_FLAGS_FAIL_ON_ERROR
| OPEN_FLAGS_NOINHERIT // V0.9.6 (2000-11-25) [pr]
// | OPEN_ACCESS_READONLY // V0.9.13 (2001-06-14) [umoeller]
| OPEN_SHARE_DENYNONE,
NULL);
// this still returns NO_ERROR for audio CDs in a
// CD-ROM drive...
// however, the WPS then attempts to read in the
// root directory for audio CDs, which produces
// a "sector not found" error box...
if ( (!arc)
&& (hf)
&& (fCDROM)
)
{
BOOL fAudio;
if ( (!(arc = doshHasAudioCD(ulLogicalDrive,
hf,
((fl & DRVFL_MIXEDMODECD) != 0),
&fAudio)))
&& (fAudio)
)
arc = ERROR_AUDIO_CD_ROM; // special private error code (10000)
}
if (hf)
DosClose(hf);
return arc;
}
/*
*@@ doshAssertDrive:
* this checks for whether the given drive
* is currently available without provoking
* those ugly white "Drive not ready" popups.
*
* "fl" can specify additional flags for testing
* and can be any combination of:
*
* -- DRVFL_MIXEDMODECD: whether to allow
* mixed-mode CD-ROMs. See error codes below.
*
* This returns (from my testing):
*
* -- NO_ERROR: drive is available.
*
* -- ERROR_INVALID_DRIVE (15): drive letter does not exist.
*
* -- ERROR_NOT_READY (21): drive exists, but is not ready.
* This is produced by floppies and CD-ROM drives
* which do not have valid media inserted.
*
* -- ERROR_AUDIO_CD_ROM (10000): special error code returned
* only by this function if a CD-ROM drive has audio
* media inserted.
*
* If DRVFL_MIXEDMODECD was specified, ERROR_AUDIO_CD_ROM
* is returned _only_ if _no_ data tracks are
* present on a CD-ROM. Since OS/2 is not very
* good at handling mixed-mode CDs, this might not
* be desireable.
*
* If DRVFL_MIXEDMODECD was not set, ERROR_AUDIO_CD_ROM
* will be returned already if _one_ audio track is present.
*
*@@changed V0.9.1 (99-12-13) [umoeller]: rewritten, prototype changed. Now using DosOpen on the drive instead of DosError.
*@@changed V0.9.1 (2000-01-08) [umoeller]: DosClose was called even if DosOpen failed, which messed up OS/2 error handling.
*@@changed V0.9.1 (2000-02-09) [umoeller]: this didn't work for network drives, including RAMFS; fixed.
*@@changed V0.9.3 (2000-03-28) [umoeller]: added check for network drives, which weren't working
*@@changed V0.9.4 (2000-08-03) [umoeller]: more network fixes
*@@changed V0.9.9 (2001-03-19) [pr]: validate drive number
*@@changed V0.9.11 (2001-04-23) [umoeller]: added an extra check for floppies
*@@changed V0.9.13 (2001-06-14) [umoeller]: added "fl" parameter and lots of CD-ROM checks
*/
APIRET doshAssertDrive(ULONG ulLogicalDrive, // in: 1 for A:, 2 for B:, 3 for C:, ...
ULONG fl) // in: DRVFL_* flags
{
APIRET arc = NO_ERROR;
BOOL fFixed = FALSE,
fCDROM = FALSE;
if ((ulLogicalDrive < 1) || (ulLogicalDrive > 26))
return(ERROR_PATH_NOT_FOUND);
arc = doshIsFixedDisk(ulLogicalDrive,
&fFixed); // V0.9.13 (2001-06-14) [umoeller]
// _Pmpf((__FUNCTION__ ": doshIsFixedDisk returned %d for disk %d", arc, ulLogicalDrive));
// _Pmpf((" fFixed is %d", fFixed));
if (!arc)
if (!fFixed)
{
// removeable disk:
// check if it's a CD-ROM
BIOSPARAMETERBLOCK bpb;
arc = doshQueryDiskParams(ulLogicalDrive,
&bpb);
// _Pmpf((" doshQueryDiskParams returned %d", arc));
if ( (!arc)
&& (DRVTYPE_CDROM == doshQueryDriveType(ulLogicalDrive,
&bpb,
fFixed))
)
{
// _Pmpf((" --> is CD-ROM"));
fCDROM = TRUE;
}
}
if (!arc)
arc = doshQueryMedia(ulLogicalDrive,
fCDROM,
fl);
switch (arc)
{
case ERROR_NETWORK_ACCESS_DENIED: // 65
// added V0.9.3 (2000-03-27) [umoeller];
// according to user reports, this is returned
// by all network drives, which apparently don't
// support DASD DosOpen
case ERROR_ACCESS_DENIED: // 5
// added V0.9.4 (2000-07-10) [umoeller]
// LAN drives still didn't work... apparently
// the above only works for NFS drives
case ERROR_PATH_NOT_FOUND: // 3
// added V0.9.4 (2000-08-03) [umoeller]:
// this is returned by some other network types...
// sigh...
case ERROR_NOT_SUPPORTED: // 50
// this is returned by file systems which don't
// support DASD DosOpen;
// use some other method then, this isn't likely
// to fail -- V0.9.1 (2000-02-09) [umoeller]
// but don't do this for floppies
// V0.9.11 (2001-04-23) [umoeller]
if (ulLogicalDrive > 2)
{
FSALLOCATE fsa;
arc = DosQueryFSInfo(ulLogicalDrive,
FSIL_ALLOC,
&fsa,
sizeof(fsa));
// _Pmpf((" re-checked, DosQueryFSInfo returned %d", arc));
}
break;
}
return arc;
}
/*
*@@ doshGetDriveInfo:
* fills the given XDISKINFO buffer with
* information about the given logical drive.
*
* This function will not provoke "Drive not
* ready" popups, hopefully.
*
* fl can be any combination of the following:
*
* -- DRVFL_MIXEDMODECD: see doshAssertDrive.
*
* -- DRVFL_TOUCHFLOPPIES: drive A: and B: should
* be touched for media checks (click, click);
* otherwise they will be left alone and
* default values will be returned.
*
* -- DRVFL_CHECKEAS: drive should always be
* checked for EA support. If this is set,
* we will call DosFSCtl for the non-well-known
* file systems so we will always have a
* value for the DFL_SUPPORTS_EAS flags.
* Otherwise that flag might or might not
* be set correctly.
*
* The EA support returned by DosFSCtl
* might not be correct for remote file
* systems since not all of them support
* that query.
*
* -- DRVFL_CHECKLONGNAMES: drive should always be
* checked for longname support. If this is
* set, we will try a DosOpen("\\long.name.file")
* on the drive to see if it supports long
* filenames (unless it's a "well-known"
* file-system and we know it does). If enabled,
* the DFL_SUPPORTS_LONGNAMES flag is reliable.
* Note that this does not check for what special
* characters are supported in file names.
*
* This should return only one of the following:
*
* -- NO_ERROR: disk info was filled, but not
* necessarily all info was available (e.g.
* if no media was present in CD-ROM drive).
* See remarks below.
*
* -- ERROR_INVALID_DRIVE 15): ulLogicalDrive
* is not used at all (invalid drive letter)
*
* -- ERROR_BAD_UNIT (20): if drive was renamed for
* some reason (according to user reports
*
* -- ERROR_NOT_READY (21): for ZIP disks where
* no media is inserted, depending on the
* driver apparently... normally ZIP drive
* letters should disappear when no media
* is present
*
* -- ERROR_DRIVE_LOCKED (108)
*
* So in order to check whether a drive is present
* and available, use this function as follows:
*
* 1) Call this function and check whether it
* returns NO_ERROR for the given drive.
* This will rule out invalid drive letters
* and drives that are presently locked by
* CHKDSK or something.
*
* 2) If so, check whether XDISKINFO.flDevice
* has the DFL_MEDIA_PRESENT flag set.
* This will rule out removeable drives without
* media and unformatted hard disks.
*
* 3) If so, you can test the other fields if
* you need more information. For example,
* it would not be a good idea to create
* a new file if the bType field is
* DRVTYPE_CDROM.
*
* If you want to exclude removeable disks,
* instead of checking bType, you should
* rather check flDevice for the DFL_FIXED
* flag, which will be set for ZIP drives also.
*
* Remarks for special drive types:
*
* -- Hard disks always have bType == DRVTYPE_HARDDISK.
* For them, we always check the file system.
* If this is reported as "UNKNOWN", this means
* that the drive is unformatted or formatted
* with a file system that OS/2 does not understand
* (e.g. NTFS). Only in that case, flDevice
* has the DFL_MEDIA_PRESENT bit clear.
*
* DFL_FIXED is always set.
*
* -- Remote (LAN) drives always have bType == DRVTYPE_LAN.
* flDevice will always have the DFL_REMOTE and
* DFL_MEDIA_PRESENT bits set.
*
* -- ZIP disks will have bType == DRVTYPE_PARTITIONABLEREMOVEABLE.
* For them, flDevice will have both the
* and DFL_PARTITIONABLEREMOVEABLE and DFL_FIXED
* bits set.
*
* ZIP disks are a bit special because they are
* dynamically mounted and unmounted when media
* is inserted and removed. In other words, if
* no media is present, the drive letter becomes
* invalid.
*
* -- CD-ROM and DVD drives and CD writers will always
* be reported as DRVTYPE_CDROM. The DFL_FIXED bit
* will be clear always. For them, always check the
* DFL_MEDIA_PRESENT present bit to avoid "Drive not
* ready" popups.
*
* As a special goody, we can also determine if the
* drive currently has audio media inserted (which
* would provoke errors also), by setting the
* DFL_AUDIO_CD bit.
*
*@@added V0.9.16 (2002-01-13) [umoeller]
*@@changed V0.9.19 (2002-04-25) [umoeller]: added CDWFS (RSJ CD-Writer)
*/
APIRET doshGetDriveInfo(ULONG ulLogicalDrive,
ULONG fl, // in: DRVFL_* flags
PXDISKINFO pdi)
{
APIRET arc = NO_ERROR;
HFILE hf;
ULONG dummy;
BOOL fCheck = TRUE,
fCheckFS = FALSE,
fCheckLongnames = FALSE,
fCheckEAs = FALSE;
memset(pdi, 0, sizeof(XDISKINFO));
pdi->cDriveLetter = 'A' + ulLogicalDrive - 1;
pdi->cLogicalDrive = ulLogicalDrive;
pdi->bType = DRVTYPE_UNKNOWN;
pdi->fPresent = TRUE; // for now
if ( (ulLogicalDrive == 1)
|| (ulLogicalDrive == 2)
)
{
// drive A: and B: are special cases,
// we don't even want to touch them (click, click)
pdi->bType = DRVTYPE_FLOPPY;
if (0 == (fl & DRVFL_TOUCHFLOPPIES))
{
fCheck = FALSE;
// these support EAs too
pdi->flDevice = DFL_MEDIA_PRESENT | DFL_SUPPORTS_EAS;
strcpy(pdi->szFileSystem, "FAT");
pdi->lFileSystem = FSYS_FAT;
}
}
if (fCheck)
{
// any other drive:
// check if it's removeable first
BOOL fFixed = FALSE;
arc = doshIsFixedDisk(ulLogicalDrive,
&fFixed);
switch (arc)
{
case ERROR_INVALID_DRIVE:
// drive letter doesn't exist at all:
pdi->fPresent = FALSE;
// return this APIRET
break;
case ERROR_NOT_SUPPORTED: // 50 for network drives
// we get this for remote drives added
// via "net use", so set these flags
pdi->bType = DRVTYPE_LAN;
pdi->lFileSystem = FSYS_REMOTE;
pdi->flDevice |= DFL_REMOTE | DFL_MEDIA_PRESENT;
// but still check what file-system we
// have and whether longnames are supported
fCheckFS = TRUE;
fCheckLongnames = TRUE;
fCheckEAs = TRUE;
break;
case NO_ERROR:
{
if (fFixed)
{
// fixed drive:
pdi->flDevice |= DFL_FIXED | DFL_MEDIA_PRESENT;
fCheckFS = TRUE;
fCheckLongnames = TRUE;
fCheckEAs = TRUE;
}
if (!(arc = doshQueryDiskParams(ulLogicalDrive,
&pdi->bpb)))
{
BYTE bTemp = doshQueryDriveType(ulLogicalDrive,
&pdi->bpb,
fFixed);
if (bTemp != DRVTYPE_UNKNOWN)
{
// recognized: store it then
pdi->bType = bTemp;
if (bTemp == DRVTYPE_PARTITIONABLEREMOVEABLE)
pdi->flDevice |= DFL_FIXED
| DFL_PARTITIONABLEREMOVEABLE;
}
if (!fFixed)
{
// removeable:
// before checking the drive, try if we have media
if (!(arc = doshQueryMedia(ulLogicalDrive,
(pdi->bType == DRVTYPE_CDROM),
fl)))
{
pdi->flDevice |= DFL_MEDIA_PRESENT;
fCheckFS = TRUE;
fCheckLongnames = TRUE;
// but never EAs
}
else if (arc == ERROR_AUDIO_CD_ROM)
{
pdi->flDevice |= DFL_AUDIO_CD;
// do not check longnames and file-system
}
else
pdi->arcQueryMedia = arc;
arc = NO_ERROR;
}
}
else
pdi->arcQueryDiskParams = arc;
}
break;
default:
pdi->arcIsFixedDisk = arc;
// and return this
break;
} // end swich arc = doshIsFixedDisk(ulLogicalDrive, &fFixed);
}
if (fCheckFS)
{
// TRUE only for local fixed disks or
// remote drives or if media was present above
if (!(arc = doshQueryDiskFSType(ulLogicalDrive,
pdi->szFileSystem,
sizeof(pdi->szFileSystem))))
{
if (!stricmp(pdi->szFileSystem, "UNKNOWN"))
{
// this is returned by the stupid DosQueryFSAttach
// if the file system is not recognized by OS/2,
// or if the drive is unformatted
pdi->lFileSystem = FSYS_UNKNOWN;
pdi->flDevice &= ~DFL_MEDIA_PRESENT;
fCheckLongnames = FALSE;
fCheckEAs = FALSE;
// should we return ERROR_NOT_DOS_DISK (26)
// in this case?
}
else if (!stricmp(pdi->szFileSystem, "FAT"))
{
pdi->lFileSystem = FSYS_FAT;
pdi->flDevice |= DFL_SUPPORTS_EAS;
fCheckLongnames = FALSE;
fCheckEAs = FALSE;
}
else if ( (!stricmp(pdi->szFileSystem, "HPFS"))
|| (!stricmp(pdi->szFileSystem, "JFS"))
)
{
pdi->lFileSystem = FSYS_HPFS_JFS;
pdi->flDevice |= DFL_SUPPORTS_EAS | DFL_SUPPORTS_LONGNAMES;
fCheckLongnames = FALSE;
fCheckEAs = FALSE;
}
else if (!stricmp(pdi->szFileSystem, "CDFS"))
pdi->lFileSystem = FSYS_CDFS;
else if ( (!stricmp(pdi->szFileSystem, "FAT32"))
|| (!stricmp(pdi->szFileSystem, "ext2"))
)
{
pdi->lFileSystem = FSYS_FAT32_EXT2;
fCheckLongnames = TRUE;
fCheckEAs = TRUE;
}
else if (!stricmp(pdi->szFileSystem, "RAMFS"))
{
pdi->lFileSystem = FSYS_RAMFS;
pdi->flDevice |= DFL_SUPPORTS_EAS | DFL_SUPPORTS_LONGNAMES;
fCheckLongnames = FALSE;
fCheckEAs = FALSE;
}
else if (!stricmp(pdi->szFileSystem, "TVFS"))
{
pdi->lFileSystem = FSYS_TVFS;
fCheckLongnames = TRUE;
fCheckEAs = TRUE;
}
else if (!stricmp(pdi->szFileSystem, "CDWFS"))
// V0.9.19 (2002-04-25) [umoeller]
{
pdi->lFileSystem = FSYS_CDWFS;
pdi->flDevice |= DFL_SUPPORTS_LONGNAMES;
fCheckLongnames = FALSE;
fCheckEAs = FALSE;
}
}
else
// store negative error code
pdi->lFileSystem = -(LONG)arc;
}
if ( (!arc)
&& (fCheckLongnames)
&& (fl & DRVFL_CHECKLONGNAMES)
)
{
CHAR szTemp[30] = "?:\\long.name.file";
szTemp[0] = ulLogicalDrive + 'A' - 1;
if (!(arc = DosOpen(szTemp,
&hf,
&dummy,
0,
0,
FILE_READONLY,
OPEN_SHARE_DENYNONE | OPEN_FLAGS_NOINHERIT,
0)))
{
DosClose(hf);
}
switch (arc)
{
case NO_ERROR:
case ERROR_OPEN_FAILED:
case ERROR_FILE_NOT_FOUND: // returned by TVFS
pdi->flDevice |= DFL_SUPPORTS_LONGNAMES;
break;
// if longnames are not supported,
// we get ERROR_INVALID_NAME
default:
pdi->arcOpenLongnames = arc;
break;
// default:
// printf(" drive %d returned %d\n", ulLogicalDrive, arc);
}
arc = NO_ERROR;
}
if ( (!arc)
&& (fCheckEAs)
&& (fl & DRVFL_CHECKEAS)
)
{
EASIZEBUF easb = {0};
ULONG cbData = sizeof(easb),
cbParams = 0;
CHAR szDrive[] = "?:\\";
szDrive[0] = pdi->cDriveLetter;
if (!(arc = DosFSCtl(&easb,
cbData,
&cbData,
NULL, // params,
cbParams,
&cbParams,
FSCTL_MAX_EASIZE,
szDrive,
-1, // HFILE
FSCTL_PATHNAME)))
if (easb.cbMaxEASize != 0)
// the other field (cbMaxEAListSize) is 0 always, I think
pdi->flDevice |= DFL_SUPPORTS_EAS;
}
if (doshQueryBootDrive() == pdi->cDriveLetter)
pdi->flDevice |= DFL_BOOTDRIVE;
return arc;
}
/*
*@@ doshSetLogicalMap:
* sets the mapping of logical floppy drives onto a single
* physical floppy drive.
* This means selecting either drive A: or drive B: to refer
* to the physical drive.
*
* Paul explained this to me as follows:
*
* "It is really very simple - in a single physical floppy
* drive system, you still have 2 logical floppy drives
* A: and B:. This was primarily to support disk copying
* e.g. diskcopy a: b: on a single floppy system without
* having to rewrite applications. Whenever the application
* accessed the other logical drive, the user would get a
* prompt from the OS to swap disks.
*
* "These calls allow applications to bypass the prompt by
* doing the mapping themselves. They just get/set which
* logical drive is currently mapped to the physical drive.
* This concept existed in DOS as well although the specifics
* of how it was done escape me now.... actually I just
* looked it up - the byte at 0:504h in low memory on
* DOS controlled this (it doesn't work in VDMs)."
*
*@@added V0.9.6 (2000-11-24) [pr]
*/
APIRET doshSetLogicalMap(ULONG ulLogicalDrive)
{
CHAR name[3] = "?:";
ULONG fd = 0,
action = 0;
// paramsize = 0;
// datasize = 0;
APIRET rc = NO_ERROR;
USHORT data,
param;
name[0] = doshQueryBootDrive();
rc = DosOpen(name,
&fd,
&action,
0,
0,
OPEN_ACTION_FAIL_IF_NEW
| OPEN_ACTION_OPEN_IF_EXISTS,
OPEN_FLAGS_DASD
| OPEN_FLAGS_FAIL_ON_ERROR
| OPEN_FLAGS_NOINHERIT
| OPEN_ACCESS_READONLY
| OPEN_SHARE_DENYNONE,
0);
if (rc == NO_ERROR)
{
param = 0;
data = (USHORT)ulLogicalDrive;
// paramsize = sizeof(param);
// datasize = sizeof(data);
rc = doshDevIOCtl(fd,
IOCTL_DISK, DSK_SETLOGICALMAP,
¶m, sizeof(param),
&data, sizeof(data));
DosClose(fd);
}
return(rc);
}
/*
*@@ doshQueryDiskSize:
* returns the size of the specified disk in bytes.
*
* Note: This returns a "double" value, because a ULONG
* can only hold values of some 4 billion, which would
* lead to funny results for drives > 4 GB.
*
*@@added V0.9.11 (2001-04-18) [umoeller]
*/
APIRET doshQueryDiskSize(ULONG ulLogicalDrive, // in: 1 for A:, 2 for B:, 3 for C:, ...
double *pdSize)
{
APIRET arc = NO_ERROR;
FSALLOCATE fsa;
// double dbl = -1;
if (!(arc = DosQueryFSInfo(ulLogicalDrive, FSIL_ALLOC, &fsa, sizeof(fsa))))
*pdSize = ((double)fsa.cSectorUnit * fsa.cbSector * fsa.cUnit);
return arc;
}
/*
*@@ doshQueryDiskFree:
* returns the number of bytes remaining on the disk
* specified by the given logical drive.
*
* Note: This returns a "double" value, because a ULONG
* can only hold values of some 4 billion, which would
* lead to funny results for drives > 4 GB.
*
*@@changed V0.9.0 [umoeller]: fixed another > 4 GB bug (thanks to Rüdiger Ihle)
*@@changed V0.9.7 (2000-12-01) [umoeller]: changed prototype
*/
APIRET doshQueryDiskFree(ULONG ulLogicalDrive, // in: 1 for A:, 2 for B:, 3 for C:, ...
double *pdFree)
{
APIRET arc = NO_ERROR;
FSALLOCATE fsa;
// double dbl = -1;
if (!(arc = DosQueryFSInfo(ulLogicalDrive, FSIL_ALLOC, &fsa, sizeof(fsa))))
*pdFree = ((double)fsa.cSectorUnit * fsa.cbSector * fsa.cUnitAvail);
// ^ fixed V0.9.0
return arc;
}
/*
*@@ doshQueryDiskFSType:
* copies the file-system type of the given disk object
* (HPFS, FAT, CDFS etc.) to pszBuf.
* Returns the DOS error code.
*
*@@changed V0.9.1 (99-12-12) [umoeller]: added cbBuf to prototype
*@@changed V0.9.14 (2001-08-01) [umoeller]: fixed, this never respected cbBuf
*@@changed V0.9.16 (2001-10-02) [umoeller]: added check for valid logical disk no
*/
APIRET doshQueryDiskFSType(ULONG ulLogicalDrive, // in: 1 for A:, 2 for B:, 3 for C:, ...
PSZ pszBuf, // out: buffer for FS type
ULONG cbBuf) // in: size of that buffer
{
APIRET arc = NO_ERROR;
CHAR szName[5];
BYTE fsqBuffer[sizeof(FSQBUFFER2) + (3 * CCHMAXPATH)] = {0};
ULONG cbBuffer = sizeof(fsqBuffer); // Buffer length)
PFSQBUFFER2 pfsqBuffer = (PFSQBUFFER2)fsqBuffer;
// compose "D:"-type string from logical drive letter
if (ulLogicalDrive > 0 && ulLogicalDrive < 27)
{
szName[0] = ulLogicalDrive + 'A' - 1;
szName[1] = ':';
szName[2] = '\0';
arc = DosQueryFSAttach(szName, // logical drive of attached FS ("D:"-style)
0, // ulOrdinal, ignored for FSAIL_QUERYNAME
FSAIL_QUERYNAME, // return name for a drive or device
pfsqBuffer, // buffer for returned data
&cbBuffer); // sizeof(*pfsqBuffer)
if (arc == NO_ERROR)
{
if (pszBuf)
{
// The data for the last three fields in the FSQBUFFER2
// structure are stored at the offset of fsqBuffer.szName.
// Each data field following fsqBuffer.szName begins
// immediately after the previous item.
strncpy(pszBuf,
(CHAR*)(&pfsqBuffer->szName) + pfsqBuffer->cbName + 1,
cbBuf); // V0.9.14 (2001-08-01) [umoeller]
*(pszBuf + cbBuf) = '\0';
}
}
}
else
arc = ERROR_INVALID_PARAMETER; // V0.9.16 (2001-10-02) [umoeller]
return arc;
}
/*
*@@ doshQueryDiskLabel:
* this returns the label of a disk into
* *pszVolumeLabel, which must be 12 bytes
* in size.
*
* This function was added because the Toolkit
* information for DosQueryFSInfo is only partly
* correct. On OS/2 2.x, that function does not
* take an FSINFO structure as input, but a VOLUMELABEL.
* On Warp, this does take an FSINFO.
*
* DosSetFSInfo is even worse. See doshSetDiskLabel.
*
* See http://zebra.asta.fh-weingarten.de/os2/Snippets/Bugi6787.HTML
* for details.
*
*@@added V0.9.0 [umoeller]
*@@changed V0.9.11 (2001-04-22) [umoeller]: this copied even with errors, fixed
*/
APIRET doshQueryDiskLabel(ULONG ulLogicalDrive, // in: 1 for A:, 2 for B:, 3 for C:, ...
PSZ pszVolumeLabel) // out: volume label (must be 12 chars in size)
{
APIRET arc;
#ifdef __OS2V2X__
VOLUMELABEL FSInfoBuf;
#else
FSINFO FSInfoBuf;
#endif
arc = DosQueryFSInfo(ulLogicalDrive,
FSIL_VOLSER,
&FSInfoBuf,
sizeof(FSInfoBuf)); // depends
if (!arc) // V0.9.11 (2001-04-22) [umoeller]
{
#ifdef __OS2V2X__
strcpy(pszVolumeLabel, FSInfoBuf.szVolLabel);
#else
strcpy(pszVolumeLabel, FSInfoBuf.vol.szVolLabel);
#endif
}
return arc;
}
/*
*@@ doshSetDiskLabel:
* this sets the label of a disk.
*
* This function was added because the Toolkit
* information for DosSetFSInfo is flat out wrong.
* That function does not take an FSINFO structure
* as input, but a VOLUMELABEL. As a result, using
* that function with the Toolkit's calling specs
* results in ERROR_LABEL_TOO_LONG always.
*
* See http://zebra.asta.fh-weingarten.de/os2/Snippets/Bugi6787.HTML
* for details.
*
*@@added V0.9.0 [umoeller]
*/
APIRET doshSetDiskLabel(ULONG ulLogicalDrive, // in: 1 for A:, 2 for B:, 3 for C:, ...
PSZ pszNewLabel)
{
VOLUMELABEL FSInfoBuf;
// check length; 11 chars plus null byte allowed
FSInfoBuf.cch = (BYTE)strlen(pszNewLabel);
if (FSInfoBuf.cch < sizeof(FSInfoBuf.szVolLabel))
{
strcpy(FSInfoBuf.szVolLabel, pszNewLabel);
return (DosSetFSInfo(ulLogicalDrive,
FSIL_VOLSER,
&FSInfoBuf,
sizeof(FSInfoBuf)));
}
else
return (ERROR_LABEL_TOO_LONG);
}
/*
*@@category: Helpers\Control program helpers\File name parsing
*/
/* ******************************************************************
*
* File name parsing
*
********************************************************************/
/*
*@@ doshGetDriveSpec:
* returns the drive specification in pcszFullFile,
* if any is present. This is useful for UNC support.
*
* This returns:
*
* -- NO_ERROR: drive spec was given, and the output
* fields have been set.
*
* -- ERROR_INVALID_NAME: incorrect UNC syntax.
*
* -- ERROR_INVALID_DRIVE: second char is ':', but
* drive letter is not in the range [A-Z].
*
* -- ERROR_INVALID_PARAMETER: no drive spec given
* at all; apparently pcszFullFile is not fully
* qualified in the first place, or it is NULL,
* or its length is <= 2.
*
*@@added V0.9.16 (2001-10-25) [umoeller]
*/
APIRET doshGetDriveSpec(PCSZ pcszFullFile, // in: fully q'fied file spec
PSZ pszDrive, // out: drive spec ("C:" or "\\SERVER\RESOURCE"; ptr can be NULL)
PULONG pulDriveLen, // out: length of drive spec (2 if local drive; ptr can be NULL)
PBOOL pfIsUNC) // out: set to TRUE if UNC name, FALSE otherwise (ptr can be NULL)
{
APIRET arc = NO_ERROR;
ULONG ulFileSpecLength;
if ( (pcszFullFile)
&& (ulFileSpecLength = strlen(pcszFullFile))
&& (ulFileSpecLength >= 2)
)
{
// upper-case the drive letter
if (pcszFullFile[1] == ':')
{
CHAR cDrive = toupper(*pcszFullFile);
// local drive specified:
if ( (cDrive >= 'A')
&& (cDrive <= 'Z')
)
{
if (pszDrive)
{
pszDrive[0] = cDrive;
pszDrive[1] = ':';
pszDrive[2] = '\0';
}
if (pulDriveLen)
*pulDriveLen = 2;
if (pfIsUNC)
*pfIsUNC = FALSE;
}
else
// this is not a valid drive:
arc = ERROR_INVALID_DRIVE;
}
else if ( (pcszFullFile[0] == '\\')
&& (pcszFullFile[1] == '\\')
)
{
// UNC drive specified:
// this better be a full \\SERVER\RESOURCE string
PCSZ pResource;
if (pResource = strchr(pcszFullFile + 3, '\\'))
{
// we got at least \\SERVER\:
ULONG ulLength;
PCSZ p;
// check if more stuff is coming
if (p = strchr(pResource + 1, '\\'))
{
// yes: copy server and resource excluding that backslash
if (p == pResource + 1)
// "\\SERVER\\" is invalid
arc = ERROR_INVALID_NAME;
else
// we got "\\SERVER\something\":
// drop the last backslash
ulLength = p - pcszFullFile;
}
else
// "\\SERVER\something" only:
ulLength = ulFileSpecLength;
if (!arc)
{
if (pszDrive)
{
memcpy(pszDrive,
pcszFullFile,
ulLength);
pszDrive[ulLength] = '\0';
}
if (pulDriveLen)
*pulDriveLen = ulLength;
if (pfIsUNC)
*pfIsUNC = TRUE;
}
}
else
// invalid UNC name:
arc = ERROR_INVALID_NAME;
}
else
// neither local, nor UNC:
arc = ERROR_INVALID_PARAMETER;
}
else
arc = ERROR_INVALID_PARAMETER;
return arc;
}
/*
*@@ doshGetExtension:
* finds the file name extension of pszFilename,
* which can be a file name only or a fully
* qualified filename.
*
* This returns a pointer into pszFilename to
* the character after the last dot.
*
* Returns NULL if not found (e.g. if the filename
* has no dot in it).
*
* In the pathological case of a dot in the path
* but not in the filename itself (e.g.
* "C:\files.new\readme"), this correctly returns
* NULL.
*
*@@added V0.9.6 (2000-10-16) [umoeller]
*@@changed V0.9.7 (2000-12-10) [umoeller]: fixed "F:filename.ext" case
*/
PSZ doshGetExtension(PCSZ pcszFilename)
{
PSZ pReturn = NULL;
if (pcszFilename)
{
// find filename
PCSZ p2,
pStartOfName = NULL,
pExtension = NULL;
if (p2 = strrchr(pcszFilename + 2, '\\'))
// works on "C:\blah" or "\\unc\blah"
pStartOfName = p2 + 1;
else
{
// no backslash found:
// maybe only a drive letter was specified:
if (pcszFilename[1] == ':')
// yes:
pStartOfName = pcszFilename + 2;
else
// then this is not qualified at all...
// use start of filename
pStartOfName = (PSZ)pcszFilename;
}
// find last dot in filename
if (pExtension = strrchr(pStartOfName, '.'))
pReturn = (PSZ)pExtension + 1;
}
return (pReturn);
}
/*
*@@category: Helpers\Control program helpers\File management
*/
/* ******************************************************************
*
* File helpers
*
********************************************************************/
/*
*@@ doshIsFileOnFAT:
* returns TRUE if pszFileName resides on
* a FAT drive. Note that pszFileName must
* be fully qualified (i.e. the drive letter
* must be the first character), or this will
* return garbage.
*/
BOOL doshIsFileOnFAT(const char* pcszFileName)
{
BOOL brc = FALSE;
CHAR szName[5];
APIRET arc;
BYTE fsqBuffer[sizeof(FSQBUFFER2) + (3 * CCHMAXPATH)] = {0};
ULONG cbBuffer = sizeof(fsqBuffer); // Buffer length)
PFSQBUFFER2 pfsqBuffer = (PFSQBUFFER2)fsqBuffer;
szName[0] = pcszFileName[0]; // copy drive letter
szName[1] = ':';
szName[2] = '\0';
if (!(arc = DosQueryFSAttach(szName, // logical drive of attached FS
0, // ulOrdinal, ignored for FSAIL_QUERYNAME
FSAIL_QUERYNAME, // return data for a Drive or Device
pfsqBuffer, // returned data
&cbBuffer))) // returned data length
{
// The data for the last three fields in the FSQBUFFER2
// structure are stored at the offset of fsqBuffer.szName.
// Each data field following fsqBuffer.szName begins
// immediately after the previous item.
if (!strncmp((PSZ)&(pfsqBuffer->szName) + pfsqBuffer->cbName + 1,
"FAT",
3))
brc = TRUE;
}
return brc;
}
/*
*@@ doshQueryFileSize:
* returns the size of an already opened file
* or 0 upon errors.
* Use doshQueryPathSize to query the size of
* any file.
*
*@@changed V0.9.16 (2001-10-19) [umoeller]: now returning APIRET
*/
APIRET doshQueryFileSize(HFILE hFile, // in: file handle
PULONG pulSize) // out: file size (ptr can be NULL)
{
APIRET arc;
FILESTATUS3 fs3;
if (!(arc = DosQueryFileInfo(hFile, FIL_STANDARD, &fs3, sizeof(fs3))))
if (pulSize)
*pulSize = fs3.cbFile;
return arc;
}
/*
*@@ doshQueryPathSize:
* returns the size of any file,
* or 0 if the file could not be
* found.
*
* Use doshQueryFileSize instead to query the
* size if you have a HFILE.
*
* Otherwise this returns:
*
* -- ERROR_FILE_NOT_FOUND
* -- ERROR_PATH_NOT_FOUND
* -- ERROR_SHARING_VIOLATION
* -- ERROR_FILENAME_EXCED_RANGE
* -- ERROR_INVALID_PARAMETER
*
*@@changed V0.9.16 (2001-10-19) [umoeller]: now returning APIRET
*/
APIRET doshQueryPathSize(PCSZ pcszFile, // in: filename
PULONG pulSize) // out: file size (ptr can be NULL)
{
APIRET arc;
if (pcszFile) // V0.9.16 (2001-12-08) [umoeller]
{
FILESTATUS3 fs3;
if (!(arc = DosQueryPathInfo((PSZ)pcszFile, FIL_STANDARD, &fs3, sizeof(fs3))))
if (pulSize)
*pulSize = fs3.cbFile;
}
else
arc = ERROR_INVALID_PARAMETER;
return arc;
}
/*
*@@ doshQueryPathAttr:
* returns the file attributes of pszFile,
* which can be fully qualified. The
* attributes will be stored in *pulAttr.
* pszFile can also specify a directory,
* although not all attributes make sense
* for directories.
*
* fAttr can be:
* -- FILE_ARCHIVED
* -- FILE_READONLY
* -- FILE_SYSTEM
* -- FILE_HIDDEN
*
* This returns the APIRET of DosQueryPathInfo.
* *pulAttr is only valid if NO_ERROR is
* returned.
*
* Otherwise this returns:
*
* -- ERROR_FILE_NOT_FOUND
* -- ERROR_PATH_NOT_FOUND
* -- ERROR_SHARING_VIOLATION
* -- ERROR_FILENAME_EXCED_RANGE
*
*@@added V0.9.0 [umoeller]
*/
APIRET doshQueryPathAttr(const char* pcszFile, // in: file or directory name
PULONG pulAttr) // out: attributes (ptr can be NULL)
{
FILESTATUS3 fs3;
APIRET arc;
if (!(arc = DosQueryPathInfo((PSZ)pcszFile,
FIL_STANDARD,
&fs3,
sizeof(fs3))))
{
if (pulAttr)
*pulAttr = fs3.attrFile;
}
return arc;
}
/*
*@@ doshSetPathAttr:
* sets the file attributes of pszFile,
* which can be fully qualified.
* pszFile can also specify a directory,
* although not all attributes make sense
* for directories.
*
* fAttr can be:
* -- FILE_ARCHIVED
* -- FILE_READONLY
* -- FILE_SYSTEM
* -- FILE_HIDDEN
*
* Note that this simply sets all the given
* attributes; the existing attributes
* are lost.
*
* This returns the APIRET of DosQueryPathInfo.
*/
APIRET doshSetPathAttr(const char* pcszFile, // in: file or directory name
ULONG ulAttr) // in: new attributes
{
APIRET arc;
if (pcszFile)
{
FILESTATUS3 fs3;
if (!(arc = DosQueryPathInfo((PSZ)pcszFile,
FIL_STANDARD,
&fs3,
sizeof(fs3))))
{
fs3.attrFile = ulAttr;
arc = DosSetPathInfo((PSZ)pcszFile,
FIL_STANDARD,
&fs3,
sizeof(fs3),
DSPI_WRTTHRU);
}
}
else
arc = ERROR_INVALID_PARAMETER;
return arc;
}
/*
*@@category: Helpers\Control program helpers\File management\XFILEs
*/
/* ******************************************************************
*
* XFILEs
*
********************************************************************/
/*
* doshOpen:
* wrapper around DosOpen for simpler opening
* of files.
*
* ulOpenMode determines the mode to open the
* file in (fptr specifies the position after
* the open):
*
+ +-------------------------+------+------------+-----------+
+ | ulOpenMode | mode | if exists | if new |
+ +-------------------------+------+------------+-----------+
+ | XOPEN_READ_EXISTING | read | opens | fails |
+ | | | fptr = 0 | |
+ +-------------------------+------+------------+-----------+
+ | XOPEN_READWRITE_EXISTING r/w | opens | fails |
+ | | | fptr = 0 | |
+ +-------------------------+------+------------+-----------+
+ | XOPEN_READWRITE_APPEND | r/w | opens, | creates |
+ | | | appends | |
+ | | | fptr = end | fptr = 0 |
+ +-------------------------+------+------------+-----------+
+ | XOPEN_READWRITE_NEW | r/w | replaces | creates |
+ | | | fptr = 0 | fptr = 0 |
+ +-------------------------+------+------------+-----------+
*
* In addition, you can OR one of the above values with
* the XOPEN_BINARY flag:
*
* -- If XOPEN_BINARY is set, no conversion is performed
* on read and write.
*
* -- If XOPEN_BINARY is _not_ set, all \n chars are
* converted to \r\n on write.
*
* *ppFile receives a new XFILE structure describing
* the open file, if NO_ERROR is returned.
*
* The file pointer is then set to the beginning of the
* file _unless_ XOPEN_READWRITE_APPEND was specified;
* in that case only, the file pointer is set to the
* end of the file so data can be appended (see above).
*
* Otherwise this returns:
*
* -- ERROR_FILE_NOT_FOUND
* -- ERROR_PATH_NOT_FOUND
* -- ERROR_SHARING_VIOLATION
* -- ERROR_FILENAME_EXCED_RANGE
*
* -- ERROR_NOT_ENOUGH_MEMORY
* -- ERROR_INVALID_PARAMETER
*
*@@added V0.9.16 (2001-10-19) [umoeller]
*@@changed V0.9.16 (2001-12-18) [umoeller]: fixed error codes
*/
APIRET doshOpen(PCSZ pcszFilename, // in: filename to open
ULONG flOpenMode, // in: XOPEN_* mode
PULONG pcbFile, // in: new file size (if new file is created)
// out: file size
PXFILE *ppFile)
{
APIRET arc = NO_ERROR;
ULONG fsOpenFlags = 0,
fsOpenMode = OPEN_FLAGS_FAIL_ON_ERROR
| OPEN_FLAGS_NO_LOCALITY
| OPEN_FLAGS_NOINHERIT;
switch (flOpenMode & XOPEN_ACCESS_MASK)
{
case XOPEN_READ_EXISTING:
fsOpenFlags = OPEN_ACTION_FAIL_IF_NEW
| OPEN_ACTION_OPEN_IF_EXISTS;
fsOpenMode |= OPEN_SHARE_DENYWRITE
| OPEN_ACCESS_READONLY;
// run this first, because if the file doesn't
// exists, DosOpen only returns ERROR_OPEN_FAILED,
// which isn't that meaningful
// V0.9.16 (2001-12-08) [umoeller]
arc = doshQueryPathSize(pcszFilename,
pcbFile);
break;
case XOPEN_READWRITE_EXISTING:
fsOpenFlags = OPEN_ACTION_FAIL_IF_NEW
| OPEN_ACTION_OPEN_IF_EXISTS;
fsOpenMode |= OPEN_SHARE_DENYWRITE
| OPEN_ACCESS_READWRITE;
arc = doshQueryPathSize(pcszFilename,
pcbFile);
break;
case XOPEN_READWRITE_APPEND:
fsOpenFlags = OPEN_ACTION_CREATE_IF_NEW
| OPEN_ACTION_OPEN_IF_EXISTS;
fsOpenMode |= OPEN_SHARE_DENYREADWRITE
| OPEN_ACCESS_READWRITE;
// _Pmpf((__FUNCTION__ ": opening XOPEN_READWRITE_APPEND"));
break;
case XOPEN_READWRITE_NEW:
fsOpenFlags = OPEN_ACTION_CREATE_IF_NEW
| OPEN_ACTION_REPLACE_IF_EXISTS;
fsOpenMode |= OPEN_SHARE_DENYREADWRITE
| OPEN_ACCESS_READWRITE;
// _Pmpf((__FUNCTION__ ": opening XOPEN_READWRITE_NEW"));
break;
}
if ((!arc) && fsOpenFlags && pcbFile && ppFile)
{
PXFILE pFile;
if (pFile = NEW(XFILE))
{
ULONG ulAction;
ZERO(pFile);
// copy open flags
pFile->flOpenMode = flOpenMode;
if (!(arc = DosOpen((PSZ)pcszFilename,
&pFile->hf,
&ulAction,
*pcbFile,
FILE_ARCHIVED,
fsOpenFlags,
fsOpenMode,
NULL))) // EAs
{
// alright, got the file:
if ( (ulAction == FILE_EXISTED)
&& ((flOpenMode & XOPEN_ACCESS_MASK) == XOPEN_READWRITE_APPEND)
)
// get its size and set ptr to end for append
arc = DosSetFilePtr(pFile->hf,
0,
FILE_END,
pcbFile);
else
arc = doshQueryFileSize(pFile->hf,
pcbFile);
// file ptr is at beginning
#ifdef DEBUG_DOSOPEN
if (arc)
_Pmpf((__FUNCTION__ ": DosSetFilePtr/queryfilesize returned %d for %s",
arc, pcszFilename));
#endif
// store file size
pFile->cbInitial
= pFile->cbCurrent
= *pcbFile;
pFile->pszFilename = strdup(pcszFilename);
}
#ifdef DEBUG_DOSOPEN
else
_Pmpf((__FUNCTION__ ": DosOpen returned %d for %s",
arc, pcszFilename));
#endif
if (arc)
doshClose(&pFile);
else
*ppFile = pFile;
}
else
arc = ERROR_NOT_ENOUGH_MEMORY;
}
else
if (!arc) // V0.9.19 (2002-04-02) [umoeller]
arc = ERROR_INVALID_PARAMETER;
return arc;
}
/*
*@@ doshReadAt:
* reads cb bytes from the position specified by
* lOffset into the buffer pointed to by pbData,
* which should be cb bytes in size.
*
* Note that lOffset is always considered to
* be from the beginning of the file (FILE_BEGIN
* method).
*
* This implements a small cache so that several
* calls with a near offset will not touch the
* disk always. The cache has been optimized for
* the exeh* functions and works quite nicely
* there.
*
* Note that the position of the file pointer is
* undefined after calling this function because
* the data might have been returned from the
* cache.
*
* fl may be any combination of the following:
*
* -- DRFL_NOCACHE: do not fill the cache with
* new data if the data is not in the cache
* currently.
*
* -- DRFL_FAILIFLESS: return ERROR_NO_DATA
* if the data returned by DosRead is less
* than what was specified. This might
* simplify error handling.
*
*@@added V0.9.13 (2001-06-14) [umoeller]
*@@changed V0.9.16 (2001-12-18) [umoeller]: now with XFILE, and always using FILE_BEGIN
*@@chaanged V0.9.19 (2002-04-02) [umoeller]: added params checking
*/
APIRET doshReadAt(PXFILE pFile,
ULONG ulOffset, // in: offset to read from (from beginning of file)
PULONG pcb, // in: bytes to read, out: bytes read (req.)
PBYTE pbData, // out: read buffer (must be cb bytes)
ULONG fl) // in: DRFL_* flags
{
APIRET arc = NO_ERROR;
ULONG cb;
ULONG ulDummy;
if (!pFile || !pcb)
// V0.9.19 (2002-04-02) [umoeller]
return ERROR_INVALID_PARAMETER;
cb = *pcb;
*pcb = 0;
// check if we have the data in the cache already;
if ( (pFile->pbCache)
// first byte must be in cache
&& (ulOffset >= pFile->ulReadFrom)
// last byte must be in cache
&& ( ulOffset + cb
<= pFile->ulReadFrom + pFile->cbCache
)
)
{
// alright, return data from cache simply
ULONG ulOfsInCache = ulOffset - pFile->ulReadFrom;
memcpy(pbData,
pFile->pbCache + ulOfsInCache,
cb);
*pcb = cb;
#ifdef DEBUG_DOSOPEN
_Pmpf((__FUNCTION__ " %s: data is fully in cache",
pFile->pszFilename));
_Pmpf((" caller wants %d bytes from %d",
cb, ulOffset));
_Pmpf((" we got %d bytes from %d",
pFile->cbCache, pFile->ulReadFrom));
_Pmpf((" so copied %d bytes from cache ofs %d",
cb, ulOfsInCache));
#endif
}
else
{
// data is not in cache:
// check how much it is... for small amounts,
// we load the cache first
if ( (cb <= 4096 - 512)
&& (!(fl & DRFL_NOCACHE))
)
{
#ifdef DEBUG_DOSOPEN
_Pmpf((__FUNCTION__ " %s: filling cache anew",
pFile->pszFilename));
_Pmpf((" caller wants %d bytes from %d",
cb, ulOffset));
#endif
// OK, then fix the offset to read from
// to a multiple of 512 to get a full sector
pFile->ulReadFrom = ulOffset / 512L * 512L;
// and read 4096 bytes always plus the
// value we cut off above
pFile->cbCache = 4096;
#ifdef DEBUG_DOSOPEN
_Pmpf((" getting %d bytes from %d",
pFile->cbCache, pFile->ulReadFrom));
#endif
// free old cache
if (pFile->pbCache)
free(pFile->pbCache);
// allocate new cache
if (!(pFile->pbCache = (PBYTE)malloc(pFile->cbCache)))
arc = ERROR_NOT_ENOUGH_MEMORY;
else
{
ULONG ulOfsInCache = 0;
if (!(arc = DosSetFilePtr(pFile->hf,
(LONG)pFile->ulReadFrom,
FILE_BEGIN,
&ulDummy)))
{
if (!(arc = DosRead(pFile->hf,
pFile->pbCache,
pFile->cbCache,
&ulDummy)))
{
// got data:
#ifdef DEBUG_DOSOPEN
_Pmpf((" %d bytes read", ulDummy));
#endif
pFile->cbCache = ulDummy;
// check bounds
ulOfsInCache = ulOffset - pFile->ulReadFrom;
/*
if (ulOfsInCache + cb > pFile->cbCache)
{
cb = pFile->cbCache - ulOfsInCache;
if (fl & DRFL_FAILIFLESS)
arc = ERROR_NO_DATA;
}
*/
}
}
if (!arc)
{
// copy to caller
memcpy(pbData,
pFile->pbCache + ulOfsInCache,
cb);
*pcb = cb;
#ifdef DEBUG_DOSOPEN
_Pmpf((" so copied %d bytes from cache ofs %d",
cb, ulOfsInCache));
#endif
}
else
{
free(pFile->pbCache);
pFile->pbCache = NULL;
}
} // end else if (!(pFile->pbCache = (PBYTE)malloc(pFile->cbCache)))
}
else
{
// read uncached:
#ifdef DEBUG_DOSOPEN
_Pmpf((" " __FUNCTION__ " %s: reading uncached",
pFile->pszFilename));
_Pmpf((" caller wants %d bytes from %d",
cb, ulOffset));
#endif
if (!(arc = DosSetFilePtr(pFile->hf,
(LONG)ulOffset,
FILE_BEGIN,
&ulDummy)))
{
if (!(arc = DosRead(pFile->hf,
pbData,
cb,
&ulDummy)))
{
if ( (fl & DRFL_FAILIFLESS)
&& (ulDummy != cb)
)
arc = ERROR_NO_DATA;
else
*pcb = ulDummy; // bytes read
}
}
}
}
return arc;
}
/*
*@@ doshWrite:
* writes the specified data to the file.
* If (cb == 0), this runs strlen on pcsz
* to find out the length.
*
* If the file is not in binary mode, all
* \n chars are converted to \r\n before
* writing.
*
* Note that this expects that the file
* pointer is at the end of the file, or
* you will get garbage.
*
*@@added V0.9.16 (2001-10-19) [umoeller]
*@@changed V0.9.16 (2001-12-02) [umoeller]: added XOPEN_BINARY \r\n support
*@@changed V0.9.16 (2001-12-06) [umoeller]: added check for pFile != NULL
*/
APIRET doshWrite(PXFILE pFile,
ULONG cb,
PCSZ pbData)
{
APIRET arc = NO_ERROR;
if ((!pFile) || (!pbData))
arc = ERROR_INVALID_PARAMETER;
else
{
if (!cb)
cb = strlen(pbData);
if (!cb)
arc = ERROR_INVALID_PARAMETER;
else
{
PSZ pszNew = NULL;
if (!(pFile->flOpenMode & XOPEN_BINARY))
{
// convert all \n to \r\n:
// V0.9.16 (2001-12-02) [umoeller]
// count all \n first
ULONG cNewLines = 0;
PCSZ pSource = pbData;
ULONG ul;
for (ul = 0;
ul < cb;
ul++)
{
if (*pSource++ == '\n')
cNewLines++;
}
if (cNewLines)
{
// we have '\n' chars:
// then we need just as many \r chars inserted
ULONG cbNew = cb + cNewLines;
if (!(pszNew = (PSZ)malloc(cbNew)))
arc = ERROR_NOT_ENOUGH_MEMORY;
else
{
PSZ pTarget = pszNew;
pSource = pbData;
for (ul = 0;
ul < cb;
ul++)
{
CHAR c = *pSource++;
if (c == '\n')
*pTarget++ = '\r';
*pTarget++ = c;
}
cb = cbNew;
}
}
}
if (!arc)
{
ULONG cbWritten;
if (!(arc = DosWrite(pFile->hf,
(pszNew)
? pszNew
: (PSZ)pbData,
cb,
&cbWritten)))
{
pFile->cbCurrent += cbWritten;
// invalidate the cache
FREE(pFile->pbCache);
}
}
if (pszNew)
free(pszNew);
}
}
return arc;
}
/*
*@@ doshWriteAt:
* writes cb bytes (pointed to by pbData) to the
* specified file at the position lOffset (from
* the beginning of the file).
*
*@@added V0.9.13 (2001-06-14) [umoeller]
*/
APIRET doshWriteAt(PXFILE pFile,
ULONG ulOffset, // in: offset to write at
ULONG cb, // in: bytes to write
PCSZ pbData) // in: ptr to bytes to write (must be cb bytes)
{
APIRET arc = NO_ERROR;
ULONG cbWritten;
if (!(arc = DosSetFilePtr(pFile->hf,
(LONG)ulOffset,
FILE_BEGIN,
&cbWritten)))
{
if (!(arc = DosWrite(pFile->hf,
(PSZ)pbData,
cb,
&cbWritten)))
{
if (ulOffset + cbWritten > pFile->cbCurrent)
pFile->cbCurrent = ulOffset + cbWritten;
// invalidate the cache V0.9.19 (2002-04-02) [umoeller]
FREE(pFile->pbCache);
}
}
return arc;
}
/*
*@@ doshWriteLogEntry
* writes a log string to an XFILE, adding a
* leading timestamp before the line.
*
* The internal string buffer is limited to 2000
* characters. Length checking is _not_ performed.
*
*@@added V0.9.16 (2001-10-19) [umoeller]
*@@changed V0.9.16 (2001-12-06) [umoeller]: added check for pFile != NULL
*/
APIRET doshWriteLogEntry(PXFILE pFile,
const char* pcszFormat,
...)
{
APIRET arc = NO_ERROR;
if ((!pFile) || (!pcszFormat))
arc = ERROR_INVALID_PARAMETER;
else
{
DATETIME dt;
CHAR szTemp[2000];
ULONG ulLength;
DosGetDateTime(&dt);
if (ulLength = sprintf(szTemp,
"%04d-%02d-%02d %02d:%02d:%02d:%02d ",
dt.year, dt.month, dt.day,
dt.hours, dt.minutes, dt.seconds, dt.hundredths))
{
if (!(arc = doshWrite(pFile,
ulLength,
szTemp)))
{
va_list arg_ptr;
va_start(arg_ptr, pcszFormat);
ulLength = vsprintf(szTemp, pcszFormat, arg_ptr);
va_end(arg_ptr);
if (pFile->flOpenMode & XOPEN_BINARY)
// if we're in binary mode, we need to add \r too
szTemp[ulLength++] = '\r';
szTemp[ulLength++] = '\n';
arc = doshWrite(pFile,
ulLength,
szTemp);
}
}
}
return arc;
}
/*
* doshClose:
* closes an XFILE opened by doshOpen and
* sets *ppFile to NULL.
*
*@@added V0.9.16 (2001-10-19) [umoeller]
*/
APIRET doshClose(PXFILE *ppFile)
{
APIRET arc = NO_ERROR;
PXFILE pFile;
if ( (ppFile)
&& (pFile = *ppFile)
)
{
// set the ptr to NULL
*ppFile = NULL;
FREE(pFile->pbCache);
FREE(pFile->pszFilename);
if (pFile->hf)
{
DosSetFileSize(pFile->hf, pFile->cbCurrent);
DosClose(pFile->hf);
pFile->hf = NULLHANDLE;
}
free(pFile);
}
else
arc = ERROR_INVALID_PARAMETER;
return arc;
}
/*
*@@ doshReadText:
* reads all the contents of the given XFILE into
* a newly allocated buffer. Handles Ctrl-Z properly.
*
* Implementation for doshLoadTextFile, but can
* be called separately now.
*
*@@added V0.9.20 (2002-07-19) [umoeller]
*/
APIRET doshReadText(PXFILE pFile,
PSZ* ppszContent, // out: newly allocated buffer with file's content
PULONG pcbRead) // out: size of that buffer including null byte (ptr can be NULL)
{
APIRET arc;
PSZ pszContent;
if (!(pszContent = (PSZ)malloc(pFile->cbCurrent + 1)))
arc = ERROR_NOT_ENOUGH_MEMORY;
else
{
ULONG cbRead = 0;
if (!(arc = DosRead(pFile->hf,
pszContent,
pFile->cbCurrent,
&cbRead)))
{
if (cbRead != pFile->cbCurrent)
arc = ERROR_NO_DATA;
else
{
PSZ p;
pszContent[cbRead] = '\0';
// check if we have a ctrl-z (EOF) marker
// this is present, for example, in config.sys
// after install, and stupid E.EXE writes this
// all the time when saving a file
// V0.9.18 (2002-03-08) [umoeller]
if (p = strchr(pszContent, '\26'))
{
*p = '\0';
cbRead = p - pszContent;
}
*ppszContent = pszContent;
if (pcbRead)
*pcbRead = cbRead + 1;
}
}
if (arc)
free(pszContent);
}
return arc;
}
/*
*@@ doshLoadTextFile:
* reads a text file from disk, allocates memory
* via malloc() and sets a pointer to this
* buffer (or NULL upon errors).
*
* This allocates one extra byte to make the
* buffer null-terminated always. The buffer
* is _not_ converted WRT the line format.
*
* If CTRL-Z (ASCII 26) is encountered in the
* content, it is set to the null character
* instead (V0.9.18).
*
* This returns the APIRET of DosOpen and DosRead.
* If any error occured, no buffer was allocated.
* Otherwise, you should free() the buffer when
* no longer needed.
*
*@@changed V0.9.7 (2001-01-15) [umoeller]: renamed from doshReadTextFile
*@@changed V0.9.16 (2002-01-05) [umoeller]: added pcbRead
*@@changed V0.9.16 (2002-01-05) [umoeller]: rewritten using doshOpen
*@@changed V0.9.18 (2002-03-08) [umoeller]: fixed ctrl-z (EOF) bug
*/
APIRET doshLoadTextFile(PCSZ pcszFile, // in: file name to read
PSZ* ppszContent, // out: newly allocated buffer with file's content
PULONG pcbRead) // out: size of that buffer including null byte (ptr can be NULL)
{
APIRET arc;
ULONG cbFile = 0;
PXFILE pFile = NULL;
if (!(arc = doshOpen(pcszFile,
XOPEN_READ_EXISTING,
&cbFile,
&pFile)))
{
doshReadText(pFile,
ppszContent,
pcbRead);
doshClose(&pFile);
}
return arc;
}
/*
*@@ doshCreateBackupFileName:
* creates a valid backup filename of pszExisting
* with a numerical file name extension which does
* not exist in the directory where pszExisting
* resides.
* Returns a PSZ to a new buffer which was allocated
* using malloc().
*
* <B>Example:</B> returns "C:\CONFIG.002" for input
* "C:\CONFIG.SYS" if "C:\CONFIG.001" already exists.
*
*@@changed V0.9.1 (99-12-13) [umoeller]: this crashed if pszExisting had no file extension
*/
PSZ doshCreateBackupFileName(const char* pszExisting)
{
CHAR szFilename[CCHMAXPATH];
PSZ pszLastDot;
ULONG ulCount = 1;
CHAR szCount[5];
ULONG ulDummy;
strcpy(szFilename, pszExisting);
if (!(pszLastDot = strrchr(szFilename, '.')))
// no dot in filename:
pszLastDot = szFilename + strlen(szFilename);
do
{
sprintf(szCount, ".%03lu", ulCount);
strcpy(pszLastDot, szCount);
ulCount++;
} while (!doshQueryPathSize(szFilename, &ulDummy));
return (strdup(szFilename));
}
/*
*@@ doshCreateTempFileName:
* produces a file name in the the specified directory
* or $(TEMP) which presently doesn't exist. This
* checks the directory for existing files, but does
* not open the temp file.
*
* If (pcszDir != NULL), we look into that directory.
* Otherwise we look into the directory specified
* by the $(TEMP) environment variable.
* If $(TEMP) is not set, $(TMP) is tried next.
*
* If the directory thus specified does not exist, the
* root directory of the boot drive is used instead.
* As a result, this function should be fairly bomb-proof.
*
* If (pcszExt != NULL), the temp file receives
* that extension, or no extension otherwise.
* Do not specify the dot in pcszExt.
*
* pszTempFileName receives the fully qualified
* file name of the temp file in that directory
* and must point to a buffer CCHMAXPATH in size.
* The file name is 8+3 compliant if pcszExt does
* not exceed three characters.
*
* If (pcszPrefix != NULL), the temp file name
* is prefixed with pcszPrefix. Since the temp
* file name must not exceed 8+3 letters, we
* can only use ( 8 - strlen(pcszPrefix ) digits
* for a random number to make the temp file name
* unique. You must therefore use a maximum of
* four characters for the prefix. Otherwise
* ERROR_INVALID_PARAMETER is returned.
*
* Example: Assuming TEMP is set to C:\TEMP,
+
+ doshCreateTempFileName(szBuffer,
+ NULL, // use $(TEMP)
+ "pre", // prefix
+ "tmp") // extension
+
* would produce something like "C:\TEMP\pre07FG2.tmp".
*
*@@added V0.9.9 (2001-04-04) [umoeller]
*/
APIRET doshCreateTempFileName(PSZ pszTempFileName, // out: fully q'fied temp file name
PCSZ pcszDir, // in: dir or NULL for %TEMP%
PCSZ pcszPrefix, // in: prefix for temp file or NULL
PCSZ pcszExt) // in: extension (without dot) or NULL
{
APIRET arc = NO_ERROR;
ULONG ulPrefixLen = (pcszPrefix)
? strlen(pcszPrefix)
: 0;
if ( (!pszTempFileName)
|| (ulPrefixLen > 4)
)
arc = ERROR_INVALID_PARAMETER;
else
{
CHAR szDir[CCHMAXPATH] = "";
FILESTATUS3 fs3;
const char *pcszTemp = pcszDir;
if (!pcszTemp)
{
if (!(pcszTemp = getenv("TEMP")))
pcszTemp = getenv("TMP");
}
if (pcszTemp) // either pcszDir or $(TEMP) or $(TMP) now
if (DosQueryPathInfo((PSZ)pcszTemp,
FIL_STANDARD,
&fs3,
sizeof(fs3)))
// TEMP doesn't exist:
pcszTemp = NULL;
if (!pcszTemp)
// not set, or doesn't exist:
// use root directory on boot drive
sprintf(szDir,
"%c:\\",
doshQueryBootDrive());
else
{
strcpy(szDir, pcszTemp);
if (szDir[strlen(szDir) - 1] != '\\')
strcat(szDir, "\\");
}
if (!szDir[0])
arc = ERROR_PATH_NOT_FOUND; // shouldn't happen
else
{
ULONG ulRandom = 0;
ULONG cAttempts = 0;
// produce random number
DosQuerySysInfo(QSV_MS_COUNT,
QSV_MS_COUNT,
&ulRandom,
sizeof(ulRandom));
do
{
CHAR szFile[20] = "",
szFullTryThis[CCHMAXPATH];
// use the lower eight hex digits of the
// system uptime as the temp dir name
sprintf(szFile,
"%08lX",
ulRandom & 0xFFFFFFFF);
// if prefix is specified, overwrite the
// first characters in the random number
if (pcszPrefix)
memcpy(szFile, pcszPrefix, ulPrefixLen);
if (pcszExt)
{
szFile[8] = '.';
strcpy(szFile + 9, pcszExt);
}
// now compose full temp file name
strcpy(szFullTryThis, szDir);
strcat(szFullTryThis, szFile);
// now we have: "C:\temp\wpiXXXXX"
if (DosQueryPathInfo(szFullTryThis,
FIL_STANDARD,
&fs3,
sizeof(fs3))
== ERROR_FILE_NOT_FOUND)
{
// file or dir doesn't exist:
// cool, we're done
strcpy(pszTempFileName, szFullTryThis);
return NO_ERROR;
}
// if this didn't work, raise ulRandom and try again
ulRandom += 123;
// try only 100 times, just to be sure
cAttempts++;
} while (cAttempts < 100);
// 100 loops elapsed:
arc = ERROR_BAD_FORMAT;
}
}
return arc;
}
/*
*@@ doshWriteTextFile:
* writes a text file to disk; pszFile must contain the
* whole path and filename.
*
* An existing file will be backed up if (pszBackup != NULL),
* using doshCreateBackupFileName. In that case, pszBackup
* receives the name of the backup created, so that buffer
* should be CCHMAXPATH in size.
*
* If (pszbackup == NULL), an existing file will be overwritten.
*
* On success (NO_ERROR returned), *pulWritten receives
* the no. of bytes written.
*
*@@changed V0.9.3 (2000-05-01) [umoeller]: optimized DosOpen; added error checking; changed prototype
*@@changed V0.9.3 (2000-05-12) [umoeller]: added pszBackup
*/
APIRET doshWriteTextFile(const char* pszFile, // in: file name
const char* pszContent, // in: text to write
PULONG pulWritten, // out: bytes written (ptr can be NULL)
PSZ pszBackup) // in/out: create-backup?
{
APIRET arc = NO_ERROR;
ULONG ulWritten = 0;
if ((!pszFile) || (!pszContent))
arc = ERROR_INVALID_PARAMETER;
else
{
ULONG ulAction = 0,
ulLocal = 0;
HFILE hFile = 0;
ULONG ulSize = strlen(pszContent); // exclude 0 byte
if (pszBackup)
{
PSZ pszBackup2 = doshCreateBackupFileName(pszFile);
DosCopy((PSZ)pszFile,
pszBackup2,
DCPY_EXISTING); // delete existing
strcpy(pszBackup, pszBackup2);
free(pszBackup2);
}
if (!(arc = DosOpen((PSZ)pszFile,
&hFile,
&ulAction, // action taken
ulSize, // primary allocation size
FILE_ARCHIVED | FILE_NORMAL, // file attribute
OPEN_ACTION_CREATE_IF_NEW
| OPEN_ACTION_REPLACE_IF_EXISTS, // open flags
OPEN_FLAGS_NOINHERIT
| OPEN_FLAGS_SEQUENTIAL // sequential, not random access
| OPEN_SHARE_DENYWRITE // deny write mode
| OPEN_ACCESS_WRITEONLY, // write mode
NULL))) // no EAs
{
if (!(arc = DosSetFilePtr(hFile,
0L,
FILE_BEGIN,
&ulLocal)))
if (!(arc = DosWrite(hFile,
(PVOID)pszContent,
ulSize,
&ulWritten)))
arc = DosSetFileSize(hFile, ulSize);
DosClose(hFile);
}
} // end if ((pszFile) && (pszContent))
if (pulWritten)
*pulWritten = ulWritten;
return arc;
}
/*
*@@category: Helpers\Control program helpers\Directory management
* directory helpers (querying, creating, deleting etc.).
*/
/* ******************************************************************
*
* Directory helpers
*
********************************************************************/
/*
*@@ doshQueryDirExist:
* returns TRUE if the given directory
* exists and really is a directory.
*/
BOOL doshQueryDirExist(PCSZ pcszDir)
{
FILESTATUS3 fs3;
APIRET arc;
if (!(arc = DosQueryPathInfo((PSZ)pcszDir,
FIL_STANDARD,
&fs3,
sizeof(fs3))))
// file found:
return ((fs3.attrFile & FILE_DIRECTORY) != 0);
else
return FALSE;
}
/*
*@@ doshCreatePath:
* this creates the specified directory.
* As opposed to DosCreateDir, this
* function can create several directories
* at the same time, if the parent
* directories do not exist yet.
*/
APIRET doshCreatePath(PCSZ pcszPath,
BOOL fHidden) // in: if TRUE, the new directories will get FILE_HIDDEN
{
APIRET arc0 = NO_ERROR;
CHAR path[CCHMAXPATH];
CHAR *cp, c;
ULONG cbPath;
strcpy(path, pcszPath);
cbPath = strlen(path);
if (path[cbPath] != '\\')
{
path[cbPath] = '\\';
path[cbPath + 1] = 0;
}
cp = path;
// advance past the drive letter only if we have one
if (*(cp+1) == ':')
cp += 3;
// go!
while (*cp != 0)
{
if (*cp == '\\')
{
c = *cp;
*cp = 0;
if (!doshQueryDirExist(path))
{
APIRET arc = DosCreateDir(path,
0); // no EAs
if (arc != NO_ERROR)
{
arc0 = arc;
break;
}
else
if (fHidden)
{
// hide the directory we just created
doshSetPathAttr(path, FILE_HIDDEN);
}
}
*cp = c;
}
cp++;
}
return (arc0);
}
/*
*@@ doshQueryCurrentDir:
* writes the current directory into
* the specified buffer, which should be
* CCHMAXPATH in size.
*
* As opposed to DosQueryCurrentDir, this
* includes the drive letter.
*
*@@added V0.9.4 (2000-07-22) [umoeller]
*/
APIRET doshQueryCurrentDir(PSZ pszBuf)
{
APIRET arc = NO_ERROR;
ULONG ulCurDisk = 0;
ULONG ulMap = 0;
if (!(arc = DosQueryCurrentDisk(&ulCurDisk, &ulMap)))
{
ULONG cbBuf = CCHMAXPATH - 3;
pszBuf[0] = ulCurDisk + 'A' - 1;
pszBuf[1] = ':';
pszBuf[2] = '\\';
pszBuf[3] = '\0';
arc = DosQueryCurrentDir(0, pszBuf + 3, &cbBuf);
}
return arc;
}
/*
*@@ doshDeleteDir:
* deletes a directory. As opposed to DosDeleteDir,
* this removes empty subdirectories and/or files
* as well.
*
* flFlags can be any combination of the following:
*
* -- DOSHDELDIR_RECURSE: recurse into subdirectories.
*
* -- DOSHDELDIR_DELETEFILES: delete all regular files
* which are found on the way.
*
* THIS IS NOT IMPLEMENTED YET.
*
* If 0 is specified, this effectively behaves just as
* DosDeleteDir.
*
* If you specify DOSHDELDIR_RECURSE only, only empty
* subdirectories are deleted as well.
*
* If you specify DOSHDELDIR_RECURSE | DOSHDELDIR_DELETEFILES,
* this removes an entire directory tree, including all
* subdirectories and files.
*
*@@added V0.9.4 (2000-07-01) [umoeller]
*/
APIRET doshDeleteDir(PCSZ pcszDir,
ULONG flFlags,
PULONG pulDirs, // out: directories found
PULONG pulFiles) // out: files found
{
APIRET arc = NO_ERROR,
arcReturn = NO_ERROR;
HDIR hdirFindHandle = HDIR_CREATE;
FILEFINDBUF3 ffb3 = {0}; // returned from FindFirst/Next
ULONG ulResultBufLen = sizeof(FILEFINDBUF3);
ULONG ulFindCount = 1; // look for 1 file at a time
CHAR szFileMask[2*CCHMAXPATH];
sprintf(szFileMask, "%s\\*", pcszDir);
// find files
arc = DosFindFirst(szFileMask,
&hdirFindHandle, // directory search handle
FILE_ARCHIVED | FILE_DIRECTORY | FILE_SYSTEM
| FILE_HIDDEN | FILE_READONLY,
// search attributes; include all, even dirs
&ffb3, // result buffer
ulResultBufLen, // result buffer length
&ulFindCount, // number of entries to find
FIL_STANDARD); // return level 1 file info
if (arc == NO_ERROR)
{
// keep finding the next file until there are no more files
while (arc == NO_ERROR) // != ERROR_NO_MORE_FILES
{
if (ffb3.attrFile & FILE_DIRECTORY)
{
// we found a directory:
// ignore the pseudo-directories
if ( (strcmp(ffb3.achName, ".") != 0)
&& (strcmp(ffb3.achName, "..") != 0)
)
{
// real directory:
if (flFlags & DOSHDELDIR_RECURSE)
{
// recurse!
CHAR szSubDir[2*CCHMAXPATH];
sprintf(szSubDir, "%s\\%s", pcszDir, ffb3.achName);
arcReturn = doshDeleteDir(szSubDir,
flFlags,
pulDirs,
pulFiles);
// this removes ffb3.achName as well
}
else
{
// directory, but no recursion:
// report "access denied"
// (this is what OS/2 reports with DosDeleteDir as well)
arcReturn = ERROR_ACCESS_DENIED; // 5
(*pulDirs)++;
}
}
}
else
{
// it's a file:
arcReturn = ERROR_ACCESS_DENIED;
(*pulFiles)++;
}
if (arc == NO_ERROR)
{
ulFindCount = 1; // reset find count
arc = DosFindNext(hdirFindHandle, // directory handle
&ffb3, // result buffer
ulResultBufLen, // result buffer length
&ulFindCount); // number of entries to find
}
} // endwhile
DosFindClose(hdirFindHandle); // close our find handle
}
if (arcReturn == NO_ERROR)
// success so far:
// delete our directory now
arcReturn = DosDeleteDir((PSZ)pcszDir);
return (arcReturn);
}
/*
*@@ doshCanonicalize:
* simplifies path specifications to remove '.'
* and '..' entries and generates a fully
* qualified path name where possible.
* File specifications are left unchanged.
*
* This returns:
*
* -- NO_ERROR: the buffers were valid.
*
* -- ERROR_INVALID_PARAMETER: the buffers
* were invalid.
*
*@@added V0.9.19 (2002-04-22) [pr]
*/
APIRET doshCanonicalize(PCSZ pcszFileIn, // in: path to canonicalize
PSZ pszFileOut, // out: canonicalized path if NO_ERROR
ULONG cbFileOut) // in: size of pszFileOut buffer
{
APIRET ulrc = NO_ERROR;
CHAR szFileTemp[CCHMAXPATH];
if (pcszFileIn && pszFileOut && cbFileOut)
{
strncpy(szFileTemp, pcszFileIn, sizeof(szFileTemp) - 1);
szFileTemp[sizeof(szFileTemp) - 1] = 0;
if ( strchr(szFileTemp, '\\')
|| strchr(szFileTemp, ':')
)
{
ULONG cbFileTemp = strlen(szFileTemp);
if ( (cbFileTemp > 3)
&& (szFileTemp[cbFileTemp - 1] == '\\')
)
{
szFileTemp[cbFileTemp - 1] = 0;
}
if (DosQueryPathInfo(szFileTemp,
FIL_QUERYFULLNAME,
pszFileOut,
cbFileOut))
{
pszFileOut[0] = 0;
}
}
else
{
strncpy(pszFileOut, pcszFileIn, cbFileOut - 1);
pszFileOut[cbFileOut - 1] = 0;
}
}
else
ulrc = ERROR_INVALID_PARAMETER;
return(ulrc);
}
/*
*@@category: Helpers\Control program helpers\Module handling
* helpers for importing functions from a module (DLL).
*/
/* ******************************************************************
*
* Module handling helpers
*
********************************************************************/
/*
*@@ doshQueryProcAddr:
* attempts to resolve the given procedure from
* the given module name. Saves you from querying
* the module handle and all that.
*
* This is intended for resolving undocumented
* APIs from OS/2 system DLls such as PMMERGE
* and DOSCALLS. It is assumed that the specified
* module is already loaded.
*
* Returns:
*
* -- NO_ERROR
*
* -- ERROR_INVALID_NAME
*
* -- ERROR_INVALID_ORDINAL
*
* plus the error codes of DosLoadModule.
*
*@@added V0.9.16 (2002-01-13) [umoeller]
*/
APIRET doshQueryProcAddr(PCSZ pcszModuleName, // in: module name (e.g. "PMMERGE")
ULONG ulOrdinal, // in: proc ordinal
PFN *ppfn) // out: proc address
{
HMODULE hmod;
APIRET arc;
if (!(arc = DosQueryModuleHandle((PSZ)pcszModuleName,
&hmod)))
{
if ((arc = DosQueryProcAddr(hmod,
ulOrdinal,
NULL,
ppfn)))
{
// the CP programming guide and reference says use
// DosLoadModule if DosQueryProcAddr fails with this error
if (arc == ERROR_INVALID_HANDLE)
{
if (!(arc = DosLoadModule(NULL,
0,
(PSZ)pcszModuleName,
&hmod)))
{
arc = DosQueryProcAddr(hmod,
ulOrdinal,
NULL,
ppfn);
}
}
}
}
return arc;
}
/*
*@@ doshResolveImports:
* this function loads the module called pszModuleName
* and resolves imports dynamically using DosQueryProcAddress.
*
* To specify the functions to be imported, a RESOLVEFUNCTION
* array is used. In each of the array items, specify the
* name of the function and a pointer to a function pointer
* where to store the resolved address.
*
*@@added V0.9.3 (2000-04-29) [umoeller]
*/
APIRET doshResolveImports(PCSZ pcszModuleName, // in: DLL to load
HMODULE *phmod, // out: module handle
PCRESOLVEFUNCTION paResolves, // in/out: function resolves
ULONG cResolves) // in: array item count (not array size!)
{
CHAR szName[CCHMAXPATH];
APIRET arc;
if (!(arc = DosLoadModule(szName,
sizeof(szName),
(PSZ)pcszModuleName,
phmod)))
{
ULONG ul;
for (ul = 0;
ul < cResolves;
ul++)
{
if (arc = DosQueryProcAddr(*phmod,
0, // ordinal, ignored
(PSZ)paResolves[ul].pcszFunctionName,
paResolves[ul].ppFuncAddress))
// error:
break;
}
if (arc)
// V0.9.16 (2001-12-08) [umoeller]
DosFreeModule(*phmod);
}
return arc;
}
/*
*@@category: Helpers\Control program helpers\Performance (CPU load) helpers
* helpers around DosPerfSysCall.
*/
/* ******************************************************************
*
* Performance Counters (CPU Load)
*
********************************************************************/
/*
*@@ doshPerfOpen:
* initializes the OS/2 DosPerfSysCall API for
* the calling thread.
*
* Note: This API is not supported on all OS/2
* versions. I believe it came up with some Warp 4
* fixpak. The API is resolved dynamically by
* this function (using DosQueryProcAddr).
*
* This properly initializes the internal counters
* which the OS/2 kernel uses for this API. Apparently,
* with newer kernels (FP13/14), IBM has chosen to no
* longer do this automatically, which is the reason
* why many "pulse" utilities display garbage with these
* fixpaks.
*
* After NO_ERROR is returned, DOSHPERFSYS.cProcessors
* contains the no. of processors found on the system.
* All pointers in DOSHPERFSYS then point to arrays
* which have exactly cProcessors array items.
*
* So after NO_ERROR was returned here, you can keep
* calling doshPerfGet to get a current snapshot of the
* IRQ and user loads for each CPU. Note that interrupts
* are only processed on CPU 0 on SMP systems.
*
* Call doshPerfClose to clean up resources allocated
* by this function.
*
* For more sample code, take a look at the "Pulse" widget
* in the XWorkplace sources (src\xcenter\w_pulse.c).
*
* Example code:
*
+ PDOSHPERFSYS pPerf = NULL;
+ APIRET arc;
+ if (!(arc = arc = doshPerfOpen(&pPerf)))
+ {
+ // this should really be in a timer,
+ // e.g. once per second
+ ULONG ulCPU;
+ if (!(arc = doshPerfGet(&pPerf)))
+ {
+ // go thru all CPUs
+ for (ulCPU = 0; ulCPU < pPerf->cProcessors; ulCPU++)
+ {
+ LONG lUserLoadThis = pPerf->palLoads[ulCPU];
+ ...
+ }
+ }
+
+ // clean up
+ doshPerfClose(&pPerf);
+ }
+
*
*@@added V0.9.7 (2000-12-02) [umoeller]
*@@changed V0.9.9 (2001-03-14) [umoeller]: added interrupt loads; thanks phaller
*/
APIRET doshPerfOpen(PDOSHPERFSYS *ppPerfSys) // out: new DOSHPERFSYS structure
{
APIRET arc = NO_ERROR;
// allocate DOSHPERFSYS structure
*ppPerfSys = (PDOSHPERFSYS)malloc(sizeof(DOSHPERFSYS));
if (!*ppPerfSys)
arc = ERROR_NOT_ENOUGH_MEMORY;
else
{
// initialize structure
PDOSHPERFSYS pPerfSys = *ppPerfSys;
memset(pPerfSys, 0, sizeof(*pPerfSys));
// resolve DosPerfSysCall API entry
if (!(arc = DosLoadModule(NULL, 0, "DOSCALLS", &pPerfSys->hmod)))
{
if (!(arc = DosQueryProcAddr(pPerfSys->hmod,
976,
"DosPerfSysCall",
(PFN*)(&pPerfSys->pDosPerfSysCall))))
{
// OK, we got the API: initialize!
if (!(arc = pPerfSys->pDosPerfSysCall(CMD_KI_ENABLE, 0, 0, 0)))
{
pPerfSys->fInitialized = TRUE;
// call CMD_KI_DISABLE later
if (!(arc = pPerfSys->pDosPerfSysCall(CMD_PERF_INFO,
0,
(ULONG)(&pPerfSys->cProcessors),
0)))
{
ULONG ul = 0,
cProcs = pPerfSys->cProcessors,
cbDouble = cProcs * sizeof(double),
cbLong = cProcs * sizeof(LONG);
// allocate arrays
if ( (!(pPerfSys->paCPUUtils = (PCPUUTIL)calloc(cProcs,
sizeof(CPUUTIL))))
|| (!(pPerfSys->padBusyPrev
= (double*)malloc(cbDouble)))
|| (!(pPerfSys->padTimePrev
= (double*)malloc(cbDouble)))
|| (!(pPerfSys->padIntrPrev
= (double*)malloc(cbDouble)))
|| (!(pPerfSys->palLoads
= (PLONG)malloc(cbLong)))
|| (!(pPerfSys->palIntrs
= (PLONG)malloc(cbLong)))
)
arc = ERROR_NOT_ENOUGH_MEMORY;
else
{
for (ul = 0; ul < cProcs; ul++)
{
pPerfSys->padBusyPrev[ul] = 0.0;
pPerfSys->padTimePrev[ul] = 0.0;
pPerfSys->padIntrPrev[ul] = 0.0;
pPerfSys->palLoads[ul] = 0;
pPerfSys->palIntrs[ul] = 0;
}
}
}
}
} // end if (arc == NO_ERROR)
} // end if (arc == NO_ERROR)
if (arc)
// error: clean up
doshPerfClose(ppPerfSys);
} // end else if (!*ppPerfSys)
return arc;
}
/*
*@@ doshPerfGet:
* calculates a current snapshot of the system load,
* compared with the load which was calculated on
* the previous call.
*
* If you want to continually measure the system CPU
* load, this is the function you will want to call
* regularly -- e.g. with a timer once per second.
*
* Call this ONLY if doshPerfOpen returned NO_ERROR,
* or you'll get crashes.
*
* If this call returns NO_ERROR, you get LONG load
* values for each CPU in the system in the arrays
* in DOSHPERFSYS (in per-mille, 0-1000).
*
* There are two arrays:
*
* -- DOSHPERFSYS.palLoads contains the "user" load
* for each CPU.
*
* -- DOSHPERFSYS.palIntrs contains the "IRQ" load
* for each CPU.
*
* Sum up the two values to get the total load for
* each CPU.
*
* For example, if there are two CPUs, after this call,
*
* -- DOSHPERFSYS.palLoads[0] contains the "user" load
* of the first CPU,
*
* -- DOSHPERFSYS.palLoads[0] contains the "user" load
* of the second CPU.
*
* See doshPerfOpen for example code.
*
*@@added V0.9.7 (2000-12-02) [umoeller]
*@@changed V0.9.9 (2001-03-14) [umoeller]: added interrupt loads; thanks phaller
*/
APIRET doshPerfGet(PDOSHPERFSYS pPerfSys)
{
APIRET arc = NO_ERROR;
if (!pPerfSys->pDosPerfSysCall)
arc = ERROR_INVALID_PARAMETER;
else
{
if (!(arc = pPerfSys->pDosPerfSysCall(CMD_KI_RDCNT,
(ULONG)pPerfSys->paCPUUtils,
0, 0)))
{
// go thru all processors
ULONG ul = 0;
for (; ul < pPerfSys->cProcessors; ul++)
{
PCPUUTIL pCPUUtilThis = &pPerfSys->paCPUUtils[ul];
double dTime = LL2F(pCPUUtilThis->ulTimeHigh,
pCPUUtilThis->ulTimeLow);
double dBusy = LL2F(pCPUUtilThis->ulBusyHigh,
pCPUUtilThis->ulBusyLow);
double dIntr = LL2F(pCPUUtilThis->ulIntrHigh,
pCPUUtilThis->ulIntrLow);
double *pdBusyPrevThis = &pPerfSys->padBusyPrev[ul];
double *pdTimePrevThis = &pPerfSys->padTimePrev[ul];
double *pdIntrPrevThis = &pPerfSys->padIntrPrev[ul];
// avoid division by zero
double dTimeDelta;
if (dTimeDelta = (dTime - *pdTimePrevThis))
{
pPerfSys->palLoads[ul]
= (LONG)( (double)( (dBusy - *pdBusyPrevThis)
/ dTimeDelta
* 1000.0
)
);
pPerfSys->palIntrs[ul]
= (LONG)( (double)( (dIntr - *pdIntrPrevThis)
/ dTimeDelta
* 1000.0
)
);
}
else
{
// no clear readings are available
pPerfSys->palLoads[ul] = 0;
pPerfSys->palIntrs[ul] = 0;
}
*pdTimePrevThis = dTime;
*pdBusyPrevThis = dBusy;
*pdIntrPrevThis = dIntr;
}
}
}
return arc;
}
/*
*@@ doshPerfClose:
* frees all resources allocated by doshPerfOpen.
*
*@@added V0.9.7 (2000-12-02) [umoeller]
*@@changed V0.9.9 (2001-02-06) [umoeller]: removed disable; this broke the WarpCenter
*@@changed V0.9.9 (2001-03-14) [umoeller]: fixed memory leak
*@@changed V0.9.9 (2001-03-14) [umoeller]: added interrupt loads; thanks phaller
*/
APIRET doshPerfClose(PDOSHPERFSYS *ppPerfSys)
{
APIRET arc = NO_ERROR;
PDOSHPERFSYS pPerfSys;
if ( (!ppPerfSys)
|| (!(pPerfSys = *ppPerfSys))
)
arc = ERROR_INVALID_PARAMETER;
else
{
// do not call this, this messes up the WarpCenter V0.9.9 (2001-02-06) [umoeller]
// if (pPerfSys->fInitialized) pPerfSys->pDosPerfSysCall(CMD_KI_DISABLE, 0, 0, 0);
if (pPerfSys->paCPUUtils)
free(pPerfSys->paCPUUtils);
if (pPerfSys->padBusyPrev)
free(pPerfSys->padBusyPrev);
if (pPerfSys->padTimePrev)
free(pPerfSys->padTimePrev);
if (pPerfSys->padIntrPrev)
free(pPerfSys->padIntrPrev);
if (pPerfSys->palLoads) // was missing V0.9.9 (2001-03-14) [umoeller]
free(pPerfSys->palLoads);
if (pPerfSys->palIntrs)
free(pPerfSys->palIntrs);
if (pPerfSys->hmod)
DosFreeModule(pPerfSys->hmod);
free(pPerfSys);
*ppPerfSys = NULL;
}
return arc;
}
/*
*@@category: Helpers\Control program helpers\Process management
* helpers for starting subprocesses.
*/
/* ******************************************************************
*
* Process helpers
*
********************************************************************/
static PVOID // G_pvGlobalInfoSeg = NULL,
G_pvLocalInfoSeg = NULL;
USHORT _Far16 _Pascal Dos16GetInfoSeg(PSEL pselGlobal,
PSEL pselLocal);
/*
* GetInfoSegs:
*
*/
static VOID GetInfoSegs(VOID)
{
SEL GlobalInfoSegSelector,
LocalInfoSegSelector;
// get selectors (old 16-bit API; this gets called only once)
Dos16GetInfoSeg(&GlobalInfoSegSelector,
&LocalInfoSegSelector);
// thunk
/* G_pvGlobalInfoSeg = (PVOID)( (GlobalInfoSegSelector << 0x000D)
& 0x01fff0000); */
G_pvLocalInfoSeg = (PVOID)( (LocalInfoSegSelector << 0x000D)
& 0x01fff0000);
}
/*
*@@ doshMyPID:
* returns the PID of the current process.
*
* This uses an interesting hack which is way
* faster than DosGetInfoBlocks.
*
*@@added V0.9.9 (2001-04-04) [umoeller]
*/
ULONG doshMyPID(VOID)
{
if (!G_pvLocalInfoSeg)
// first call:
GetInfoSegs();
// PID is at offset 0 in the local info seg
return (*(PUSHORT)G_pvLocalInfoSeg);
}
/*
*@@ doshMyTID:
* returns the TID of the current thread.
*
* This uses an interesting hack which is way
* faster than DosGetInfoBlocks.
*
*@@added V0.9.9 (2001-04-04) [umoeller]
*/
ULONG doshMyTID(VOID)
{
if (!G_pvLocalInfoSeg)
// first call:
GetInfoSegs();
// TID is at offset 6 in the local info seg
return (*(PUSHORT)((PBYTE)G_pvLocalInfoSeg + 6));
}
/*
*@@ doshExecVIO:
* executes cmd.exe with the /c parameter
* and pcszExecWithArgs. This is equivalent
* to the C library system() function,
* except that an OS/2 error code is returned
* and that this works with the VAC subsystem
* library.
*
* This uses DosExecPgm and handles the sick
* argument passing.
*
* If NO_ERROR is returned, *plExitCode receives
* the exit code of the process. If the process
* was terminated abnormally, *plExitCode receives:
*
* -- -1: hard error halt
* -- -2: 16-bit trap
* -- -3: DosKillProcess
* -- -4: 32-bit exception
*
*@@added V0.9.4 (2000-07-27) [umoeller]
*/
APIRET doshExecVIO(PCSZ pcszExecWithArgs,
PLONG plExitCode) // out: exit code (ptr can be NULL)
{
APIRET arc = NO_ERROR;
if (strlen(pcszExecWithArgs) > 255)
arc = ERROR_INSUFFICIENT_BUFFER;
else
{
CHAR szObj[CCHMAXPATH];
RESULTCODES res = {0};
CHAR szBuffer[300];
// DosExecPgm expects two args in szBuffer:
// -- cmd.exe\0
// -- then the actual argument, terminated by two 0 bytes.
memset(szBuffer, 0, sizeof(szBuffer));
strcpy(szBuffer, "cmd.exe\0");
sprintf(szBuffer + 8, "/c %s",
pcszExecWithArgs);
arc = DosExecPgm(szObj, sizeof(szObj),
EXEC_SYNC,
szBuffer,
0,
&res,
"cmd.exe");
if ((arc == NO_ERROR) && (plExitCode))
{
if (res.codeTerminate == 0)
// normal exit:
*plExitCode = res.codeResult;
else
*plExitCode = -((LONG)res.codeTerminate);
}
}
return arc;
}
/*
*@@ doshQuickStartSession:
* this is a shortcut to DosStartSession w/out having to
* deal with all the messy parameters.
*
* This one starts pszPath as a child session and passes
* pszParams to it.
*
* In usPgmCtl, OR any or none of the following (V0.9.0):
* -- SSF_CONTROL_NOAUTOCLOSE (0x0008): do not close automatically.
* This bit is used only for VIO Windowable apps and ignored
* for all other applications.
* -- SSF_CONTROL_MINIMIZE (0x0004)
* -- SSF_CONTROL_MAXIMIZE (0x0002)
* -- SSF_CONTROL_INVISIBLE (0x0001)
* -- SSF_CONTROL_VISIBLE (0x0000)
*
* Specifying 0 will therefore auto-close the session and make it
* visible.
*
* If (fWait), this function will create a termination queue
* and not return until the child session has ended. Be warned,
* this blocks the calling thread.
*
* Otherwise the function will return immediately.
*
* The session and process IDs of the child session will be
* written to *pulSID and *ppid. Of course, in "wait" mode,
* these are no longer valid after this function returns.
*
* Returns the error code of DosStartSession.
*
* Note: According to CPREF, calling DosStartSession calls
* DosExecPgm in turn for all full-screen, VIO, and PM sessions.
*
*@@changed V0.9.0 [umoeller]: prototype changed to include usPgmCtl
*@@changed V0.9.1 (99-12-30) [umoeller]: queue was sometimes not closed. Fixed.
*@@changed V0.9.3 (2000-05-03) [umoeller]: added fForeground
*@@changed V0.9.14 (2001-08-03) [umoeller]: fixed potential queue leak
*@@changed V0.9.14 (2001-08-03) [umoeller]: fixed memory leak in wait mode; added pusReturn to prototype
*/
APIRET doshQuickStartSession(PCSZ pcszPath, // in: program to start
PCSZ pcszParams, // in: parameters for program
BOOL fForeground, // in: if TRUE, session will be in foreground
USHORT usPgmCtl, // in: STARTDATA.PgmControl
BOOL fWait, // in: wait for termination?
PULONG pulSID, // out: session ID (req.)
PPID ppid, // out: process ID (req.)
PUSHORT pusReturn) // out: in wait mode, session's return code (ptr can be NULL)
{
APIRET arc = NO_ERROR;
// queue stuff
const char *pcszQueueName = "\\queues\\xwphlpsw.que";
HQUEUE hq = 0;
PID qpid = 0;
if (fWait)
{
if (!(arc = DosCreateQueue(&hq,
QUE_FIFO | QUE_CONVERT_ADDRESS,
(PSZ)pcszQueueName)))
arc = DosOpenQueue(&qpid, &hq, (PSZ)pcszQueueName);
}
if (!arc) // V0.9.14 (2001-08-03) [umoeller]
{
STARTDATA SData;
CHAR szObjBuf[CCHMAXPATH];
SData.Length = sizeof(STARTDATA);
SData.Related = SSF_RELATED_CHILD; //INDEPENDENT;
SData.FgBg = (fForeground) ? SSF_FGBG_FORE : SSF_FGBG_BACK;
// V0.9.3 (2000-05-03) [umoeller]
SData.TraceOpt = SSF_TRACEOPT_NONE;
SData.PgmTitle = (PSZ)pcszPath; // title for window
SData.PgmName = (PSZ)pcszPath;
SData.PgmInputs = (PSZ)pcszParams;
SData.TermQ = (fWait) ? (PSZ)pcszQueueName : NULL;
SData.Environment = 0;
SData.InheritOpt = SSF_INHERTOPT_PARENT;
SData.SessionType = SSF_TYPE_DEFAULT;
SData.IconFile = 0;
SData.PgmHandle = 0;
SData.PgmControl = usPgmCtl;
SData.InitXPos = 30;
SData.InitYPos = 40;
SData.InitXSize = 200;
SData.InitYSize = 140;
SData.Reserved = 0;
SData.ObjectBuffer = szObjBuf;
SData.ObjectBuffLen = (ULONG)sizeof(szObjBuf);
if ( (!(arc = DosStartSession(&SData, pulSID, ppid)))
&& (fWait)
)
{
// block on the termination queue, which is written
// to when the subprocess ends
REQUESTDATA rqdata;
ULONG cbData = 0;
PULONG pulData = NULL;
BYTE elpri;
rqdata.pid = qpid;
if (!(arc = DosReadQueue(hq, // in: queue handle
&rqdata, // out: pid and ulData
&cbData, // out: size of data returned
(PVOID*)&pulData, // out: data returned
0, // in: remove first element in queue
0, // in: wait for queue data (block thread)
&elpri, // out: element's priority
0))) // in: event semaphore to be posted
{
if (!rqdata.ulData)
{
// child session ended:
// V0.9.14 (2001-08-03) [umoeller]
// *pulSID = (*pulData) & 0xffff;
if (pusReturn)
*pusReturn = ((*pulData) >> 16) & 0xffff;
}
// else: continue looping
if (pulData)
DosFreeMem(pulData);
}
}
}
if (hq)
DosCloseQueue(hq);
return arc;
}