
/*
 *@@sourcefile level.c:
 *      contains helper functions for querying SYSLEVEL files.
 *
 *      This is all new with V0.9.2.
 *
 *      Function prefixes:
 *      --  lvl*    syslevel helpers
 *
 *@@added V0.9.2 (2000-03-08) [umoeller]
 *@@header "helpers\level.h"
 */

/*
 *      Copyright (C) 1994 Martin Lafaix (EDM/2 2-05).
 *      Copyright (C) 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_DOSFILEMGR
#include <os2.h>

#include <stdlib.h>
#include <string.h>

#include "setup.h"

#define SYSLEVEL_PRIVATE
#include "helpers\level.h"

// redefine the FIELDOFFSET macro; the one
// in the OS/2 header files doesn't work with C++
#undef FIELDOFFSET
#define FIELDOFFSET(type, field)    ((ULONG)&(((type *)0)->field))

/*
 *@@category: Helpers\Control program helpers\SYSLEVEL parsing
 *      helpers for parsing SYSLEVEL files. See lvlOpenLevelFile.
 */

/* ******************************************************************
 *
 *   Interface
 *
 ********************************************************************/

/*
 *@@ lvlOpenLevelFile:
 *      finds, opens, or creates a SYSLEVEL file. *phFile
 *      receives the DosOpen file handle, which can then be
 *      passed to lvlQueryLevelFileData and such. Alternatively,
 *      you can use DosOpen directly.
 *
 *      ulOpenMode can be:
 *
 *      -- OLF_OPEN:
 *              open the specified file. The default behavior is to scan
 *              the current disk, starting from the current directory.
 *              It returns the first SYSLEVEL file whose extension
 *              matches pszName. You can override this by
 *              combining OLF_OPEN with OLF_SCANDISK or
 *              OLF_CHECKID.
 *
 *      -- OLF_SCANDISKS:
 *              scans all disks (starting from C:). Use this flag to
 *              modify the default OLF_OPEN behavior.
 *
 *      -- OLF_CHECKID:
 *              finds file(s) whose ID matches the specified one. Use
 *              this flag to override the default OLF_OPEN behavior.
 *
 *      -- OLF_CREATE:
 *              creates the specified file in the current directory. A
 *              valid pszName and ID should be provided.
 */

