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.
1158 lines
36 KiB
1158 lines
36 KiB
//
|
|
// 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)
|
|
{
|
|
if (!InitializeCriticalSectionAndSpinCount(&_cs, 0))
|
|
{
|
|
hr = E_FAIL;
|
|
goto Exit;
|
|
}
|
|
_fCritsecInitialized = TRUE;
|
|
}
|
|
|
|
if (NULL == _pRpcAsyncState)
|
|
{
|
|
_pRpcAsyncState = new 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 = {0};
|
|
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.
|
|
|
|
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
|
|
|
|
ZeroMemory(pfdOut, 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;
|
|
}
|
|
|
|
CopyMemory(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;
|
|
}
|
|
|