windows-nt/Source/XPSP1/NT/shell/shell32/tracker.cpp
2020-09-26 16:20:57 +08:00

1166 lines
35 KiB
C++

//
// History: 1-Mar-95 BillMo Created.
// ...
// 01-Dec-96 MikeHill Converted to new NT5 implementation.
#include "shellprv.h"
#pragma hdrstop
#define LINKDATA_AS_CLASS
#include <linkdata.hxx>
#include "shelllnk.h"
// NTRAID95363-2000-03-19: These four inlines are copied from private\net\svcdlls\trksvcs\common\trklib.hxx
// They should be moved to linkdata.hxx
inline
CDomainRelativeObjId::operator == (const CDomainRelativeObjId &Other) const
{
return(_volume == Other._volume && _object == Other._object);
}
inline
CDomainRelativeObjId::operator != (const CDomainRelativeObjId &Other) const
{
return !(*this == Other);
}
inline
CVolumeId:: operator == (const CVolumeId & Other) const
{
return(0 == memcmp(&_volume, &Other._volume, sizeof(_volume)));
}
inline
CVolumeId:: operator != (const CVolumeId & Other) const
{
return ! (Other == *this);
}
//+----------------------------------------------------------------------------
//
// Function: RPC free/alloc routines
//
// Synopsis: CTracker uses MIDL-generated code to call an RPC server,
// and MIDL-generated code assumes that the following routines
// be provided.
//
//+----------------------------------------------------------------------------
void __RPC_USER MIDL_user_free(void __RPC_FAR *pv)
{
LocalFree(pv);
}
void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t s)
{
return (void __RPC_FAR *) LocalAlloc(LMEM_FIXED, s);
}
//+----------------------------------------------------------------------------
//
// Method: IUnknown methods
//
// Synopsis: IUnknown methods for the ISLTracker interface.
//
//+----------------------------------------------------------------------------
STDMETHODIMP CTracker::QueryInterface(REFIID riid, void **ppvObj)
{
return _psl->QueryInterface(riid, ppvObj);
}
STDMETHODIMP_(ULONG) CTracker::AddRef()
{
return _psl->AddRef();
}
STDMETHODIMP_(ULONG) CTracker::Release()
{
return _psl->Release();
}
//+----------------------------------------------------------------------------
//
// Method: ISLTracker custom methods
//
// Synopsis: This interface is private and is only used for testing.
// This provides test programs the ability to specify the
// TrackerRestrictions (from the TrkMendRestrictions enum)
// and the ability to get the internal IDs.
//
//+----------------------------------------------------------------------------
HRESULT CTracker::Resolve(HWND hwnd, DWORD dwResolveFlags, DWORD dwTracker)
{
return _psl->_Resolve(hwnd, dwResolveFlags, dwTracker);
}
HRESULT CTracker::GetIDs(CDomainRelativeObjId *pdroidBirth, CDomainRelativeObjId *pdroidLast, CMachineId *pmcid)
{
if (!_fLoaded)
return E_UNEXPECTED;
*pdroidBirth = _droidBirth;
*pdroidLast = _droidLast;
*pmcid = _mcidLast;
return S_OK;
}
//+----------------------------------------------------------------------------
// Synopsis: Initializes the data members used for RPC. This should be
// called either by InitNew or Load.
//
// Arguments: None
//
// Returns: [HRESULT]
//
//+----------------------------------------------------------------------------
HRESULT CTracker::InitRPC()
{
HRESULT hr = S_OK;
if (!_fCritsecInitialized)
{
InitializeCriticalSection(&_cs);
_fCritsecInitialized = TRUE;
}
if (NULL == _pRpcAsyncState)
{
_pRpcAsyncState = reinterpret_cast<PRPC_ASYNC_STATE>(new BYTE[ sizeof(RPC_ASYNC_STATE) ]);
if (NULL == _pRpcAsyncState)
{
hr = HRESULT_FROM_WIN32(E_OUTOFMEMORY);
goto Exit;
}
}
if (NULL == _hEvent)
{
_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // Auto-reset, not initially signaled
if (NULL == _hEvent)
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
}
Exit:
return hr;
}
//+----------------------------------------------------------------------------
//
// Synopsis: Initializes the CTracker object. This method may be called
// repeatedly, i.e. it may be called to clear/reinit the object.
// This method need not be called before calling the Load method.
//
// Arguments: None
//
// Returns: [HRESULT]
//
//+----------------------------------------------------------------------------
HRESULT CTracker::InitNew()
{
HRESULT hr = InitRPC();
if (SUCCEEDED(hr))
{
_mcidLast = CMachineId();
_droidLast = CDomainRelativeObjId();
_droidBirth = CDomainRelativeObjId();
_fDirty = FALSE;
_fLoaded = FALSE;
_fMendInProgress = FALSE;
_fUserCancelled = FALSE;
}
return hr;
} // CTracker::InitNew()
//+----------------------------------------------------------------------------
//
// Synopsis: Get tracking state from the given file handle. Note that this
// is expected to fail if the referrent file isn't on an
// NTFS5 volume.
//
//
// Arguments: [hFile]
// The file to track
// [ptszFile]
// The name of the file
//
// Returns: [HRESULT]
//
//-----------------------------------------------------------------------------
HRESULT CTracker::InitFromHandle(const HANDLE hFile, const TCHAR* ptszFile)
{
NTSTATUS status = STATUS_SUCCESS;
FILE_OBJECTID_BUFFER fobOID;
DWORD cbReturned;
CDomainRelativeObjId droidLast;
CDomainRelativeObjId droidBirth;
CMachineId mcidLast;
// Initialize the RPC members
HRESULT hr = InitRPC();
if (FAILED(hr))
goto Exit;
// -----------------------------------
// Get the Object ID Buffer (64 bytes)
// -----------------------------------
// Use the file handle to get the file's Object ID. Tell the filesystem to give us the
// existing object ID if the file already has one, or to create a new one otherwise.
memset(&fobOID, 0, sizeof(fobOID));
if (!DeviceIoControl(hFile, FSCTL_CREATE_OR_GET_OBJECT_ID,
NULL, 0, // No input buffer
&fobOID, sizeof(fobOID), // Output buffer
&cbReturned, NULL))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
// ----------------------
// Load the Droids & MCID
// ----------------------
status = droidLast.InitFromFile(hFile, fobOID);
if (!NT_SUCCESS(status))
{
hr = HRESULT_FROM_WIN32(RtlNtStatusToDosError(status));
goto Exit;
}
droidBirth.InitFromFOB(fobOID);
droidBirth.GetVolumeId().Normalize();
if (FAILED(mcidLast.InitFromPath(ptszFile, hFile)))
mcidLast = CMachineId();
// ----
// Exit
// ----
if (_mcidLast != mcidLast
||
_droidLast != droidLast
||
_droidBirth != droidBirth
)
{
_mcidLast = mcidLast;
_droidLast = droidLast;
_droidBirth = droidBirth;
_fDirty = TRUE;
}
_fLoaded = TRUE; // Cleared in InitNew
_fLoadedAtLeastOnce = TRUE; // Not cleared in InitNew
hr = S_OK;
Exit:
return hr;
}
//+-------------------------------------------------------------------
//
// Synopsis: Load the tracker from the memory buffer. The InitNew
// method need not be called before calling this method.
//
// Arguments: [pb] -- buffer to load from
// [cb] -- size of pb buffer
//
// Returns: [HRESULT]
//
//--------------------------------------------------------------------
#define CTRACKER_VERSION 0
HRESULT CTracker::Load(BYTE *pb, ULONG cb)
{
DWORD dwLength;
// Initialize RPC if it hasn't been already.
HRESULT hr = InitRPC();
if (FAILED(hr))
goto Exit;
// Check the length
dwLength = *reinterpret_cast<DWORD*>(pb);
if (dwLength < GetSize())
{
hr = E_INVALIDARG;
goto Exit;
}
pb += sizeof(dwLength);
// Check the version number
if (CTRACKER_VERSION != *reinterpret_cast<DWORD*>(pb))
{
hr = HRESULT_FROM_WIN32(ERROR_REVISION_MISMATCH);
goto Exit;
}
pb += sizeof(DWORD); // Skip past the version
// Get the machine ID & droids
_mcidLast = *reinterpret_cast<CMachineId*>(pb);
pb += sizeof(_mcidLast);
_droidLast = *reinterpret_cast<CDomainRelativeObjId*>(pb);
pb += sizeof(_droidLast);
_droidBirth = *reinterpret_cast<CDomainRelativeObjId*>(pb);
pb += sizeof(_droidBirth);
_fLoaded = TRUE; // Cleared in InitNew
_fLoadedAtLeastOnce = TRUE; // Not cleared in InitNew
hr = S_OK;
Exit:
return hr;
}
//+-------------------------------------------------------------------
//
// Member: CTracker::Save
//
// Synopsis: Save tracker to the given buffer.
//
// Arguments: [pb] -- buffer for tracker.
// [cbSize] -- size of buffer in pb
//
// Returns: None
//
//--------------------------------------------------------------------
VOID CTracker::Save(BYTE *pb, ULONG cbSize)
{
// Save the length
*reinterpret_cast<DWORD*>(pb) = GetSize();
pb += sizeof(DWORD);
// Save a version number
*reinterpret_cast<DWORD*>(pb) = CTRACKER_VERSION;
pb += sizeof(DWORD);
// Save the machine & DROIDs
*reinterpret_cast<CMachineId*>(pb) = _mcidLast;
pb += sizeof(_mcidLast);
*reinterpret_cast<CDomainRelativeObjId*>(pb) = _droidLast;
pb += sizeof(_droidLast);
*reinterpret_cast<CDomainRelativeObjId*>(pb) = _droidBirth;
pb += sizeof(_droidBirth);
_fDirty = FALSE;
} // CTracker::Save()
//+-------------------------------------------------------------------
//
// Synopsis: Search for the object referred to by the tracker.
//
// Arguments: [dwTickCountDeadline] -- absolute tick count for deadline
// [pfdIn] -- may not be NULL
// [pfdOut] -- may not be NULL
// will contain updated data on success
// [uShlinkFlags] -- SLR_ flags
// [TrackerRestrictions] -- TrkMendRestrictions enumeration
//
// Returns: [HRESULT]
// S_OK
// found (pfdOut contains new info)
// E_UNEXPECTED
// CTracker::InitNew hasn't bee called.
// HRESULT_FROM_WIN32(ERROR_OPERATION_ABORTED)
// Restrictions (set in registry) are set such that
// this operation isn't to be performed.
//
//--------------------------------------------------------------------
HRESULT CTracker::Search(const DWORD dwTickCountDeadline,
const WIN32_FIND_DATA *pfdIn,
WIN32_FIND_DATA *pfdOut,
UINT uShlinkFlags,
DWORD TrackerRestrictions)
{
HRESULT hr = S_OK;
TCHAR ptszError = NULL;
WIN32_FILE_ATTRIBUTE_DATA fadNew;
WIN32_FIND_DATA fdNew = *pfdIn;
DWORD cbFileName;
BOOL fPotentialFileFound = FALSE;
BOOL fLocked = FALSE;
DWORD dwCurrentTickCount = 0;
RPC_TCHAR *ptszStringBinding = NULL;
RPC_BINDING_HANDLE BindingHandle;
RPC_STATUS rpcstatus;
CDomainRelativeObjId droidBirth, droidLast, droidCurrent;
CMachineId mcidCurrent;
// Initialize the output
memset(pfdOut, 0, sizeof(*pfdOut));
// Abort if restrictions don't allow this operation
if (SHRestricted(REST_NORESOLVETRACK) ||
(SLR_NOTRACK & uShlinkFlags))
{
hr = HRESULT_FROM_WIN32(ERROR_OPERATION_ABORTED);
goto Exit;
}
// Ensure that we've been loaded first
else if (!_fLoaded)
{
hr = E_UNEXPECTED;
goto Exit;
}
// Capture the current tick count
dwCurrentTickCount = GetTickCount();
if ((long) dwTickCountDeadline <= (long) dwCurrentTickCount)
{
hr = HRESULT_FROM_WIN32(ERROR_SERVICE_REQUEST_TIMEOUT);
goto Exit;
}
//
// Create an RPC binding
//
rpcstatus = RpcStringBindingCompose(NULL,
TEXT("ncalrpc"),
NULL,
TRKWKS_LRPC_ENDPOINT_NAME,
NULL,
&ptszStringBinding);
if (RPC_S_OK == rpcstatus)
rpcstatus = RpcBindingFromStringBinding(ptszStringBinding, &BindingHandle);
if (RPC_S_OK != rpcstatus)
{
hr = HRESULT_FROM_WIN32(rpcstatus);
goto Exit;
}
//
// Initialize an RPC Async handle
//
// Take the lock
EnterCriticalSection(&_cs); fLocked = TRUE;
// Verify that we were initialized properly
if (NULL == _hEvent || NULL == _pRpcAsyncState)
{
hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
goto Exit;
}
rpcstatus = RpcAsyncInitializeHandle(_pRpcAsyncState, RPC_ASYNC_VERSION_1_0);
if (RPC_S_OK != rpcstatus)
{
hr = HRESULT_FROM_WIN32(rpcstatus);
goto Exit;
}
_pRpcAsyncState->NotificationType = RpcNotificationTypeEvent;
_pRpcAsyncState->u.hEvent = _hEvent;
_pRpcAsyncState->UserInfo = NULL;
//
// Call the tracking service to find the file
//
__try
{
SYSTEMTIME stNow;
FILETIME ftDeadline;
DWORD dwDeltaMillisecToDeadline;
// NOTE: The following four assignments used to be above the
// __try. But that appears to trigger a compiler problem, where
// some of the assignments do not make it to the .obj in an optimized
// build (bug 265255).
droidLast = _droidLast;
droidBirth = _droidBirth;
mcidCurrent = _mcidLast;
cbFileName = sizeof(fdNew.cFileName);
// Convert the tick-count deadline into a UTC filetime.
dwDeltaMillisecToDeadline = (DWORD)((long)dwTickCountDeadline - (long)dwCurrentTickCount);
GetSystemTime(&stNow);
SystemTimeToFileTime(&stNow, &ftDeadline);
*reinterpret_cast<LONGLONG*>(&ftDeadline) += (dwDeltaMillisecToDeadline * 10*1000);
// Start the async RPC call to the tracking service
_fMendInProgress = TRUE;
LnkMendLink(_pRpcAsyncState,
BindingHandle,
ftDeadline,
TrackerRestrictions,
const_cast<CDomainRelativeObjId*>(&droidBirth),
const_cast<CDomainRelativeObjId*>(&droidLast),
const_cast<CMachineId*>(&_mcidLast),
&droidCurrent,
&mcidCurrent,
&cbFileName,
fdNew.cFileName);
// Wait for the call to return. Release the lock first, though, so that
// the UI thread can come in and cancel.
LeaveCriticalSection(&_cs); fLocked = FALSE;
DWORD dwWaitReturn = WaitForSingleObject(_hEvent, dwDeltaMillisecToDeadline);
// Now take the lock back and see what happenned.
EnterCriticalSection(&_cs); fLocked = TRUE;
_fMendInProgress = FALSE;
if ((WAIT_TIMEOUT == dwWaitReturn) || _fUserCancelled)
{
// We timed out waiting for a response. Cancel the call.
// If the call should complete between the time
// we exited the WaitForSingleObject and the cancel call below,
// then the cancel will be ignored by RPC.
rpcstatus = RpcAsyncCancelCall(_pRpcAsyncState, TRUE); // fAbort
if (_fUserCancelled)
{
_fUserCancelled = FALSE;
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
__leave;
}
else if (RPC_S_OK != rpcstatus)
{
hr = HRESULT_FROM_WIN32(rpcstatus);
__leave;
}
}
else if (WAIT_OBJECT_0 != dwWaitReturn)
{
// There was an error of some kind.
hr = HRESULT_FROM_WIN32(GetLastError());
__leave;
}
// Now we find out how the LnkMendLink call completed. If we get
// RPC_S_OK, then it completed normally, and the result is
// in hr.
rpcstatus = RpcAsyncCompleteCall(_pRpcAsyncState, &hr);
if (RPC_S_OK != rpcstatus)
{
// The call either failed or was cancelled (the reason for the
// cancel would be that the UI thread called CTracker::CancelSearch,
// or because we timed out above and called RpcAsyncCancelCall).
hr = HRESULT_FROM_WIN32(rpcstatus);
__leave;
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
_fMendInProgress = FALSE;
_fUserCancelled = FALSE;
hr = HRESULT_FROM_WIN32(RpcExceptionCode());
}
// free the binding
RpcBindingFree(&BindingHandle);
if (HRESULT_FROM_WIN32(ERROR_POTENTIAL_FILE_FOUND) == hr)
{
fPotentialFileFound = TRUE;
hr = S_OK;
}
if (FAILED(hr)) goto Exit;
//
// See if this is in the recycle bin
//
if (IsFileInBitBucket(fdNew.cFileName))
{
hr = E_FAIL;
goto Exit;
}
//
// Now that we know what the new filename is, let's get all
// the FindData info.
//
if (!GetFileAttributesEx(fdNew.cFileName, GetFileExInfoStandard, &fadNew))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
// Ensure that the file we found has the same "directory-ness"
// as the last known link source (either they're both a directory
// or they're both a file). Also ensure that the file we found
// isn't itself a link client (a shell shortcut).
if (((fadNew.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
^
(pfdIn->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
)
||
PathIsLnk(fdNew.cFileName)
)
{
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
goto Exit;
}
// Copy the file attributes into the WIN32_FIND_DATA structure.
fdNew.dwFileAttributes = fadNew.dwFileAttributes;
fdNew.ftCreationTime = fadNew.ftCreationTime;
fdNew.ftLastAccessTime = fadNew.ftLastAccessTime;
fdNew.ftLastWriteTime = fadNew.ftLastWriteTime;
fdNew.nFileSizeLow = fadNew.nFileSizeLow;
// Return the new finddata to the caller.
*pfdOut = fdNew;
// Update our local state
if (_droidLast != droidCurrent
||
_droidBirth != droidBirth
||
_mcidLast != mcidCurrent
)
{
_droidLast = droidCurrent;
_droidBirth = droidBirth;
_mcidLast = mcidCurrent;
_fDirty = TRUE;
}
Exit:
if (fLocked)
LeaveCriticalSection(&_cs);
if (ptszStringBinding)
RpcStringFree(&ptszStringBinding);
if (FAILED(hr))
DebugMsg(DM_TRACE, TEXT("CTracker::Search failed (hr=0x%08X)"), hr);
else if (fPotentialFileFound)
hr = HRESULT_FROM_WIN32(ERROR_POTENTIAL_FILE_FOUND);
return(hr);
} // CTracker::Search()
//+----------------------------------------------------------------------------
//
// Synopsis: This method is called on a thread signal another thread
// which is in CTracker::Search to abort the LnkMendLink
// call.
//
// Returns: [HRESULT]
//
//-----------------------------------------------------------------------------
STDMETHODIMP CTracker::CancelSearch()
{
EnterCriticalSection(&_cs);
// If a search is in progress, cancel it.
if (_fMendInProgress && NULL != _pRpcAsyncState)
{
_fUserCancelled = TRUE;
SetEvent(_hEvent); // SetEvent so as to unblock the Tracker Worker thread.
}
LeaveCriticalSection(&_cs);
return S_OK;
}
//+----------------------------------------------------------------------------
//
// Look at a path and determine the computer name of the host machine.
// In the future, we should remove this code, and add the capbility to query
// handles for their computer name.
//
// GetServerComputer name uses ScanForComputerName and ConvertDfsPath
// as helper functions.
//
// The name can only be obtained for NetBios paths - if the path is IP or DNS
// an error is returned. (If the NetBios name has a "." in it, it will
// cause an error because it will be misinterpreted as a DNS path. This case
// becomes less and less likely as the NT5 UI doesn't allow such computer names.)
// For DFS paths, the leaf server's name is returned, as long as it wasn't
// joined to its parent with an IP or DNS path name.
//
//+----------------------------------------------------------------------------
const UNICODE_STRING NtUncPathNamePrefix = { 16, 18, L"\\??\\UNC\\"};
#define cchNtUncPathNamePrefix 8
const UNICODE_STRING NtDrivePathNamePrefix = { 8, 10, L"\\??\\" };
#define cchNtDrivePathNamePrefix 4
const WCHAR RedirectorMappingPrefix[] = { L"\\Device\\LanmanRedirector\\;" };
const WCHAR LocalVolumeMappingPrefix[] = { L"\\Device\\Volume" };
const WCHAR CDRomMappingPrefix[] = { L"\\Device\\CDRom" };
const WCHAR FloppyMappingPrefix[] = { L"\\Device\\Floppy" };
const WCHAR DfsMappingPrefix[] = { L"\\Device\\WinDfs\\" };
//
// ScanForComputerName:
//
// Scan the path in ServerFileName (which is a UNICODE_STRING with
// a full NT path name), searching for the computer name. If it's
// found, point to it with UnicodeComputerName.Buffer, and set
// *AvailableLength to show how much readable memory is after that
// point.
//
HRESULT ScanForComputerName(HANDLE hFile, const UNICODE_STRING &ServerFileName,
UNICODE_STRING *UnicodeComputerName, ULONG *AvailableLength,
WCHAR *DosDeviceMapping, ULONG cchDosDeviceMapping,
PFILE_NAME_INFORMATION FileNameInfo, ULONG cbFileNameInfo,
BOOL *CheckForDfs)
{
HRESULT hr = S_OK;
// Is this a UNC path?
if (RtlPrefixString((PSTRING)&NtUncPathNamePrefix, (PSTRING)&ServerFileName, TRUE))
{
// Make sure there's some more to this path than just the prefix
if (ServerFileName.Length <= NtUncPathNamePrefix.Length)
{
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
goto Exit;
}
// It appears to be a valid UNC path. Point to the beginning of the computer
// name, and calculate how much room is left in ServerFileName after that.
UnicodeComputerName->Buffer = &ServerFileName.Buffer[ NtUncPathNamePrefix.Length/sizeof(WCHAR) ];
*AvailableLength = ServerFileName.Length - NtUncPathNamePrefix.Length;
}
else if (RtlPrefixString((PSTRING)&NtDrivePathNamePrefix, (PSTRING)&ServerFileName, TRUE)
&&
ServerFileName.Buffer[ cchNtDrivePathNamePrefix + 1 ] == L':')
{
// Get the correct, upper-cased, drive letter into DosDevice.
WCHAR DosDevice[3] = { L"A:" };
DosDevice[0] = ServerFileName.Buffer[ cchNtDrivePathNamePrefix ];
if (L'a' <= DosDevice[0] && DosDevice[0] <= L'z')
DosDevice[0] = L'A' + (DosDevice[0] - L'a');
// Map the drive letter to its symbolic link under \??. E.g., say D: & R:
// are DFS/rdr drives, you would then see something like:
//
// D: => \Device\WinDfs\G
// R: => \Device\LanmanRedirector\;R:0\scratch\scratch
if (!QueryDosDevice(DosDevice, DosDeviceMapping, cchDosDeviceMapping))
{
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
// Now that we have the DosDeviceMapping, we can check ... Is this a rdr drive?
if (// Does it begin with "\Device\LanmanRedirector\;" ?
0 == wcsncmp(DosDeviceMapping, RedirectorMappingPrefix, lstrlenW(RedirectorMappingPrefix))
&&
// Are the next letters the correct drive letter, a colon, and a whack?
(DosDevice[0] == DosDeviceMapping[ sizeof(RedirectorMappingPrefix)/sizeof(WCHAR) - 1 ]
&&
L':' == DosDeviceMapping[ sizeof(RedirectorMappingPrefix)/sizeof(WCHAR) ]
&&
(UnicodeComputerName->Buffer = StrChrW(&DosDeviceMapping[ sizeof(RedirectorMappingPrefix)/sizeof(WCHAR) + 1 ], L'\\'))
))
{
// We have a valid rdr drive. Point to the beginning of the computer
// name, and calculate how much room is availble in DosDeviceMapping after that.
UnicodeComputerName->Buffer += 1;
*AvailableLength = sizeof(DosDeviceMapping) - sizeof(DosDeviceMapping[0]) * (ULONG)(UnicodeComputerName->Buffer - DosDeviceMapping);
// We know now that it's not a DFS path
*CheckForDfs = FALSE;
}
else if (0 == wcsncmp(DosDeviceMapping, DfsMappingPrefix, lstrlenW(DfsMappingPrefix)))
{
// Get the full UNC name of this DFS path. Later, we'll call the DFS
// driver to find out what the actual server name is.
IO_STATUS_BLOCK IoStatusBlock;
NTSTATUS NtStatus = NtQueryInformationFile(hFile,
&IoStatusBlock, FileNameInfo, cbFileNameInfo, FileNameInformation);
if (!NT_SUCCESS(NtStatus))
{
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
goto Exit;
}
UnicodeComputerName->Buffer = FileNameInfo->FileName + 1;
*AvailableLength = FileNameInfo->FileNameLength;
}
else
{
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
goto Exit;
}
} // else if (RtlPrefixString((PSTRING)&NtDrivePathNamePrefix, (PSTRING)&ServerFileName, TRUE) ...
else
{
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
goto Exit;
}
Exit:
return hr;
}
//
// Try to convert the path name pointed to by UnicodeComputerName.Buffer
// into a DFS path name. The caller provides DfsServerPathName as a buffer
// for the converted name. If it's a DFS path, then update UnicodeComputerName.Buffer
// to point to the conversion, otherwise leave it unchanged.
//
HRESULT ConvertDfsPath(HANDLE hFile, UNICODE_STRING *UnicodeComputerName,
ULONG *AvailableLength, WCHAR *DfsServerPathName, ULONG cbDfsServerPathName)
{
HRESULT hr = S_OK;
HANDLE hDFS = INVALID_HANDLE_VALUE;
UNICODE_STRING DfsDriverName;
NTSTATUS NtStatus;
IO_STATUS_BLOCK IoStatusBlock;
OBJECT_ATTRIBUTES ObjectAttributes;
WCHAR *DfsPathName = UnicodeComputerName->Buffer - 1; // Back up to the whack
ULONG DfsPathNameLength = *AvailableLength + sizeof(WCHAR);
// Open the DFS driver
RtlInitUnicodeString(&DfsDriverName, L"\\Dfs");
InitializeObjectAttributes(&ObjectAttributes,
&DfsDriverName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
NtStatus = NtCreateFile(
&hDFS,
SYNCHRONIZE,
&ObjectAttributes,
&IoStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
FILE_OPEN_IF,
FILE_CREATE_TREE_CONNECTION | FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0
);
if (!NT_SUCCESS(NtStatus))
{
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
goto Exit;
}
// Query DFS's cache for the server name. The name is guaranteed to
// remain in the cache as long as the file is open.
if (L'\\' != DfsPathName[0])
{
NtClose(hDFS);
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
goto Exit;
}
NtStatus = NtFsControlFile(
hDFS,
NULL, // Event,
NULL, // ApcRoutine,
NULL, // ApcContext,
&IoStatusBlock,
FSCTL_DFS_GET_SERVER_NAME,
DfsPathName,
DfsPathNameLength,
DfsServerPathName,
cbDfsServerPathName);
NtClose(hDFS);
// STATUS_OBJECT_NAME_NOT_FOUND means that it's not a DFS path
if (!NT_SUCCESS(NtStatus))
{
if (STATUS_OBJECT_NAME_NOT_FOUND != NtStatus )
{
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
goto Exit;
}
}
else if (L'\0' != DfsServerPathName[0])
{
// The previous DFS call returns the server-specific path to the file in UNC form.
// Point UnicodeComputerName to just past the two whacks.
*AvailableLength = lstrlenW(DfsServerPathName) * sizeof(WCHAR);
if (3*sizeof(WCHAR) > *AvailableLength
||
L'\\' != DfsServerPathName[0]
||
L'\\' != DfsServerPathName[1])
{
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
goto Exit;
}
UnicodeComputerName->Buffer = DfsServerPathName + 2;
*AvailableLength -= 2 * sizeof(WCHAR);
}
Exit:
return hr;
}
// Take pwszFile, which is a path to a remote machine, and get the
// server machine's computer name.
HRESULT GetRemoteServerComputerName(LPCWSTR pwszFile, HANDLE hFile, WCHAR *pwszComputer)
{
HRESULT hr = S_OK;
ULONG cbComputer = 0;
ULONG AvailableLength = 0;
PWCHAR PathCharacter = NULL;
BOOL CheckForDfs = TRUE;
NTSTATUS NtStatus = STATUS_SUCCESS;
WCHAR FileNameInfoBuffer[MAX_PATH+sizeof(FILE_NAME_INFORMATION)];
PFILE_NAME_INFORMATION FileNameInfo = (PFILE_NAME_INFORMATION)FileNameInfoBuffer;
WCHAR DfsServerPathName[ MAX_PATH + 1 ];
WCHAR DosDeviceMapping[ MAX_PATH + 1 ];
UNICODE_STRING UnicodeComputerName;
UNICODE_STRING ServerFileName;
// Canonicalize the file name into the NT object directory namespace.
RtlInitUnicodeString(&UnicodeComputerName, NULL);
RtlInitUnicodeString(&ServerFileName, NULL);
if (!RtlDosPathNameToNtPathName_U(pwszFile, &ServerFileName, NULL, NULL))
{
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
goto Exit;
}
// Point UnicodeComputerName.Buffer at the beginning of the computer name.
hr = ScanForComputerName(hFile, ServerFileName, &UnicodeComputerName, &AvailableLength,
DosDeviceMapping, ARRAYSIZE(DosDeviceMapping),
FileNameInfo, sizeof(FileNameInfoBuffer), &CheckForDfs);
if (FAILED(hr))
goto Exit;
// If there was no error but we don't have a computer name, then the file is on
// the local computer.
if (NULL == UnicodeComputerName.Buffer)
{
DWORD cchName = MAX_COMPUTERNAME_LENGTH + 1;
hr = S_OK;
if (!GetComputerNameW(pwszComputer, &cchName))
hr = HRESULT_FROM_WIN32(GetLastError());
goto Exit;
}
// If we couldn't determine above whether or not this is a DFS path, let the
// DFS driver decide now.
if (CheckForDfs && INVALID_HANDLE_VALUE != hFile)
{
// On return, UnicodeComputerName.Buffer points to the leaf machine's
// UNC name if it's a DFS path. If it's not a DFS path,
// .Buffer is left unchanged.
hr = ConvertDfsPath(hFile, &UnicodeComputerName, &AvailableLength,
DfsServerPathName, sizeof(DfsServerPathName));
if (FAILED(hr))
goto Exit;
}
// If we get here, then the computer name\share is pointed to by UnicodeComputerName.Buffer.
// But the Length is currently zero, so we search for the whack that separates
// the computer name from the share, and set the Length to include just the computer name.
PathCharacter = UnicodeComputerName.Buffer;
while(((ULONG) ((PCHAR)PathCharacter - (PCHAR)UnicodeComputerName.Buffer) < AvailableLength)
&&
*PathCharacter != L'\\')
{
// If we found a '.', we fail because this is probably a DNS or IP name.
if (L'.' == *PathCharacter)
{
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
goto Exit;
}
PathCharacter++;
}
// Set the computer name length
UnicodeComputerName.Length = UnicodeComputerName.MaximumLength
= (USHORT) ((PCHAR)PathCharacter - (PCHAR)UnicodeComputerName.Buffer);
// Fail if the computer name exceeded the length of the input ServerFileName,
// or if the length exceeds that allowed.
if (UnicodeComputerName.Length >= AvailableLength
||
UnicodeComputerName.Length > MAX_COMPUTERNAME_LENGTH*sizeof(WCHAR))
{
goto Exit;
}
// Copy the computer name into the caller's buffer, as long as there's enough
// room for the name & a terminating '\0'.
if (UnicodeComputerName.Length + sizeof(WCHAR) > (MAX_COMPUTERNAME_LENGTH+1)*sizeof(WCHAR))
{
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
goto Exit;
}
RtlCopyMemory(pwszComputer, UnicodeComputerName.Buffer, UnicodeComputerName.Length);
pwszComputer[ UnicodeComputerName.Length / sizeof(WCHAR) ] = L'\0';
hr = S_OK;
Exit:
RtlFreeHeap(RtlProcessHeap(), 0, ServerFileName.Buffer);
return hr;
}
// Give a file's path & handle, determine the computer name of the server
// on which that file resides (which could just be this machine).
HRESULT GetServerComputerName(LPCWSTR pwszFile, HANDLE hFile, WCHAR *pwszComputer)
{
// pwszFile may be a local path name. Convert it into an absolute name.
HRESULT hr;
WCHAR wszAbsoluteName[ MAX_PATH + 1 ], *pwszFilePart;
if (GetFullPathName(pwszFile, ARRAYSIZE(wszAbsoluteName), wszAbsoluteName, &pwszFilePart))
{
if (pwszFilePart)
*pwszFilePart = 0;
// Check to see if this points to a local or remote drive. Terminate
// the path at the beginning of the file name, so that the path ends in
// a whack. This allows GetDriveType to determine the type without being
// give a root path.
UINT DriveType = GetDriveType(wszAbsoluteName);
if (DRIVE_REMOTE == DriveType)
{
// We have a remote drive (could be a UNC path or a redirected drive).
hr = GetRemoteServerComputerName(wszAbsoluteName, hFile, pwszComputer);
}
else if (DRIVE_UNKNOWN == DriveType ||
DRIVE_NO_ROOT_DIR == DriveType)
{
// We have an unsupported type
hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
}
else
{
// We have a path to the local machine.
DWORD cchName = MAX_COMPUTERNAME_LENGTH + 1;
if (!GetComputerNameW(pwszComputer, &cchName))
hr = HRESULT_FROM_WIN32(GetLastError());
else
hr = S_OK;
}
}
else
{
hr = HRESULT_FROM_WIN32(GetLastError());
}
return hr;
}