
/*
 *@@sourcefile dosh.c:
 *      dosh.c contains helper functions that are independent
 *      of a single application, i.e. you can use them in any
 *      program. All of the following work with OS/2 only.
 *
 *      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.
 *
 *      Save dosh.h to enforce a complete recompile of XFolder.
 *      That header is checked for in the main makefile.
 *
 *@@include #define INCL_DOSPROCESS
 *@@include #include <os2.h>
 *@@include #include "dosh.h"
 */

/*
 *      Copyright (C) 1997-99 Ulrich M”ller.
 *      This file is part of the XFolder source package.
 *      XFolder 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 XFolder 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 INCL_DOSDEVIOCTL
#define INCL_DOS
#define INCL_DOSERRORS
#define INCL_WIN
#define INCL_GPI
#include <os2.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>

#include "dosh.h"

// #define _PMPRINTF_
#include "pmprintf.h"

const CHAR acDriveLetters[28] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";

/*
 *@@ doshGetULongTime:
 *      this returns the current time as a ULONG value (in milliseconds).
 *      Useful for stopping how much time the machine has spent in
 *      a certain function. To do this, simply call this function twice,
 *      and subtract the two values, which will give you the execution
 *      time in milliseconds.
 */

ULONG doshGetULongTime(VOID) {
    DATETIME    dt;
    DosGetDateTime(&dt);
    return (10*(dt.hundredths + 100*(dt.seconds + 60*(dt.minutes + 60*(dt.hours)))));
}

/*
 *@@ doshQueryShiftState:
 *      returns TRUE if any of the SHIFT keys are
 *      currently pressed.
 */

BOOL doshQueryShiftState(VOID)
{
    // BYTE abBuf[512];
    HFILE hfKbd;
    ULONG ulAction; //, cbRead, cbWritten;
    SHIFTSTATE      ShiftState;
    ULONG           cbDataLen = sizeof(ShiftState);

    DosOpen("KBD$", &hfKbd, &ulAction, 0,
             FILE_NORMAL, FILE_OPEN,
             OPEN_ACCESS_READONLY | OPEN_SHARE_DENYWRITE,
             (PEAOP2)NULL);

    DosDevIOCtl(hfKbd, IOCTL_KEYBOARD, KBD_GETSHIFTSTATE,
                      NULL, 0, NULL,      // no parameters
                      &ShiftState, cbDataLen, &cbDataLen);

    DosClose(hfKbd);

    return ((ShiftState.fsState & 3) != 0);
}


/*
 *@@ doshIsWarp4:
 *      returns TRUE only if at least OS/2 Warp 4 is running.
 */

BOOL doshIsWarp4(VOID)
{
    ULONG       aulBuf[3];

    DosQuerySysInfo(QSV_VERSION_MAJOR, QSV_VERSION_MINOR,
                    &aulBuf, sizeof(aulBuf));
    return ((aulBuf[0] != 20) || (aulBuf[1] > 30));
}

/*
 *@@ doshEnumDrives:
 *      this copies the drive letters of the drives on the
 *      system to pszBuffer which match the given pszFileSystem
 *      string (for example, "HPFS"). If pszFileSystem
 *      is NULL, all drives are enumerated.
 *      pszBuffer should be 27 characters in size to hold
 *      information for all drives.
 */

