/*++

Copyright (c) 1990-1994  Microsoft Corporation
All rights reserved

Module Name:

    Cluster registry apis.

Abstract:

    Determines whether ClusterReg or Reg apis should be used.

    The printer information is stored in the registry.  When we access
    local printers, we hit the local registry; when we access cluster
    printers, we hit the cluster registry.

Author:

    Albert Ting (AlbertT) 8-Oct-96

Environment:

    User Mode -Win32

Revision History:

    Felix Maxa (amaxa) 18-Jun-2000
       Added ClusterGetResourceID
             ClusterGetResourceDriveLetter

--*/

#include "precomp.h"
#pragma hdrstop

#include <clusapi.h>
#include "clusspl.h"

enum  
{
    kDriveLetterStringSize = 3,
    kGuidStringSize        = 40
};


/********************************************************************

    Globals.

********************************************************************/

typedef struct _CLUSAPI {

    HCLUSTER
    (*pfnOpenCluster)(
        IN LPCWSTR lpszClusterName
        );

    BOOL
    (*pfnCloseCluster)(
        IN HCLUSTER hCluster
        );

    HRESOURCE
    (*pfnOpenClusterResource)(
        IN HCLUSTER hCluster,
        IN LPCWSTR lpszResourceName
        );

    BOOL
    (*pfnCloseClusterResource)(
        IN HRESOURCE hResource
        );

    HKEY 
    (*pfnGetClusterKey)(
        IN HCLUSTER hCluster, 
        IN REGSAM   samDesired  
        );

    HKEY
    (*pfnGetClusterResourceKey)(
        IN HRESOURCE hResource,
        IN REGSAM samDesired
        );

    LONG
    (*pfnClusterRegCreateKey)(
        IN HKEY hKey,
        IN LPCWSTR lpszSubKey,
        IN DWORD dwOptions,
        IN REGSAM samDesired,
        IN LPSECURITY_ATTRIBUTES lpSecurityAttributes,
        OUT PHKEY phkResult,
        OUT OPTIONAL LPDWORD lpdwDisposition
        );

    LONG
    (*pfnClusterRegOpenKey)(
        IN HKEY hKey,
        IN LPCWSTR lpszSubKey,
        IN REGSAM samDesired,
        OUT PHKEY phkResult
        );

    LONG
    (*pfnClusterRegDeleteKey)(
        IN HKEY hKey,
        IN LPCWSTR lpszSubKey
        );

    LONG
    (*pfnClusterRegCloseKey)(
        IN HKEY hKey
        );

    LONG
    (*pfnClusterRegEnumKey)(
        IN HKEY hKey,
        IN DWORD dwIndex,
        OUT LPWSTR lpszName,
        IN OUT LPDWORD lpcbName,
        OUT PFILETIME lpftLastWriteTime
        );

    DWORD
    (*pfnClusterRegSetValue)(
        IN HKEY hKey,
        IN LPCWSTR lpszValueName,
        IN DWORD dwType,
        IN CONST BYTE* lpData,
        IN DWORD cbData
        );

    DWORD
    (*pfnClusterRegDeleteValue)(
        IN HKEY hKey,
        IN LPCWSTR lpszValueName
        );

    LONG
    (*pfnClusterRegQueryValue)(
        IN HKEY hKey,
        IN LPCWSTR lpszValueName,
        OUT LPDWORD lpValueType,
        OUT LPBYTE lpData,
        IN OUT LPDWORD lpcbData
        );

    DWORD
    (*pfnClusterRegEnumValue)(
        IN HKEY hKey,
        IN DWORD dwIndex,
        OUT LPWSTR lpszValueName,
        IN OUT LPDWORD lpcbValueName,
        OUT LPDWORD lpType,
        OUT LPBYTE lpData,
        IN OUT LPDWORD lpcbData
        );

    LONG
    (*pfnClusterRegQueryInfoKey)(
        HKEY hKey,
        LPDWORD lpcSubKeys,
        LPDWORD lpcbMaxSubKeyLen,
        LPDWORD lpcValues,
        LPDWORD lpcbMaxValueNameLen,
        LPDWORD lpcbMaxValueLen,
        LPDWORD lpcbSecurityDescriptor,
        PFILETIME lpftLastWriteTime
        );

    DWORD 
    (*pfnClusterResourceControl)(
        HRESOURCE hResource,  
        HNODE     hHostNode,      
        DWORD     dwControlCode,  
        LPVOID    lpInBuffer,    
        DWORD     cbInBufferSize,  
        LPVOID    lpOutBuffer,   
        DWORD     cbOutBufferSize, 
        LPDWORD   lpcbBytesReturned  
        );

} CLUSAPI, *PCLUSAPI;