APIRET lvlOpenLevelFile(PSZ pszName,        // in: syslevel file name extension
                        PHFILE phFile,      // out: Dos file handle
                        ULONG ulOpenMode,   // in: OLF_* flags
                        PSZ pszCID)         // in: if OLF_CHECKID is specified, the ID
{
    APIRET rc = 0;
    ULONG ulAction;
    CHAR achFileName[CCHMAXPATHCOMP];

    switch (ulOpenMode)
    {
        case OLF_OPEN:
        case OLF_OPEN | OLF_CHECKID:
            if (pszName || pszCID)
            {
                rc = _findsyslevelfile(pszName, pszCID, achFileName);

                if (rc == 18)
                    rc = _searchsubdirs(pszName, pszCID, achFileName);

                if (rc == 0)
                    rc = DosOpen(achFileName,
                                 phFile,
                                 &ulAction,
                                 0,
                                 FILE_NORMAL,
                                 FILE_OPEN,
                                 OPEN_ACCESS_READWRITE | OPEN_SHARE_DENYWRITE,
                                 0);

                if (rc == 18)
                    rc = 2;
            }
            else
                rc = ERROR_INVALID_PARAMETER;
            break;

        case OLF_SCANDISKS:
        case OLF_SCANDISKS | OLF_CHECKID:
        case OLF_SCANDISKS | OLF_OPEN:
        case OLF_SCANDISKS | OLF_OPEN | OLF_CHECKID:
            if (pszName || pszCID)
            {
                ULONG ulDrive, ulDriveMap, ulCurrent, ulFound = 0;

                rc = DosQueryCurrentDisk(&ulDrive, &ulDriveMap);
                ulCurrent = _firstdrive(ulDriveMap);

                while (ulCurrent && !ulFound)
                {
                    rc = _findsyslevelfile(pszName, pszCID, achFileName);
                    if (rc == 18)
                        rc = _searchsubdirs(pszName, pszCID, achFileName);

                    if (rc == 0)
                    {
                        ulFound = 1;

                        rc = DosOpen(achFileName,
                                     phFile,
                                     &ulAction,
                                     0,
                                     FILE_NORMAL,
                                     FILE_OPEN,
                                     OPEN_ACCESS_READWRITE | OPEN_SHARE_DENYWRITE,
                                     0);
                    }
                    else
                        ulCurrent = _nextdrive(ulDriveMap, ulCurrent);
                }

                DosSetDefaultDisk(ulDrive);
                if (ulFound == 0)
                    rc = 2;
            }
            else
                rc = ERROR_INVALID_PARAMETER;
            break;

        case OLF_CREATE:
            if (pszName && pszCID)
            {
                SYSLEVELHEADER slh;
                SYSLEVELDATA sld;

                strcat(strcpy(achFileName, "syslevel."), pszName);

                rc = DosOpen(achFileName,
                             phFile,
                             &ulAction,
                             0,
                             FILE_NORMAL,
                             FILE_CREATE,
                             OPEN_ACCESS_READWRITE | OPEN_SHARE_DENYWRITE,
                             0);

                slh.h_magic[0] = slh.h_magic[1] = 0xFF;
                strcpy((PSZ)slh.h_name, "SYSLEVEL");
                for (ulAction = 0; ulAction < sizeof(slh.h_reserved1); ulAction++)
                    slh.h_reserved1[ulAction] = 0;
                slh.h_updated = 0;
                for (ulAction = 0; ulAction < sizeof(slh.h_reserved2); ulAction++)
                    slh.h_reserved2[ulAction] = 0;
                slh.h_data = sizeof(SYSLEVELHEADER);

                if (rc == 0)
                    rc = _writesyslevelheader(*phFile, &slh);

                memset(&sld, 0, sizeof(sld));
                sld.d_kind[0] = SLK_STANDARD;
                sld.d_reserved3 = sld.d_reserved4 = 0x5F;
                memcpy(sld.d_cid, pszCID, sizeof(sld.d_cid));

                if (rc == 0)
                    rc = DosWrite(*phFile, &sld, sizeof(SYSLEVELDATA), &ulAction);
            }
            else
                rc = ERROR_INVALID_PARAMETER;
            break;
        default:
            rc = ERROR_INVALID_PARAMETER;
            break;
    }

    return rc;
}

/*
 *@@ lvlQueryLevelFile:
 *      finds all SYSLEVEL files on the system and
 *      writes them to pBuffer.
 */

APIRET lvlQueryLevelFile(PSZ pszName,
                         PSZ pszCID,
                         PVOID pBuffer,
                         ULONG ulBufSize)
{
    ULONG ulDrive, ulDriveMap, ulCurrent;
    CHAR achFileName[CCHMAXPATHCOMP];
    APIRET rc = DosQueryCurrentDisk(&ulDrive, &ulDriveMap);

    strcat(strcpy(achFileName, "syslevel."),
           (pszName == NULL) ? (PSZ) "???" : pszName);
    memset(pBuffer, 0, ulBufSize);

    ulCurrent = _firstdrive(ulDriveMap);

    while (ulCurrent && (rc == 0))
    {
        rc = _findallsyslevelfiles(achFileName, pszCID, &pBuffer, &ulBufSize);

        if (rc == 0)
            ulCurrent = _nextdrive(ulDriveMap, ulCurrent);
    }

    if (rc == 0)
        rc = DosSetDefaultDisk(ulDrive);

    return rc;
}

