Cómo obtener el nombre asociado con el MANGO abierto

12 minutos de lectura

avatar de usuario
Max Cáceres

¿Cuál es la forma más fácil de obtener el nombre de archivo asociado con un HANDLE abierto en Win32?

avatar de usuario
Elmué

Probé el código publicado por Mehrdad aquí. Funciona, pero con limitaciones:

  1. No debe usarse para recursos compartidos de red porque MountPointManager puede bloquearse durante mucho tiempo.
  2. Utiliza API no documentada (IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH) No me gusta mucho eso
  3. No es compatible con dispositivos USB que crean puertos COM virtuales (lo necesito en mi proyecto)

También estudié otros enfoques como GetFileInformationByHandleEx() y GetFinalPathNameByHandle(), pero estos son inútiles ya que solo devuelven Ruta + Nombre de archivo pero sin unidad. Adicionalmente GetFinalPathNameByHandle() también tiene el bicho colgante.

los GetMappedFileName() El enfoque en MSDN (publicado por Max aquí) también es muy limitado:

  1. Funciona solo con archivos reales.
  2. El tamaño del archivo no debe ser cero bytes.
  3. Los directorios, la red y los puertos COM no son compatibles
  4. El código es torpe.

Así que escribí mi propio código. Lo probé en Win XP y en Win 7, 8 y 10. Funciona perfectamente.

NOTA: ¡NO necesita ningún archivo LIB adicional para compilar este código!

ARCHIVO CPP:

t_NtQueryObject NtQueryObject()
{
    static t_NtQueryObject f_NtQueryObject = NULL;
    if (!f_NtQueryObject)
    {
        HMODULE h_NtDll = GetModuleHandle(L"Ntdll.dll"); // Ntdll is loaded into EVERY process!
        f_NtQueryObject = (t_NtQueryObject)GetProcAddress(h_NtDll, "NtQueryObject");
    }
    return f_NtQueryObject;
}


// returns
// "\Device\HarddiskVolume3"                                (Harddisk Drive)
// "\Device\HarddiskVolume3\Temp"                           (Harddisk Directory)
// "\Device\HarddiskVolume3\Temp\transparent.jpeg"          (Harddisk File)
// "\Device\Harddisk1\DP(1)0-0+6\foto.jpg"                  (USB stick)
// "\Device\TrueCryptVolumeP\Data\Passwords.txt"            (Truecrypt Volume)
// "\Device\Floppy0\Autoexec.bat"                           (Floppy disk)
// "\Device\CdRom1\VIDEO_TS\VTS_01_0.VOB"                   (DVD drive)
// "\Device\Serial1"                                        (real COM port)
// "\Device\USBSER000"                                      (virtual COM port)
// "\Device\Mup\ComputerName\C$\Boot.ini"                   (network drive share,  Windows 7)
// "\Device\LanmanRedirector\ComputerName\C$\Boot.ini"      (network drive share,  Windwos XP)
// "\Device\LanmanRedirector\ComputerName\Shares\Dance.m3u" (network folder share, Windwos XP)
// "\Device\Afd"                                            (internet socket)
// "\Device\Console000F"                                    (unique name for any Console handle)
// "\Device\NamedPipe\Pipename"                             (named pipe)
// "\BaseNamedObjects\Objectname"                           (named mutex, named event, named semaphore)
// "\REGISTRY\MACHINE\SOFTWARE\Classes\.txt"                (HKEY_CLASSES_ROOT\.txt)
DWORD GetNtPathFromHandle(HANDLE h_File, CString* ps_NTPath)
{
    if (h_File == 0 || h_File == INVALID_HANDLE_VALUE)
        return ERROR_INVALID_HANDLE;

    // NtQueryObject() returns STATUS_INVALID_HANDLE for Console handles
    if (IsConsoleHandle(h_File))
    {
        ps_NTPath->Format(L"\\Device\\Console%04X", (DWORD)(DWORD_PTR)h_File);
        return 0;
    }

    BYTE  u8_Buffer[2000];
    DWORD u32_ReqLength = 0;

    UNICODE_STRING* pk_Info = &((OBJECT_NAME_INFORMATION*)u8_Buffer)->Name;
    pk_Info->Buffer = 0;
    pk_Info->Length = 0;

    // IMPORTANT: The return value from NtQueryObject is bullshit! (driver bug?)
    // - The function may return STATUS_NOT_SUPPORTED although it has successfully written to the buffer.
    // - The function returns STATUS_SUCCESS although h_File == 0xFFFFFFFF
    NtQueryObject()(h_File, ObjectNameInformation, u8_Buffer, sizeof(u8_Buffer), &u32_ReqLength);

    // On error pk_Info->Buffer is NULL
    if (!pk_Info->Buffer || !pk_Info->Length)
        return ERROR_FILE_NOT_FOUND;

    pk_Info->Buffer[pk_Info->Length /2] = 0; // Length in Bytes!

    *ps_NTPath = pk_Info->Buffer;
    return 0;
}

