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