/*
 *@@ lvlQueryLevelFileData:
 *      parses syslevel data from a SYSLEVEL file previously
 *      opened with lvlOpenLevelFile. Alternatively, you can
 *      open the SYSLEVEL file directly using DosOpen.
 *
 *      ulWhat can be:
 *
 *      -- QLD_MAJORVERSION
 *              Query (or update) the major version field. It's a
 *              one-character field. It should be in the range '0'-'9'.
 *              The value is placed in (or taken from) the first
 *              character of pBuffer. (Buffer size should be at least 1.)
 *
 *      -- QLD_MINORVERSION
 *              Query (or update) the minor version field. It's a
 *              two-character field. It should be in range '00'-'99'. The
 *              value is placed in (or taken from) the first two chars
 *              of pBuffer. (Buffer size should be at least 2.)
 *
 *      -- QLD_REVISION
 *              Query (or update) the revision field. It's should fit in
 *              a character. If it's '0', there's no revision available. It
 *              can be a letter as well as a digit. The value is placed
 *              in (or taken from) the first character of pBuffer.
 *              (Buffer size should be at least 1.)
 *
 *      -- QLD_KIND
 *              Query (or update) the kind field. The value is placed
 *              in (or taken from) the first character of *pBuffer.
 *              (Buffer size should be at least 1.)
 *
 *      -- QLD_CURRENTCSD
 *              Query (or update) the current CSD level (when you
 *              update this field, its old value is copied to the old
 *              CSD level field). It's a seven-character field, and it
 *              does not have to be null-terminated. The value is
 *              placed in (or taken from) the first seven characters of
 *              pBuffer. (Buffer size should be at least 7.)
 *
 *      -- QLD_PREVIOUSCSD
 *              Query the previous CSD level. You can't update this
 *              field. The value is placed in the first seven chars of
 *              pBuffer. (Buffer size should be at least 7.)
 *              Note: CSD levels are not null-terminated. Be careful
 *              when using such a returned value.
 *
 *      -- QLD_TITLE
 *              Query (or update) the component title field. It's an
 *              eighty-character string (required ending null
 *              included). The value is placed in (or taken from) the
 *              first eighty characters of pBuffer. On input, the buffer
 *              size should be at least 80. On output, the buffer size
 *              can exceed 80, but the written string is truncated
 *              (and null-terminated) to eighty characters.
 *
 *      -- QLD_ID
 *              Query (or update) the component ID field. It's a
 *              nine-character field. It does not have to be
 *              null-terminated. The value is placed in (or taken
 *              from) the first nine characters of pBuffer. (Buffer size
 *              should be at least 9.)
 *              Note: IDs are not null-terminated. Be careful when
 *              using such a returned value.
 *
 *      -- QLD_TYPE
 *              Query (or update) the component type field.
 */

APIRET lvlQueryLevelFileData(HFILE hFile,       // in: file handle returned by lvlOpenLevelFile
                             ULONG ulWhat,      // in: QLD* flags
                             PVOID pBuffer,     // in/out: data
                             ULONG ulBufSize,   // in: sizeof(*pBuffer)
                             PULONG pulSize)    // out: size required if
                                                // ERROR_INSUFFICIENT_BUFFER is returned;
                                                // otherwise bytes written
{
    APIRET rc = _locatesysleveldata(hFile);
    PSYSLEVELDATA psld = _allocsysleveldata(hFile);

    if (rc == 0)
        rc = pBuffer ? rc : ERROR_INVALID_PARAMETER;
    if (rc == 0)
        rc = _readsysleveldata(hFile, psld);

    if (rc == 0)
        switch (ulWhat)
        {
            case QLD_MAJORVERSION:
                if (ulBufSize >= 1)
                    _achBuffer[0] = '0' + (psld->d_version[0] >> 4);
                else
                    rc = ERROR_INSUFFICIENT_BUFFER;
                _setsize(1);
                break;
            case QLD_MINORVERSION:
                if (ulBufSize >= 2)
                {
                    _achBuffer[0] = '0' + (psld->d_version[0] & 0x0F);
                    _achBuffer[1] = '0' + psld->d_version[1];
                }
                else
                    rc = ERROR_INSUFFICIENT_BUFFER;
                _setsize(2);
                break;
            case QLD_REVISION:
                if (ulBufSize >= 1)
                    _achBuffer[0] = '0' + psld->d_revision[0];
                else
                    rc = ERROR_INSUFFICIENT_BUFFER;
                _setsize(1);
                break;
            case QLD_KIND:
                _getfield(d_kind);
                break;
            case QLD_CURRENTCSD:
                _getfield(d_clevel);
                break;
            case QLD_PREVIOUSCSD:
                _getfield(d_plevel);
                break;
            case QLD_TITLE:
                _getfield(d_title);
                break;
            case QLD_ID:
                _getfield(d_cid);
                break;
            case QLD_TYPE:
                if (ulBufSize >= strlen((PSZ)psld->d_type))
                    memcpy(pBuffer, psld->d_type, strlen((PSZ)psld->d_type));
                else
                    rc = ERROR_INSUFFICIENT_BUFFER;
                _setsize(strlen((PSZ)psld->d_type));
                break;
            default:
                rc = ERROR_INVALID_PARAMETER;
                break;
        }

    _freesysleveldata(psld);

    return rc;
}