// converts
// "\Device\HarddiskVolume3"                                -> "E:"
// "\Device\HarddiskVolume3\Temp"                           -> "E:\Temp"
// "\Device\HarddiskVolume3\Temp\transparent.jpeg"          -> "E:\Temp\transparent.jpeg"
// "\Device\Harddisk1\DP(1)0-0+6\foto.jpg"                  -> "I:\foto.jpg"
// "\Device\TrueCryptVolumeP\Data\Passwords.txt"            -> "P:\Data\Passwords.txt"
// "\Device\Floppy0\Autoexec.bat"                           -> "A:\Autoexec.bat"
// "\Device\CdRom1\VIDEO_TS\VTS_01_0.VOB"                   -> "H:\VIDEO_TS\VTS_01_0.VOB"
// "\Device\Serial1"                                        -> "COM1"
// "\Device\USBSER000"                                      -> "COM4"
// "\Device\Mup\ComputerName\C$\Boot.ini"                   -> "\\ComputerName\C$\Boot.ini"
// "\Device\LanmanRedirector\ComputerName\C$\Boot.ini"      -> "\\ComputerName\C$\Boot.ini"
// "\Device\LanmanRedirector\ComputerName\Shares\Dance.m3u" -> "\\ComputerName\Shares\Dance.m3u"
// returns an error for any other device type
DWORD GetDosPathFromNtPath(const WCHAR* u16_NTPath, CString* ps_DosPath)
{
    DWORD u32_Error;

    if (wcsnicmp(u16_NTPath, L"\\Device\\Serial", 14) == 0 || // e.g. "Serial1"
        wcsnicmp(u16_NTPath, L"\\Device\\UsbSer", 14) == 0)   // e.g. "USBSER000"
    {
        HKEY h_Key; 
        if (u32_Error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Hardware\\DeviceMap\\SerialComm", 0, KEY_QUERY_VALUE, &h_Key))
            return u32_Error;

        WCHAR u16_ComPort[50];

        DWORD u32_Type;
        DWORD u32_Size = sizeof(u16_ComPort); 
        if (u32_Error = RegQueryValueEx(h_Key, u16_NTPath, 0, &u32_Type, (BYTE*)u16_ComPort, &u32_Size))
        {
            RegCloseKey(h_Key);
            return ERROR_UNKNOWN_PORT;
        }

        *ps_DosPath = u16_ComPort;
        RegCloseKey(h_Key);
        return 0;
    }

    if (wcsnicmp(u16_NTPath, L"\\Device\\LanmanRedirector\\", 25) == 0) // Win XP
    {
        *ps_DosPath  = L"\\\\";
        *ps_DosPath += (u16_NTPath + 25);
        return 0;
    }

    if (wcsnicmp(u16_NTPath, L"\\Device\\Mup\\", 12) == 0) // Win 7
    {
        *ps_DosPath  = L"\\\\";
        *ps_DosPath += (u16_NTPath + 12);
        return 0;
    }

    WCHAR u16_Drives[300];
    if (!GetLogicalDriveStrings(300, u16_Drives))
        return GetLastError();

    WCHAR* u16_Drv = u16_Drives;
    while (u16_Drv[0])
    {
        WCHAR* u16_Next = u16_Drv +wcslen(u16_Drv) +1;

        u16_Drv[2] = 0; // the backslash is not allowed for QueryDosDevice()

        WCHAR u16_NtVolume[1000];
        u16_NtVolume[0] = 0;

        // may return multiple strings!
        // returns very weird strings for network shares
        if (!QueryDosDevice(u16_Drv, u16_NtVolume, sizeof(u16_NtVolume) /2))
            return GetLastError();

        int s32_Len = (int)wcslen(u16_NtVolume);
        if (s32_Len > 0 && wcsnicmp(u16_NTPath, u16_NtVolume, s32_Len) == 0)
        {
            *ps_DosPath  =  u16_Drv;
            *ps_DosPath += (u16_NTPath + s32_Len);
            return 0;
        }

        u16_Drv = u16_Next;
    }
    return ERROR_BAD_PATHNAME;
}

ARCHIVO DE CABECERA:

#pragma warning(disable: 4996) // wcsnicmp deprecated
#include <winternl.h>

// This makro assures that INVALID_HANDLE_VALUE (0xFFFFFFFF) returns FALSE
#define IsConsoleHandle(h) (((((ULONG_PTR)h) & 0x10000003) == 0x3) ? TRUE : FALSE)

enum OBJECT_INFORMATION_CLASS 
{
    ObjectBasicInformation, 
    ObjectNameInformation,
    ObjectTypeInformation, 
    ObjectAllInformation, 
    ObjectDataInformation
};

struct OBJECT_NAME_INFORMATION 
{
    UNICODE_STRING Name; // defined in winternl.h
    WCHAR NameBuffer;
};

typedef NTSTATUS (NTAPI* t_NtQueryObject)(HANDLE Handle, OBJECT_INFORMATION_CLASS Info, PVOID Buffer, ULONG BufferSize, PULONG ReturnLength);   

  • En primer lugar, este código necesita al menos kernel32.lib y advapi32.lib para compilarse. Parece que Elmue “quería decir” que este código no necesita bibliotecas adicionales (por ejemplo: ntdll.lib) para compilar. 🙂 En segundo lugar, este código no es perfecto para todos los redirectores de red (los proveedores UNC), por ejemplo, en el caso de VirtualBox (host) compartido (Device\VBoxMiniRdr como destino de (DosDevices\)VBoxMiniRdrDN enlace simbólico). 🙂

    – Moscú

    13 de diciembre de 2014 a las 1:24

  • Buena respuesta. Esto es básicamente lo que hace Process Hacker (y probablemente también Process Explorer de Sysinternals) y funciona para prácticamente todos los tipos de identificadores y no solo para archivos.

    -conio

    2 de junio de 2015 a las 13:20

  • Hola, @Elmue, ¡esto funciona espléndido! Me preguntaba si ya tuvo la oportunidad de probar con Win8, 8.1 o 10.

    – Noitidart

    11 de febrero de 2016 a las 6:50

avatar de usuario
usuario541686

Hay una forma correcta (aunque no documentada) de hacer esto en Windows XP que también funciona con directorios — el mismo método GetFinalPathNameByHandle usos en Windows Vista y versiones posteriores.

Aquí están las declaraciones eneded. Algunos de estos ya están en WInternl.h y MountMgr.h pero los acabo de poner aquí de todos modos:

#include "stdafx.h"
#include <Windows.h>
#include <assert.h>

enum OBJECT_INFORMATION_CLASS { ObjectNameInformation = 1 };
enum FILE_INFORMATION_CLASS { FileNameInformation = 9 };
struct FILE_NAME_INFORMATION { ULONG FileNameLength; WCHAR FileName[1]; };
struct IO_STATUS_BLOCK { PVOID Dummy; ULONG_PTR Information; };
struct UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; };
struct MOUNTMGR_TARGET_NAME { USHORT DeviceNameLength; WCHAR DeviceName[1]; };
struct MOUNTMGR_VOLUME_PATHS { ULONG MultiSzLength; WCHAR MultiSz[1]; };

