Leaked source code of windows server 2003
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.
 
 
 
 
 
 

1282 lines
36 KiB

//
// Copyright (C) 2000, Microsoft Corporation
//
// File: DfsReparseSupport.cxx
//
// Contents: This handles all reparse point work.
//
//
// History: April 2002, Author: SupW
//
//-----------------------------------------------------------------------------
#include <stdlib.h>
#include <time.h>
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <ntstatus.h>
#include <ntioapi.h>
#include <windows.h>
#include <shlwapi.h>
#include <strsafe.h>
#include <dfsgeneric.hxx>
#include <dfsheader.h>
#include <DfsInit.hxx>
#include <DfsRootFolder.hxx>
#include <DfsReparse.hxx>
#include "DfsReparseSupport.tmh"
// Local support routines
DFSSTATUS
DfsGetVolumePathName(
IN PUNICODE_STRING pDirectoryName,
OUT PUNICODE_STRING ppVolumePath);
VOID
DfsFreeVolumePathName(
PUNICODE_STRING VolumeName);
DFSSTATUS
DfsInsertInReparseVolList(
LPWSTR VolumeName);
DFSSTATUS
DfsOpenReparseIndex(
IN PUNICODE_STRING pVolume,
OUT HANDLE *pHandle);
DFSSTATUS
DfsGetNextReparseRecord(
HANDLE hIndex,
PFILE_REPARSE_POINT_INFORMATION pReparseInfo,
PBOOLEAN pDone);
DFSSTATUS
DfsRemoveReparseIfOrphaned(
IN HANDLE VolumeHandle,
IN PUNICODE_STRING pVolumeName,
IN LONGLONG FileReference,
IN FILETIME ServiceStartupTime);
DFSSTATUS
DfsDeleteReparseDirectory(
PUNICODE_STRING pVolumeName,
LPWSTR pDfsDirectory);
DFSSTATUS
DfsIsReparseOrphaned(
IN HANDLE Handle,
IN FILETIME ServiceStartupTime,
OUT PBOOLEAN pOrphaned);
DFSSTATUS
DfsOpenReparseByID(
IN HANDLE VolumeHandle,
IN LONGLONG FileReference,
OUT PHANDLE pReparseHandle);
DFSSTATUS
DfsGetVolumeHandleByName(
IN PUNICODE_STRING pVolume,
OUT PHANDLE pVolumeHandle);
VOID
DfsGetReparseVolumeToScan(
PDFS_REPARSE_VOLUME_INFO *ppVolumeInfo );
NTSTATUS
DfsIsDirectoryReparsePoint(
HANDLE DirHandle,
PBOOLEAN pReparsePoint,
PBOOLEAN pDfsReparsePoint );
NTSTATUS
DfsClearDfsReparsePoint(
IN HANDLE DirHandle );
NTSTATUS
DfsDeleteLinkDirectories(
PUNICODE_STRING pLinkName,
HANDLE RelativeHandle,
BOOLEAN bRemoveParentDirs);
BOOLEAN
DfsIsEmptyDirectory(
HANDLE DirectoryHandle,
PVOID pDirectoryBuffer,
ULONG DirectoryBufferSize );
NTSTATUS
DfsOpenDirectory(
PUNICODE_STRING pDirectoryName,
ULONG ShareMode,
HANDLE RelativeHandle,
PHANDLE pOpenedHandle,
PBOOLEAN pIsNewlyCreated )
{
NTSTATUS NtStatus;
OBJECT_ATTRIBUTES ObjectAttributes;
ACCESS_MASK DesiredAccess;
PLARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG CreateDisposition;
ULONG CreateOptions;
IO_STATUS_BLOCK IoStatusBlock;
AllocationSize = NULL;
FileAttributes = FILE_ATTRIBUTE_NORMAL;
CreateDisposition = FILE_OPEN_IF;
CreateOptions = FILE_DIRECTORY_FILE |
FILE_OPEN_REPARSE_POINT |
FILE_SYNCHRONOUS_IO_NONALERT |
FILE_OPEN_FOR_BACKUP_INTENT;
DesiredAccess = FILE_READ_DATA |
FILE_WRITE_DATA |
FILE_READ_ATTRIBUTES |
FILE_WRITE_ATTRIBUTES |
SYNCHRONIZE;
InitializeObjectAttributes (
&ObjectAttributes,
pDirectoryName, //Object Name
OBJ_CASE_INSENSITIVE, //Attributes
RelativeHandle, //Root handle
NULL); //Security descriptor.
NtStatus = NtCreateFile(pOpenedHandle,
DesiredAccess,
&ObjectAttributes,
&IoStatusBlock,
AllocationSize,
FileAttributes,
ShareMode,
CreateDisposition,
CreateOptions,
NULL, // EaBuffer
0 ); // EaLength
DFSLOG("Open on %wZ: Status %x\n", pDirectoryName, NtStatus);
if ( (NtStatus == STATUS_SUCCESS) && (pIsNewlyCreated != NULL) )
{
*pIsNewlyCreated = (IoStatusBlock.Information == FILE_CREATED)? TRUE : FALSE;
}
return NtStatus;
}
VOID
DfsCloseDirectory(
HANDLE DirHandle )
{
NtClose( DirHandle );
}
//+-------------------------------------------------------------------------
//
// Function: ClearDfsReparsePoint
//
// Arguments: DirHandle - handle on open directory
//
// Returns: SUCCESS or error
//
// Description: This routine takes a handle to an open directory and
// makes that directory a reparse point with the DFS tag
//
//--------------------------------------------------------------------------
NTSTATUS
DfsClearDfsReparsePoint(
IN HANDLE DirHandle )
{
NTSTATUS NtStatus;
REPARSE_DATA_BUFFER ReparseDataBuffer;
IO_STATUS_BLOCK IoStatusBlock;
//
// Attempt to set a reparse point on the directory
//
RtlZeroMemory( &ReparseDataBuffer, sizeof(ReparseDataBuffer) );
ReparseDataBuffer.ReparseTag = IO_REPARSE_TAG_DFS;
ReparseDataBuffer.ReparseDataLength = 0;
NtStatus = NtFsControlFile( DirHandle,
NULL,
NULL,
NULL,
&IoStatusBlock,
FSCTL_DELETE_REPARSE_POINT,
&ReparseDataBuffer,
REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseDataBuffer.ReparseDataLength,
NULL,
0 );
return NtStatus;
}
NTSTATUS
DfsDeleteLinkDirectories(
PUNICODE_STRING pLinkName,
HANDLE RelativeHandle,
BOOLEAN bRemoveParentDirs)
{
UNICODE_STRING DirectoryToDelete = *pLinkName;
NTSTATUS NtStatus = STATUS_SUCCESS;
OBJECT_ATTRIBUTES ObjectAttributes;
HANDLE CurrentDirectory = NULL;
ULONG ShareMode = 0;
ShareMode = FILE_SHARE_READ;
//
// dfsdev: fix this fixed size limit. it will hurt us in the future.
//
ULONG DirectoryBufferSize = 4096;
PBYTE pDirectoryBuffer = new BYTE [DirectoryBufferSize];
if (pDirectoryBuffer == NULL)
{
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
}
while ( (NtStatus == STATUS_SUCCESS) && (DirectoryToDelete.Length != 0) )
{
NtStatus = DfsOpenDirectory( &DirectoryToDelete,
ShareMode,
RelativeHandle,
&CurrentDirectory,
NULL );
if (NtStatus == ERROR_SUCCESS)
{
if (DfsIsEmptyDirectory(CurrentDirectory,
pDirectoryBuffer,
DirectoryBufferSize) == FALSE)
{
NtClose( CurrentDirectory );
break;
}
NtClose( CurrentDirectory );
InitializeObjectAttributes (
&ObjectAttributes,
&DirectoryToDelete,
OBJ_CASE_INSENSITIVE,
RelativeHandle,
NULL);
NtStatus = NtDeleteFile( &ObjectAttributes );
//
// When the worker thread is trying to clean up orphaned
// reparse points, don't try to iterate and remove all parent
// dirs all the way to the root. All we want to do is to
// remove the reparse dir.
// BUG 701594.
//
if (!bRemoveParentDirs)
{
break;
}
StripLastPathComponent( &DirectoryToDelete );
}
}
if (pDirectoryBuffer != NULL)
{
delete [] pDirectoryBuffer;
}
return NtStatus;
}
BOOLEAN
DfsIsEmptyDirectory(
HANDLE DirectoryHandle,
PVOID pDirectoryBuffer,
ULONG DirectoryBufferSize )
{
NTSTATUS NtStatus;
FILE_NAMES_INFORMATION *pFileInfo;
ULONG NumberOfFiles = 1;
BOOLEAN ReturnValue = FALSE;
IO_STATUS_BLOCK IoStatus;
NtStatus = NtQueryDirectoryFile ( DirectoryHandle,
NULL, // no event
NULL, // no apc routine
NULL, // no apc context
&IoStatus,
pDirectoryBuffer,
DirectoryBufferSize,
FileNamesInformation,
FALSE, // return single entry = false
NULL, // filename
FALSE ); // restart scan = false
if (NtStatus == ERROR_SUCCESS)
{
pFileInfo = (FILE_NAMES_INFORMATION *)pDirectoryBuffer;
while (pFileInfo->NextEntryOffset) {
NumberOfFiles++;
if (NumberOfFiles > 3)
{
break;
}
pFileInfo = (FILE_NAMES_INFORMATION *)((ULONG_PTR)(pFileInfo) +
pFileInfo->NextEntryOffset);
}
if (NumberOfFiles <= 2)
{
ReturnValue = TRUE;
}
}
return ReturnValue;
}
//+-------------------------------------------------------------------------
//
// Function: IsDirectoryReparsePoint
//
// Arguments: DirHandle - handle to open directory.
// pReparsePoint - returned boolean: true if this directory is
// a reparse point
// pDfsReparsePoint - returned boolean: true if this
// directory is a dfs reparse point
//
//
// Returns: SUCCESS or error
//
// Description: This routine takes a handle to an open directory and
// sets 2 booleans to indicate if this directory is a
// reparse point, and if so, if this directory is a dfs
// reparse point. The booleans are initialized if this
// function returns success.
//
//--------------------------------------------------------------------------
NTSTATUS
DfsIsDirectoryReparsePoint(
IN HANDLE DirHandle,
OUT PBOOLEAN pReparsePoint,
OUT PBOOLEAN pDfsReparsePoint )
{
NTSTATUS NtStatus;
FILE_BASIC_INFORMATION BasicInfo;
IO_STATUS_BLOCK IoStatusBlock;
//
//we assume these are not reparse points.
//
*pReparsePoint = FALSE;
*pDfsReparsePoint = FALSE;
//
// Query for the basic information, which has the attributes.
//
NtStatus = NtQueryInformationFile( DirHandle,
&IoStatusBlock,
(PVOID)&BasicInfo,
sizeof(BasicInfo),
FileBasicInformation );
if (NtStatus == STATUS_SUCCESS)
{
//
// If the attributes indicate reparse point, we have a reparse
// point directory on our hands.
//
if ( BasicInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT )
{
FILE_ATTRIBUTE_TAG_INFORMATION FileTagInformation;
*pReparsePoint = TRUE;
NtStatus = NtQueryInformationFile( DirHandle,
&IoStatusBlock,
(PVOID)&FileTagInformation,
sizeof(FileTagInformation),
FileAttributeTagInformation );
if (NtStatus == STATUS_SUCCESS)
{
//
// Checkif the tag indicates its a DFS reparse point,
// and setup the return accordingly.
//
if (FileTagInformation.ReparseTag == IO_REPARSE_TAG_DFS)
{
*pDfsReparsePoint = TRUE;
}
}
}
}
return NtStatus;
}
NTSTATUS
DfsDeleteLinkReparsePoint(
PUNICODE_STRING pDirectoryName,
HANDLE ParentHandle,
BOOLEAN bRemoveParentDirs)
{
NTSTATUS NtStatus;
HANDLE LinkDirectoryHandle;
BOOLEAN IsReparsePoint, IsDfsReparsePoint;
NtStatus = DfsOpenDirectory( pDirectoryName,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
ParentHandle,
&LinkDirectoryHandle,
NULL );
if (NtStatus == STATUS_SUCCESS)
{
NtStatus = DfsIsDirectoryReparsePoint( LinkDirectoryHandle,
&IsReparsePoint,
&IsDfsReparsePoint );
if ((NtStatus == STATUS_SUCCESS) &&
(IsDfsReparsePoint == TRUE) )
{
NtStatus = DfsClearDfsReparsePoint( LinkDirectoryHandle );
DFS_TRACE_NORM( REFERRAL_SERVER, "ClearDfsReparsePoint: %wZ, NtStatus 0x%x\n",
pDirectoryName, NtStatus );
}
NtClose( LinkDirectoryHandle );
}
if (NtStatus == STATUS_SUCCESS)
{
NtStatus = DfsDeleteLinkDirectories( pDirectoryName,
ParentHandle,
bRemoveParentDirs );
DFS_TRACE_NORM( REFERRAL_SERVER, "DfsDeleteLinkDirectories: %wZ, NtStatus 0x%x\n",
pDirectoryName, NtStatus );
}
return NtStatus;
}
//
// Given a directory name, return the volume it is on.
//
DFSSTATUS
DfsGetVolumePathName(
IN PUNICODE_STRING pDirectoryName,
OUT PUNICODE_STRING pVolumePath)
{
DWORD BufferSize = MAX_PATH;
PWSTR pName = NULL;
BOOL bResult = FALSE;
DFSSTATUS Status = ERROR_SUCCESS;
do
{
// Buffersize is just a rough guess. We adjust it later.
pName = new WCHAR[BufferSize];
if(pName == NULL)
{
Status = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// Now get the volume path
bResult = GetVolumePathName(
pDirectoryName->Buffer,
pName,
BufferSize);
// If we failed, see if it's because we needed a longer buffer.
if (!bResult)
{
delete [] pName;
pName = NULL;
Status = GetLastError();
//
// We assume a well behaved GetVolumePathName
// that returns OVERFLOW finite number of times.
//
if (Status == ERROR_BUFFER_OVERFLOW)
{
BufferSize *= 2;
}
else
{
break;
}
}
} while (!bResult);
Status = DfsRtlInitUnicodeStringEx( pVolumePath, pName );
if (Status != ERROR_SUCCESS && pName != NULL)
{
delete [] pName;
pName = NULL;
}
return Status;
}
VOID
DfsFreeVolumePathName(
PUNICODE_STRING pVolumeName)
{
if (pVolumeName != NULL)
{
delete [] pVolumeName->Buffer;
pVolumeName->Buffer = NULL;
}
}
DFSSTATUS
DfsInsertInReparseVolList(
LPWSTR VolumeName)
{
DFSSTATUS Status = ERROR_SUCCESS;
PDFS_REPARSE_VOLUME_INFO pNewReparseEntry = NULL;
pNewReparseEntry = new DFS_REPARSE_VOLUME_INFO;
if (pNewReparseEntry != NULL)
{
//
// We create a new null terminated string to keep. The input may be unnecessarily longer.
//
Status = DfsCreateUnicodeStringFromString( &pNewReparseEntry->VolumeName, VolumeName );
if (Status == ERROR_SUCCESS)
{
//
// Add to the global reparse volume list. The caller knows that the entry isn't there,
// and has held the datalock through out.
//
InsertTailList( &DfsServerGlobalData.ReparseVolumeList, &pNewReparseEntry->ListEntry);
DFS_TRACE_NORM( REFERRAL_SERVER, "[%!FUNC! Added %ws to ReparseVolumeList\n",
VolumeName );
}
}
else
{
Status = ERROR_NOT_ENOUGH_MEMORY;
}
// Error path
if (Status != ERROR_SUCCESS)
{
if (pNewReparseEntry != NULL)
{
delete pNewReparseEntry;
pNewReparseEntry = NULL;
}
}
DFS_TRACE_ERROR_HIGH( Status, REFERRAL_SERVER,
"[%!FUNC!- Level %!LEVEL!] OUT OF RESOURCES adding %ws to ReparseVolumeList\n",
VolumeName );
return Status;
}
DFSSTATUS
DfsGetNextReparseRecord(
HANDLE hIndex,
PFILE_REPARSE_POINT_INFORMATION pReparseInfo,
PBOOLEAN pDone)
{
BOOLEAN bResult = FALSE;
DFSSTATUS Status = ERROR_SUCCESS;
IO_STATUS_BLOCK IoStatus;
NTSTATUS NtStatus = STATUS_SUCCESS;
NtStatus = NtQueryDirectoryFile(hIndex,
NULL,
NULL,
NULL,
&IoStatus,
pReparseInfo,
sizeof(FILE_REPARSE_POINT_INFORMATION),
FileReparsePointInformation,
TRUE,
NULL,
FALSE);
if (!NT_SUCCESS(NtStatus))
{
Status = RtlNtStatusToDosError(NtStatus);
if (Status == ERROR_NO_MORE_FILES)
{
Status = ERROR_SUCCESS;
}
*pDone = TRUE;
}
return Status;
}
DFSSTATUS
DfsOpenReparseIndex(
IN PUNICODE_STRING pVolume,
OUT HANDLE *pHandle)
{
HANDLE ReparseHandle = INVALID_HANDLE_VALUE;
LPWSTR pReparsePathName = NULL;
DFSSTATUS Status = ERROR_SUCCESS;
size_t CchPathLen;
HRESULT Hr = S_OK;
LPWSTR pIndexAllocPath = REPARSE_INDEX_PATH;
*pHandle = INVALID_HANDLE_VALUE;
do {
CchPathLen = pVolume->Length;
//
// Extra space is to concat "\$Extend\\$Reparse:$R:$INDEX_ALLOCATION"
//
CchPathLen += REPARSE_INDEX_PATH_LEN;
pReparsePathName = new WCHAR[ CchPathLen ];
if (pReparsePathName == NULL)
{
Status = ERROR_NOT_ENOUGH_MEMORY;
break;
}
// The volume name.
Hr = StringCchCopy( pReparsePathName, CchPathLen, pVolume->Buffer );
if (!SUCCEEDED(Hr))
{
Status = HRESULT_CODE(Hr);
break;
}
(VOID)PathAddBackslash( pReparsePathName );
//
// $Extend\\$Reparse:$R:$INDEX_ALLOCATION
//
Hr = StringCchCat( pReparsePathName, CchPathLen, pIndexAllocPath );
if (!SUCCEEDED(Hr))
{
Status = HRESULT_CODE(Hr);
break;
}
ReparseHandle = CreateFile(
pReparsePathName,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | SECURITY_IMPERSONATION,
NULL);
Status = GetLastError();
// paranoia
if (Status == ERROR_SUCCESS)
{
*pHandle = ReparseHandle;
}
} while (FALSE);
//
// Clean up
//
if (pReparsePathName != NULL)
{
delete [] pReparsePathName;
pReparsePathName = NULL;
}
return Status;
}
//
// Given the volume name, return a handle to it.
//
DFSSTATUS
DfsGetVolumeHandleByName(
IN PUNICODE_STRING pVolume,
OUT PHANDLE pVolumeHandle)
{
LPWSTR VolumeName = NULL;
DFSSTATUS Status = ERROR_SUCCESS;
BOOL bResult = FALSE;
*pVolumeHandle = INVALID_HANDLE_VALUE;
VolumeName = new WCHAR[ MAX_PATH ];
if (VolumeName == NULL)
{
Status = ERROR_NOT_ENOUGH_MEMORY;
return Status;
}
bResult = GetVolumeNameForVolumeMountPoint(
pVolume->Buffer,
VolumeName,
MAX_PATH);
if (bResult)
{
*pVolumeHandle = CreateFile(
VolumeName,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | SECURITY_IMPERSONATION,
NULL);
}
Status = GetLastError();
delete [] VolumeName;
return Status;
}
DFSSTATUS
DfsOpenReparseByID(
IN HANDLE VolumeHandle,
IN LONGLONG FileReference,
OUT PHANDLE pReparseHandle)
{
DFSSTATUS Status = ERROR_SUCCESS;
NTSTATUS NtStatus = STATUS_SUCCESS;
UNICODE_STRING FileIdString;
OBJECT_ATTRIBUTES ObjectAttributes;
IO_STATUS_BLOCK IoStatusBlock;
*pReparseHandle = INVALID_HANDLE_VALUE;
//
// Open the file by its file reference.
//
FileIdString.Length = sizeof(LONGLONG);
FileIdString.MaximumLength = sizeof(LONGLONG);
FileIdString.Buffer = (PWCHAR)&FileReference;
InitializeObjectAttributes(
&ObjectAttributes,
&FileIdString,
OBJ_CASE_INSENSITIVE,
VolumeHandle,
NULL); // security descriptor
NtStatus = NtCreateFile(
pReparseHandle,
FILE_READ_ATTRIBUTES | SYNCHRONIZE,
&ObjectAttributes,
&IoStatusBlock,
NULL, // allocation size
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN,
FILE_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT | FILE_OPEN_BY_FILE_ID,
NULL, // EA buffer
0); // EA length
Status = RtlNtStatusToDosError( NtStatus );
return Status;
}
DFSSTATUS
DfsIsReparseOrphaned(
IN HANDLE Handle,
IN FILETIME ServiceStartupTime,
OUT PBOOLEAN pOrphaned)
{
DFSSTATUS Status = ERROR_SUCCESS;
NTSTATUS NtStatus = STATUS_SUCCESS;
IO_STATUS_BLOCK IoStatusBlock;
FILE_BASIC_INFORMATION FileBasicInfo;
FILETIME ReparseTimeStamp;
*pOrphaned = FALSE;
//
// Query the LastWriteTime to see if this reparse point is orphaned.
//
ZeroMemory( &FileBasicInfo, sizeof( FileBasicInfo ));
NtStatus = NtQueryInformationFile(
Handle,
&IoStatusBlock,
&FileBasicInfo,
sizeof(FileBasicInfo),
FileBasicInformation);
Status = RtlNtStatusToDosError( NtStatus );
if (Status == ERROR_SUCCESS)
{
//
// Since we would've re-written all reparse points
// when the service had started up, a good reparse point
// can't have an older timestamp.
//
LARGE_INTEGER_TO_FILETIME( &ReparseTimeStamp, &FileBasicInfo.ChangeTime );
if (CompareFileTime( &ReparseTimeStamp, &ServiceStartupTime ) == -1)
{
*pOrphaned = TRUE;
}
}
return Status;
}
DFSSTATUS
DfsDeleteReparseDirectory(
PUNICODE_STRING pVolumeName,
LPWSTR pDfsDirectory,
ULONG CbDirLength)
{
DFSSTATUS Status = ERROR_SUCCESS;
NTSTATUS NtStatus;
DWORD BuffLen = 0;
LPWSTR FullFileName = NULL;
UNICODE_STRING UnicodeFileName;
ULONG CbCurrentPos;
BuffLen = WHACKWHACKQQ_SIZE;
BuffLen += (pVolumeName->Length);
BuffLen += CbDirLength;
BuffLen += sizeof(UNICODE_NULL);
//
// Unicodes can't handle paths longer than MAXUSHORT.
//
if (BuffLen >= MAXUSHORT)
{
Status = ERROR_INVALID_PARAMETER;
return Status;
}
FullFileName = new WCHAR[ BuffLen/sizeof(WCHAR) ];
if (FullFileName == NULL)
{
Status = ERROR_NOT_ENOUGH_MEMORY;
return Status;
}
CbCurrentPos = 0;
// First the \??\ portion.
RtlCopyMemory( FullFileName,
WHACKWHACKQQ,
WHACKWHACKQQ_SIZE );
CbCurrentPos += WHACKWHACKQQ_SIZE;
// Volume name goes next.
RtlCopyMemory( &FullFileName[ CbCurrentPos / sizeof(WCHAR) ],
pVolumeName->Buffer,
pVolumeName->Length );
CbCurrentPos += pVolumeName->Length;
// The reparse path itself.
RtlCopyMemory( &FullFileName[ CbCurrentPos / sizeof(WCHAR) ],
pDfsDirectory,
CbDirLength);
CbCurrentPos += CbDirLength;
FullFileName[ CbCurrentPos / sizeof(WCHAR) ] = UNICODE_NULL;
UnicodeFileName.Buffer = FullFileName;
UnicodeFileName.Length = (USHORT)CbCurrentPos;
CbCurrentPos += sizeof(UNICODE_NULL);
ASSERT( BuffLen == CbCurrentPos );
UnicodeFileName.MaximumLength = (USHORT)CbCurrentPos;
//
// Finally get rid of this reparse point directory.
// We delete only the reparse directory, not anything above it.
// We don't want it to go all the way up and delete the root directory, for example.
// BUG 701594
//
NtStatus = DfsDeleteLinkReparsePointDir( &UnicodeFileName, NULL );
if (NtStatus != STATUS_SUCCESS)
{
Status = RtlNtStatusToDosError(NtStatus);
}
delete [] FullFileName;
return Status;
}
DFSSTATUS
DfsRemoveReparseIfOrphaned(
IN HANDLE VolumeHandle,
IN PUNICODE_STRING pVolumeName,
IN LONGLONG FileReference,
IN FILETIME ServiceStartupTime)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
DFSSTATUS Status = ERROR_SUCCESS;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE ReparseDirectoryHandle = INVALID_HANDLE_VALUE;
BOOLEAN Orphaned = FALSE;
BOOLEAN ReparseOpened = FALSE;
PFILE_NAME_INFORMATION pFileInfo = NULL;
ULONG CbPathLen = MAX_PATH * sizeof(WCHAR);
ULONG CbBufSize = 0;
do {
//
// First get a handle to the reparse directory. We have its FileID.
//
Status = DfsOpenReparseByID( VolumeHandle,
FileReference,
&ReparseDirectoryHandle );
if (Status != ERROR_SUCCESS)
break;
ReparseOpened = TRUE;
Status = DfsIsReparseOrphaned( ReparseDirectoryHandle,
ServiceStartupTime,
&Orphaned);
//
// If this reparse point is active, we are done.
//
if (Status != ERROR_SUCCESS || Orphaned == FALSE)
{
break;
}
//
// Query the name of the file.
//
do {
CbBufSize = sizeof( FILE_NAME_INFORMATION ) + CbPathLen;
pFileInfo = (PFILE_NAME_INFORMATION) new BYTE[CbBufSize];
if (pFileInfo == NULL)
{
NtStatus = STATUS_INSUFFICIENT_RESOURCES;
break;
}
//
// Zero the buffer before querying the filename.
//
ZeroMemory( pFileInfo, CbBufSize );
NtStatus = NtQueryInformationFile(
ReparseDirectoryHandle,
&IoStatusBlock,
pFileInfo,
CbBufSize,
FileNameInformation);
//
// If we need to resize the buffer, do it in
// multiples of two, but cap it at ULONGMAX.
//
if (NtStatus == STATUS_BUFFER_OVERFLOW)
{
delete [] pFileInfo;
pFileInfo = NULL;
if (CbPathLen >= (ULONG_MAX / 2))
{
// this isn't the ideal error message, but...
NtStatus = STATUS_INVALID_PARAMETER;
break;
}
CbPathLen *= 2;
}
} while (NtStatus == STATUS_BUFFER_OVERFLOW);
Status = RtlNtStatusToDosError( NtStatus );
if (Status != ERROR_SUCCESS ||
pFileInfo == NULL) // To keep PREFAST happy
{
break;
}
CloseHandle( ReparseDirectoryHandle );
ReparseOpened = FALSE;
//
// Now do the actual deletion
//
Status = DfsDeleteReparseDirectory( pVolumeName, pFileInfo->FileName, pFileInfo->FileNameLength );
} while (FALSE);
//
// If we still haven't closed the reparse handle, do so before we get out.
//
if (ReparseOpened)
{
CloseHandle( ReparseDirectoryHandle );
ReparseOpened = FALSE;
}
if (pFileInfo != NULL)
{
delete [] pFileInfo;
pFileInfo = NULL;
}
return Status;
}
VOID
DfsGetReparseVolumeToScan(
PDFS_REPARSE_VOLUME_INFO *ppVolumeInfo )
{
PLIST_ENTRY pNext = NULL;
PDFS_REPARSE_VOLUME_INFO pVolInfo = NULL;
*ppVolumeInfo = NULL;
//
// this needs to be optimized to return a subset or LRU entries.
//
DfsAcquireGlobalDataLock();
if (!IsListEmpty( &DfsServerGlobalData.ReparseVolumeList ))
{
pNext = RemoveHeadList( &DfsServerGlobalData.ReparseVolumeList );
ASSERT( pNext != NULL);
pVolInfo = CONTAINING_RECORD( pNext,
DFS_REPARSE_VOLUME_INFO,
ListEntry );
if (!IsEmptyUnicodeString( &pVolInfo->VolumeName ))
{
*ppVolumeInfo = pVolInfo;
}
}
DfsReleaseGlobalDataLock();
}
DFSSTATUS
DfsRemoveOrphanedReparsePoints(
IN PUNICODE_STRING pVolumeName,
IN FILETIME ServiceStartupTime)
{
DFSSTATUS Status = ERROR_SUCCESS;
HANDLE ReparseIndexHandle = INVALID_HANDLE_VALUE;
HANDLE ReparseDirectoryHandle = INVALID_HANDLE_VALUE;
HANDLE VolumeHandle = INVALID_HANDLE_VALUE;
FILE_REPARSE_POINT_INFORMATION ReparseInfo;
BOOLEAN Done = FALSE;
do {
//
// Open the $Reparse index of the volume.
//
Status = DfsOpenReparseIndex( pVolumeName, &ReparseIndexHandle );
if (Status != ERROR_SUCCESS)
{
break;
}
//
// First get a handle to this volume.
//
Status = DfsGetVolumeHandleByName( pVolumeName, &VolumeHandle );
if (Status != ERROR_SUCCESS)
{
break;
}
//
// Go through all the reparse points in the index.
//
Done = FALSE;
Status = DfsGetNextReparseRecord( ReparseIndexHandle,
&ReparseInfo,
&Done );
while (!Done && Status == ERROR_SUCCESS)
{
//
// If we find a DFS reparse point...
//
if (ReparseInfo.Tag == IO_REPARSE_TAG_DFS)
{
DFSSTATUS TempStatus;
TempStatus = DfsRemoveReparseIfOrphaned( VolumeHandle,
pVolumeName,
ReparseInfo.FileReference,
ServiceStartupTime );
//
// Ignore and move on if we hit an error. This will get retried when the
// service starts up next.
//
DFS_TRACE_ERROR_NORM( Status, REFERRAL_SERVER,
"[%!FUNC!] Status 0x%x in ReparseIfOrphaned for Volume %wZ, FileRef 0x%x\n",
TempStatus, pVolumeName, (ULONG)ReparseInfo.FileReference);
}
//
// Iterate to the next reparse record.
//
Status = DfsGetNextReparseRecord( ReparseIndexHandle,
&ReparseInfo,
&Done );
}
} while (FALSE);
if (ReparseIndexHandle != INVALID_HANDLE_VALUE)
{
CloseHandle( ReparseIndexHandle );
ReparseIndexHandle = INVALID_HANDLE_VALUE;
}
if (VolumeHandle != INVALID_HANDLE_VALUE)
{
CloseHandle( VolumeHandle );
VolumeHandle = INVALID_HANDLE_VALUE;
}
return Status;
}
//
// Given a path to a reparse point, this adds the volume that it resides in
// to our list of volumes to scan (for orphaned reparse points) later.
//
DFSSTATUS
DfsAddReparseVolumeToList (
IN PUNICODE_STRING pDirectoryName)
{
DFSSTATUS Status = ERROR_SUCCESS;
UNICODE_STRING VolumeName;
PLIST_ENTRY pNext = NULL;
BOOLEAN Found = FALSE;
PDFS_REPARSE_VOLUME_INFO pReparseVolInfo = NULL;
BOOLEAN VolumeAdded = FALSE;
do {
//
// First find the volume this path belongs in.
//
Status = DfsGetVolumePathName( pDirectoryName, &VolumeName );
if (Status != ERROR_SUCCESS)
{
break;
}
DfsAcquireGlobalDataLock();
{
//
// See if the volume is already on the list
//
pNext = DfsServerGlobalData.ReparseVolumeList.Flink;
while (pNext != &DfsServerGlobalData.ReparseVolumeList)
{
pReparseVolInfo = CONTAINING_RECORD( pNext,
DFS_REPARSE_VOLUME_INFO,
ListEntry );
if (RtlCompareUnicodeString(&pReparseVolInfo->VolumeName,
&VolumeName,
TRUE) == 0) // Case insensitive
{
Found = TRUE;
break;
}
pNext = pNext->Flink;
}
//
// Insert this volume only if it isn't already there.
//
if (!Found)
{
Status = DfsInsertInReparseVolList( VolumeName.Buffer );
}
}
DfsReleaseGlobalDataLock();
} while (FALSE);
//
// We've made a copy of the volume name, so we are ok to free it.
//
if (VolumeName.Buffer != NULL)
{
DfsFreeVolumePathName( &VolumeName );
}
return Status;
}
//
// This is the entry point for cleaning up reparse points.
// It'll iterate through all local volumes that are known to have DFS roots on them
// (and therefore reparse points) and inspect their respective $Reparse indices.
// This assumes that all good reparse points will have been written to when the service
// started up.
//
VOID
DfsRemoveOrphanedReparsePoints(
IN FILETIME ServiceStartupTime)
{
PDFS_REPARSE_VOLUME_INFO pVolInfo = NULL;
DfsGetReparseVolumeToScan( &pVolInfo );
while (pVolInfo != NULL)
{
DFS_TRACE_NORM( REFERRAL_SERVER, "[%!FUNC!] Starting ReparsePt cleanup on volume %wZ\n",
&pVolInfo->VolumeName);
// We have no choice but to ignore errors and keep going
(VOID)DfsRemoveOrphanedReparsePoints( &pVolInfo->VolumeName,
ServiceStartupTime );
pVolInfo = NULL; // paranoia
// Get the next volume if any.
DfsGetReparseVolumeToScan( &pVolInfo );
}
DFS_TRACE_NORM( REFERRAL_SERVER, "[%!FUNC!] Done reparse cleanup\n");
return;
}