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
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;
|
|
}
|
|
|