VOID doshEnumDrives(PSZ pszBuffer,      // out: drive letters
                    PSZ pszFileSystem)  // in: FS's to match or NULL
{
    CHAR szName[5] = "";
    ULONG ulLogicalDrive = 3, // start with drive C:
          ulFound = 0;        // found drives count
    APIRET arc             = NO_ERROR; // return code

    // go thru the drives, start with C: (== 3)
    do {
        UCHAR nonRemovable=0;
        ULONG parmSize=2;
        ULONG dataLen=1;

        #pragma pack(1)
        struct
        {
            UCHAR dummy,drive;
        } parms;
        #pragma pack()

        parms.drive=(UCHAR)(ulLogicalDrive-1);
        arc = DosDevIOCtl((HFILE)-1,
                        IOCTL_DISK,
                        DSK_BLOCKREMOVABLE,
                        &parms,
                        parmSize,
                        &parmSize,
                        &nonRemovable,
                        1,
                        &dataLen);

        if (arc == NO_ERROR)
        {
            if (nonRemovable) {
                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] = acDriveLetters[ulLogicalDrive];
                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 (pszFileSystem == NULL) {
                        // enum-all mode: always copy
                        pszBuffer[ulFound] = szName[0]; // drive letter
                        ulFound++;
                    } else if (strcmp(pszFSDName, pszFileSystem) == 0) {
                        pszBuffer[ulFound] = szName[0]; // drive letter
                        ulFound++;
                    }
                }
            }
            ulLogicalDrive++;
        }
    } while ( arc == NO_ERROR );

    pszBuffer[ulFound] = '\0';
}

/*
 *@@ doshQueryBootDrive:
 *      returns the letter of the boot drive
 */

CHAR doshQueryBootDrive(VOID)
{
    ULONG ulBootDrive;
    DosQuerySysInfo(5, 5, &ulBootDrive, sizeof(ulBootDrive));
    return (acDriveLetters[ulBootDrive]);
}

/*
 *@@ 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.
 *      Warning: This uses DosDevIOCtl, which has proved
 *      to cause problems with some device drivers for
 *      removeable disks.
 */

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) {
        // parameter packet
        struct {
            UCHAR command, drive;
        } parms;

        // data packet
        UCHAR ucNonRemoveable;

        ULONG ulParmSize = sizeof(parms);
        ULONG ulDataSize = sizeof(ucNonRemoveable);

        parms.drive = (UCHAR)(ulLogicalDrive-1);
        arc = DosDevIOCtl((HFILE)-1,
                        IOCTL_DISK,
                        DSK_BLOCKREMOVABLE,
                        &parms,
                        ulParmSize,
                        &ulParmSize,
                        &ucNonRemoveable,
                        ulDataSize,
                        &ulDataSize);

        if (arc == NO_ERROR)
            *pfFixed = (BOOL)ucNonRemoveable;
    }

    return (arc);
}

/*
 *@@ doshAssertDrive:
 *      this checks for whether the given drive
 *      is currently available. If fSuppress == TRUE,
 *      DosError is used in a critical section to
 *      avoid "Drive not ready" popups.
 */

APIRET doshAssertDrive(ULONG ulLogicalDrive, // in: 1 for A:, 2 for B:, 3 for C:, ...
                       BOOL fSuppress)       // in: suppress-popups flag
{
    FSALLOCATE  fsa;
    APIRET arc = NO_ERROR;

    if (fSuppress) {
        DosError(FERR_DISABLEHARDERR | FERR_DISABLEEXCEPTION);
        DosEnterCritSec();
    }

    arc = DosQueryFSInfo(ulLogicalDrive, FSIL_ALLOC, &fsa, sizeof(fsa));

    if (fSuppress) {
        DosError(FERR_ENABLEHARDERR | FERR_ENABLEEXCEPTION);
        DosExitCritSec();
    }

    return (arc);
}

/*
 *@@ doshQueryDiskFree:
 *       returns the number of bytes remaining on the disk
 *       specified by the given logical drive, or -1 upon errors.
 *       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.
 */

double doshQueryDiskFree(ULONG ulLogicalDrive) // in: 1 for A:, 2 for B:, 3 for C:, ...
{
    FSALLOCATE  fsa;
    double      dbl = -1;

    if (ulLogicalDrive)
        if (DosQueryFSInfo(ulLogicalDrive, FSIL_ALLOC, &fsa, sizeof(fsa))
                == NO_ERROR)
            dbl = (fsa.cSectorUnit * fsa.cbSector * fsa.cUnitAvail);

    return (dbl);
}

/*
 *@@ doshQueryDiskFSType:
 *       copies the file-system type of the given disk object
 *       (HPFS, FAT, CDFS etc.) to pszBuf.
 *       Returns the DOS error code.
 */

