
/*
 *@@sourcefile wphandle.c:
 *      this file contains the logic for dealing with
 *      those annoying WPS object handles in OS2SYS.INI.
 *      This does not use WPS interfaces, but parses
 *      the profiles directly.
 *
 *      Usage: All OS/2 programs.
 *
 *      Function prefixes (new with V0.81):
 *      --  wph*   WPS object helper functions
 *
 *      Thanks go out to Henk Kelder for telling me the
 *      format of the WPS INI data. With V0.9.16, this
 *      file was completely rewritten and no longer uses
 *      his code though.
 *
 *      Note: Version numbering in this file relates to XWorkplace version
 *            numbering.
 *
 *@@header "helpers\wphandle.h"
 */

/*
 *      This file Copyright (C) 1997-2008 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_DOSEXCEPTIONS
#define INCL_DOSPROCESS
#define INCL_DOSERRORS

#define INCL_WINSHELLDATA
#define INCL_WINNLS
#include <os2.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <io.h>
#include <setjmp.h>

#include "setup.h"                      // code generation and debugging options

#include "helpers\except.h"
#include "helpers\linklist.h"
#include "helpers\nls.h"
#include "helpers\prfh.h"
#include "helpers\standards.h"
#include "helpers\stringh.h"
#include "helpers\tree.h"
#include "helpers\xstring.h"

#define INCLUDE_WPHANDLE_PRIVATE
#include "helpers\wphandle.h"

/*
 *@@category: Helpers\PM helpers\Workplace Shell\Handles (OS2SYS.INI)
 *      See wphandle.c.
 */

/* ******************************************************************
 *
 *   Load handles functions
 *
 ********************************************************************/

/*
 *@@ wphQueryActiveHandles:
 *      returns the value of PM_Workplace:ActiveHandles
 *      in OS2SYS.INI as a new buffer.
 *
 *      There are always two buffers in OS2SYS.INI for object
 *      handles, called "PM_Workplace:HandlesX" with "X" either
 *      being "0" or "1".
 *
 *      It seems that every time the WPS does something in the
 *      handles section, it writes the data to the inactive
 *      buffer first and then makes it the active buffer by
 *      changing the "active handles" key. You can test this
 *      by creating a shadow on your Desktop.
 *
 *      This returns a new PSZ which the caller must free()
 *      after use.
 *
 *      This gets called by the one-shot function
 *      wphQueryHandleFromPath.
 *
 *@@changed V0.9.16 (2001-10-02) [umoeller]: rewritten
 */

APIRET wphQueryActiveHandles(HINI hiniSystem,
                             PSZ *ppszActiveHandles)        // out: active handles string (new buffer)
{
    PSZ pszActiveHandles;
    if (pszActiveHandles = prfhQueryProfileData(hiniSystem,
                                                WPINIAPP_ACTIVEHANDLES,
                                                WPINIAPP_HANDLESAPP,
                                                NULL))
    {
        *ppszActiveHandles = pszActiveHandles;
        return NO_ERROR;
    }

    return ERROR_WPH_NO_ACTIVEHANDLES_DATA;
}

/*
 *@@ wphQueryBaseClassesHiwords:
 *      returns the hiwords for the WPS base
 *      classes. Unless the user's system is
 *      really badly configured, this should
 *      set
 *
 *      --  pusHiwordAbstract to 2;
 *      --  pusHiwordFileSystem to 3.
 *
 *      Returns:
 *
 *      --  NO_ERROR
 *
 *      --  ERROR_WPH_NO_BASECLASS_DATA
 *
 *      --  ERROR_WPH_INCOMPLETE_BASECLASS_DATA
 *
 *      This gets called automatically from wphLoadHandles.
 *
 *@@added V0.9.16 (2001-10-02) [umoeller]
 */