CLUSAPI ClusApi;

LPCSTR aszClusApi[] = {
    "OpenCluster",
    "CloseCluster",
    "OpenClusterResource",
    "CloseClusterResource",
    "GetClusterKey",
    "GetClusterResourceKey",
    "ClusterRegCreateKey",
    "ClusterRegOpenKey",
    "ClusterRegDeleteKey",
    "ClusterRegCloseKey",
    "ClusterRegEnumKey",
    "ClusterRegSetValue",
    "ClusterRegDeleteValue",
    "ClusterRegQueryValue",
    "ClusterRegEnumValue",
    "ClusterRegQueryInfoKey",
    "ClusterResourceControl"
};

/********************************************************************

    OpenCluster
    CloseCluster

    OpenClusterResource
    CloseClusterResource
    GetClusterResourceKey

    ClusterRegCreateKey
    ClusterRegOpenKey
    ClusterRegDeleteKey
    ClusterRegCloseKey
    ClusterRegEnumKey

    ClusterRegSetValue
    ClusterRegDeleteValue
    ClusterRegQueryValue
    ClusterRegEnumValue
    ClusterRegQueryInfoKey

********************************************************************/

BOOL
LoadClusterFunctions(
    VOID
    )

/*++

Routine Description:

    Load ClusApi functions.  Must be called before any cluster api
    is used.

Arguments:

Return Value:

    TRUE - Success

    FALSE - Fail

--*/

{
    HANDLE hLibrary;
    UINT i;
    FARPROC* pFarProc = (FARPROC*)&ClusApi;

    //
    // Size of string table and structure are identical.
    //
    SPLASSERT( COUNTOF( aszClusApi ) == sizeof( ClusApi )/sizeof( FARPROC ));

    if( ClusApi.pfnOpenCluster ){
        return TRUE;
    }

    i = SetErrorMode(SEM_FAILCRITICALERRORS);
    hLibrary = LoadLibrary( TEXT( "clusapi.dll" ));
    SetErrorMode(i);
    if( !hLibrary ){
        goto Fail;
    }

    for( i=0; i< COUNTOF( aszClusApi ); ++i, ++pFarProc) {

        *pFarProc = GetProcAddress( hLibrary, aszClusApi[i] );
        if( !*pFarProc ){

            DBGMSG( DBG_WARN,
                    ( "LoadClusterFunctions: Loading function %hs failed %d\n",
                      aszClusApi[i], GetLastError( )));
            goto Fail;
        }
    }

    return TRUE;

Fail:

    if( hLibrary ){
        FreeLibrary( hLibrary );
    }

    ClusApi.pfnOpenCluster = NULL;
    return FALSE;
}

HKEY
OpenClusterParameterKey(
    IN LPCTSTR pszResource
    )

/*++

Routine Description:

    Based on a resource string, open the cluster key with FULL access.

Arguments:

    pszResource - Name of the resource key.

Return Value:

    HKEY - Success.  Key must be closed with

    NULL - Failure.  LastError set.

--*/