APIRET doshQueryDiskFSType(ULONG ulLogicalDrive, // in:  1 for A:, 2 for B:, 3 for C:, ...
                           PSZ   pszBuf)         // out: buffer for FS type
{
    APIRET arc = NO_ERROR;
    CHAR szName[5];

    szName[0] = acDriveLetters[ulLogicalDrive];
    szName[1] = ':';
    szName[2] = '\0';

    {
        ULONG  ulOrdinal       = 0;     // ordinal of entry in name list
        // PBYTE  pszFSDName      = NULL;  // pointer to FS name
        // PBYTE  prgFSAData      = NULL;  // pointer to FS data
        BYTE   fsqBuffer[sizeof(FSQBUFFER2) + (3 * CCHMAXPATH)] = {0};
        ULONG  cbBuffer   = sizeof(fsqBuffer);        // Buffer length)
        PFSQBUFFER2 pfsqBuffer = (PFSQBUFFER2)fsqBuffer;

        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.
            PSZ pszFSDName = (CHAR*)(&pfsqBuffer->szName) + pfsqBuffer->cbName + 1;
            strcpy(pszBuf, pszFSDName);
        }
    }

    return (arc);
}

/*
 *@@ doshQueryDiskType:
 *      this retrieves more information about a given drive,
 *      which is stored in *pulDeviceType. According to the
 *      IBM Control Program Reference, this value can be:
 * <BR>     0  48 TPI low-density diskette drive
 * <BR>     1  96 TPI high-density diskette drive
 * <BR>     2  3.5-inch 720KB diskette drive
 * <BR>     3  8-Inch single-density diskette drive
 * <BR>     4  8-Inch double-density diskette drive
 * <BR>     5  Fixed disk
 * <BR>     6  Tape drive
 * <BR>     7  Other (includes 1.44MB 3.5-inch diskette drive)
 * <BR>     8  R/W optical disk
 * <BR>     9  3.5-inch 4.0MB diskette drive (2.88MB formatted)
 * <BR> Returns DOS error code.
 *      Warning: This uses DosDevIOCtl, which has proved
 *      to cause problems with some device drivers for
 *      removeable disks.
 */

APIRET doshQueryDiskType(ULONG  ulLogicalDrive,      // in: 1 = A:, 2 = B:, ...
                        PULONG pulDeviceType)       // out: device type
{
    APIRET arc = ERROR_INVALID_DRIVE;

    if (ulLogicalDrive) {
        #pragma pack(1)
        // parameter packet
        struct {
            UCHAR command, drive;
        } parms;
        // data packet
        struct {
            UCHAR   aucBPB[31];         // BIOS parameter block
            USHORT  usCylinders;
            UCHAR   ucDeviceType;
            USHORT  usDeviceAttrs;
        } data;
        #pragma pack()

        ULONG ulParmSize = sizeof(parms);
        ULONG ulDataSize = sizeof(data);

        parms.command = 1; // read currently inserted media
        parms.drive=(UCHAR)(ulLogicalDrive-1);

        arc = DosDevIOCtl((HFILE)-1,
                        IOCTL_DISK,
                        DSK_GETDEVICEPARAMS,
                        &parms, ulParmSize, &ulParmSize,
                        &data,  ulDataSize, &ulDataSize);

        if (arc == NO_ERROR)
            *pulDeviceType = (ULONG)(data.ucDeviceType);
    }

    return (arc);
}

/*
 *@@ doshIsFileOnFAT:
 *      returns TRUE if pszFileName resides on
 *      a FAT drive.
 */