APIRET wphQueryBaseClassesHiwords(HINI hiniUser,
                                  PUSHORT pusHiwordAbstract,
                                  PUSHORT pusHiwordFileSystem)
{
    APIRET arc = NO_ERROR;

    // get the index of WPFileSystem from the base classes list...
    // we need this to determine the hiword for file-system handles
    // properly. Normally, this should be 3.
    ULONG cbBaseClasses = 0;
    PSZ pszBaseClasses;
    if (pszBaseClasses = prfhQueryProfileData(hiniUser,
                                              "PM_Workplace:BaseClass",
                                              "ClassList",
                                              &cbBaseClasses))
    {
        // parse that buffer... these has the base class names,
        // separated by 0. List is terminated by two zeroes.
        PSZ     pszClassThis = pszBaseClasses;
        ULONG   ulHiwordThis = 1;
        while (    (*pszClassThis)
                && (pszClassThis - pszBaseClasses < cbBaseClasses)
              )
        {
            if (!strcmp(pszClassThis, "WPFileSystem"))
                *pusHiwordFileSystem = ulHiwordThis;
            else if (!strcmp(pszClassThis, "WPAbstract"))
                *pusHiwordAbstract = ulHiwordThis;

            ulHiwordThis++;
            pszClassThis += strlen(pszClassThis) + 1;
        }

        // now check if we found both
        if (    (!(*pusHiwordFileSystem))
             || (!(*pusHiwordAbstract))
           )
            arc = ERROR_WPH_INCOMPLETE_BASECLASS_DATA;

        free(pszBaseClasses);
    }
    else
        arc = ERROR_WPH_NO_BASECLASS_DATA;

    return arc;
}

/*
 *@@ FreeChildrenTree:
 *      called from NukeNameTrees for recursion.
 *
 *@@added V0.9.16 (2001-10-19) [umoeller]
 */

STATIC VOID FreeChildrenTree(TREE **ppChildrenTree,
                             PLONG plCount)
{
    LONG    cItems = *plCount;
    TREE**  papNodes = treeBuildArray(*ppChildrenTree,
                                      &cItems);
    if (papNodes)
    {
        ULONG ul;
        for (ul = 0; ul < cItems; ul++)
        {
            PNODETREENODE pNodeThis = (PNODETREENODE)papNodes[ul];

            FreeChildrenTree(&pNodeThis->ChildrenTree,
                             &pNodeThis->cChildren);

            free(pNodeThis);
        }

        free(papNodes);
        *plCount = 0;

        treeInit(ppChildrenTree, plCount);
    }
}

/*
 *@@ NukeNameTrees:
 *      frees all cache trees.
 *
 *@@added V0.9.16 (2001-10-19) [umoeller]
 */

STATIC APIRET NukeNameTrees(PHANDLESBUF pHandlesBuf)
{
    APIRET arc = NO_ERROR;

    LONG    cItems = pHandlesBuf->cDrives;
    TREE**  papNodes = treeBuildArray(pHandlesBuf->DrivesTree,
                                      &cItems);
    if (papNodes)
    {
        ULONG ul;
        for (ul = 0; ul < cItems; ul++)
        {
            PDRIVETREENODE pNodeThis = (PDRIVETREENODE)papNodes[ul];

            FreeChildrenTree(&pNodeThis->ChildrenTree,
                             &pNodeThis->cChildren);

            free(pNodeThis);
        }

        free(papNodes);

        treeInit(&pHandlesBuf->DrivesTree,
                 &pHandlesBuf->cDrives);
    }

    return arc;
}

/*
 *@@ wphRebuildNodeHashTable:
 *      builds all the complex cache trees in the
 *      given handles buffer.
 *
 *      If (fQuitOnErrors == TRUE), we'll fail as
 *      soon as an invalid handle is found. Otherwise
 *      we will try to continue if the error is not fatal.
 *
 *      Returns:
 *
 *      --  NO_ERROR
 *
 *      --  ERROR_INVALID_PARAMETER
 *
 *      --  ERROR_WPH_CORRUPT_HANDLES_DATA
 *
 *      --  ERROR_WPH_DRIV_TREEINSERT_FAILED: duplicate DRIV node
 *          (non-fatal)
 *
 *@@added V0.9.16 (2001-10-02) [umoeller]
 *@@changed V0.9.17 (2002-02-05) [umoeller]: added fQuitOnErrors
 *@@changed XWP V1.0.8 (2008-05-25) [pr]: made DBCS safe @@fixes 1070
 */