{
    HCLUSTER hCluster;
    HRESOURCE hResource = NULL;
    HKEY hKeyResource = NULL;
    HKEY hKey = NULL;
    DWORD Status;
    DWORD dwDisposition;

    if( !LoadClusterFunctions( )){
        return NULL;
    }

    hCluster = ClusApi.pfnOpenCluster( NULL );

    if( !hCluster ){

        DBGMSG( DBG_WARN,
                ( "OpenClusterResourceKey: failed to open cluster %d\n",
                  GetLastError() ));
        goto Fail;
    }

    hResource = ClusApi.pfnOpenClusterResource( hCluster, pszResource );

    if( !hResource ){
        DBGMSG( DBG_WARN,
                ( "OpenClusterResourceKey: failed to open resource "TSTR" %d\n",
                  pszResource, GetLastError() ));
        goto Fail;
    }

    hKeyResource = ClusApi.pfnGetClusterResourceKey( hResource,
                                                     KEY_ALL_ACCESS );

    if( !hKeyResource ){
        DBGMSG( DBG_WARN,
                ( "OpenClusterResourceKey: failed to open resource key %d\n",
                  GetLastError() ));
        goto Fail;
    }

    

    if((Status = ClusApi.pfnClusterRegOpenKey( hKeyResource,
                                               szParameters,
                                               KEY_CREATE_SUB_KEY | KEY_ALL_ACCESS,
                                               &hKey )) == ERROR_FILE_NOT_FOUND)
    {
        Status = ClusApi.pfnClusterRegCreateKey( hKeyResource,
                                                 szParameters,
                                                 0,
                                                 KEY_ALL_ACCESS,
                                                 NULL,
                                                 &hKey,
                                                 &dwDisposition );
    }

    if( Status != ERROR_SUCCESS ){

        SetLastError( Status );
        hKey = NULL;
        DBGMSG( DBG_WARN,
                ( "OpenClusterResourceKey: failed to create resource key %d\n",
                  Status ));
    }

Fail:

    if( hKeyResource ){
        ClusApi.pfnClusterRegCloseKey( hKeyResource );
    }

    if( hResource ){
        ClusApi.pfnCloseClusterResource( hResource );
    }

    if( hCluster ){
        ClusApi.pfnCloseCluster( hCluster );
    }

    return hKey;
}


/********************************************************************

    SplReg*Key functions:

    Used for printer registry access.

********************************************************************/

LONG
SplRegCreateKey(
    IN     HKEY hKey,
    IN     LPCTSTR pszSubKey,
    IN     DWORD dwOptions,
    IN     REGSAM samDesired,
    IN     PSECURITY_ATTRIBUTES pSecurityAttributes,
       OUT PHKEY phkResult,
       OUT PDWORD pdwDisposition,
    IN     PINISPOOLER pIniSpooler OPTIONAL
    )
{
    DWORD dwDisposition;
    DWORD Status;

    if( !pdwDisposition ){
        pdwDisposition = &dwDisposition;
    }

    if( pIniSpooler && pIniSpooler->SpoolerFlags & SPL_CLUSTER_REG )
    {

        if( !LoadClusterFunctions( ))
        {
            return GetLastError();
        }
        

        if((Status = ClusApi.pfnClusterRegOpenKey( hKey,
                                         pszSubKey,
                                         KEY_CREATE_SUB_KEY | samDesired,
                                         phkResult)) == ERROR_FILE_NOT_FOUND)
        {
            Status = ClusApi.pfnClusterRegCreateKey( hKey,
                                                     pszSubKey,
                                                     dwOptions,
                                                     samDesired,
                                                     pSecurityAttributes,
                                                     phkResult,
                                                     &dwDisposition );
        }
    }

    else
    {
        Status = RegCreateKeyEx( hKey,
                                 pszSubKey,
                                 0,
                                 NULL,
                                 dwOptions,
                                 samDesired,
                                 pSecurityAttributes,
                                 phkResult,
                                 &dwDisposition );    
    }
    return(Status);
}

LONG
SplRegOpenKey(
    IN     HKEY hKey,
    IN     LPCTSTR pszSubKey,
    IN     REGSAM samDesired,
       OUT PHKEY phkResult,
    IN     PINISPOOLER pIniSpooler OPTIONAL
    )
{
    if( pIniSpooler && pIniSpooler->SpoolerFlags & SPL_CLUSTER_REG ){

        if( !LoadClusterFunctions( )){
            return GetLastError();
        }
        return ClusApi.pfnClusterRegOpenKey( hKey,
                                             pszSubKey,
                                             samDesired,
                                             phkResult );
    }

    return RegOpenKeyEx( hKey,
                         pszSubKey,
                         0,
                         samDesired,
                         phkResult );
}

LONG
SplRegCloseKey(
    IN HKEY hKey,
    IN PINISPOOLER pIniSpooler OPTIONAL
    )
{
    if( pIniSpooler && pIniSpooler->SpoolerFlags & SPL_CLUSTER_REG ){

        if( !LoadClusterFunctions( )){
            return GetLastError();
        }
        return ClusApi.pfnClusterRegCloseKey( hKey );
    }

    return RegCloseKey( hKey );
}