extern "C" NTSYSAPI NTSTATUS NTAPI NtQueryObject(IN HANDLE Handle OPTIONAL,
    IN OBJECT_INFORMATION_CLASS ObjectInformationClass,
    OUT PVOID ObjectInformation OPTIONAL, IN ULONG ObjectInformationLength,
    OUT PULONG ReturnLength OPTIONAL);
extern "C" NTSYSAPI NTSTATUS NTAPI NtQueryInformationFile(IN HANDLE FileHandle,
    OUT PIO_STATUS_BLOCK IoStatusBlock, OUT PVOID FileInformation,
    IN ULONG Length, IN FILE_INFORMATION_CLASS FileInformationClass);

#define MOUNTMGRCONTROLTYPE ((ULONG) 'm')
#define IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH \
    CTL_CODE(MOUNTMGRCONTROLTYPE, 12, METHOD_BUFFERED, FILE_ANY_ACCESS)

union ANY_BUFFER {
    MOUNTMGR_TARGET_NAME TargetName;
    MOUNTMGR_VOLUME_PATHS TargetPaths;
    FILE_NAME_INFORMATION NameInfo;
    UNICODE_STRING UnicodeString;
    WCHAR Buffer[USHRT_MAX];
};

Aquí está la función principal:

LPWSTR GetFilePath(HANDLE hFile)
{
    static ANY_BUFFER nameFull, nameRel, nameMnt;
    ULONG returnedLength; IO_STATUS_BLOCK iosb; NTSTATUS status;
    status = NtQueryObject(hFile, ObjectNameInformation,
        nameFull.Buffer, sizeof(nameFull.Buffer), &returnedLength);
    assert(status == 0);
    status = NtQueryInformationFile(hFile, &iosb, nameRel.Buffer,
        sizeof(nameRel.Buffer), FileNameInformation);
    assert(status == 0);
    //I'm not sure how this works with network paths...
    assert(nameFull.UnicodeString.Length >= nameRel.NameInfo.FileNameLength);
    nameMnt.TargetName.DeviceNameLength = (USHORT)(
        nameFull.UnicodeString.Length - nameRel.NameInfo.FileNameLength);
    wcsncpy(nameMnt.TargetName.DeviceName, nameFull.UnicodeString.Buffer,
        nameMnt.TargetName.DeviceNameLength / sizeof(WCHAR));
    HANDLE hMountPointMgr = CreateFile(_T("\\\\.\\MountPointManager"),
        0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL, OPEN_EXISTING, 0, NULL);
    __try
    {
        DWORD bytesReturned;
        BOOL success = DeviceIoControl(hMountPointMgr,
            IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH, &nameMnt,
            sizeof(nameMnt), &nameMnt, sizeof(nameMnt),
            &bytesReturned, NULL);
        assert(success && nameMnt.TargetPaths.MultiSzLength > 0);
        wcsncat(nameMnt.TargetPaths.MultiSz, nameRel.NameInfo.FileName,
            nameRel.NameInfo.FileNameLength / sizeof(WCHAR));
        return nameMnt.TargetPaths.MultiSz;
    }
    __finally { CloseHandle(hMountPointMgr); }
}