APIRET wphRebuildNodeHashTable(HHANDLES hHandles,
                               BOOL fQuitOnErrors)
{
    APIRET arc = NO_ERROR;

    PHANDLESBUF pHandlesBuf;

    if (    (!(pHandlesBuf = (PHANDLESBUF)hHandles))
         || (!pHandlesBuf->pbData)
         || (!pHandlesBuf->cbData)
       )
        arc = ERROR_INVALID_PARAMETER;
    else
    {
        // start at beginning of buffer
        PBYTE pCur = pHandlesBuf->pbData + 4;
        PBYTE pEnd = pHandlesBuf->pbData + pHandlesBuf->cbData;

        PDRIVETREENODE  pLastDriveTreeNode = NULL;

        memset(pHandlesBuf->NodeHashTable, 0, sizeof(pHandlesBuf->NodeHashTable));
        NukeNameTrees(pHandlesBuf);

        // now set up hash table
        while (    (pCur < pEnd)
                && (!arc)
              )
        {
            if (!memicmp(pCur, "DRIV", 4))
            {
                // pCur points to a DRIVE node:
                // these never have handles, so skip this
                PDRIVE pDriv = (PDRIVE)pCur;

                // upper the node name for string comparisons
                nlsUpper(pDriv->szName);  // V1.0.8 (2008-05-25)

                // create a drive tree node
                // (stored so we can append to this)
                if (!(pLastDriveTreeNode = NEW(DRIVETREENODE)))
                    arc = ERROR_NOT_ENOUGH_MEMORY;
                else
                {
                    pLastDriveTreeNode->Tree.ulKey = (ULONG)pDriv->szName;
                    pLastDriveTreeNode->pDriv = pDriv;
                    treeInit(&pLastDriveTreeNode->ChildrenTree,
                             &pLastDriveTreeNode->cChildren);
                    if (treeInsert(&pHandlesBuf->DrivesTree,
                                   &pHandlesBuf->cDrives,
                                   (TREE*)pLastDriveTreeNode,
                                   treeCompareStrings))
                        if (fQuitOnErrors)
                            arc = ERROR_WPH_DRIV_TREEINSERT_FAILED;
                }

                // next item
                pCur += sizeof(DRIVE) + strlen(pDriv->szName);
            }
            else if (!memicmp(pCur, "NODE", 4))
            {
                // pCur points to a regular NODE: offset pointer first
                PNODE pNode = (PNODE)pCur;
                PNODETREENODE pNew;

                // upper the node name for string comparisons
                nlsUpper(pNode->szName);  // V1.0.8 (2008-05-25)

                // create a node tree node
                if (!(pNew = NEW(NODETREENODE)))
                    arc = ERROR_NOT_ENOUGH_MEMORY;
                else
                {
                    TREE **ppTree = NULL;
                    PLONG pcChildren = NULL;

                    pNew->Tree.ulKey = (ULONG)pNode->szName;
                    pNew->pNode = pNode;
                    treeInit(&pNew->ChildrenTree,
                             &pNew->cChildren);
                    // now check where to insert this...
                    // does it have a parent?
                    if (pNode->usParentHandle)
                    {
                        PNODETREENODE pParent;
                        if (!(pParent = pHandlesBuf->NodeHashTable[pNode->usParentHandle]))
                        {
                            // this parent handle is invalid:
                            if (fQuitOnErrors)
                                arc = ERROR_WPH_INVALID_PARENT_HANDLE;
                        }
                        else
                        {
                            ppTree = &pParent->ChildrenTree;
                            pcChildren = &pParent->cChildren;
                        }
                    }
                    else
                        // null parent handle: then the parent
                        // must be a DRIVE node
                        if (pLastDriveTreeNode)
                        {
                            ppTree = &pLastDriveTreeNode->ChildrenTree;
                            pcChildren = &pLastDriveTreeNode->cChildren;
                        }
                        else
                            if (fQuitOnErrors)
                                arc = ERROR_WPH_NODE_BEFORE_DRIV;

                    if (ppTree && pcChildren)
                        if (!treeInsert(ppTree,
                                        pcChildren,
                                        (TREE*)pNew,
                                        treeCompareStrings))
                        {
                            // store PNODE in hash table
                            pHandlesBuf->NodeHashTable[pNode->usHandle] = pNew;
                            // do not free
                            pNew = NULL;
                        }
                        else
                            ;
                            // @@todo if this fails, there are
                            // several handles for short name!!!
                            // arc = ERROR_WPH_NODE_TREEINSERT_FAILED;

                    if (pNew)
                        free(pNew);

                }

                pCur += sizeof(NODE) + pNode->usNameSize;
            }
            else
            {
                arc = ERROR_WPH_CORRUPT_HANDLES_DATA;
                break;
            }
        }
    }

    if (!arc)
        pHandlesBuf->fCacheValid = TRUE;

    return arc;
}