LONG
SplRegDeleteKey(
    IN HKEY hKey,
    IN LPCTSTR pszSubKey,
    IN PINISPOOLER pIniSpooler OPTIONAL
    )
{
    if( pIniSpooler && pIniSpooler->SpoolerFlags & SPL_CLUSTER_REG ){

        if( !LoadClusterFunctions( )){
            return GetLastError();
        }
        return ClusApi.pfnClusterRegDeleteKey( hKey, pszSubKey );
    }

    return RegDeleteKey( hKey, pszSubKey );
}

LONG
SplRegEnumKey(
    IN     HKEY hKey,
    IN     DWORD dwIndex,
    IN     LPTSTR pszName,
    IN OUT PDWORD pcchName,
       OUT PFILETIME pft,
    IN PINISPOOLER pIniSpooler OPTIONAL
    )
{
    FILETIME ft;

    if( !pft ){
        pft = &ft;
    }

    if( pIniSpooler && pIniSpooler->SpoolerFlags & SPL_CLUSTER_REG ){

        if( !LoadClusterFunctions( )){
            return GetLastError();
        }
        return ClusApi.pfnClusterRegEnumKey( hKey,
                                             dwIndex,
                                             pszName,
                                             pcchName,
                                             pft );
    }

    return RegEnumKeyEx( hKey,
                         dwIndex,
                         pszName,
                         pcchName,
                         NULL,
                         NULL,
                         NULL,
                         pft );
}

LONG
SplRegSetValue(
    IN HKEY hKey,
    IN LPCTSTR pszValue,
    IN DWORD dwType,
    IN const BYTE* pData,
    IN DWORD cbData,
    IN PINISPOOLER pIniSpooler OPTIONAL
    )
{
    if( pIniSpooler && pIniSpooler->SpoolerFlags & SPL_CLUSTER_REG ){

        if( !LoadClusterFunctions( )){
            return GetLastError();
        }

        //
        // ClusterRegSetValue doesn't like NULL pointers.
        //
        if( cbData == 0 ){
            pData = (PBYTE)&cbData;
        }
        return ClusApi.pfnClusterRegSetValue( hKey,
                                              pszValue,
                                              dwType,
                                              pData,
                                              cbData );
    }

    return RegSetValueEx( hKey,
                          pszValue,
                          0,
                          dwType,
                          pData,
                          cbData );
}

LONG
SplRegDeleteValue(
    IN HKEY hKey,
    IN LPCTSTR pszValue,
    IN PINISPOOLER pIniSpooler OPTIONAL
    )
{
    if( pIniSpooler && pIniSpooler->SpoolerFlags & SPL_CLUSTER_REG ){

        if( !LoadClusterFunctions( )){
            return GetLastError();
        }
        return ClusApi.pfnClusterRegDeleteValue( hKey, pszValue );
    }

    return RegDeleteValue( hKey, pszValue );
}

LONG
SplRegQueryValue(
    IN     HKEY hKey,
    IN     LPCTSTR pszValue,
       OUT PDWORD pType, OPTIONAL
       OUT PBYTE pData,
    IN OUT PDWORD pcbData,
    IN PINISPOOLER pIniSpooler OPTIONAL
    )
{
    if( pIniSpooler && pIniSpooler->SpoolerFlags & SPL_CLUSTER_REG ){

        if( !LoadClusterFunctions( )){
            return GetLastError();
        }
        return ClusApi.pfnClusterRegQueryValue( hKey,
                                                pszValue,
                                                pType,
                                                pData,
                                                pcbData );
    }

    return RegQueryValueEx( hKey,
                            pszValue,
                            NULL,
                            pType,
                            pData,
                            pcbData );
}

LONG
SplRegEnumValue(
    IN     HKEY hKey,
    IN     DWORD dwIndex,
       OUT LPTSTR pszValue,
    IN OUT PDWORD pcbValue,
       OUT PDWORD pType, OPTIONAL
       OUT PBYTE pData,
    IN OUT PDWORD pcbData,
    IN PINISPOOLER pIniSpooler OPTIONAL
    )
{
    if( pIniSpooler && pIniSpooler->SpoolerFlags & SPL_CLUSTER_REG ){

        if( !LoadClusterFunctions( )){
            return GetLastError();
        }
        return ClusApi.pfnClusterRegEnumValue( hKey,
                                               dwIndex,
                                               pszValue,
                                               pcbValue,
                                               pType,
                                               pData,
                                               pcbData );
    }

    return RegEnumValue( hKey,
                         dwIndex,
                         pszValue,
                         pcbValue,
                         NULL,
                         pType,
                         pData,
                         pcbData );
}



