Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1043 lines
24 KiB

//+----------------------------------------------------------------------------
//
// Copyright (C) 1992, Microsoft Corporation
//
// File: dfssvc.c
//
// Contents: Code to interact with service manager.
//
// Classes:
//
// Functions:
//
// History: 12 Nov 92 Milans created.
//
//-----------------------------------------------------------------------------
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <dfsfsctl.h>
#include <windows.h>
#include <stdlib.h>
#include <lm.h>
#include <dsrole.h>
#include <dfsstr.h>
#include <libsup.h>
#include <dfsmsrv.h>
#include <winldap.h>
#include "dfsipc.h"
#include "dominfo.h"
#include "wmlum.h"
#include "wmlmacro.h"
#include "svcwml.h"
#include <debug.h>
DECLARE_DEBUG(DfsSvc)
DECLARE_INFOLEVEL(DfsSvc)
#if DBG == 1
#define svc_debug_error(x,y) DfsSvcInlineDebugOut(DEB_ERROR, x, y)
#define svc_debug_trace(x,y) DfsSvcInlineDebugOut(DEB_TRACE, x, y)
#else // DBG == 1
#define svc_debug_error(x,y)
#define svc_debug_trace(x,y)
#endif // DBG == 1
#define MAX_HINT_PERIODS 1000
BOOLEAN fStartAsService;
const PWSTR wszDfsServiceName = L"DfsService";
SERVICE_STATUS DfsStatus;
SERVICE_STATUS_HANDLE hDfsService;
VOID DfsSvcMsgProc(
DWORD dwControl);
VOID StartDfsService(
DWORD dwNumServiceArgs,
LPWSTR *lpServiceArgs);
DWORD
DfsStartDfssrv(VOID);
VOID
DfsStopDfssrv( VOID);
BOOL
DfsIsThisADfsRoot();
DWORD
DfsInitializationLoop(
LPVOID lpThreadParams);
DWORD DfsManagerProc();
DWORD DfsRegDeleteKeyAndChildren(HKEY hkey, LPWSTR s);
DWORD DfsCleanLocalVols(void);
DWORD DfsDeleteChildKeys(HKEY hkey, LPWSTR s);
void UpdateStatus(
SERVICE_STATUS_HANDLE hService,
SERVICE_STATUS *pSStatus,
DWORD Status);
//
// Event logging and debugging globals
//
extern ULONG DfsSvcVerbose;
extern ULONG DfsEventLog;
typedef void (FAR WINAPI *DLL_ENTRY_PROC)(PWSTR);
//
// Our domain name and machine name
//
WCHAR MachineName[MAX_PATH];
WCHAR DomainName[MAX_PATH];
WCHAR DomainNameDns[MAX_PATH];
WCHAR LastMachineName[MAX_PATH];
WCHAR LastDomainName[MAX_PATH];
WCHAR SiteName[MAX_PATH];
CRITICAL_SECTION DomListCritSection;
//
// Our role in life (see dsrole.h)
//
DSROLE_MACHINE_ROLE DfsMachineRole;
//
// Type of dfs (FtDfs==DFS_MANAGER_FTDFS/Machine-based==DFS_MANAGER_SERVER)
//
ULONG DfsServerType = 0;
//
// Long-lived ldap handle to the ds on this machine, if it is a DC
//
PLDAP pLdap = NULL;
GUID DfsRtlTraceGuid = { // 79d1da1f-7268-441b-b835-7c7bed5ab39e
0x79d1da1f, 0x7268, 0x441b,
{0xb8, 0x35, 0x7c, 0x7b, 0xed, 0x5a, 0xb3, 0x9e}};
extern void DfsInitWml();
//+----------------------------------------------------------------------------
//
// Function: WinMain
//
// Synopsis: This guy will set up link to service manager and install
// ServiceMain as the service's entry point. Hopefully, the service
// control dispatcher will call ServiceMain soon thereafter.
//
// Arguments:
//
// Returns:
//
//-----------------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpszCmdLine,
int nCmdShow)
{
SERVICE_TABLE_ENTRYW aServiceEntry[2];
DWORD status;
if (_stricmp( lpszCmdLine, "-noservice" ) == 0) {
fStartAsService = FALSE;
StartDfsService( 0, NULL );
//
// Since we were not started as a service, we wait for ever...
//
Sleep( INFINITE );
} else {
fStartAsService = TRUE;
aServiceEntry[0].lpServiceName = wszDfsServiceName;
aServiceEntry[0].lpServiceProc = StartDfsService;
aServiceEntry[1].lpServiceName = NULL;
aServiceEntry[1].lpServiceProc = NULL;
svc_debug_trace("Starting Dfs Services...\n", 0);
if (!StartServiceCtrlDispatcherW(aServiceEntry)) {
svc_debug_error("Error %d starting as service!\n", GetLastError());
return(GetLastError());
}
}
//
// If the StartServiceCtrlDispatcher call succeeded, we will never get to
// this point until someone stops this service.
//
return(0);
}
//+----------------------------------------------------------------------------
//
// Function: StartDfsService
//
// Synopsis: Call back for DfsService service. This is called *once* by the
// Service controller when the DfsService service is to be inited
// This function is responsible for registering a message
// handler function for the DfsService service.
//
// Arguments: Unused
//
// Returns: Nothing
//
//-----------------------------------------------------------------------------
VOID
StartDfsService(DWORD dwNumServiceArgs, LPWSTR *lpServiceArgs)
{
HANDLE hInit;
DWORD dwErr;
DWORD idThread;
HKEY hkey;
PSECURITY_ATTRIBUTES pSecAttribs = NULL;
PDSROLE_PRIMARY_DOMAIN_INFO_BASIC pPrimaryDomainInfo;
ULONG CheckPoint = 0;
#if (DBG == 1) || (_CT_TEST_HOOK == 1)
SECURITY_DESCRIPTOR SD;
SECURITY_ATTRIBUTES SA;
BOOL fRC = InitializeSecurityDescriptor(
&SD,
SECURITY_DESCRIPTOR_REVISION);
if( fRC == TRUE ) {
fRC = SetSecurityDescriptorDacl(
&SD,
TRUE,
NULL,
FALSE);
}
SA.nLength = sizeof(SECURITY_ATTRIBUTES);
SA.lpSecurityDescriptor = &SD;
SA.bInheritHandle = FALSE;
pSecAttribs = &SA;
#endif
svc_debug_trace("StartDfsService: fStartAsService = %d\n", fStartAsService);
if (fStartAsService) {
hDfsService = RegisterServiceCtrlHandlerW(
wszDfsServiceName,
DfsSvcMsgProc);
if (!hDfsService) {
svc_debug_error("Error %d installing Dfs msg handler\n", GetLastError());
return;
}
DfsStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
DfsStatus.dwCurrentState = SERVICE_STOPPED;
DfsStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
DfsStatus.dwWin32ExitCode = 0;
DfsStatus.dwServiceSpecificExitCode = 0;
DfsStatus.dwCheckPoint = CheckPoint++;
DfsStatus.dwWaitHint = 1000 * 30;
svc_debug_trace("Updating Status to Start Pending...\n", 0);
UpdateStatus(hDfsService, &DfsStatus, SERVICE_START_PENDING);
}
//
// Remove any old exit pt info from the registry
//
DfsCleanLocalVols();
InitializeCriticalSection(&DomListCritSection);
//
// Get our machine name and type/role.
//
dwErr = DsRoleGetPrimaryDomainInformation(
NULL,
DsRolePrimaryDomainInfoBasic,
(PBYTE *)&pPrimaryDomainInfo);
if (dwErr == ERROR_SUCCESS) {
DfsMachineRole = pPrimaryDomainInfo->MachineRole;
DomainName[0] = LastDomainName[0] = L'\0';
DomainNameDns[0] = L'\0';
if (pPrimaryDomainInfo->DomainNameFlat != NULL) {
if (wcslen(pPrimaryDomainInfo->DomainNameFlat) < MAX_PATH) {
wcscpy(DomainName,pPrimaryDomainInfo->DomainNameFlat);
wcscpy(LastDomainName, DomainName);
} else {
dwErr = ERROR_NOT_ENOUGH_MEMORY;
}
}
if (pPrimaryDomainInfo->DomainNameDns != NULL) {
if (wcslen(pPrimaryDomainInfo->DomainNameDns) < MAX_PATH) {
wcscpy(DomainNameDns,pPrimaryDomainInfo->DomainNameDns);
} else {
dwErr = ERROR_NOT_ENOUGH_MEMORY;
}
}
DsRoleFreeMemory(pPrimaryDomainInfo);
}
if (dwErr != ERROR_SUCCESS) {
svc_debug_error("StartDfsService:DsRoleGetPrimaryDomainInformation %08lx!\n", dwErr);
DfsStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
DfsStatus.dwServiceSpecificExitCode = dwErr;
DfsStatus.dwCheckPoint = 0;
UpdateStatus(hDfsService, &DfsStatus, SERVICE_STOPPED);
return;
}
//
// Create a thread to finish initialization
//
hInit = CreateThread(
NULL, // Security attributes
0, // Use default stack size
DfsInitializationLoop, // Thread entry procedure
0, // Thread context parameter
0, // Start immediately
&idThread); // Thread ID
if (hInit == NULL) {
svc_debug_error(
"Unable to create Driver Init thread %08lx\n", GetLastError());
DfsStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
DfsStatus.dwServiceSpecificExitCode = GetLastError();
DfsStatus.dwCheckPoint = 0;
UpdateStatus(hDfsService, &DfsStatus, SERVICE_STOPPED);
return;
}
else {
// 428812: no need to keep the handle around. prevent thread leak.
CloseHandle( hInit );
}
DfsStatus.dwCheckPoint = CheckPoint++;
UpdateStatus(hDfsService, &DfsStatus, SERVICE_RUNNING);
return;
}
DWORD
DfsInitializationLoop(
LPVOID lpThreadParams)
{
HANDLE hLPC;
DWORD idLpcThread;
HANDLE hDfs;
ULONG Retry;
DWORD dwErr;
NTSTATUS Status;
Retry = 0;
DfsInitWml();
do {
dwErr = DfsManagerProc();
} while (dwErr != ERROR_SUCCESS && ++Retry < 10);
if (dwErr != ERROR_SUCCESS) {
svc_debug_error("Dfs Manager failed %08lx!\n", dwErr);
}
switch (DfsMachineRole) {
case DsRole_RoleBackupDomainController:
case DsRole_RolePrimaryDomainController:
//
// Init the special name table
//
Retry = 0;
DfsInitDomainList();
do {
Status = DfsInitOurDomainDCs();
if (Status != ERROR_SUCCESS) {
Sleep(10*1000);
}
} while (Status != ERROR_SUCCESS && ++Retry < 10);
DfsInitRemainingDomainDCs();
pLdap = ldap_init(L"LocalHost", LDAP_PORT);
if (pLdap != NULL) {
dwErr = ldap_set_option(pLdap, LDAP_OPT_AREC_EXCLUSIVE, LDAP_OPT_ON);
if (dwErr != LDAP_SUCCESS) {
pLdap = NULL;
} else {
dwErr = ldap_bind_s(pLdap, NULL, NULL, LDAP_AUTH_SSPI);
if (dwErr != ERROR_SUCCESS) {
svc_debug_error("Could not bind to LocalHost\n", 0);
pLdap = NULL;
}
}
} else {
svc_debug_error("Could not open LocalHost\n", 0);
}
/* Fall THRU */
case DsRole_RoleMemberServer:
//
// Start the dfs lpc server
//
hLPC = CreateThread(
NULL, // Security attributes
0, // Use default stack size
(LPTHREAD_START_ROUTINE)DfsStartDfssrv, // Thread entry procedure
0, // Thread context parameter
0, // Start immediately
&idLpcThread); // Thread ID
if (hLPC == NULL) {
svc_debug_error(
"Unable to create Driver LPC thread %08lx\n", GetLastError());
}
else {
CloseHandle(hLPC);
}
}
Status = DfsOpen( &hDfs, NULL );
if (NT_SUCCESS(Status)) {
switch (DfsMachineRole) {
case DsRole_RoleBackupDomainController:
case DsRole_RolePrimaryDomainController:
Status = DfsFsctl(
hDfs,
FSCTL_DFS_ISDC,
NULL,
0L,
NULL,
0L);
}
NtClose( hDfs );
} else {
svc_debug_error("Unable to open dfs driver %08lx\n", Status);
svc_debug_error("UNC names will not work!\n", 0);
}
switch (DfsMachineRole) {
case DsRole_RoleBackupDomainController:
case DsRole_RolePrimaryDomainController:
do {
Status = DfsInitRemainingDomainDCs();
if (Status != ERROR_SUCCESS) {
Sleep(1000 * 60 * 10); // 10 min
}
} while (Status != ERROR_SUCCESS && ++Retry < 100);
do {
Sleep(1000 * 60 * 15); // 15 min
DfsInitDomainList();
DfsInitOurDomainDCs();
DfsInitRemainingDomainDCs();
} while (TRUE);
}
return 0;
}
//+----------------------------------------------------------------------------
//
// Function: DfsSvcMsgProc
//
// Synopsis: Service-Message handler for DFSInit.
//
// Arguments: [dwControl] - the message
//
// Returns: nothing
//
//-----------------------------------------------------------------------------
VOID
DfsSvcMsgProc(DWORD dwControl)
{
NTSTATUS Status;
HANDLE hDfs;
switch(dwControl) {
case SERVICE_CONTROL_STOP:
#if DBG
if (DfsSvcVerbose)
DbgPrint("DfsSvcMsgProc(SERVICE_CONTROL_STOP)\n");
#endif
//
// Stop the driver
//
Status = DfsOpen( &hDfs, NULL );
if (NT_SUCCESS(Status)) {
Status = DfsFsctl(
hDfs,
FSCTL_DFS_STOP_DFS,
NULL,
0L,
NULL,
0L);
svc_debug_trace("Fsctl STOP_DFS returned %08lx\n", Status);
Status = DfsFsctl(
hDfs,
FSCTL_DFS_RESET_PKT,
NULL,
0L,
NULL,
0L);
svc_debug_trace("Fsctl FSCTL_DFS_RESET_PKT returned %08lx\n", Status);
NtClose( hDfs );
}
#if DBG
if (DfsSvcVerbose)
DbgPrint("DfsSvcMsgProc: calling DfsStopDfssvc\n");
#endif
DfsStopDfssrv();
#if DBG
if (DfsSvcVerbose)
DbgPrint("DfsSvcMsgProc: DfsStopDfssvc returned\n");
#endif
UpdateStatus(hDfsService, &DfsStatus, SERVICE_STOPPED);
break;
case SERVICE_INTERROGATE:
#if DBG
if (DfsSvcVerbose)
DbgPrint("DfsSvcMsgProc(SERVICE_INTERROGATE)\n");
#endif
//
// We don't seem to be called with SERVICE_INTERROGATE ever!
//
if (DfsStatus.dwCurrentState == SERVICE_START_PENDING &&
DfsStatus.dwCheckPoint < MAX_HINT_PERIODS) {
DfsStatus.dwCheckPoint++;
svc_debug_trace("DFSInit Checkpoint == %d\n", DfsStatus.dwCheckPoint);
UpdateStatus(hDfsService, &DfsStatus, SERVICE_START_PENDING);
} else {
DfsStatus.dwCheckPoint = 0;
UpdateStatus(hDfsService, &DfsStatus, DfsStatus.dwCurrentState);
}
break;
default:
break;
}
}
//+----------------------------------------------------------------------------
//
// Function: UpdateStatus
//
// Synopsis: Pushes a ServiceStatus to the service manager.
//
// Arguments: [hService] - handle returned from RegisterServiceCtrlHandler
// [pSStatus] - pointer to service-status block
// [Status] - The status to set.
//
// Returns: Nothing.
//
//-----------------------------------------------------------------------------
static void
UpdateStatus(SERVICE_STATUS_HANDLE hService, SERVICE_STATUS *pSStatus, DWORD Status)
{
if (fStartAsService) {
pSStatus->dwCurrentState = Status;
if (Status == SERVICE_START_PENDING) {
pSStatus->dwCheckPoint++;
pSStatus->dwWaitHint = 1000;
} else {
pSStatus->dwCheckPoint = 0;
pSStatus->dwWaitHint = 0;
}
SetServiceStatus(hService, pSStatus);
}
}
//+----------------------------------------------------------------------------
//
// Function: DfsManagerIsDomainDfsEnabled
//
// Synopsis:
//
// Arguments:
//
// Returns:
//
//-----------------------------------------------------------------------------
BOOL
DfsManagerIsDomainDfsEnabled()
{
DWORD dwErr;
HKEY hkey;
dwErr = RegOpenKey(HKEY_LOCAL_MACHINE, REG_KEY_ENABLE_DOMAIN_DFS, &hkey);
if (dwErr == ERROR_SUCCESS) {
RegCloseKey( hkey );
return( TRUE );
} else {
return( FALSE );
}
}
//+----------------------------------------------------------------------------
//
// Function: DfsIsThisADfsRoot
//
// Synopsis:
//
// Arguments:
//
// Returns:
//
//-----------------------------------------------------------------------------
BOOL
DfsIsThisADfsRoot()
{
DWORD dwErr;
HKEY hkey;
dwErr = RegOpenKey( HKEY_LOCAL_MACHINE, VOLUMES_DIR, &hkey );
if (dwErr == ERROR_SUCCESS) {
RegCloseKey( hkey );
return( TRUE );
}
return( FALSE );
}
//+----------------------------------------------------------------------------
//
// Function: DfsManagerProc
//
// Synopsis: The Dfs Manager side implementation of Dfs Service.
//
// Arguments: None
//
// Returns: TRUE if everything went ok, FALSE otherwise
//
//-----------------------------------------------------------------------------
DWORD
DfsManagerProc()
{
DWORD dwErr;
PWKSTA_INFO_100 wkstaInfo = NULL;
HKEY hkey;
WCHAR wszFTDfsName[ MAX_PATH ];
DWORD cbName, dwType;
BOOLEAN fIsFTDfs = FALSE;
//
// Get our Machine name
//
dwErr = NetWkstaGetInfo( NULL, 100, (LPBYTE *) &wkstaInfo );
if (dwErr == ERROR_SUCCESS) {
if (wcslen(wkstaInfo->wki100_computername) < MAX_PATH) {
wcscpy(MachineName,wkstaInfo->wki100_computername);
wcscpy(LastMachineName, MachineName);
} else {
dwErr = ERROR_NOT_ENOUGH_MEMORY;
}
NetApiBufferFree( wkstaInfo );
}
if (dwErr != ERROR_SUCCESS) {
svc_debug_error("DfsManagerProc:NetWkstaGetInfo %08lx!\n", dwErr);
}
//
// Check VOLUMES_DIR, if it exists, get machine and domain name, and determine
// if this is an FtDfs participant
//
if (dwErr == ERROR_SUCCESS) {
dwErr = RegOpenKey( HKEY_LOCAL_MACHINE, VOLUMES_DIR, &hkey );
if (dwErr == ERROR_SUCCESS) {
cbName = sizeof(LastMachineName);
dwErr = RegQueryValueEx(
hkey,
MACHINE_VALUE_NAME,
NULL,
&dwType,
(PBYTE) LastMachineName,
&cbName);
if (dwErr != ERROR_SUCCESS) {
dwErr = RegSetValueEx(
hkey,
MACHINE_VALUE_NAME,
0,
REG_SZ,
(PCHAR)MachineName,
wcslen(MachineName) * sizeof(WCHAR));
} else if (dwType != REG_SZ) {
LastMachineName[0] = L'\0';
}
cbName = sizeof(LastDomainName);
dwErr = RegQueryValueEx(
hkey,
DOMAIN_VALUE_NAME,
NULL,
&dwType,
(PBYTE) LastDomainName,
&cbName);
if (dwErr != ERROR_SUCCESS) {
dwErr = RegSetValueEx(
hkey,
DOMAIN_VALUE_NAME,
0,
REG_SZ,
(PCHAR)DomainName,
wcslen(DomainName) * sizeof(WCHAR));
} else if (dwType != REG_SZ) {
LastDomainName[0] = L'\0';
}
//
// See if this is a Fault-Tolerant Dfs vs Server-Based Dfs
//
cbName = sizeof(wszFTDfsName);
dwErr = RegQueryValueEx(
hkey,
FTDFS_VALUE_NAME,
NULL,
&dwType,
(PBYTE) wszFTDfsName,
&cbName);
if ((dwErr == ERROR_SUCCESS) && (dwType == REG_SZ)) {
fIsFTDfs = TRUE;
}
RegCloseKey( hkey );
}
dwErr = ERROR_SUCCESS;
}
//
// Now we check if the machine role is appropriate
//
if (dwErr == ERROR_SUCCESS) {
switch(DfsMachineRole) {
//
// Somehow we were started on a workstation
//
case DsRole_RoleStandaloneWorkstation:
case DsRole_RoleMemberWorkstation:
dwErr = ERROR_NOT_SUPPORTED;
break;
//
// We can run, but not in FtDFS mode,
//
// If the domain name has changed, clean up the registry.
//
case DsRole_RoleStandaloneServer:
if (fIsFTDfs == TRUE || _wcsicmp(DomainName, LastDomainName) != 0) {
fIsFTDfs = FALSE;
}
break;
//
// Fully supported modes
//
case DsRole_RoleMemberServer:
case DsRole_RoleBackupDomainController:
case DsRole_RolePrimaryDomainController:
break;
}
}
if (dwErr == ERROR_SUCCESS) {
if (fIsFTDfs) {
dwErr = DfsManager(
wszFTDfsName,
DFS_MANAGER_FTDFS );
} else {
dwErr = DfsManager(
MachineName,
DFS_MANAGER_SERVER );
}
}
return( dwErr );
}
//+----------------------------------------------------------------------------
//
// Function: DfsCleanLocalVols
//
// Synopsis: Cleans out the LocalVolumes part of the registry, if there are
// dfs-link keys left over from older versions.
//
// Arguments:
//
// Returns:
//
//-----------------------------------------------------------------------------
DWORD
DfsCleanLocalVols(void)
{
HKEY hLvolKey = NULL;
HKEY hRootKey = NULL;
DWORD dwErr = ERROR_SUCCESS;
PWCHAR wCp;
wCp = malloc(4096);
if (wCp == NULL) {
dwErr = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
dwErr = RegOpenKey( HKEY_LOCAL_MACHINE, REG_KEY_LOCAL_VOLUMES, &hLvolKey);
if (dwErr != ERROR_SUCCESS)
goto Cleanup;
//
// Find the key representing the root
//
dwErr = RegEnumKey(hLvolKey, 0, wCp, 100);
if (dwErr != ERROR_SUCCESS)
goto Cleanup;
dwErr = RegOpenKey(hLvolKey, wCp, &hRootKey);
if (dwErr != ERROR_SUCCESS)
goto Cleanup;
while (dwErr == ERROR_SUCCESS && RegEnumKey(hRootKey, 0, wCp, 100) == ERROR_SUCCESS)
dwErr = DfsDeleteChildKeys(hRootKey, wCp);
Cleanup:
if (hLvolKey != NULL)
RegCloseKey(hLvolKey);
if (hRootKey != NULL)
RegCloseKey(hRootKey);
if (wCp != NULL)
free(wCp);
return dwErr;
}
//+----------------------------------------------------------------------------
//
// Function: DfsRegDeleteChildKeys
//
// Synopsis: Helper for DfsRegDeleteKeyAndChildren
//
// Arguments:
//
// Returns: ERROR_SUCCESS or failure code
//
//-----------------------------------------------------------------------------
DWORD
DfsDeleteChildKeys(HKEY hKey, LPWSTR s)
{
WCHAR *wCp;
HKEY nKey = NULL;
DWORD dwErr;
DWORD i = 0;
dwErr = RegOpenKey(hKey, s, &nKey);
if (dwErr != ERROR_SUCCESS)
return dwErr;
for (wCp = s; *wCp; wCp++)
;
while (dwErr == ERROR_SUCCESS && RegEnumKey(nKey, 0, wCp, 100) == ERROR_SUCCESS)
dwErr = DfsDeleteChildKeys(nKey, wCp);
*wCp = L'\0';
if (nKey != NULL)
RegCloseKey(nKey);
dwErr = RegDeleteKey(hKey, s);
return dwErr;
}