/*
 *@@ wphLoadHandles:
 *      returns a HANDLESBUF structure which will hold
 *      all the handles from OS2SYS.INI. In addition,
 *      this calls wphQueryBaseClassesHiwords and puts
 *      the hiwords for WPAbstract and WPFileSystem into
 *      the HANDLESBUF as well.
 *
 *      Prerequisite before using any of the other wph*
 *      functions.
 *
 *      Call wphFreeHandles to free all data allocated
 *      by this function.
 *
 *      Returns:
 *
 *      --  NO_ERROR
 *
 *      --  ERROR_NOT_ENOUGH_MEMORY
 *
 *      --  ERROR_INVALID_PARAMETER
 *
 *      --  ERROR_WPH_NO_HANDLES_DATA: cannot read handle blocks.
 *
 *      --  ERROR_WPH_CORRUPT_HANDLES_DATA: cannot read handle blocks.
 *
 *@@added V0.9.16 (2001-10-02) [umoeller]
 */

APIRET wphLoadHandles(HINI hiniUser,      // in: HINI_USER or other INI handle
                      HINI hiniSystem,    // in: HINI_SYSTEM or other INI handle
                      const char *pcszActiveHandles,
                      HHANDLES *phHandles)
{
    APIRET arc = NO_ERROR;

    if (!phHandles)
        arc = ERROR_INVALID_PARAMETER;
    else
    {
        PSZ pszKeysList;
        if (!(arc = prfhQueryKeysForApp(hiniSystem,
                                        pcszActiveHandles,
                                        &pszKeysList)))
        {
            PHANDLESBUF pReturn = NULL;

            ULONG   ulHighestBlock = 0,
                    ul,
                    cbTotal;
            PBYTE   pbData;

            const char *pKey2 = pszKeysList;
            while (*pKey2)
            {
                if (!memicmp((PVOID)pKey2, "BLOCK", 5))
                {
                    ULONG ulBlockThis = atoi(pKey2 + 5);
                    if (ulBlockThis > ulHighestBlock)
                        ulHighestBlock = ulBlockThis;
                }

                pKey2 += strlen(pKey2)+1; // next key
            }

            free(pszKeysList);

            if (!ulHighestBlock)
                arc = ERROR_WPH_NO_HANDLES_DATA;
            else
            {
                // now go read the data
                // (BLOCK1, BLOCK2, ..., BLOCKn)
                cbTotal = 0;
                pbData = NULL;
                for (ul = 1;
                     ul <= ulHighestBlock;
                     ul++)
                {
                    ULONG   cbBlockThis;
                    CHAR    szBlockThis[10];
                    sprintf(szBlockThis, "BLOCK%d", ul);
                    if (!PrfQueryProfileSize(hiniSystem,
                                             (PSZ)pcszActiveHandles,
                                             szBlockThis,
                                             &cbBlockThis))
                    {
                        arc = ERROR_WPH_PRFQUERYPROFILESIZE_BLOCK;
                        break;
                    }
                    else
                    {
                        ULONG   cbTotalOld = cbTotal;
                        cbTotal += cbBlockThis;
                        if (!(pbData = (BYTE*)realloc(pbData, cbTotal)))
                                // on first call, pbData is NULL and this
                                // behaves like malloc()
                        {
                            arc = ERROR_NOT_ENOUGH_MEMORY;
                            break;
                        }

                        if (!PrfQueryProfileData(hiniSystem,
                                                 (PSZ)pcszActiveHandles,
                                                 szBlockThis,
                                                 pbData + cbTotalOld,
                                                 &cbBlockThis))
                        {
                            arc = ERROR_WPH_PRFQUERYPROFILEDATA_BLOCK;
                            break;
                        }
                    }
                }
            }

            if (!arc)
            {
                // all went OK:
                if (pReturn = NEW(HANDLESBUF))
                {
                    ZERO(pReturn);

                    treeInit(&pReturn->DrivesTree,
                             &pReturn->cDrives);

                    pReturn->pbData = pbData;
                    pReturn->cbData = cbTotal;

                    // and load the hiwords too
                    if (!(arc = wphQueryBaseClassesHiwords(hiniUser,
                                                           &pReturn->usHiwordAbstract,
                                                           &pReturn->usHiwordFileSystem)))
                        *phHandles = (HHANDLES)pReturn;
                }
                else
                    arc = ERROR_NOT_ENOUGH_MEMORY;
            }

            if (arc)
                // error:
                wphFreeHandles((HHANDLES*)&pReturn);
        }
    }

    return arc;
}

