// // 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 #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(pb); if (dwLength < GetSize()) { hr = E_INVALIDARG; goto Exit; } pb += sizeof(dwLength); // Check the version number if (CTRACKER_VERSION != *reinterpret_cast(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(pb); pb += sizeof(_mcidLast); _droidLast = *reinterpret_cast(pb); pb += sizeof(_droidLast); _droidBirth = *reinterpret_cast(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(pb) = GetSize(); pb += sizeof(DWORD); // Save a version number *reinterpret_cast(pb) = CTRACKER_VERSION; pb += sizeof(DWORD); // Save the machine & DROIDs *reinterpret_cast(pb) = _mcidLast; pb += sizeof(_mcidLast); *reinterpret_cast(pb) = _droidLast; pb += sizeof(_droidLast); *reinterpret_cast(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(&ftDeadline) += (dwDeltaMillisecToDeadline * 10*1000); // Start the async RPC call to the tracking service _fMendInProgress = TRUE; LnkMendLink(_pRpcAsyncState, BindingHandle, ftDeadline, TrackerRestrictions, const_cast(&droidBirth), const_cast(&droidLast), const_cast(&_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; }