BOOL doshIsFileOnFAT(PSZ pszFileName)
{
    BOOL brc = FALSE;
    CHAR szName[5];

    szName[0] = pszFileName[0];
    szName[1] = ':';
    szName[2] = '\0';

    #ifdef DEBUG_TITLECLASH
        _Pmpf(("cmnIsFAT for %s -> %s", pszFileName, szName));
    #endif
    {
        ULONG  ulOrdinal       = 0;     // ordinal of entry in name list
        // PBYTE  pszFSDName      = NULL;  // pointer to FS name
        // PBYTE  prgFSAData      = NULL;  // pointer to FS data
        APIRET rc              = NO_ERROR; // return code
        BYTE   fsqBuffer[sizeof(FSQBUFFER2) + (3 * CCHMAXPATH)] = {0};
        ULONG  cbBuffer   = sizeof(fsqBuffer);        // Buffer length)
        PFSQBUFFER2 pfsqBuffer = (PFSQBUFFER2)fsqBuffer;

        rc = 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 (rc == 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.
            PSZ pszFSDName = (PSZ)&(pfsqBuffer->szName) + pfsqBuffer->cbName + 1;
            #ifdef DEBUG_TITLECLASH
                _Pmpf(("  cmnIsFAT -> File system: %s", pszFSDName));
            #endif
            if (strncmp(pszFSDName, "FAT", 3) == 0)
                brc = TRUE;
        }
    }
    return (brc);
}

/*
 *@@ doshIsValidFileName:
 *      this returns NO_ERROR only if pszFile is a valid file name.
 *      This may include a full path.
 *      If a drive letter is specified, this checks for whether
 *      that drive is a FAT drive and adjust the checks accordingly,
 *      i.e. 8+3 syntax and more invalid characters.
 *      If no drive letter is specified, this check is performed
 *      for the current drive.
 *      Note: this performs syntactic checks only. This does not
 *      check for whether the specified path components exist.
 *      However, it _is_ checked for whether the given drive
 *      exists.
 *      If an error is found, the corresponding DOS error code
 *      is returned:
 * <BR>     ERROR_INVALID_DRIVE
 * <BR>     ERROR_FILENAME_EXCED_RANGE  (on FAT: no 8+3 filename)
 * <BR>     ERROR_INVALID_NAME          (invalid character)
 */

APIRET doshIsValidFileName(PSZ pszFile)
{
    CHAR    szPath[CCHMAXPATH+4] = " :";
    CHAR    szComponent[CCHMAXPATH];
    PSZ     p1, p2;
    BOOL    fIsFAT = FALSE;
    PSZ     pszInvalid;

    // check drive first
    if (*(pszFile + 1) == ':')
    {
        CHAR cDrive = toupper(*pszFile);
        // drive specified:
        strcpy(szPath, pszFile);
        szPath[0] = toupper(*pszFile);
        if (doshQueryDiskFree(cDrive - 'A' + 1) == -1)
            return (ERROR_INVALID_DRIVE);
    }
    else
    {
        // no drive specified: take current
        ULONG   ulDriveNum = 0,
                ulDriveMap = 0;
        DosQueryCurrentDisk(&ulDriveNum, &ulDriveMap);
        szPath[0] = ((UCHAR)ulDriveNum) + 'A' - 1;
        szPath[1] = ':';
        strcpy(&szPath[2], pszFile);
    }

    fIsFAT = doshIsFileOnFAT(szPath);

    pszInvalid = (fIsFAT)
                              ? "<>|+=:;,\"/[] "  // invalid characters in FAT
                              : "<>|:\"/";        // invalid characters in IFS's

    // now separate path components
    p1 = &szPath[2];       // advance past ':'

    do {

        if (*p1 == '\\')
            p1++;

        p2 = strchr(p1, '\\');
        if (p2 == NULL)
            p2 = p1 + strlen(p1);

        if (p1 != p2)
        {
            LONG    lDotOfs = -1,
                    lAfterDot = -1;
            ULONG   cbFile,
                    ul;
            PSZ     pSource = szComponent;

            strncpy(szComponent, p1, p2-p1);
            szComponent[p2-p1] = 0;
            cbFile = strlen(szComponent);

            // now check each path component
            for (ul = 0; ul < cbFile; ul++)
            {
                if (fIsFAT)
                {
                    // on FAT: only 8 characters allowed before dot
                    if (*pSource == '.')
                    {
                        lDotOfs = ul;
                        lAfterDot = 0;
                        if (ul > 7)
                            return (ERROR_FILENAME_EXCED_RANGE);
                    }
                }
                // and check for invalid characters
                if (strchr(pszInvalid, *pSource) != NULL)
                    return (ERROR_INVALID_NAME);

                pSource++;

                // on FAT, allow only three chars after dot
                if (fIsFAT)
                    if (lAfterDot != -1)
                    {
                        lAfterDot++;
                        if (lAfterDot > 3)
                            return(ERROR_FILENAME_EXCED_RANGE);
                    }
            }

            // we are still missing the case of a FAT file
            // name without extension; if so, check whether
            // the file stem is <= 8 chars
            if (fIsFAT)
                if (lDotOfs == -1)  // dot not found:
                    if (cbFile > 8)
                        return (ERROR_FILENAME_EXCED_RANGE);
        }

        // go for next component
        p1 = p2+1;
    } while (*p2);

    return (NO_ERROR);
}