/*
 *@@ wphFreeHandles:
 *      frees all data allocated by wphLoadHandles
 *      and sets *ppHandlesBuf to NULL, for safety.
 *
 *@@added V0.9.16 (2001-10-02) [umoeller]
 */

APIRET wphFreeHandles(HHANDLES *phHandles)
{
    APIRET arc = NO_ERROR;

    PHANDLESBUF pHandlesBuf;
    if (    (phHandles)
         && (pHandlesBuf = (PHANDLESBUF)*phHandles)
       )
    {
        PBYTE pbData;

        NukeNameTrees(pHandlesBuf);

        if (pbData = pHandlesBuf->pbData)
            free(pbData);

        free(pHandlesBuf);
        *phHandles = NULLHANDLE;
    }
    else
        arc = ERROR_INVALID_PARAMETER;

    return arc;
}

/* ******************************************************************
 *
 *   Get HOBJECT from filename
 *
 ********************************************************************/

/*
 *@@ wphSearchBufferForHandle:
 *      returns the 16-bit file-system handle which corresponds
 *      to pszFilename, searching pHandlesBuffer. Note that you
 *      must OR the return value with the proper hiword to make
 *      this a valid WPS file-system handle.
 *
 *      You must pass a handles buffer to this function which
 *      has been filled using wphReadAllBlocks above.
 *
 *      This gets called by the one-shot function
 *      wphQueryHandleFromPath.
 *
 *      Returns:
 *
 *      --  NO_ERROR
 *
 *      --  ERROR_INVALID_PARAMETER
 *
 *      --  ERROR_FILE_NOT_FOUND
 *
 *      --  ERROR_INVALID_NAME
 *
 *      --  ERROR_WPH_CORRUPT_HANDLES_DATA
 *
 *      --  ERROR_WPH_CANNOT_FIND_HANDLE: no handle exists for the
 *          given filename.
 *
 *@@changed V0.9.16 (2001-10-19) [umoeller]: rewritten
 *@@changed V1.0.8 (2008-05-25) [pr]: made DBCS safe @@fixes 1070
 */

APIRET wphSearchBufferForHandle(HHANDLES hHandles,
                                PCSZ pcszFile,  // in: fully qlf'd filename to search for
                                PUSHORT pusHandle)  // out: 16-bit handle
{
    APIRET arc = NO_ERROR;

    PHANDLESBUF pHandlesBuf;

    _Pmpf((__FUNCTION__ ": entering"));

    if (    (hHandles)
         && (pHandlesBuf = (PHANDLESBUF)hHandles)
       )
    {
        // rebuild cache
        if (!pHandlesBuf->fCacheValid)
            arc = wphRebuildNodeHashTable(hHandles,
                                          TRUE);        // fail on errors

        if (!arc)
        {
            // We can thus work our way through the buffer by splitting the
            // fully qualified filename that we're searching for into the
            // different directory names and, each time, searching for the
            // corresponding NODE. If we have found that, we go for the next.
            // Example for C:\OS2\E.EXE:
            //   1) first search for the "C:" NODE
            //   2) then find the "OS2" node which has "C" as its parent NODE
            //      (we do this by comparing the parent object handles)
            //   3) then find the "E.EXE" NODE the same way
            // The "E.EXE" NODE then has the object handle we're looking for.

            // make a copy of the filename so we can play
            PSZ     pszFilename = strdup(pcszFile),
                    pEnd = NULL;

            nlsUpper(pszFilename);  // V1.0.8 (2008-05-25)

            // 1) OK, find the drive.

            // If this is an UNC name, the DRIVE node has the form
            // \\SERVER\RESOURCE.
            if (    (*pszFilename == '\\')
                 && (*(pszFilename + 1) == '\\')
               )
            {
                // UNC:
                // @@todo
            }
            else if (*(pszFilename + 1) == ':')
                // extract the drive then (without \)
                pEnd = pszFilename + 2;

            if (!pEnd)
                arc = ERROR_INVALID_NAME;
            else
            {
                PDRIVETREENODE pDrive = NULL;
                PNODETREENODE pNode;

                // find the DRIVE node
                CHAR cOld = *pEnd;
                *pEnd = 0;

                _Pmpf(("  searching for drive \"%s\"", pszFilename));

                if (!(pDrive = (PDRIVETREENODE)treeFind(pHandlesBuf->DrivesTree,
                                                        (ULONG)pszFilename,   // drive name
                                                        treeCompareStrings)))
                    arc = ERROR_WPH_NO_MATCHING_DRIVE_BLOCK;
                // find the root dir, which has the same name
                else if (!(pNode = (PNODETREENODE)treeFind(pDrive->ChildrenTree,
                                                           (ULONG)pszFilename,
                                                           treeCompareStrings)))
                    arc = ERROR_WPH_NO_MATCHING_ROOT_DIR;
                else
                {
                    // now we got the root dir... go for next path component
                    while (    (pEnd)
                            && (*pEnd = cOld)       // not null char
                            && (!arc)
                          )
                    {
                        // got another path component to search:
                        PSZ pCurrent = pEnd + 1,
                            pNext;

                        if (pNext = strchr(pCurrent, '\\'))
                        {
                            cOld = *pNext;
                            *pNext = 0;
                        }
                        else
                            // done:
                            cOld = 0;

                        _Pmpf(("  searching for node \"%s\"", pCurrent));

                        // find the next node
                        if (!(pNode = (PNODETREENODE)treeFind(pNode->ChildrenTree,
                                                              (ULONG)pCurrent,
                                                              treeCompareStrings)))
                            arc = ERROR_WPH_CANNOT_FIND_HANDLE;

                        pEnd = pNext;
                    }

                    if (!arc && pNode)
                        // found everything:
                        *pusHandle = pNode->pNode->usHandle;
                }
            } // end while

            free(pszFilename);
        }
    }
    else
        arc = ERROR_INVALID_PARAMETER;

    _Pmpf((__FUNCTION__ ": returning %d", arc));

    // not found: end of buffer reached
    return arc;
}