LONG
SplRegQueryInfoKey(
    HKEY hKey,
    PDWORD pcSubKeys,
    PDWORD pcbKey,
    PDWORD pcValues,
    PDWORD pcbValue,
    PDWORD pcbData,
    PDWORD pcbSecurityDescriptor,
    PFILETIME pftLastWriteTime,
    PINISPOOLER pIniSpooler
    )
{
    LONG rc;

    if( pIniSpooler && pIniSpooler->SpoolerFlags & SPL_CLUSTER_REG ){

        if( !LoadClusterFunctions( )){
            return GetLastError();
        }
        rc = ClusApi.pfnClusterRegQueryInfoKey( hKey,
                                                pcSubKeys,
                                                pcbKey,
                                                pcValues,
                                                pcbValue,
                                                pcbData,
                                                pcbSecurityDescriptor,
                                                pftLastWriteTime);
    } else {

        rc = RegQueryInfoKey( hKey,           // Key
                              NULL,           // lpClass
                              NULL,           // lpcbClass
                              NULL,           // lpReserved
                              pcSubKeys,      // lpcSubKeys
                              pcbKey,         // lpcbMaxSubKeyLen
                              NULL,           // lpcbMaxClassLen
                              pcValues,       // lpcValues
                              pcbValue,       // lpcbMaxValueNameLen
                              pcbData,        // lpcbMaxValueLen
                              pcbSecurityDescriptor, // lpcbSecurityDescriptor
                              pftLastWriteTime       // lpftLastWriteTime
                              );
    }

    if( pcbValue ){
        *pcbValue = ( *pcbValue + 1 ) * sizeof(WCHAR);
    }

    return rc;
}


/*++

Routine Name:
    
    ClusterGetResourceDriveLetter
    
Routine Description:
    
    Gets the dependent disk for a cluster resource
    (a cluster spooler resource)

Arguments:
    
    pszResource            - spooler resource name
    ppszClusResDriveLetter - pointer that will get the pointer to string
                             Must be freed by caller using FreeSplMem()  
    
Return Value:
    
    Win32 error code
    
--*/
DWORD 
ClusterGetResourceDriveLetter(
    IN     LPCWSTR  pszResource, 
       OUT LPWSTR  *ppszClusResDriveLetter
    )
{
    DWORD dwError = ERROR_INVALID_PARAMETER;
    
    if (pszResource && ppszClusResDriveLetter) 
    {    
        HCLUSTER    hCluster   = NULL;
        HRESOURCE   hRes       = NULL;
        LPCWSTR     pszDllName = L"resutils.dll";
        HMODULE     hModule    = NULL;
        typedef DWORD (WINAPI *PFNFINDDISK)(HCLUSTER, HRESOURCE, LPWSTR, LPDWORD); 
        PFNFINDDISK pfnFindDisk;

        //
        // Don't leave the out var uninitialized
        //
        *ppszClusResDriveLetter = NULL;

        if (LoadClusterFunctions() &&
            (hCluster    = ClusApi.pfnOpenCluster(NULL)) &&
            (hRes        = ClusApi.pfnOpenClusterResource(hCluster, pszResource)) &&
            (hModule     = LoadLibrary(pszDllName)) &&
            (pfnFindDisk = (PFNFINDDISK)GetProcAddress(hModule, "ResUtilFindDependentDiskResourceDriveLetter")))
        {
            //
            // We make a guess for how large the buffer must be. We may not have to call
            // the resutil function twice. Driver letter + colon + NULL = 3
            //
            DWORD cchDriveLetter = kDriveLetterStringSize;

            dwError = ERROR_NOT_ENOUGH_MEMORY;
                            
            if (*ppszClusResDriveLetter = AllocSplMem(cchDriveLetter * sizeof(WCHAR)))
            {
                dwError = pfnFindDisk(hCluster, hRes, *ppszClusResDriveLetter, &cchDriveLetter);
    
                //
                // Reallocate buffer if it was not sufficient
                //
                if (dwError == ERROR_MORE_DATA) 
                {
                    FreeSplMem(*ppszClusResDriveLetter);
                                    
                    dwError = ERROR_NOT_ENOUGH_MEMORY;
                    
                    if (*ppszClusResDriveLetter = AllocSplMem(cchDriveLetter * sizeof(WCHAR)))
                    {
                        dwError = pfnFindDisk(hCluster, hRes, *ppszClusResDriveLetter, &cchDriveLetter);
                    }
                }
    
                if (dwError != ERROR_SUCCESS) 
                {
                    //
                    // Clean up in case of failure
                    //
                    FreeSplMem(*ppszClusResDriveLetter);
                    *ppszClusResDriveLetter = NULL;
                }                                                                                                     
            }
        }
        else
        {
            dwError = GetLastError();
        }

        if (hCluster) 
        {
            ClusApi.pfnCloseCluster(hCluster);
        }

        if (hRes)
        {   
            ClusApi.pfnCloseClusterResource(hRes);
        }

        if (hModule) 
        {
            FreeLibrary(hModule);
        }
    }
    
    DBGMSG(DBG_CLUSTER, ("ClusterGetResourceDriveLetter returns Win32 error %u\n", dwError));

    return dwError;
}