/*
 *@@ doshMakeRealName:
 *      this copies pszSource to pszTarget, replacing
 *      all characters which are not supported by file
 *      systems with cReplace.
 *      pszTarget must be at least the same size as pszSource.
 *      If (fIsFAT), the file name will be made FAT-compliant (8+3).
 *      Returns TRUE if characters were replaced.
 */

BOOL doshMakeRealName(PSZ pszTarget,    // out: new real name
                      PSZ pszSource,    // in: filename to translate
                      CHAR cReplace,    // in: replacement char for invalid
                                        //     characters (e.g. '!')
                      BOOL fIsFAT)      // in: make-FAT-compatible flag
{
    ULONG ul,
          cbSource = strlen(pszSource);
    LONG  lDotOfs = -1,
          lAfterDot = -1;
    BOOL  brc = FALSE;
    PSZ   pSource = pszSource,
          pTarget = pszTarget,
          pszInvalid = (fIsFAT)
                            ? "<>|+=:;,\"/\\[] "  // invalid characters in FAT
                            : "<>|:\"/\\"; // invalid characters in IFS's

    for (ul = 0; ul < cbSource; ul++)
    {
        if (fIsFAT) {
            // on FAT: truncate filename if neccessary
            if (*pSource == '.')
            {
                lDotOfs = ul;
                lAfterDot = 0;
                if (ul > 7) {
                    // only 8 characters allowed before dot,
                    // so set target ptr to dot pos
                    pTarget = pszTarget+8;
                }
            }
        }
        // and replace invalid characters
        if (strchr(pszInvalid, *pSource) == NULL)
            *pTarget = *pSource;
        else {
            *pTarget = cReplace;
            brc = TRUE;
        }
        pTarget++;
        pSource++;

        // on FAT, allow only three chars after dot
        if (fIsFAT)
            if (lAfterDot != -1) {
                lAfterDot++;
                if (lAfterDot > 3)
                    break;
            }
    }
    *pTarget = '\0';
    #ifdef DEBUG_TITLECLASH
        _Pmpf(("      strMakeRealName: returning %s", pszTarget));
    #endif

    if (fIsFAT)
    {
        // we are still missing the case of a FAT file
        // name without extension; if so, check whether
        // the file stem is <= 8 chars
        if (lDotOfs == -1)  // dot not found:
            if (cbSource > 8)
                *(pszTarget+8) = 0; // truncate

        // convert to upper case
        strupr(pszTarget);
    }

    return (brc);
}

/*
 *@@ doshQueryFileSize:
 *      returns the size of an already opened file.
 */

ULONG doshQueryFileSize(HFILE hFile)
{
    FILESTATUS3 fs3;
    if (DosQueryFileInfo(hFile, FIL_STANDARD, &fs3, sizeof(fs3)))
        return (0);
    else
        return (fs3.cbFile);
}

/*
 *@@ doshQueryPathSize:
 *      returns the size of any file,
 *      or 0 if the file could not be
 *      found.
 */

ULONG doshQueryPathSize(PSZ pszFile)
{
    FILESTATUS3 fs3;
    if (DosQueryPathInfo(pszFile, FIL_STANDARD, &fs3, sizeof(fs3)))
        return (0);
    else
        return (fs3.cbFile);
}