/*
 *@@ wphQueryHandleFromPath:
 *      finds the object handle for the given fully qualified
 *      filename.
 *      This is a one-shot function, using wphQueryActiveHandles,
 *      wphReadAllBlocks, and wphSearchBufferForHandle.
 *
 *      Returns:
 *
 *      --  NO_ERROR: *phobj has received the object handle.
 *
 *      --  ERROR_FILE_NOT_FOUND: file does not exist.
 *
 *      plus the error codes of the other wph* functions called.
 *
 *@@changed V0.9.16 (2001-10-02) [umoeller]: rewritten
 */

APIRET wphQueryHandleFromPath(HINI hiniUser,      // in: HINI_USER or other INI handle
                              HINI hiniSystem,    // in: HINI_SYSTEM or other INI handle
                              const char *pcszName,    // in: fully qlf'd filename
                              HOBJECT *phobj)      // out: object handle found if NO_ERROR
{
    APIRET      arc = NO_ERROR;

    volatile PSZ         pszActiveHandles = NULL; // XWP V1.0.4 (2005-10-09) [pr]
    volatile HHANDLES    hHandles = NULLHANDLE;

    TRY_LOUD(excpt1)
    {
        // not found there: check the handles then

        if (arc = wphQueryActiveHandles(hiniSystem, (PSZ *) &pszActiveHandles))
            _Pmpf((__FUNCTION__ ": wphQueryActiveHandles returned %d", arc));
        else
        {
            if (arc = wphLoadHandles(hiniUser,
                                     hiniSystem,
                                     pszActiveHandles,
                                     (HHANDLES *) &hHandles))
                _Pmpf((__FUNCTION__ ": wphLoadHandles returned %d", arc));
            else
            {
                USHORT      usObjID;
                CHAR        szFullPath[2*CCHMAXPATH];
                _fullpath(szFullPath, (PSZ)pcszName, sizeof(szFullPath));

                // search that buffer
                if (!(arc = wphSearchBufferForHandle(hHandles,
                                                     szFullPath,
                                                     &usObjID)))
                    // found: OR 0x30000
                    *phobj = usObjID | (((PHANDLESBUF)hHandles)->usHiwordFileSystem << 16);
                else
                    arc = ERROR_FILE_NOT_FOUND;
            }
        }
    }
    CATCH(excpt1)
    {
        arc = ERROR_PROTECTION_VIOLATION; // V0.9.19 (2002-07-01) [umoeller]
    } END_CATCH();

    if (pszActiveHandles)
        free(pszActiveHandles);
    if (hHandles)
        wphFreeHandles((HHANDLES *) &hHandles);

    return arc;
}

/* ******************************************************************
 *
 *   Get filename from HOBJECT
 *
 ********************************************************************/

/*
 *@@ ComposeThis:
 *      helper for wphComposePath recursion.
 *
 *@@added V0.9.16 (2001-10-02) [umoeller]
 *@@changed V0.9.19 (2002-04-14) [umoeller]: fixed wrong error for parent handles
 */

