Как получить имя файла по его хэндлу и привести имя из Win32 в Nt-формат
Настоящая статья фактически делится на 2 большие части. В первой части будет рассмотрено приведение имени файла из формата Win32 к «родному» имени вида \Device\HarddiskVolume1\WINNT. Во второй части — получение имени файла по его хэндлу.
Отметим, если по Win32-имени текущее Native-имя устанавливается однозначно (небольшое исключение из правила есть для сетевых файлов, о нем будет написано ниже), то обратное преобразование, то есть, получение из \Device\HardiskVolume3\WINNT имени K:\WINNT, явно может не быть однозначным даже в случае компьютера без службы сервера, ибо существует программа subst. Поэтому сравнивать имена файлов, если приспичит, надо только после преобразования их в Native-формат. Чем мы и займемся.
Первая функция будет получать по имени раздела его Native-имя. Делать она это будет через чтение элементов пространства имен диспетчера объектов. Как и положено, она может оперировать как буквенными обозначениями разделов, так и через GUID, приписанный разделу. Ибо и для того и для другого создается символическая ссылка, разрешнием которой функция и занимается. Используются функции ZwOpenDirectoryObject, ZwOpenSymbolicLinkObject, и ZwQuerySymbolicLinkObject.
Функция записывает в буфер szBuffer Native-имя для szWin32Name.
NTSTATUS NTAPI GetDriveName(IN PWSTR szWin32Name, OUT PWSTR szBuffer, IN USHORT BufferLen) { HANDLE hDir = NULL; UNICODE_STRING usRoot; RtlInitUnicodeString(&usRoot,L"\\??"); OBJECT_ATTRIBUTES oa = {sizeof(OBJECT_ATTRIBUTES), NULL, &usRoot, OBJ_CASE_INSENSITIVE}; NTSTATUS ns = ZwOpenDirectoryObject(&hDir, DIRECTORY_QUERY | DIRECTORY_TRAVERSE, &oa); if (ns == STATUS_SUCCESS) { HANDLE hSym = NULL; RtlInitUnicodeString(&usRoot, szWin32Name); oa.RootDirectory = hDir; ns = ZwOpenSymbolicLinkObject(&hSym, SYMBOLIC_LINK_QUERY, &oa); if (ns == STATUS_SUCCESS) { UNICODE_STRING usSym; usSym.Length = BufferLen; usSym.MaximumLength = usSym.Length; usSym.Buffer = szBuffer; ns = ZwQuerySymbolicLinkObject(hSym, &usSym, NULL); ZwClose(hSym); } ZwClose(hDir); } return ns; }
А вторая будет делать то же самое, только для полного имени файла. Она при необходимости вызывается рекурсивно, также правильно обрабатывает префиксы (см. код). И еще отличие — буфер она будет выделять сама, указатель на него и будет возвращаться.
Функция выделяет буфер для Native-имени и возвращает в нем соответствующее имя для Win32-имени файла.
PWSTR NTAPI GetNativeNameEx(IN PWSTR szFileName) { if (!szFileName) return NULL; ULONG szLen = wcslen(szFileName); //обработка переменных окружения if ((szLen>0) && (szFileName[0] == '%')) { PWSTR Result = NULL; ULONG BytesNeeded = 0; UNICODE_STRING usFN; RtlInitUnicodeString(&usFN,szFileName); UNICODE_STRING usOut = {0,0,0}; NTSTATUS ns = RtlExpandEnvironmentStrings_U(NULL, &usFN, &usOut, &BytesNeeded); if ((ns == STATUS_BUFFER_TOO_SMALL) && (BytesNeeded)) { if (Result = (PWSTR)malloc(BytesNeeded)) { usOut.Buffer = Result; usOut.MaximumLength = (USHORT)BytesNeeded; ns = RtlExpandEnvironmentStrings_U(NULL, &usFN, &usOut, &BytesNeeded); if (ns == STATUS_SUCCESS) { PWSTR Result2 = Result; Result = GetNativeNameEx(Result2); free(Result2); } else { free(Result); Result = NULL; } } } return Result; } //обработка префиксов \??\, \?\\ и \\.\ if ((szLen>4) && (szFileName[0] == '\\') && (szFileName[3] == '\\')) { if ( ((szFileName[1] == '?') && (szFileName[2] == '?')) || ((szFileName[1] == '\\') && (szFileName[2] == '?')) || ((szFileName[1] == '\\') && (szFileName[2] == '.')) ) return GetNativeNameEx(&(szFileName[4])); } //обработка префикса \\компьютер (UNC-имя) if ((szLen>2) && (szFileName[0] == '\\') && (szFileName[1] == '\\')) { ULONG LanLen = wcslen(DD_LANMANREDIRECTOR_DEVICE_NAME); PWSTR Result = (PWSTR)malloc((szLen+LanLen)*sizeof(WCHAR)); if (Result) { wcscpy(Result,DD_LANMANREDIRECTOR_DEVICE_NAME); wcscat(Result,szFileName+1); } return Result; } //обработка обычных файлов if ((szLen >= 2) && (szFileName[1] == ':')) { WCHAR Drive[3] = {szFileName[0],szFileName[1],'\0'}; WCHAR DriveDevice[64]; RtlZeroMemory(DriveDevice,64*sizeof(WCHAR)); NTSTATUS ns = GetDriveNameEx(Drive,DriveDevice,128); if (ns == STATUS_SUCCESS) { PWSTR Result = (PWSTR)malloc((wcslen(DriveDevice)+wcslen(szFileName)+4)*sizeof(WCHAR)); if (Result) { wcscpy(Result,DriveDevice); ULONG L = wcslen(Result); if (Result[L-1] == '\\') Result[L-1] = '\0'; if (wcslen(szFileName) > 3) { wcscat(Result,L"\\"); wcscat(Result,szFileName+3); } return Result; } } } //обработка префикса \System32 if ((!_wcsnicmp(szFileName,L"\\System32",9)) && ((szFileName[9] == '\\') || (szFileName[9] == '\0'))) { PWSTR Result = NULL; PWSTR szTmp = (PWSTR)malloc((szLen+1)*sizeof(WCHAR)+40); if (szTmp) { swprintf(szTmp,L"%%SystemRoot%%%s",szFileName); Result = GetNativeNameEx(szTmp); free(szTmp); } return Result; } //обработка префикса System32 if ((!_wcsnicmp(szFileName,L"System32",8)) && ((szFileName[8] == '\\') || (szFileName[8] == '\0'))) { PWSTR Result = NULL; PWSTR szTmp = (PWSTR)malloc((szLen+1)*sizeof(WCHAR)+40); if (szTmp) { swprintf(szTmp,L"%%SystemRoot%%\\%s",szFileName); Result = GetNativeNameEx(szTmp); free(szTmp); } return Result; } //обработка префикса \SystemRoot if ((!_wcsnicmp(szFileName,L"\\SystemRoot",11)) && ((szFileName[11] == '\\') || (szFileName[11] == '\0'))) { PWSTR Result = NULL; PWSTR szTmp = (PWSTR)malloc((szLen+1)*sizeof(WCHAR)+8); if (szTmp) { swprintf(szTmp,L"%%SystemRoot%%%s",(PWSTR)(&(szFileName[11]))); Result = GetNativeNameEx(szTmp); free(szTmp); } return Result; } //обработка префикса SystemRoot if ((!_wcsnicmp(szFileName,L"SystemRoot",10)) && ((szFileName[10] == '\\') || (szFileName[10] == '\0'))) { PWSTR Result = NULL; PWSTR szTmp = (PWSTR)malloc((szLen+1)*sizeof(WCHAR)+8); if (szTmp) { swprintf(szTmp,L"%%SystemRoot%%\\%s",(PWSTR)(&(szFileName[10]))); Result = GetNativeNameEx(szTmp); free(szTmp); } return Result; } return NULL; }
Бросается в глаза некоторая исключительность префиксов типа \SystemRoot. На самом деле такие префиксы, как видно в коде, разрешаются через добавление % перед и после префикса и повторный разбор уже как переменной окружения (через RtlExpandEnvironmentStrings_U). Явная реализация здесь двух пар префиксов просто призвана продемонстрировать эту методику.
По поводу неоднозначности Native-имени. При подключении сетевого диска в проводнике после \Device\LanmanRedirector до имени компьютера возможно использование дополнительных символов, например, в Windows 2000 добавляется имя диска и номер сессии, а в NT4 — только имя диска. Вот именно в них-то и может быть отличие в именах. Полное имя может выглядеть как \Device\LanmanRedirector\;X:0\Server\Share\Dir\File или как \Device\LanmanRedirector\;Y:1\Server\Share\Dir\File, но, в действительности, это будет один и тот же файл \Device\LanmanRedirector\Server\Share\Dir\File. На Windows NT 4.0 имя файла может иметь вид \Device\LanmanRedirector\U:\Server\Share\Dir\File. Способ получения Native-имени, предложенный здесь, из-за отсутствия обработки префикса \Device\LanmanRedirector\ не всегда будет давать «настоящее» имя файлов по его Win32-имени, чтоб это было так, необходимо использовать следующую функцию. Критерием наличия дополнительных символов в имени, которые надо убрать, будет наличие символа ':' в следующем разделе между '\' после LanmanRedirector.
Функция дополняет GetNativeName, проверяя выходное имя на предмет наличия префикса «\Device\LanmanRedirector», и при необходимости преобразует его.
PWSTR NTAPI GetNativeName(IN PWSTR szFileName) { PWSTR Result = GetNativeNameEx(szFileName); if (Result) { ULONG LanLen = wcslen(DD_LANMANREDIRECTOR_DEVICE_NAME); ULONG Len = wcslen(Result); if ((Len > LanLen + 1) && (!_wcsnicmp(Result,DD_LANMANREDIRECTOR_DEVICE_NAME,LanLen)) && (Result[LanLen] == '\\')) { PWSTR pSlash = wcschr(&(Result[LanLen+1]),'\\'); PWSTR pDots = wcschr(&(Result[LanLen+1]),':'); if ((pDots) && (pDotsЕще стоит отметить, что при разрешении имени файла по UNC-имени MUP (Multiple UNC Provider) использует всегда «стандартное» имя, без дополнительных символов.
А теперь вторая часть. По hFile получить FileName не просто, а очень просто. Делать мы это будем через ZwQueryObject. На самом деле мы решим более общую задачу, а именно, рассмотрим правильное использование функции ZwQueryObject исходя из того, что требуемый размер буфера неизвестен, и не только на примере запроса имени файла.
Функция возвращает информацию класса ObjectInformationClass об объекте ObjectHandle.
NTSTATUS NTAPI GetObjInfo(IN HANDLE ObjectHandle, IN OBJECT_INFORMATION_CLASS ObjectInformationClass, OUT PVOID * ObjectInformation, IN OUT PULONG RealSize OPTIONAL, IN ULONG FixedSize OPTIONAL) { ULONG Dummy = 0; if (RealSize) { NTSTATUS ns = STATUS_INFO_LENGTH_MISMATCH; Dummy = ( FixedSize ? FixedSize : (RealSize ? (*RealSize) : 0 ) ); for (;;) { if ( (ObjectInformation && (*ObjectInformation) ) || (!Dummy) ) ns = ZwQueryObject(ObjectHandle, ObjectInformationClass, ObjectInformation ? (*ObjectInformation) : NULL, Dummy, &Dummy); if (((ns == STATUS_BUFFER_TOO_SMALL) || (ns == STATUS_INFO_LENGTH_MISMATCH) || (ns == STATUS_BUFFER_OVERFLOW)) && (Dummy)) { if (Dummy > (*RealSize)) if (ObjectInformation) if (*ObjectInformation) { free(*ObjectInformation); *ObjectInformation = NULL; (*RealSize) = 0; } if (ObjectInformation) if (!(*ObjectInformation)) if ((*ObjectInformation = malloc(Dummy))) { (*RealSize) = Dummy; } else { ns = STATUS_NO_MEMORY; break; } } else break; } return ns; } else { return ZwQueryObject(ObjectHandle, ObjectInformationClass, ObjectInformation ? (*ObjectInformation) : NULL, FixedSize, &Dummy); } }Осталось только разобраться в параметрах функции. Буфер для информации она получает по указателю на его адрес (потому как если размера буфера будет недостаточно, придется его выделять по новой и возвращать его новый адрес). Аналогично получается и возвращается размер буфера. Параметр FixedSize используется в качестве точного размера буфера, если требуемый размер буфера известен заранее, например, в буфер помещается известная структура. Особенности реализации этой функции такие. Во-первых, она позволяет использовать только часть буфера, как в случае фиксированноого размера запрашиваемой информации, так и в случае заранее неизвестного размера информации (как раз в случае определения имени). При необходимости будет выделен буфер требуемого размера. Во-вторых — будет ниже, а пока вернемся к нашим хэндлам.
//Функция возвращает имя объектаNTSTATUS NTAPI GetObjName(IN HANDLE ObjectHandle, OUT POBJECT_NAME_INFORMATION * ObjectInformation, IN OUT PULONG Size) { return GetObjInfo(ObjectHandle, ObjectNameInformation, (PVOID*)ObjectInformation, Size, 0); }Теперь рассмотрим особенности получения имени для объектов типа File. При запросе имени для канала (Pipe), если тот открыт в синхронном режиме, запрос имени также встает в очередь, и поток останавливается. Вообще говоря, запрос имени объекта типа File не всегда корректен, исходя из той роли, которую играют такие объекты в системе, потому гарантировать определенную реакцию на такой запрос не представляется возможным. В общем случае возможно и принудительное завершение потока, ведь неизвестно, как система и отдельные драйвера могут охранять свои внутренности. По этой причине запрос имени файла всегда надо выполнять в отдельном потоке, дабы потом была возможность прибить этот поток. Аналогичная реакция в принципе может быть прогнозируема для объектов типа Key, но примеры «неадекватного» поведения системы для ключей реестра пока неизвестны.
А теперь обещанное «во-вторых», так сказать, на сладкое. Глядя на формат функций ZwQueryEvent, ZwQueryInformationAtom, ZwQueryInformationJobObject, ZwQueryInformationPort, ZwQueryInformationProcess, ZwQueryInformationThread, ZwQueryInformationToken, ZwQueryIoCompletion, ZwQueryKey, ZwQueryMutant, ZwQueryObject, ZwQuerySection, ZwQuerySecurityObject, ZwQuerySemaphore, ZwQueryTimer можно заметить определенное сходство в передаваемых параметрах. В качестве домашнего задания предлагаю реализовать «универсальную» функцию для запросов свойств объектов, одним из параметров которой и будет реально вызываемая функция. Слово «универсальную» взято в кавычки, потому что есть объект File и функция ZwQueryInformationFile, которая реализована по-другому. Зато нет худа без добра, глядим на ZwQueryVolumeInformationFile и пишем еще одну «универсальную» функцию (только в этом случае советую передать еще параметры для определения необходимости ожидания на объекте, если возвращен результат STATUS_PENDING). По аналогии «универсального геттера» возможно создание и «универсального сеттера». Он, кстати, будет попроще, раз точно известно, что именно устанавливается, можно передавать адрес самого буфера и обойтись только FixedSize.
Успехов.
Автор: Сергей Васкецов
Дата: 03.02.2003
Избранное
Остальное
По вопросам сотрудничества и другим вопросам по работе сайта пишите на cleogroup[собака]yandex.ru