/*
 *@@ doshQueryDirExist:
 *      returns TRUE if the given directory
 *      exists.
 */

BOOL doshQueryDirExist(PSZ pszDir)
{
    FILESTATUS3 fs3;
    APIRET arc = DosQueryPathInfo(pszDir, FIL_STANDARD, &fs3, sizeof(fs3));
    if (arc == NO_ERROR)
        // 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(PSZ pszPath)
{
    APIRET  arc0 = NO_ERROR;
    CHAR    path[CCHMAXPATH];
    CHAR    *cp, c;
    ULONG   cbPath;

    strcpy(path, pszPath);
    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;

    // Now, make the directories
    while (*cp != 0)
    {
        if (*cp == '\\')
        {
            c = *cp;
            *cp = 0;
            if (!doshQueryDirExist(path))
            {
                APIRET arc = DosCreateDir(path, 0);
                if (arc != NO_ERROR)
                {
                    arc0 = arc;
                    break;
                }
            }
            *cp = c;
        }
        cp++;
    }
    return (arc0);
}

/*
 *@@ doshSetCurrentDir:
 *      sets the current working directory
 *      to the given path.
 *      As opposed to DosSetCurrentDir, this
 *      one will change the current drive
 *      also, if one is specified.
 */

APIRET doshSetCurrentDir(PSZ pszDir)
{
    if (pszDir)
    {
        if (*pszDir != 0)
            if (*(pszDir+1) == ':')
            {
                // drive given:
                CHAR    cDrive = toupper(*(pszDir));
                APIRET  arc;
                // change drive
                arc = DosSetDefaultDisk( (ULONG)(cDrive - 'A' + 1) );
                        // 1 = A:, 2 = B:, ...
                if (arc != NO_ERROR)
                    return (arc);
            }

        return (DosSetCurrentDir(pszDir));
    }
    return (ERROR_INVALID_PARAMETER);
}

/*
 *@@ doshReadTextFile:
 *      reads a text file from disk, allocates memory
 *      via malloc() and returns a pointer to this
 *      buffer (or NULL upon errors). Specify in
 *      ulExtraMemory how much extra memory (in addition
 *      to the file's size) should be allocated to
 *      allow for text manipulation.
 *      Returns NULL if an error occured.
 */

PSZ doshReadTextFile(PSZ pszFile, ULONG ulExtraMemory)
{
    ULONG   ulSize,
            ulBytesRead = 0,
            ulAction, ulLocal;
    HFILE   hFile;
    PSZ     pszContent = NULL;
    APIRET rc = DosOpen(pszFile,
                 &hFile,
                 &ulAction,                      // action taken
                 5000L,                          // primary allocation size
                 FILE_ARCHIVED | FILE_NORMAL,    // file attribute
                 /* OPEN_ACTION_CREATE_IF_NEW | */
                 OPEN_ACTION_OPEN_IF_EXISTS,     // open flags
                 OPEN_FLAGS_NOINHERIT |
                 OPEN_SHARE_DENYNONE  |
                 OPEN_ACCESS_READONLY,               // read-write mode
                 NULL);                          // no EAs

    if (rc == NO_ERROR) {
        ulSize = doshQueryFileSize(hFile);
        pszContent = (PSZ)malloc(ulSize+10+ulExtraMemory);
        rc = DosSetFilePtr(hFile,
                            0L,
                            FILE_BEGIN,
                            &ulLocal);
        rc = DosRead(hFile,
                      pszContent,
                      ulSize,
                      &ulBytesRead);
        DosClose(hFile);
        *(pszContent+ulBytesRead) = '\0';
    }
    return (pszContent);
}

/*
 *@@ 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().
 *      <P><B>Example:</B> returns "C:\CONFIG.002" for input
 *      "C:\CONFIG.SYS" if "C:\CONFIG.001" already exists.
 */

PSZ doshCreateBackupFileName(PSZ pszExisting)
{
    // CHAR    szTemp[CCHMAXPATH];
    PSZ     pszNew = strdup(pszExisting),
            pszLastDot = strrchr(pszNew, '.');
    ULONG   ulCount = 1;
    CHAR    szCount[5];
    do {
        sprintf(szCount, ".%03d", ulCount);
        strcpy(pszLastDot, szCount);
        ulCount++;
    } while (doshQueryPathSize(pszNew) != 0);
    return (pszNew);
}

/*
 *@@ doshWriteTextFile:
 *      writes a text file to disk; pszFile must contain the
 *      whole path and filename.
 *      An existing file will be backed up if (fBackup == TRUE),
 *      using doshCreateBackupFileName; otherwise the file
 *      will be overwritten.
 *      Returns the number of bytes written or 0 upon errors.
 */

ULONG doshWriteTextFile(PSZ pszFile,        // in: file name
                        PSZ pszContent,     // in: text to write
                        BOOL fBackup)       // in: create-backup flag
{
    ULONG   ulWritten = 0,
            ulAction, ulLocal;
    HFILE   hFile;
    APIRET rc;

    if (fBackup) {
        PSZ     pszBackup = doshCreateBackupFileName(pszFile);
        DosCopy(pszFile, pszBackup, DCPY_EXISTING);
        free(pszBackup);
    }

    rc = DosOpen(pszFile,
                 &hFile,
                 &ulAction,                      // action taken
                 5000L,                          // 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_WRITE_THROUGH      // write immediately w/out cache
                 | OPEN_FLAGS_NO_CACHE           // do not store in cache
                 | OPEN_FLAGS_SEQUENTIAL         // sequential, not random access
                 | OPEN_SHARE_DENYNONE
                 | OPEN_ACCESS_WRITEONLY,        // read-write mode
                 NULL);                          // no EAs

    if (rc == NO_ERROR)
    {
        ULONG ulSize = strlen(pszContent);      // exclude 0 byte

        rc = DosSetFilePtr(hFile,
                            0L,
                            FILE_BEGIN,
                            &ulLocal);
        if (rc == NO_ERROR) {
            rc = DosWrite(hFile,
                          pszContent,
                          ulSize,
                          &ulWritten);
            if (rc == NO_ERROR)
                rc = DosSetFileSize(hFile, ulSize);
        }

        DosClose(hFile);

        if (rc != NO_ERROR)
            ulWritten = 0;
    }

    return (ulWritten);
}

/*
 *@@ doshOpenLogFile:
 *      this opens a log file in the root directory of
 *      the boot drive; it is titled pszFilename, and
 *      the file handle is returned.
 */

HFILE doshOpenLogFile(PSZ pszFileName)
{
    APIRET  rc;
    CHAR    szFileName[CCHMAXPATH];
    HFILE   hfLog;
    ULONG   ulAction;
    ULONG   ibActual;

    sprintf(szFileName, "%c:\\%s", doshQueryBootDrive(), pszFileName);
    rc = DosOpen(szFileName, &hfLog, &ulAction, 0,
                 FILE_NORMAL,
                 OPEN_ACTION_CREATE_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS,
                 OPEN_ACCESS_WRITEONLY | OPEN_SHARE_DENYWRITE,
                 (PEAOP2)NULL);
    if (rc == NO_ERROR) {
        DosSetFilePtr(hfLog, 0, FILE_END, &ibActual);
        return (hfLog);
    } else
        return (0);
}

/*
 * doshWriteToLogFile
 *      writes a string to a log file, adding a
 *      leading timestamp.
 */

APIRET doshWriteToLogFile(HFILE hfLog, PSZ psz)
{
    if (hfLog) {
        DATETIME dt;
        CHAR szTemp[2000];
        ULONG   cbWritten;
        DosGetDateTime(&dt);
        sprintf(szTemp, "Time: %02d:%02d:%02d %s",
            dt.hours, dt.minutes, dt.seconds,
            psz);
        return (DosWrite(hfLog, psz, strlen(psz), &cbWritten));
    }
    else return (ERROR_INVALID_HANDLE);
}

/*
 *@@ doshQuickStartSession:
 *      shortcut to DosStartSession w/out having to deal
 *      with all the parameters. This one starts a session
 *      as a child session and can optionally wait for the
 *      session to end (using a termination queue), if
 *      Wait == TRUE; otherwise, this function returns
 *      immediately.
 *      Returns the error code of DosStartSession.
 */

APIRET doshQuickStartSession(PSZ pszPath,     // in: program to start
                             PSZ pszParams,   // in: parameters for program
                             BOOL Visible,    // in: show program?
                             BOOL Wait,       // in: wait for termination?
                             PULONG pulSID,   // out: session ID
                             PPID ppid)       // out: process ID
{
    APIRET rc;
    REQUESTDATA rqdata;
    ULONG DataLength; PULONG DataAddress; BYTE elpri;
    int er;
    // Queue Stuff
    HQUEUE hq;
    PID qpid=0;
    STARTDATA   SData;
    CHAR        szObjBuf[CCHMAXPATH];

    if (Wait) {
        if (    (er=DosCreateQueue(&hq, QUE_FIFO|QUE_CONVERT_ADDRESS, "\\queues\\kfgstart.que"))
             != NO_ERROR)
        {
            char str[80];
            sprintf(str, "Create %ld\n", er);
        }

        if ((er=DosOpenQueue(&qpid, &hq, "\\queues\\kfgstart.que")) != NO_ERROR)
        {
            char str[80];
            sprintf(str, "Open %ld\n", er);
        }
    }

    SData.Length  = sizeof(STARTDATA);
    SData.Related = SSF_RELATED_CHILD; //INDEPENDENT;
    SData.FgBg    = SSF_FGBG_FORE;
    SData.TraceOpt = SSF_TRACEOPT_NONE;

    SData.PgmTitle = pszPath;       // title for window
    SData.PgmName = pszPath;
    SData.PgmInputs = pszParams;

    SData.TermQ = (Wait) ? "\\queues\\kfgstart.que" : NULL;
    SData.Environment = 0;
    SData.InheritOpt = SSF_INHERTOPT_SHELL;
    SData.SessionType = SSF_TYPE_DEFAULT;
    SData.IconFile = 0;
    SData.PgmHandle = 0;

    SData.PgmControl = ((Visible) ? SSF_CONTROL_VISIBLE : SSF_CONTROL_INVISIBLE);
    SData.InitXPos  = 30;
    SData.InitYPos  = 40;
    SData.InitXSize = 200;
    SData.InitYSize = 140;
    SData.Reserved = 0;
    SData.ObjectBuffer  = (CHAR*)&szObjBuf;
    SData.ObjectBuffLen = (ULONG)sizeof(szObjBuf);

    rc = DosStartSession(&SData, pulSID, ppid);

    if (rc == NO_ERROR) {
        if (Wait) {
            rqdata.pid=qpid;
            DosReadQueue(hq, &rqdata, &DataLength, (PVOID*)&DataAddress, 0, 0, &elpri, 0);
            DosCloseQueue(hq);
        }
    }
    return (rc);
}

/*
 *@@ doshSetPathAttr:
 *      sets the file attributes of pszFile,
 *      which can be fully qualified.
 *      fAttr can be:
 * <BR>         FILE_ARCHIVED
 * <BR>         FILE_READONLY
 * <BR>         FILE_SYSTEM
 * <BR>         FILE_HIDDEN
 * <BR> Note that this sets all the given
 *      attributes; the existing attributes
 *      are lost.
 */

APIRET doshSetPathAttr(PSZ pszFile, ULONG fAttr)
{
    FILESTATUS3 fs3;
    APIRET rc = DosQueryPathInfo(pszFile,
            FIL_STANDARD,
            &fs3,
            sizeof(fs3));

    if (rc == NO_ERROR) {
        fs3.attrFile = fAttr;
        rc = DosSetPathInfo(pszFile,
                FIL_STANDARD,
                &fs3,
                sizeof(fs3),
                DSPI_WRTTHRU);
    }
    return (rc);
}