STATIC APIRET ComposeThis(PHANDLESBUF pHandlesBuf,
                          USHORT usHandle,         // in: handle to search for
                          PXSTRING pstrFilename,   // in/out: filename
                          ULONG ulLevel,           // in: recursion level (initially 0)
                          PNODE *ppNode)           // out: node found (ptr can be NULL)
{
    APIRET          arc = NO_ERROR;
    PNODETREENODE   pTreeNode;
    PNODE           pNode;
    if (    (pTreeNode = pHandlesBuf->NodeHashTable[usHandle])
         && (pNode = pTreeNode->pNode)
       )
    {
        // handle exists:
        if (pNode->usParentHandle)
        {
            // node has parent:
            // recurse first
            if (!(arc = ComposeThis(pHandlesBuf,
                                    pNode->usParentHandle,
                                    pstrFilename,
                                    ulLevel + 1,
                                    ppNode)))
            {
                // no error:
                xstrcatc(pstrFilename, '\\');
                xstrcat(pstrFilename, pNode->szName, pNode->usNameSize);
            }
        }
        else
            // no parent:
            xstrcpy(pstrFilename, pNode->szName, pNode->usNameSize);
    }
    else
        // handle not found:
        if (ulLevel == 0)       // V0.9.19 (2002-04-14) [umoeller]
            arc = ERROR_INVALID_HANDLE;
        else
            arc = ERROR_WPH_INVALID_PARENT_HANDLE;

    if (!arc)
        if (ppNode)
            *ppNode = pNode;

    return arc;
}

/*
 *@@ wphComposePath:
 *      returns the fully qualified path name for the specified
 *      file-system handle. This function is very fast because
 *      it uses a hash table for all the handles internally.
 *
 *      Warning: This calls a helper, which recurses.
 *
 *      This returns:
 *
 *      --  NO_ERROR
 *
 *      --  ERROR_WPH_CORRUPT_HANDLES_DATA: buffer data cannot be parsed.
 *
 *      --  ERROR_WPH_INVALID_HANDLE: usHandle cannot be found.
 *
 *      --  ERROR_WPH_INVALID_PARENT_HANDLE: a handle was found
 *          that has a broken parent handle.
 *
 *      --  ERROR_BUFFER_OVERFLOW: cbFilename is too small to
 *          hold the full path that was composed.
 *
 *@@added V0.9.16 (2001-10-02) [umoeller]
 */

APIRET wphComposePath(HHANDLES hHandles,
                      USHORT usHandle,      // in: loword of handle to search for
                      PSZ pszFilename,
                      ULONG cbFilename,
                      PNODE *ppNode)        // out: node found (ptr can be NULL)
{
    APIRET arc = NO_ERROR;

    PHANDLESBUF pHandlesBuf;
    if (    (hHandles)
         && (pHandlesBuf = (PHANDLESBUF)hHandles)
       )
    {
        TRY_LOUD(excpt1)
        {
            if (!pHandlesBuf->fCacheValid)
                arc = wphRebuildNodeHashTable(hHandles,
                                              TRUE);        // fail on errors

            if (!arc)
            {
                XSTRING str;
                xstrInit(&str, CCHMAXPATH);
                if (!(arc = ComposeThis(pHandlesBuf,
                                        usHandle,
                                        &str,
                                        0,      // initial recursion level
                                        ppNode)))
                    if (str.ulLength > cbFilename - 1)
                        arc = ERROR_BUFFER_OVERFLOW;
                    else
                        memcpy(pszFilename,
                               str.psz,
                               str.ulLength + 1);
                xstrClear(&str);
            }
        }
        CATCH(excpt1)
        {
            arc = ERROR_PROTECTION_VIOLATION; // V0.9.19 (2002-07-01) [umoeller]
        } END_CATCH();
    }

    return arc;
}

/*
 *@@ wphQueryPathFromHandle:
 *      reverse to wphQueryHandleFromPath, this gets the
 *      filename for hObject.
 *      This is a one-shot function, using wphQueryActiveHandles,
 *      wphLoadHandles, and wphComposePath.
 *      As a result, this function is _very_ expensive.
 *
 *      Returns:
 *
 *      --  NO_ERROR
 *
 *      --  ERROR_INVALID_HANDLE: hObject is invalid.
 *
 *      --  ERROR_WPH_NOT_FILESYSTEM_HANDLE: hObject's hiword is wrong.
 *
 *@@changed V0.9.16 (2001-10-02) [umoeller]: rewritten
 */