/*
 *@@ lvlWriteLevelFileData:
 *      writes data to a SYSLEVEL file.
 */

APIRET lvlWriteLevelFileData(HFILE hFile,
                             ULONG ulWhat,
                             PVOID pBuffer,
                             ULONG ulBufSize,
                             PULONG pulSize)
{
    APIRET rc = _locatesysleveldata(hFile);
    PSYSLEVELDATA psld = _allocsysleveldata(hFile);

    if (rc == 0)
        rc = pBuffer ? rc : ERROR_INVALID_PARAMETER;
    if (rc == 0)
        rc = _readsysleveldata(hFile, psld);

    if (rc == 0)
        switch (ulWhat)
        {
            case QLD_MAJORVERSION:
                if (ulBufSize >= 1)
                {
                    psld->d_version[0] &= 0x0F;
                    psld->d_version[0] |= (_achBuffer[0] - '0') << 4;
                }
                else
                    rc = ERROR_INSUFFICIENT_BUFFER;
                _setsize(1);
                break;
            case QLD_MINORVERSION:
                if (ulBufSize >= 2)
                {
                    psld->d_version[0] &= 0xF0;
                    psld->d_version[0] |= (_achBuffer[0] - '0') && 0x0F;
                    psld->d_version[1] = _achBuffer[1] - '0';
                }
                else
                    rc = ERROR_INSUFFICIENT_BUFFER;
                _setsize(2);
                break;
            case QLD_REVISION:
                _setfield(d_revision);
                break;
            case QLD_KIND:
                _setfield(d_kind);
                break;
            case QLD_CURRENTCSD:
                memcpy(psld->d_plevel, psld->d_clevel, sizeof(psld->d_plevel));
                _setfield(d_clevel);
                break;
            case QLD_TITLE:
                memcpy(psld->d_title, pBuffer, min(ulBufSize, sizeof(psld->d_title)));
                _setsize(min(ulBufSize, sizeof(psld->d_title)));
                break;
            case QLD_ID:
                _setfield(d_cid);
                break;
            case QLD_TYPE:
                break;
            default:
                rc = ERROR_INVALID_PARAMETER;
                break;
        }

    if (rc == 0)
        rc = _locatesysleveldata(hFile);
    if (rc == 0)
        rc = _writesysleveldata(hFile, psld);

    _freesysleveldata(psld);

    return rc;
}

/* ******************************************************************
 *
 *   Local helper functions
 *
 ********************************************************************/

/*
 * _readsysleveldata:
 *
 */

STATIC APIRET _readsysleveldata(HFILE hFile,
                                PSYSLEVELDATA psld)
{
    ULONG ulSize;

    return DosRead(hFile, psld, sizeof(SYSLEVELDATA), &ulSize);
}

/*
 * _writesysleveldata:
 *
 */

STATIC APIRET _writesysleveldata(HFILE hFile,
                                 PSYSLEVELDATA psld)
{
    ULONG ulSize;

    return DosWrite(hFile, psld, sizeof(SYSLEVELDATA), &ulSize);
}

/*
 * _locatesysleveldata:
 *
 */

STATIC APIRET _locatesysleveldata(HFILE hFile)
{
    ULONG ulPos, ulData;
    APIRET rc = DosSetFilePtr(hFile,
                              FIELDOFFSET(SYSLEVELHEADER, h_data),
                              FILE_BEGIN,
                              &ulPos);

    rc = rc ? rc : DosRead(hFile, &ulData, sizeof(ulData), &ulPos);

    return rc ? rc : DosSetFilePtr(hFile, ulData, FILE_BEGIN, &ulPos);
}

/*
 * _allocsysleveldata:
 *
 */

STATIC PSYSLEVELDATA _allocsysleveldata(HFILE hFile)
{
    return (PSYSLEVELDATA)malloc(sizeof(SYSLEVELDATA));
}

/*
 * _freesysleveldata:
 *
 */

