windows-nt/Source/XPSP1/NT/sdktools/vmmreg32/reghive.c
2020-09-26 16:20:57 +08:00

843 lines
21 KiB
C

//
// REGHIVE.C
//
// Copyright (C) Microsoft Corporation, 1995
//
// Implementation of RegLoadKey, RegUnLoadKey, RegSaveKey, RegReplaceKey and
// supporting functions.
//
#include "pch.h"
#ifdef WANT_HIVE_SUPPORT
// Maximum number of times we'll allow RgCopyBranch to be reentered.
#define MAXIMUM_COPY_RECURSION 32
LPSTR g_RgNameBufferPtr; // Temporary buffer for RgCopyBranch
LPBYTE g_RgDataBufferPtr; // Temporary buffer for RgCopyBranch
UINT g_RgRecursionCount; // Tracks depth of RgCopyBranch
#if MAXIMUM_VALUE_NAME_LENGTH > MAXIMUM_SUB_KEY_LENGTH
#error Code assumes a value name can fit in a subkey buffer.
#endif
#ifdef VXD
#pragma VxD_RARE_CODE_SEG
#endif
//
// RgValidateHiveSubKey
//
// Note that unlike most parameter validation routines, this routine must be
// called with the registry lock taken because we call RgGetNextSubSubKey.
//
// Pass back the length of the subkey to deal with the trailing backslash
// problem.
//
// Returns TRUE if lpSubKey is a valid subkey string for hive functions.
//
BOOL
INTERNAL
RgValidateHiveSubKey(
LPCSTR lpSubKey,
UINT FAR* lpHiveKeyLength
)
{
LPCSTR lpSubSubKey;
UINT SubSubKeyLength;
// Verify that we have a valid subkey that has one and only one sub-subkey.
// Win95 messed this up and it was possible to load a hive with a keyname
// containing a backslash!
return !IsNullPtr(lpSubKey) && !RgIsBadSubKey(lpSubKey) &&
(RgGetNextSubSubKey(lpSubKey, &lpSubSubKey, lpHiveKeyLength) > 0) &&
(RgGetNextSubSubKey(NULL, &lpSubSubKey, &SubSubKeyLength) == 0);
}
//
// VMMRegLoadKey
//
// See Win32 documentation of RegLoadKey.
//
LONG
REGAPI
VMMRegLoadKey(
HKEY hKey,
LPCSTR lpSubKey,
LPCSTR lpFileName
)
{
int ErrorCode;
HKEY hSubKey;
UINT SubKeyLength;
LPHIVE_INFO lpHiveInfo;
if (IsBadStringPtr(lpFileName, (UINT) -1))
return ERROR_INVALID_PARAMETER;
if ((hKey != HKEY_LOCAL_MACHINE) && (hKey != HKEY_USERS))
return ERROR_BADKEY;
if (!RgLockRegistry())
return ERROR_LOCK_FAILED;
if ((ErrorCode = RgValidateAndConvertKeyHandle(&hKey)) != ERROR_SUCCESS)
goto ReturnErrorCode;
if (!RgValidateHiveSubKey(lpSubKey, &SubKeyLength)) {
ErrorCode = ERROR_BADKEY;
goto ReturnErrorCode;
}
// Check if a subkey with the specified name already exists.
if (RgLookupKey(hKey, lpSubKey, &hSubKey, LK_OPEN) == ERROR_SUCCESS) {
RgDestroyKeyHandle(hSubKey);
ErrorCode = ERROR_BADKEY; // Win95 compatibility
goto ReturnErrorCode;
}
if (IsNullPtr((lpHiveInfo = (LPHIVE_INFO)
RgSmAllocMemory(sizeof(HIVE_INFO) + SubKeyLength)))) {
ErrorCode = ERROR_OUTOFMEMORY;
goto ReturnErrorCode;
}
// Fill in the HIVE_INFO.
StrCpy(lpHiveInfo-> Name, lpSubKey);
lpHiveInfo-> NameLength = SubKeyLength;
lpHiveInfo-> Hash = (BYTE) RgHashString(lpSubKey, SubKeyLength);
// Attempt to create a FILE_INFO for the specified file. If successful,
// link this HIVE_INFO into the parent FILE_INFO's hive list.
if ((ErrorCode = RgCreateFileInfoExisting(&lpHiveInfo-> lpFileInfo,
lpFileName)) == ERROR_SUCCESS) {
#ifdef WANT_NOTIFY_CHANGE_SUPPORT
lpHiveInfo-> lpFileInfo-> lpParentFileInfo = hKey-> lpFileInfo;
#endif
lpHiveInfo-> lpNextHiveInfo = hKey-> lpFileInfo-> lpHiveInfoList;
hKey-> lpFileInfo-> lpHiveInfoList = lpHiveInfo;
// Signal any notifications waiting on this top-level key.
RgSignalWaitingNotifies(hKey-> lpFileInfo, hKey-> KeynodeIndex,
REG_NOTIFY_CHANGE_NAME);
}
else
RgFreeMemory(lpHiveInfo);
ReturnErrorCode:
RgUnlockRegistry();
return ErrorCode;
}
//
// VMMRegUnLoadKey
//
// See Win32 documentation of RegUnLoadKey.
//
LONG
REGAPI
VMMRegUnLoadKey(
HKEY hKey,
LPCSTR lpSubKey
)
{
int ErrorCode;
UINT SubKeyLength;
LPFILE_INFO lpFileInfo;
LPHIVE_INFO lpPrevHiveInfo;
LPHIVE_INFO lpCurrHiveInfo;
if ((hKey != HKEY_LOCAL_MACHINE) && (hKey != HKEY_USERS))
return ERROR_BADKEY;
if (!RgLockRegistry())
return ERROR_LOCK_FAILED;
if ((ErrorCode = RgValidateAndConvertKeyHandle(&hKey)) != ERROR_SUCCESS)
goto ReturnErrorCode;
ErrorCode = ERROR_BADKEY; // Assume this error code
if (!RgValidateHiveSubKey(lpSubKey, &SubKeyLength))
goto ReturnErrorCode;
lpPrevHiveInfo = NULL;
lpCurrHiveInfo = hKey-> lpFileInfo-> lpHiveInfoList;
while (!IsNullPtr(lpCurrHiveInfo)) {
if (SubKeyLength == lpCurrHiveInfo-> NameLength && RgStrCmpNI(lpSubKey,
lpCurrHiveInfo-> Name, SubKeyLength) == 0) {
// Unlink this HIVE_INFO structure.
if (IsNullPtr(lpPrevHiveInfo))
hKey-> lpFileInfo-> lpHiveInfoList = lpCurrHiveInfo->
lpNextHiveInfo;
else
lpPrevHiveInfo-> lpNextHiveInfo = lpCurrHiveInfo->
lpNextHiveInfo;
// Flush and destroy it's associated FILE_INFO structure. When we
// destroy the FILE_INFO, all open keys in this hive will be
// invalidated.
lpFileInfo = lpCurrHiveInfo-> lpFileInfo;
RgFlushFileInfo(lpFileInfo);
RgDestroyFileInfo(lpFileInfo);
// Free the HIVE_INFO itself.
RgSmFreeMemory(lpCurrHiveInfo);
// Signal any notifications waiting on this top-level key.
RgSignalWaitingNotifies(hKey-> lpFileInfo, hKey-> KeynodeIndex,
REG_NOTIFY_CHANGE_NAME);
ErrorCode = ERROR_SUCCESS;
break;
}
lpPrevHiveInfo = lpCurrHiveInfo;
lpCurrHiveInfo = lpCurrHiveInfo-> lpNextHiveInfo;
}
ReturnErrorCode:
RgUnlockRegistry();
return ErrorCode;
}
//
// RgCopyBranchHelper
//
// Copies all of the values and subkeys starting at the specified source key to
// the specified destination key.
//
// For Win95 compatibility, we don't stop the copy process if we encounter an
// error. (But unlike Win95, we do actually check more error codes)
//
// SHOULD ONLY BE CALLED BY RgCopyBranch.
//
VOID
INTERNAL
RgCopyBranchHelper(
HKEY hSourceKey,
HKEY hDestinationKey
)
{
UINT Index;
DWORD cbNameBuffer;
LPVALUE_RECORD lpValueRecord;
//
// Copy all of the values from the source key to the destination key.
//
Index = 0;
while (RgLookupValueByIndex(hSourceKey, Index++, &lpValueRecord) ==
ERROR_SUCCESS) {
DWORD cbDataBuffer;
DWORD Type;
cbNameBuffer = MAXIMUM_VALUE_NAME_LENGTH;
cbDataBuffer = MAXIMUM_DATA_LENGTH + 1; // Terminating null
if (RgCopyFromValueRecord(hSourceKey, lpValueRecord, g_RgNameBufferPtr,
&cbNameBuffer, &Type, g_RgDataBufferPtr, &cbDataBuffer) ==
ERROR_SUCCESS) {
// Subtract the terminating null that RgCopyFromValueRecord added
// to cbDataBuffer. We don't save that in the file.
if (Type == REG_SZ) {
ASSERT(cbDataBuffer > 0); // Must have the null!
cbDataBuffer--;
}
RgSetValue(hDestinationKey, g_RgNameBufferPtr, Type,
g_RgDataBufferPtr, cbDataBuffer);
}
RgUnlockDatablock(hSourceKey-> lpFileInfo, hSourceKey-> BlockIndex,
FALSE);
}
// We can't recurse forever, so enforce a maximum depth like Win95.
if (g_RgRecursionCount > MAXIMUM_COPY_RECURSION)
return;
g_RgRecursionCount++;
//
// Copy all of the subkeys from the source key to the destination key.
//
Index = 0;
while (TRUE) {
HKEY hSubSourceKey;
HKEY hSubDestinationKey;
cbNameBuffer = MAXIMUM_SUB_KEY_LENGTH;
if (RgLookupKeyByIndex(hSourceKey, Index++, g_RgNameBufferPtr,
&cbNameBuffer) != ERROR_SUCCESS)
break;
if (RgLookupKey(hSourceKey, g_RgNameBufferPtr, &hSubSourceKey,
LK_OPEN) == ERROR_SUCCESS) {
if (RgLookupKey(hDestinationKey, g_RgNameBufferPtr,
&hSubDestinationKey, LK_CREATE) == ERROR_SUCCESS) {
RgYield();
RgCopyBranchHelper(hSubSourceKey, hSubDestinationKey);
RgDestroyKeyHandle(hSubDestinationKey);
}
else
TRAP();
RgDestroyKeyHandle(hSubSourceKey);
}
else
TRAP();
}
g_RgRecursionCount--;
}
//
// RgCopyBranch
//
// Copies all of the values and subkeys starting at the specified source key to
// the specified destination key.
//
// This function sets and cleans up for RgCopyBranchHelper who does all
// the real copying.
//
// The backing store of the destination file is flushed if successful.
//
int
INTERNAL
RgCopyBranch(
HKEY hSourceKey,
HKEY hDestinationKey
)
{
int ErrorCode;
if (IsNullPtr(g_RgNameBufferPtr = RgSmAllocMemory(MAXIMUM_SUB_KEY_LENGTH)))
ErrorCode = ERROR_OUTOFMEMORY;
else {
if (IsNullPtr(g_RgDataBufferPtr = RgSmAllocMemory(MAXIMUM_DATA_LENGTH +
1))) // + terminating null
ErrorCode = ERROR_OUTOFMEMORY;
else {
g_RgRecursionCount = 0;
RgCopyBranchHelper(hSourceKey, hDestinationKey);
// Everything should be copied over, so flush the file now since
// all callers will be immediately destroying this FILE_INFO
// anyways.
ErrorCode = RgFlushFileInfo(hDestinationKey-> lpFileInfo);
}
RgSmFreeMemory(g_RgNameBufferPtr);
}
RgSmFreeMemory(g_RgDataBufferPtr);
return ErrorCode;
}
//
// RgSaveKey
//
// Worker routine for VMMRegSaveKey and VMMRegReplaceKey. Saves all the keys
// and values starting at hKey, which must point at a valid KEY structure, to
// the location specified by lpFileName. The file must not currently exist.
//
int
INTERNAL
RgSaveKey(
HKEY hKey,
LPCSTR lpFileName
)
{
int ErrorCode;
HKEY hHiveKey;
if (IsNullPtr(hHiveKey = RgCreateKeyHandle()))
ErrorCode = ERROR_OUTOFMEMORY;
else {
// Artificially increment the count, so the below destroy will work.
RgIncrementKeyReferenceCount(hHiveKey);
if ((ErrorCode = RgCreateFileInfoNew(&hHiveKey-> lpFileInfo, lpFileName,
CFIN_SECONDARY)) == ERROR_SUCCESS) {
if (((ErrorCode = RgInitRootKeyFromFileInfo(hHiveKey)) != ERROR_SUCCESS) ||
((ErrorCode = RgCopyBranch(hKey, hHiveKey)) != ERROR_SUCCESS)) {
RgSetFileAttributes(hHiveKey-> lpFileInfo-> FileName,
FILE_ATTRIBUTE_NONE);
RgDeleteFile(hHiveKey-> lpFileInfo-> FileName);
}
// If successful, then RgCopyBranch has already flushed the file.
RgDestroyFileInfo(hHiveKey-> lpFileInfo);
}
RgDestroyKeyHandle(hHiveKey);
}
return ErrorCode;
}
//
// VMMRegSaveKey
//
// See Win32 documentation of RegSaveKey.
//
LONG
REGAPI
VMMRegSaveKey(
HKEY hKey,
LPCSTR lpFileName,
LPVOID lpSecurityAttributes
)
{
int ErrorCode;
if (IsBadStringPtr(lpFileName, (UINT) -1))
return ERROR_INVALID_PARAMETER;
if (!RgLockRegistry())
return ERROR_LOCK_FAILED;
if ((ErrorCode = RgValidateAndConvertKeyHandle(&hKey)) == ERROR_SUCCESS)
ErrorCode = RgSaveKey(hKey, lpFileName);
RgUnlockRegistry();
return ErrorCode;
UNREFERENCED_PARAMETER(lpSecurityAttributes);
}
#ifdef WANT_REGREPLACEKEY
//
// RgGetKeyName
//
LPSTR
INTERNAL
RgGetKeyName(
HKEY hKey
)
{
LPSTR lpKeyName;
LPKEY_RECORD lpKeyRecord;
if (RgLockKeyRecord(hKey-> lpFileInfo, hKey-> BlockIndex, hKey->
KeyRecordIndex, &lpKeyRecord) != ERROR_SUCCESS)
lpKeyName = NULL;
else {
// A registry is corrupt if we ever hit this. We'll continue to
// allocate a buffer and let downstream code fail when we try to use
// the string.
ASSERT(lpKeyRecord-> NameLength < MAXIMUM_SUB_KEY_LENGTH);
if (!IsNullPtr(lpKeyName = (LPSTR) RgSmAllocMemory(lpKeyRecord->
NameLength + 1))) { // + terminating null
MoveMemory(lpKeyName, lpKeyRecord-> Name, lpKeyRecord->
NameLength);
lpKeyName[lpKeyRecord-> NameLength] = '\0';
}
RgUnlockDatablock(hKey-> lpFileInfo, hKey-> BlockIndex, FALSE);
}
return lpKeyName;
}
//
// RgCreateRootKeyForFile
//
// Creates a KEY and a FILE_INFO to access the specified file.
//
int
INTERNAL
RgCreateRootKeyForFile(
LPHKEY lphKey,
LPCSTR lpFileName
)
{
int ErrorCode;
HKEY hKey;
if (IsNullPtr(hKey = RgCreateKeyHandle()))
ErrorCode = ERROR_OUTOFMEMORY;
else {
// Artificially increment the count, so RgDestroyKeyHandle will work.
RgIncrementKeyReferenceCount(hKey);
if ((ErrorCode = RgCreateFileInfoExisting(&hKey-> lpFileInfo,
lpFileName)) == ERROR_SUCCESS) {
if ((ErrorCode = RgInitRootKeyFromFileInfo(hKey)) ==
ERROR_SUCCESS) {
*lphKey = hKey;
return ERROR_SUCCESS;
}
RgDestroyFileInfo(hKey-> lpFileInfo);
}
RgDestroyKeyHandle(hKey);
}
return ErrorCode;
}
//
// RgDestroyRootKeyForFile
//
// Destroys the resources allocated by RgCreateRootKeyForFile.
//
VOID
INTERNAL
RgDestroyRootKeyForFile(
HKEY hKey
)
{
RgDestroyFileInfo(hKey-> lpFileInfo);
RgDestroyKeyHandle(hKey);
}
//
// RgDeleteHiveFile
//
// Deletes the specified hive file after clearing its file attributes.
//
BOOL
INTERNAL
RgDeleteHiveFile(
LPCSTR lpFileName
)
{
RgSetFileAttributes(lpFileName, FILE_ATTRIBUTE_NONE);
// RgSetFileAttributes may fail, but try to delete the file anyway.
return RgDeleteFile(lpFileName);
}
//
// VMMRegReplaceKey
//
// See Win32 documentation of RegReplaceKey.
//
LONG
REGAPI
VMMRegReplaceKey(
HKEY hKey,
LPCSTR lpSubKey,
LPCSTR lpNewFileName,
LPCSTR lpOldFileName
)
{
int ErrorCode;
HKEY hSubKey;
LPKEYNODE lpKeynode;
DWORD KeynodeIndex;
HKEY hParentKey;
char ReplaceFileName[MAX_PATH];
BOOL fCreatedReplaceFile;
HKEY hReplaceKey;
HKEY hNewKey;
HKEY hReplaceSubKey;
LPSTR lpReplaceSubKey;
if (IsBadOptionalStringPtr(lpSubKey, (UINT) -1) ||
IsBadStringPtr(lpNewFileName, (UINT) -1) ||
IsBadStringPtr(lpOldFileName, (UINT) -1))
return ERROR_INVALID_PARAMETER;
if (!RgLockRegistry())
return ERROR_LOCK_FAILED;
if ((ErrorCode = RgValidateAndConvertKeyHandle(&hKey)) != ERROR_SUCCESS)
goto ErrorReturn;
if ((ErrorCode = RgLookupKey(hKey, lpSubKey, &hSubKey, LK_OPEN)) !=
ERROR_SUCCESS)
goto ErrorReturn;
//
// The provided key handle must an immediate child from the same backing
// store (not a hive) as either HKEY_LOCAL_MACHINE or HKEY_USERS.
//
if (RgLockInUseKeynode(hSubKey-> lpFileInfo, hSubKey-> KeynodeIndex,
&lpKeynode) != ERROR_SUCCESS) {
ErrorCode = ERROR_OUTOFMEMORY;
goto ErrorDestroySubKey;
}
KeynodeIndex = lpKeynode-> ParentIndex;
RgUnlockKeynode(hSubKey-> lpFileInfo, hSubKey-> KeynodeIndex, FALSE);
// Find an open key on the parent check if it's HKEY_LOCAL_MACHINE or
// HKEY_USERS. If not, bail out. KeynodeIndex may be REG_NULL, but
// RgFindOpenKeyHandle handles that case.
if (IsNullPtr(hParentKey = RgFindOpenKeyHandle(hSubKey-> lpFileInfo,
KeynodeIndex)) || ((hParentKey != &g_RgLocalMachineKey) &&
(hParentKey != &g_RgUsersKey))) {
ErrorCode = ERROR_INVALID_PARAMETER;
goto ErrorDestroySubKey;
}
//
// All parameters have been validated, so begin the real work of the API.
//
// Because we'll be doing a file copy below, all changes must be flushed
// now.
if ((ErrorCode = RgFlushFileInfo(hSubKey-> lpFileInfo)) != ERROR_SUCCESS)
goto ErrorDestroySubKey;
// Make a backup of the current contents of the subkey.
if ((ErrorCode = RgSaveKey(hSubKey, lpOldFileName)) != ERROR_SUCCESS)
goto ErrorDestroySubKey;
RgGenerateAltFileName(hSubKey-> lpFileInfo-> FileName, ReplaceFileName, 'R');
// Check if the magic replacement file already exists and if not, create
// it.
if (RgGetFileAttributes(ReplaceFileName) == (DWORD) -1) {
if ((ErrorCode = RgCopyFile(hSubKey-> lpFileInfo-> FileName,
ReplaceFileName)) != ERROR_SUCCESS)
goto ErrorDeleteOldFile;
fCreatedReplaceFile = TRUE;
}
else
fCreatedReplaceFile = FALSE;
if ((ErrorCode = RgCreateRootKeyForFile(&hNewKey, lpNewFileName)) !=
ERROR_SUCCESS)
goto ErrorDeleteReplaceFile;
if ((ErrorCode = RgCreateRootKeyForFile(&hReplaceKey, ReplaceFileName)) !=
ERROR_SUCCESS)
goto ErrorDestroyNewRootKey;
// The original key that we were given may reference the subkey, so
// lpSubKey would be a NULL or empty string. But we need the name that
// this subkey refers to, so we have to go back to the file to pull out
// the name.
if (hKey != hSubKey)
lpReplaceSubKey = (LPSTR) lpSubKey;
else {
// We allocate this from the heap to reduce the requirements of an
// already strained stack. If this fails, we're likely out of memory.
// Even if that's not why we failed, this is such an infrequent path
// that it's a good enough error code.
if (IsNullPtr(lpReplaceSubKey = RgGetKeyName(hSubKey))) {
ErrorCode = ERROR_OUTOFMEMORY;
goto ErrorDestroyReplaceRootKey;
}
}
// Check if the specified subkey already exists and if it does, delete it.
if (RgLookupKey(hReplaceKey, lpReplaceSubKey, &hReplaceSubKey, LK_OPEN) ==
ERROR_SUCCESS) {
RgDeleteKey(hReplaceSubKey);
RgDestroyKeyHandle(hReplaceSubKey);
}
// Create the specified subkey in the replacement registry and copy the
// new hive to that key.
if ((ErrorCode = RgLookupKey(hReplaceKey, lpReplaceSubKey, &hReplaceSubKey,
LK_CREATE)) == ERROR_SUCCESS) {
// If successful, tag the FILE_INFO so that on system exit, we'll go
// and rename the replacement file to actual filename.
if ((ErrorCode = RgCopyBranch(hNewKey, hReplaceSubKey)) ==
ERROR_SUCCESS)
hKey-> lpFileInfo-> Flags |= FI_REPLACEMENTEXISTS;
RgDestroyKeyHandle(hReplaceSubKey);
}
if (lpSubKey != lpReplaceSubKey)
RgSmFreeMemory(lpReplaceSubKey);
ErrorDestroyReplaceRootKey:
RgDestroyRootKeyForFile(hReplaceKey);
ErrorDestroyNewRootKey:
RgDestroyRootKeyForFile(hNewKey);
ErrorDeleteReplaceFile:
if (ErrorCode != ERROR_SUCCESS && fCreatedReplaceFile)
RgDeleteHiveFile(ReplaceFileName);
ErrorDeleteOldFile:
if (ErrorCode != ERROR_SUCCESS)
RgDeleteHiveFile(lpOldFileName);
ErrorDestroySubKey:
RgDestroyKeyHandle(hSubKey);
ErrorReturn:
RgUnlockRegistry();
return ErrorCode;
}
#ifdef VXD
#pragma VxD_SYSEXIT_CODE_SEG
#endif
//
// RgReplaceFileOnSysExit
//
// Essentially the same algorithm as rlReplaceFile from the Win95 registry
// code with modifications for how file I/O is handled in this library.
//
int
INTERNAL
RgReplaceFileOnSysExit(
LPCSTR lpFileName
)
{
int ErrorCode;
char ReplaceFileName[MAX_PATH];
char SaveFileName[MAX_PATH];
ErrorCode = ERROR_SUCCESS;
if (RgGenerateAltFileName(lpFileName, ReplaceFileName, 'R') &&
RgGetFileAttributes(ReplaceFileName) == (FILE_ATTRIBUTE_READONLY |
FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) {
// If we were able to generate the replace file name, then we must be
// able to generate the save file name, so ignore the result.
RgGenerateAltFileName(lpFileName, SaveFileName, 'S');
RgDeleteHiveFile(SaveFileName);
// Preserve the current hive in case something fails below.
if (!RgSetFileAttributes(lpFileName, FILE_ATTRIBUTE_NONE) ||
!RgRenameFile(lpFileName, SaveFileName))
ErrorCode = ERROR_REGISTRY_IO_FAILED;
else {
// Now try to move the replacement in.
if (!RgSetFileAttributes(ReplaceFileName, FILE_ATTRIBUTE_NONE) ||
!RgRenameFile(ReplaceFileName, lpFileName)) {
ErrorCode = ERROR_REGISTRY_IO_FAILED;
RgRenameFile(SaveFileName, lpFileName);
}
else
RgDeleteFile(SaveFileName);
}
RgSetFileAttributes(lpFileName, FILE_ATTRIBUTE_READONLY |
FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM);
}
return ErrorCode;
}
//
// RgReplaceFileInfo
//
// Called during registry detach to do any necessary file replacements as a
// result of calling RegReplaceKey.
//
int
INTERNAL
RgReplaceFileInfo(
LPFILE_INFO lpFileInfo
)
{
if (lpFileInfo-> Flags & FI_REPLACEMENTEXISTS)
RgReplaceFileOnSysExit(lpFileInfo-> FileName);
return ERROR_SUCCESS;
}
#endif // WANT_REGREPLACEKEY
#endif // WANT_HIVE_SUPPORT