APIRET wphQueryPathFromHandle(HINI hiniUser,      // in: HINI_USER or other INI handle
                              HINI hiniSystem,    // in: HINI_SYSTEM or other INI handle
                              HOBJECT hObject,    // in: 32-bit object handle
                              PSZ pszFilename,    // out: filename, if found
                              ULONG cbFilename)   // in: sizeof(*pszFilename)
{
    APIRET arc = NO_ERROR;

    TRY_LOUD(excpt1)
    {
        PSZ pszActiveHandles;
        if (arc = wphQueryActiveHandles(hiniSystem, &pszActiveHandles))
            _Pmpf((__FUNCTION__ ": wphQueryActiveHandles returned %d", arc));
        else
        {
            HHANDLES hHandles;
            if (arc = wphLoadHandles(hiniUser,
                                     hiniSystem,
                                     pszActiveHandles,
                                     &hHandles))
                _Pmpf((__FUNCTION__ ": wphLoadHandles returned %d", arc));
            else
            {
                USHORT usHiwordFileSystem = ((PHANDLESBUF)hHandles)->usHiwordFileSystem;

                // is this really a file-system object?
                if (HIUSHORT(hObject) == usHiwordFileSystem)
                {
                    // use loword only
                    USHORT      usObjID = LOUSHORT(hObject);

                    memset(pszFilename, 0, cbFilename);
                    arc = wphComposePath(hHandles,
                                         usObjID,
                                         pszFilename,
                                         cbFilename,
                                         NULL);

                    // _Pmpf((__FUNCTION__ ": wphFindPartName returned %d", arc));
                }
                else
                    arc = ERROR_WPH_NOT_FILESYSTEM_HANDLE;

                wphFreeHandles(&hHandles);
            }

            free(pszActiveHandles);
        }
    }
    CATCH(excpt1)
    {
        arc = ERROR_PROTECTION_VIOLATION; // V0.9.19 (2002-07-01) [umoeller]
    } END_CATCH();

    return arc;
}

/*
 *@@ wphDescribeError:
 *      returns an error description for one of the handles
 *      engine errors, or NULL if the error code is not
 *      recognized.
 *
 *@@added V0.9.19 (2002-07-01) [umoeller]
 */

PCSZ wphDescribeError(APIRET arc)
{
    switch (arc)
    {
        case ERROR_WPH_NO_BASECLASS_DATA:
            return "Cannot find PM_Workplace:BaseClass in OS2.INI";

        case ERROR_WPH_NO_ACTIVEHANDLES_DATA:
            return "Cannot find PM_Workplace:ActiveHandles in OS2SYS.INI";

        case ERROR_WPH_INCOMPLETE_BASECLASS_DATA:
            return "PM_Workplace:ActiveHandles in OS2SYS.INI is incomplete";

        case ERROR_WPH_NO_HANDLES_DATA:
            return "Active handles block in OS2SYS.INI is empty";

        case ERROR_WPH_CORRUPT_HANDLES_DATA:
            return "Cannot parse data in active handles block in OS2SYS.INI";

        case ERROR_WPH_INVALID_PARENT_HANDLE:
            return "Handle has invalid parent handle";

        case ERROR_WPH_CANNOT_FIND_HANDLE:
            return "No handle exists for the given filename";

        case ERROR_WPH_DRIV_TREEINSERT_FAILED:
            return "Duplicate DRIV node (treeInsert failed)";

        case ERROR_WPH_NODE_TREEINSERT_FAILED:
            return "Duplicate NODE node (treeInsert failed)";

        case ERROR_WPH_NODE_BEFORE_DRIV:
            return "NODE node before DRIV node";

        case ERROR_WPH_NO_MATCHING_DRIVE_BLOCK:
            return "No matching DRIV node";

        case ERROR_WPH_NO_MATCHING_ROOT_DIR:
            return "No matching root directory";

        case ERROR_WPH_NOT_FILESYSTEM_HANDLE:
            return "Handle is not a file-system handle";

        case ERROR_WPH_PRFQUERYPROFILESIZE_BLOCK:
            return "PrfQueryProfileSize failed on reading one BLOCK in OS2SYS.INI";

        case ERROR_WPH_PRFQUERYPROFILEDATA_BLOCK:
            return "PrfQueryProfileData failed on reading one BLOCK in OS2SYS.INI";
    }

    return NULL;
}