STATIC VOID _freesysleveldata(PSYSLEVELDATA psld)
{
    if (psld)
        free(psld);
}

/*
 * _writesyslevelheader:
 *
 */

STATIC APIRET _writesyslevelheader(HFILE hFile,
                                   PSYSLEVELHEADER pslh)
{
    ULONG ulSize;

    return DosWrite(hFile, pslh, sizeof(SYSLEVELHEADER), &ulSize);
}

/*
 * _isasyslevelfile:
 *
 */

STATIC BOOL _isasyslevelfile(PSZ pszFile,
                             PSZ pszCID)
{
    HFILE hFile;
    ULONG ulAction;
    SYSLEVELHEADER slh;
    APIRET rc = DosOpen(pszFile,
                        &hFile,
                        &ulAction,
                        0,
                        FILE_NORMAL,
                        FILE_OPEN,
                        OPEN_ACCESS_READONLY | OPEN_SHARE_DENYWRITE,
                        0);

    if (rc == 0)
        rc = DosRead(hFile, &slh, sizeof(slh), &ulAction);

    ulAction = (slh.h_magic[0] == 0xFF) && (slh.h_magic[1] == 0xFF);
    if (ulAction)
        ulAction = strcmp((PSZ)slh.h_name, "SYSLEVEL") == 0;
    if (ulAction)
        if (pszCID)
        {
            CHAR achCID[9];
            ULONG ulCID;

            rc = lvlQueryLevelFileData(hFile, QLD_ID, achCID, 9, &ulCID);
            ulAction = (strcmp(achCID, pszCID) == 0);
        }

    rc = DosClose(hFile);

    return ulAction;
}

/*
 * _getfullname:
 *
 */

STATIC APIRET _getfullname(PVOID pBuffer,
                           PSZ pszFileName)
{
    ULONG ulDrive, ulDriveMap, ulBufSize = CCHMAXPATHCOMP;
    APIRET rc = DosQueryCurrentDisk(&ulDrive, &ulDriveMap);

    _achBuffer[0] = '@' + ulDrive;
    _achBuffer[1] = ':';
    _achBuffer[2] = '\\';
    rc = DosQueryCurrentDir(0, _achBuffer + 3, &ulBufSize);
    strcat(strcat((PSZ)pBuffer, "\\"), pszFileName);

    return rc;
}

/*
 * _findsyslevelfile:
 *
 */

STATIC APIRET _findsyslevelfile(PSZ pszName,
                                PSZ pszCID,
                                PVOID pBuffer)
{
    HDIR hDirHandle = HDIR_CREATE;
    FILEFINDBUF3 ffb;
    ULONG ulCount = 1, ulFound = 0;
    CHAR achFileName[CCHMAXPATHCOMP] = "syslevel.";
    APIRET rc = 0;

    if (pszName)
        strcat(achFileName, pszName);
    else
        strcat(achFileName, "???");

    rc = DosFindFirst(achFileName,
                      &hDirHandle,
                      FILE_NORMAL,
                      (PVOID) & ffb,
                      sizeof(ffb),
                      &ulCount,
                      FIL_STANDARD);

    while ((!ulFound) && (rc == 0))
    {
        if (_isasyslevelfile(ffb.achName, pszCID))
        {
            _getfullname(pBuffer, ffb.achName);
            ulFound = 1;
            break;
        }

        rc = DosFindNext(hDirHandle, (PVOID) & ffb, sizeof(ffb), &ulCount);
    }

    if ((rc == 0) || (rc == 18))
        rc = DosFindClose(hDirHandle);

    return ulFound ? 0 : 18;
}

/*
 * _findallsyslevelfiles:
 *
 */

STATIC APIRET _findallsyslevelfiles(PSZ achFileName,
                                    PSZ pszCID,
                                    PPVOID ppBuffer,
                                    PULONG pulBufSize)
{
    HDIR hDirHandle = HDIR_CREATE;
    FILEFINDBUF3 ffb;
    ULONG ulCount = 1, ulSize;
    APIRET rc = DosFindFirst(achFileName,
                             &hDirHandle,
                             FILE_NORMAL,
                             (PVOID) & ffb,
                             sizeof(ffb),
                             &ulCount,
                             FIL_STANDARD);

    while (rc == 0)
    {
        if (_isasyslevelfile(ffb.achName, pszCID))
        {
            CHAR achBuf[CCHMAXPATHCOMP];

            _getfullname(achBuf, ffb.achName);
            ulSize = strlen(achBuf);
            if ((*pulBufSize) > ulSize)
            {
                *pulBufSize -= ulSize + 1;
                memcpy(*ppBuffer, achBuf, ulSize);
                *ppBuffer = ((CHAR *) (*ppBuffer)) + ulSize + 1;
            }
            else
                rc = ERROR_INSUFFICIENT_BUFFER;
        }

        if (rc == 0)
            rc = DosFindNext(hDirHandle, (PVOID) & ffb, sizeof(ffb), &ulCount);
    }

    if ((rc == 0) || (rc == 18))
    {
        rc = DosFindClose(hDirHandle);
        if (rc == 6)
            rc = 0;
    }

    hDirHandle = HDIR_CREATE;
    if (rc == 0)
        rc = DosFindFirst("*",
                          &hDirHandle,
                          MUST_HAVE_DIRECTORY,
                          (PVOID) & ffb,
                          sizeof(ffb),
                          &ulCount,
                          FIL_STANDARD);

    while (rc == 0)
    {
        if (ffb.achName[0] == '.' &&
            (ffb.achName[1] == 0 || (ffb.achName[1] == '.' && ffb.achName[2] == 0)))
        {
        }
        else
        {
            rc = DosSetCurrentDir(ffb.achName);

            if (rc == 0)
                rc = _findallsyslevelfiles(achFileName, pszCID, ppBuffer, pulBufSize);

            if ((rc == 0) || (rc == 18))
                rc = DosSetCurrentDir("..");
        }

        if (rc == 0)
            rc = DosFindNext(hDirHandle, (PVOID) & ffb, sizeof(ffb), &ulCount);
    }

    if ((rc == 0) || (rc == 18))
    {
        rc = DosFindClose(hDirHandle);
        if (rc == 6)
            rc = 0;
    }

    return rc;
}

/*
 * _searchsubdirs:
 *
 */

STATIC APIRET _searchsubdirs(PSZ pszName,
                             PSZ pszCID,
                             PVOID pBuffer)
{
    HDIR hDirHandle = HDIR_CREATE;
    FILEFINDBUF3 ffb;
    ULONG ulCount = 1, ulFound = 0;
    APIRET rc = DosFindFirst("*",
                             &hDirHandle,
                             MUST_HAVE_DIRECTORY,
                             (PVOID) & ffb,
                             sizeof(ffb),
                             &ulCount,
                             FIL_STANDARD);

    while ((!ulFound) && (rc == 0))
    {
        if (ffb.achName[0] == '.' &&
            (ffb.achName[1] == 0 || (ffb.achName[1] == '.' && ffb.achName[2] == 0)))
        {
        }
        else
        {
            rc = DosSetCurrentDir(ffb.achName);

            if (rc == 0)
                rc = _findsyslevelfile(pszName, pszCID, pBuffer);

            if (rc == 18)
                rc = _searchsubdirs(pszName, pszCID, pBuffer);

            if (rc == 0)
                ulFound = 1;

            if ((rc == 0) || (rc == 18))
                rc = DosSetCurrentDir("..");
        }

        if ((!ulFound) && (rc == 0))
            rc = DosFindNext(hDirHandle, (PVOID) & ffb, sizeof(ffb), &ulCount);
    }

    rc = DosFindClose(hDirHandle);

    return ulFound ? 0 : 18;
}

/*
 * _firstdrive:
 *
 */

STATIC ULONG _firstdrive(ULONG ulDriveMap)
{
    return _nextdrive(ulDriveMap, 2);
}

/*
 * _nextdrive:
 *
 */

STATIC ULONG _nextdrive(ULONG ulDriveMap,
                        ULONG ulCurrent)
{
    ULONG ulNext = ulCurrent + 1;

    while (ulNext < 27)
        if (ulDriveMap & (0x1 << (ulNext - 1)))
            break;
        else
            ulNext++;

    if (ulNext < 27)
    {
        DosSetDefaultDisk(ulNext);
        DosSetCurrentDir("\\");
    }

    return (ulNext == 27) ? 0 : ulNext;
}