y aquí hay un ejemplo de uso:

int _tmain(int argc, _TCHAR* argv[])
{
    HANDLE hFile = CreateFile(_T("\\\\.\\C:\\Windows\\Notepad.exe"),
        0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    assert(hFile != NULL && hFile != INVALID_HANDLE_VALUE);
    __try
    {
        wprintf(L"%s\n", GetFilePath(hFile));
        //  Prints:
        //  C:\Windows\notepad.exe
    }
    __finally { CloseHandle(hFile); }
    return 0;
}

  • UPPERCASE / lowercase == RELACIÓN DEMASIADO GRANDE

    – pmg

    13 de marzo de 2011 a las 1:59


  • @pmg: Estoy de acuerdo, pero no elegí los nombres… la API de Windows ya es así, solo copié/pegué lo que necesitaba aquí.

    – usuario541686

    13 de marzo de 2011 a las 2:03

  • @ybungalobill: ¿Realmente necesitas el DDK? Creo que copié todo lo que estaba fuera del SDK aquí…

    – usuario541686

    30 de mayo de 2011 a las 21:09


  • Aunque vale la pena mencionar que GetProcAddress evitaría la necesidad del DDK.

    – usuario541686

    16 de enero de 2013 a las 23:38

  • Este código funciona, pero tiene algunas limitaciones. Para más detalles ver mi publicación aquí.

    – Elmue

    13 de septiembre de 2013 a las 19:17


avatar de usuario
precio de taylor

editar Gracias por los comentarios acerca de que esto es solo para Vista o Server 2008. Me perdí eso en la página. Supongo que debería haber leído el entero artículo 😉

Parece que puedes usar GetFileInformationByHandleEx() para obtener esta información.

Es probable que desee hacer algo como:

GetFileInformationByHandleEx( fileHandle, FILE_NAME_INFO, lpFileInformation, sizeof(FILE_NAME_INFO));

Verifique dos veces la página de MSDN para asegurarse de que no lo haya engañado demasiado 🙂

Salud,

taylor

  • El único problema con esta solución es que GetFileInformationByHandleEx requiere Windows Vista o Server 2008 (o posterior).

    – ChrisN

    15 de septiembre de 2008 a las 18:39

  • Me encanta la simplicidad de este pero estoy en XP 🙁

    – Max Cáceres

    15 de septiembre de 2008 a las 18:55

  • Parece simple, pero solo devuelve ruta + nombre de archivo, pero falta la unidad, por lo que no es realmente útil.

    – Elmué

    13/09/2013 a las 19:15

  • FILE_NAME_INFO es una estructura de datos de longitud variable con una sola entrada para el primer carácter. Entonces, en lugar de sizeof (FILE_NAME_INFO), creo que le gustaría calcular dwBufferSize como sizeof (DWORD) + MAX_PATH * sizeof (WCHAR).

    –Dwayne Robinson

    1 de abril de 2015 a las 12:43

  • Lamento mi comentario anterior, ya que la publicación original solo pedía un nombre de archivo. Por lo tanto, podría estar bien que GetFileInformationByHandleEx() no informe ninguna información de la unidad. De todos modos, prefiero usar GetFinalPathNameByHandle() para Windows Vista y versiones posteriores.

    – Holger

    8 de marzo de 2016 a las 9:36

FWIW, aquí está la misma solución del artículo de MSDN sugerido por Prakash en Python usando el maravilloso ctipos:

from ctypes import *
# get handle to  c:\boot.ini to test
handle = windll.kernel32.CreateFileA("c:\\boot.ini", 0x80000000, 3, 0, 3, 0x80, 0)
hfilemap = windll.kernel32.CreateFileMappingA(handle, 0, 2, 0, 1, 0)
pmem = windll.kernel32.MapViewOfFile(hfilemap, 4, 0, 0, 1)
name = create_string_buffer(1024)
windll.psapi.GetMappedFileNameA(windll.kernel32.GetCurrentProcess(), pmem, name, 1024)
print "The name for the handle 0x%08x is %s" % (handle, name.value)
# convert device name to drive letter
buf = create_string_buffer(512)
size = windll.kernel32.GetLogicalDriveStringsA(511, buf)
names = buf.raw[0:size-1].split("\0")
for drive in names:
    windll.kernel32.QueryDosDeviceA(drive[0:2], buf, 512)
    if name.value.startswith(buf.value):
        print "%s%s" % (drive[0:2], name.value[len(buf.value):])
        break

avatar de usuario
Holger

Para Windows Vista y posteriores prefiero usar
GetFinalPathNameByHandle()

char buf[MAX_PATH];
GetFinalPathNameByHandleA(fileHandle, buf, sizeof(buf), VOLUME_NAME_DOS)

Para Windows XP prefiero la solución de Mehrdad.

Entonces cargo GetFinalPathNameByHandle() dinámicamente a través de GetProcAddress() y si esto falla (porque es Windows XP) busco la solución de Mehrdad con NtQueryObject()

Si necesita hacer esto en Win32 pre-Vista o Server 2008, mire el GetMappedFileName(...) función, que es uno de los secretos mejor guardados en Win32. Con un poco C/C++-Fupuede mapear en memoria una pequeña porción del archivo en cuestión y luego pasar ese identificador a esta función.

Además, en Win32, realmente no puede eliminar un archivo que está abierto (el problema de abrir/desvincular mencionado en otra respuesta); puede marcarlo para eliminarlo al cerrarlo, pero aún permanecerá hasta que se cierre su último identificador abierto. No sé si el mapeo (a través de mmap(...)) el archivo en este caso ayudaría, porque tiene que apuntar a un archivo físico…

-=-James.

En Unixes no hay una forma real de hacer esto de manera confiable. En Unix con el sistema de archivos tradicional de Unix, puede abrir un archivo y luego desvincularlo (eliminar su entrada del directorio) y usarlo, momento en el cual el nombre no se almacena en ninguna parte. Además, debido a que un archivo puede tener múltiples enlaces fijos en el sistema de archivos, cada uno de los nombres es equivalente, por lo que una vez que tenga solo el identificador abierto, no estará claro hacia qué nombre de archivo debe volver a asignar.

Por lo tanto, es posible que pueda hacer esto en Win32 usando las otras respuestas, pero si alguna vez necesita trasladar la aplicación a un entorno Unix, no tendrá suerte. Mi consejo para usted es refactorizar su programa, si es posible, para que no necesite el sistema operativo para poder mantener una conexión abierta de recursos a nombres de archivos.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad