Listing 1: gfnfhnt.c GetFileNameFromHandleNT()
#define STRICT #include <assert.h> #include <limits.h> #include <Tchar.h> #include "gfnfhnt.h" #include <WindowsX.h> #ifdef _MSC_VER #pragma comment (lib, "MPR") // VC++ needs MPR.LIB to link WNet... functions #endif #define DIM(_array) (sizeof(_array) / sizeof(_array[0])) #ifdef _UNICODE #define SUFFIX "W" #else #define SUFFIX "A" #endif #define BACK_SLASH _T('\\') BOOL GetFileNameFromHandleNT (HANDLE hFile, LPTSTR string, INT size) { BOOL success = FALSE; DWORD lasterror; HINSTANCE hiPSAPI = NULL; // PSAPI.DLL HINSTANCE hiKERNEL32 = NULL; // KERNEL32.DLL // Pointer to function getMappedFileName() in PSAPI.DLL // typedef DWORD (WINAPI * GetMappedFileName_t) (HANDLE, LPVOID, LPTSTR, DWORD); GetMappedFileName_t pfGMFN; // Pointer to function QueryDosDevice() in KERNEL32.DLL // typedef DWORD (WINAPI * QueryDosDevice_t) (LPCTSTR, LPTSTR, DWORD); QueryDosDevice_t pfQDD; HANDLE hFileMap = NULL; LPVOID pMem = NULL; TCHAR tName [_MAX_PATH+100] = __TEXT(""); LPTSTR scratch = NULL; // malloc'ed working buffer DWORD scr_size; // size of scratch in bytes. HANDLE hNetWalker = INVALID_HANDLE_VALUE; DWORD FileSizeHi = 0; // 64-bit file size. DWORD FileSizeLo; LPCTSTR devname; int devlength; SetLastError (0); do { // Get the file size. // I will attempt to memory-map the whole file or 1024 // bytes, whichever is smaller. // FileSizeLo = GetFileSize (hFile, & FileSizeHi); if (0 == FileSizeHi) { if (0 == FileSizeLo) break; // Failed: can't mem-map empty file! if (FileSizeLo > 1024) FileSizeLo = 1024; } else // FileSize > 4Gb FileSizeLo = 1024; // Memory-map a portion of the file. // hFileMap = CreateFileMapping ( hFile, NULL, PAGE_READONLY, 0, FileSizeLo, NULL); if (NULL == hFileMap) break; // Failed: Can't memory-map this handle pMem = MapViewOfFile (hFileMap, FILE_MAP_READ, 0, 0, 0); if (NULL == pMem) break; // Failed: unexpected error // Call GetMappedFileName() in PSAPI.DLL // This library is only available on WinNT, not on Win9x. // hiPSAPI = LoadLibraryA ("PSAPI.DLL"); if (NULL == hiPSAPI) break; // Failed: Not on WinNT pfGMFN = (GetMappedFileName_t) GetProcAddress ( hiPSAPI, "GetMappedFileName" SUFFIX); if (! pfGMFN) break; // Failed: unexpected error if (0 >= (*pfGMFN) ( GetCurrentProcess(), pMem, tName, DIM(tName) )) break; // Failed: unexpected error if (__TEXT('\0') == tName[0]) break; // Failed: unexpected error // Access QueryDosDevice() API in KERNEL32.DLL. // This function is NOT present on Win95. // hiKERNEL32 = LoadLibraryA ("KERNEL32.DLL"); if (NULL == hiKERNEL32) break; // Failed: unexpected error pfQDD = (QueryDosDevice_t) GetProcAddress (hiKERNEL32, "QueryDosDevice" SUFFIX); if (! pfQDD) break; // Failed: Win95 // The first call to QueryDosDevice() fetches the // list of Dos Device names. We must be prepared to // allocate a larger buffer if necessary. // lasterror = S_OK; for (scr_size = 4096; scr_size < 1000000; scr_size <<= 1) { scratch = (LPTSTR) malloc (scr_size); if (! scratch) break; // out of memory SetLastError (0); (*pfQDD) (NULL, scratch, scr_size); lasterror = GetLastError (); if (ERROR_INSUFFICIENT_BUFFER != lasterror) break; free (scratch); scratch = NULL; } if (! scratch) break; // Failed: out of memory if (lasterror) break; // Failed: unexpected error // The scratch[] buffer now contains a list of DOS device // names, seperated by null characters, terminated by a // double null character, Like this: // "D:\0DISPLAY1\0E:\0PIPE\0PhysicalDrive0\0PRN\0A:\0AUX\NUL\0\0" // For each Dos Device Name in the buffer, // If that is a drive letter (ends in colon), // call QueryDosDevice() again to get the internal name // and check for a match. // for (devname = scratch; (0 < (devlength = _tcslen (devname))); devname += devlength+1) { _TCHAR objectname [_MAX_PATH]; int objlength; if (2 != devlength) continue; if (_T(':') != devname[devlength-1]) continue; objlength = (*pfQDD) ( devname, objectname, DIM(objectname)); if (0 >= objlength) continue; // this shouldn't happen // *** UNDOCUMENTED BEHAVIOR: // For mapped network drives, // QueryDosDevice ("X:") on NT4 will typically return // "\Device\LanmanRedirector\X:\Server\SERVICE" // and on Win2K (RC2) will typically return // "\Device\LanmanRedirector\;X:0\Server\SERVICE" // I need to edit out the extra drive letter, etc. // do { LPTSTR ptmp1, ptmp2; ptmp2 = _tcsstr (objectname, devname); if (!ptmp2) break; *ptmp2 = 0; ptmp1 = _tcsrchr (objectname, BACK_SLASH); *ptmp2 = devname[0]; if (NULL == ptmp1) break; ptmp2 = _tcschr (ptmp2+devlength, BACK_SLASH); if (NULL == ptmp2) break; _tcscpy (ptmp1, ptmp2); } while (0); // Test for a match // objlength = _tcslen (objectname); if (0 != _tcsnicmp (objectname, tName, objlength)) continue; if ((BACK_SLASH != tName[objlength]) && (BACK_SLASH != objectname[objlength-1]) ) continue; // We have a match; Make the substitution // assert (objlength >= devlength); _tcscpy (tName, devname); _tcscat (tName, tName+objlength); success = TRUE; break; } // for if (success) break; // If the file is not on a local or mapped drive, // it could have been opened by UNC. // (Or a new redirector may have broken the code above.) // Enumerate the current network connections. // if (NO_ERROR != WNetOpenEnum (RESOURCE_CONNECTED, RESOURCETYPE_ANY, 0, NULL, &hNetWalker )) break; { DWORD bytes_needed = scr_size; NETRESOURCE* pnr = NULL; int count = 0; DWORD code; LPTSTR remote, ptmp; int length, match; int best_match = INT_MAX; for (; ; --count, (pnr = (NULL!=pnr) ? pnr+1 : pnr) ) { // If the buffer is empty, fill it up again // if (count <= 0) { // Allocate or reallocate a memory buffer, // if necessary. // if ((NULL != scratch) && (scr_size >= bytes_needed)) // ok, I've already got enough memory bytes_needed = scr_size; else { if (scratch) free (scratch); scratch = (LPTSTR) malloc (bytes_needed); if (! scratch) break; // Failed: out of memory scr_size = bytes_needed; } count = -1; code = WNetEnumResource (hNetWalker, (DWORD *) &count, scratch, &bytes_needed); if (NO_ERROR == code) pnr = (NETRESOURCE *) scratch; else if (ERROR_MORE_DATA == code) { // Buffer isn't big enough; // allocate a bigger one. // bytes_needed *= 2; continue; } else break; // ERROR_NO_MORE_ITEMS } // The NETRESOURCE::lpRemoteName should point // to the UNC name for the service: // \\Server\Service // See if the kernel object name contains // ...\Server\Service\... // remote = pnr->lpRemoteName; if ((remote == NULL) // impossible? || (remote[0] != BACK_SLASH) || (remote[1] != BACK_SLASH) ) continue; ptmp = _tcsstr (tName+1, remote+1); if (! ptmp) continue; length = _tcslen (remote+1); // At this point ptmp points to the // substring "\Server\Service\...\file.ext". // Discard spurious matches. // First, \server\foobar != \server\foo // if ((remote[length] != BACK_SLASH) && (ptmp[length] != BACK_SLASH) ) continue; // Test if the match identifies a valid, // existing file. // { DWORD attributes; _TCHAR save = ptmp[-1]; ptmp[-1] = BACK_SLASH; attributes = GetFileAttributes (ptmp-1); ptmp[-1] = save; if ((DWORD) -1 == attributes) continue; } // If multiple matches identify valid files, // (highly unlikely!) take the match // closest to the root // match = ptmp - tName; if (best_match > match) best_match = match; } // for if (best_match < INT_MAX) { assert ((0 < best_match) && (best_match < (int) _tcslen (tName))); _tcscpy (tName+1, tName+best_match); success = TRUE; } } } while (0); // Clean up your mess: release all resources. // (This code is executed whether I succeed or fail.) // if (INVALID_HANDLE_VALUE != hNetWalker) WNetCloseEnum (hNetWalker); if (NULL != pMem) UnmapViewOfFile (pMem); if (NULL != hFileMap) CloseHandle (hFileMap); if (NULL != hiPSAPI) FreeLibrary (hiPSAPI); if (NULL != hiKERNEL32) FreeLibrary (hiKERNEL32); if (scratch) free (scratch); // Return the results // _tcsncpy (string, tName, size-1); string[size-1] = 0; return (BOOL) success; } // end of GetFileNameFromHandleNT () //End of File