static int
GetKeyNames(
    Tcl_Interp *interp,		/* Current interpreter. */
    Tcl_Obj *keyNameObj,	/* Key to enumerate. */
    Tcl_Obj *patternObj,	/* Optional match pattern. */
    REGSAM mode)		/* Mode flags to pass. */
{
    const char *pattern;	/* Pattern being matched against subkeys */
    HKEY key;			/* Handle to the key being examined */
    DWORD index;		/* Position of the current subkey */
    Tcl_Obj *resultPtr;		/* List of subkeys being accumulated */
    int result;

    if (patternObj) {
	pattern = Tcl_GetString(patternObj);
    } else {
	pattern = NULL;
    }

    /*
     * Attempt to open the key for enumeration.
     */

    mode |= KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS;
    if (OpenKey(interp, keyNameObj, mode, 0, &key) != TCL_OK) {
	return TCL_ERROR;
    }

    /*
     * NOTE: the original code used RegQueryInfoKey to get the key count
     * and max subkey len. There are several issues with this, including
     * a race condition (others might modify counts and/or lengths),
     * as well as a mysterious bug (seemingly in RegQueryInfoKey) where
     * a subkey is included in the return count but its length is not
     * accounted for - observed in a running system through a debugger
     *
     * The modified code below makes use of the fact that a key is
     * at most 256 bytes long and buffer is simply allocated on stack. It then
     * iterates until "eof" (ERROR_NO_MORE_ITEMS). So we do not need
     * RegQueryInfoKey or memory allocs/frees.
     */

    /* resultPtr is ALWAYS used, both on success, as well as failure */
    resultPtr = Tcl_NewObj();

    /* Loop until we break out on eof or error */
    for (result = ERROR_SUCCESS, index = 0; ; ++index) {
        /*
         * Microsoft specifies the max key length as 255. Plus 1 for
         * terminating \0. However, it turns out you can actually write 256
         * chars to a key (even though you are not supposed to) and get it
         * back so we make the buffer 256+1 bytes
         */
        TCHAR subkeyName[257];
        DWORD bufSize;		/* Size of the buffer */
        Tcl_DString ds;

        /* Must init bufSize on each iteration */
	bufSize = sizeof(subkeyName)/sizeof(subkeyName[0]);
	result = RegEnumKeyEx(key, index, subkeyName, &bufSize,
		NULL, NULL, NULL, NULL);

        if (result != ERROR_SUCCESS)
            break;

        /* Got subkey, store it after pattern match check (if there is one) */

	Tcl_WinTCharToUtf(subkeyName, bufSize * sizeof(TCHAR), &ds);
	if (pattern == NULL
            || Tcl_StringMatch(Tcl_DStringValue(&ds), pattern)) {
            Tcl_ListObjAppendElement(interp, resultPtr,
                                     Tcl_NewStringObj(Tcl_DStringValue(&ds),
                                                      Tcl_DStringLength(&ds)));
        }
	Tcl_DStringFree(&ds);
    }

    RegCloseKey(key);

    /*
     * On loop termination, result contains a Windows error code.
     * ERROR_NO_MORE_ITEMS means eof - i.e. success retrieving all
     * anything else is a genuine error 
     */
    if (result == ERROR_NO_MORE_ITEMS) {
        /* SUCCESS RETURN - All done with subkeys */

        Tcl_SetObjResult(interp, resultPtr);
        result = TCL_OK;
    } else {
        /* ERROR RETURN */

        Tcl_SetObjLength(resultPtr, 0); /* Discard collected data */
        Tcl_AppendStringsToObj(resultPtr,
                               "unable to enumerate subkeys of \"",
                               Tcl_GetString(keyNameObj), "\": ", NULL);
        Tcl_SetObjResult(interp, resultPtr);
        AppendSystemError(interp, result);
        result =  TCL_ERROR;
    }

    return result;
}