/*++

Routine Name:
    
    ClusterGetResourceID
    
Routine Description:
    
    Gets the resource id (guid) for a specified cluster resource.                             

Arguments:
    
    pszResource   - spooler resource name
    ppszClusResID - pointer that will get the pointer to string
                    Must be freed by caller using FreeSplMem()  
    
Return Value:
    
    Win32 error code
    
--*/
DWORD 
ClusterGetResourceID(
    IN  LPCWSTR  pszResource, 
    OUT LPWSTR  *ppszClusResID
    )
{
    DWORD dwError = ERROR_INVALID_PARAMETER;
    
    if (pszResource && ppszClusResID) 
    {    
        HCLUSTER  hCluster = NULL;
        HRESOURCE hRes     = NULL;

        *ppszClusResID = NULL;

        if (LoadClusterFunctions() &&
           (hCluster = ClusApi.pfnOpenCluster(NULL)) &&
           (hRes     = ClusApi.pfnOpenClusterResource(hCluster, pszResource)))
        {
            //
            // The resource ID is a GUID. We make a gues for its size, maybe we
            // get around calling the function ClusterResourceControl twice.
            //
            DWORD cbIDString = kGuidStringSize * sizeof(WCHAR);

            dwError = ERROR_NOT_ENOUGH_MEMORY;
                            
            if (*ppszClusResID = AllocSplMem(cbIDString))
            {
                dwError = ClusApi.pfnClusterResourceControl(hRes, 
                                                            NULL,
                                                            CLUSCTL_RESOURCE_GET_ID,
                                                            NULL,                                               
                                                            0,
                                                            *ppszClusResID,
                                                            cbIDString,
                                                            &cbIDString);
                //
                // Reallocate buffer if it was not sufficiently large
                //
                if (dwError == ERROR_MORE_DATA) 
                {
                    FreeSplMem(*ppszClusResID);
                                    
                    dwError = ERROR_NOT_ENOUGH_MEMORY;
                            
                    if (*ppszClusResID = AllocSplMem(cbIDString ))
                    {
                        dwError = ClusApi.pfnClusterResourceControl(hRes, 
                                                                    NULL,
                                                                    CLUSCTL_RESOURCE_GET_ID,
                                                                    NULL,                                               
                                                                    0,
                                                                    *ppszClusResID,
                                                                    cbIDString,
                                                                    &cbIDString);
                    }
                }
    
                if (dwError != ERROR_SUCCESS) 
                {
                    //
                    // Clean up in case of failure
                    //
                    FreeSplMem(*ppszClusResID);
                    *ppszClusResID = NULL;
                }                                                                                                     
            }
        }
        else
        {
            dwError = GetLastError();
        }
                    
        if (hRes)
        {
            ClusApi.pfnCloseClusterResource(hRes);
        }
                    
        if (hCluster) 
        {
            ClusApi.pfnCloseCluster(hCluster);
        }       
    }
    
    DBGMSG(DBG_CLUSTER, ("ClusterGetResourceID returns Win32 error %u\n", dwError));

    return dwError;
}