Функции для работы с файлами
Невозможно представить полноценное средство разработки кода, если в нем нет способов работы с файлами. Для чего файлы могут понадобиться в драйвере? Два примера, первыми приходящие на ум: необходимость считывания кода прошивок внешних устройств (которые могут быть размером до десятков килобайт) и ведение протоколирования в файле на диске.
Для открытия файла из драйвера режима ядра используется универсальная функция ZwCreateFile. Универсальность этого вызова состоит в том, что с его помощью производится и открытие существующих файлов, и создание новых.
Специфика системной функции ZwCreateFile состоит в том, что она имеет протокольных параметров даже больше, чем пользовательский вызов CreateFile. Существенная часть входной информации об открываемом объекте поступает внутри структуры OBJECT_ATTRIBUTES, которую следует предварительно создать и заполнить соответствующим корректными данными. Для ведения учетной информации открытого объекта используется структура данных IO_STATUS_BLOCK, которую следует предоставить при вызове (инициализировать ее не следует).
Поскольку вызов ZwCreateFile имеет большое разнообразие флаговых значений практически для каждого из входных параметров, ограничимся кратким общим обзором, представленным в таблице ниже. Более полную информацию обо всех возможных значения параметров вызова ZwCreateFile следует получить из документации пакета DDK. (Джозеф Ньюкамер уделил в своей книге описанию этой функции 7 страниц!)
Таблица 7.39. Прототип вызова ZwCreateFile
NTSTATUS ZwCreateFile | IRQL == PASSIVE_LEVEL |
Параметры | Предоставляет доступ к системным ресурсам (в том числе файлам) в режиме ядра |
OUT PHANDLE pHandle | Указатель на переменную, куда следует поместить дескриптор открытого объекта (файла, подраздела реестра и т.п.) |
IN ACCESS_MASK DesiredAccess | Характеристика доступа к объекту. Для файлов вполне приемлемы значения GENERIC_READ или GENERIC_WRITE, которые представляют сложные комбинации из более простых масок доступа (типа FILE_APPEND_DATA и т.п.) |
IN POBJECT_ATTRIBUTES pObjAttributes | Указатель на заполненную вызывающим кодом структуру данных, которая описывает имя, местоположение и некоторые другие характеристики открываемого объекта (см. ниже) |
OUT PIO_STATUS_BLOCK pIOStatus |
Указатель на буфер, в котором будет размещена информация об открытом объекте в формате структуры IO_STATUS_BLOCK |
IN PLARGE_INTEGER AllocationSize OPTIONAL |
Начальный размер файла в байтах. Ненулевое значение принимается во внимание только при создании и перезаписи файла |
IN ULONG FileAttributes | Атрибуты открываемого файла. Типовым является значение FILE_ATTRIBUTE_NORMAL |
IN ULONG SharedAccessFlags | Описывает, разрешен ли совместный доступ, например, FILE_SHARE_READ — для чтения |
IN ULONG CreateDispositionFlags | Способ открытия файла, например, FILE_OPEN_IF — если не существует, создать |
IN ULONG CreateOptions | Комбинация флагов создания, например, FILE_SYNCHRONOUS_IO_NONALERT — все операции над файлом выполняются как синхронные (DesiredAccess должен включать флаг SYNCHRONIZE) |
IN PVOID EaBuffer OPTIONAL | Для драйверов устройств и драйверов средних слоев следует указывать NULL |
IN ULONG EaLength | Для драйверов устройств и драйверов средних слоев следует указывать 0 |
Возвращаемое значение | STATUS_SUCCESS или код ошибки (несколько более подробную информацию можно найти в структуре IO_STATUS_BLOCK) |
Поле pIOStatus->Information ( структуры IO_STATUS_BLOCK) может после вызова иметь одно из следующих значений: FILE_CREATED, FILE_OPENED, FILE_OVERWRITTEN, FILE_SUPERSEDED, FILE_EXISTS или FILE_DOES_NOT_EXIST.
Ниже приводится пример создания (или открытия — при повторных обращениях) файла C:\Example\testfile.txt и запись в него строки " string : test write!\"
NTSTATUS status; UNICODE_STRING fullFileName; HANDLE fileHandle; IO_STATUS_BLOCK iostatus; OBJECT_ATTRIBUTES oa;
RtlInitUnicodeString( &fullFileName, L"\\??\\C:\\Example\\testfile.txt");
InitializeObjectAttributes( &oa, &fullFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL );
status = ZwCreateFile ( &fileHandle, GENERIC_WRITE | SYNCHRONIZE, &oa, &iostatus, 0, // alloc size = none FILE_ATTRIBUTE_NORMAL, FILE_SHARE_WRITE, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); // Здесь: // GENERIC_WRITE равно STANDARD(0x40000000L) // // FILE_GENERIC_WRITE равно STANDARD_RIGHTS_WRITE|FILE_WRITE_DATA | // FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | // SYNCHRONIZE, что можно увидеть в заголовочном файле winnt.h
if( NT_SUCCESS(status)) { // Строка для записи в файл char myString[100]="string : test write!\r\n"; // Структура, которая поможет определить длину файла: FILE_STANDARD_INFORMATION fileInfo;
status = // Получаем информацию о файле ZwQueryInformationFile( fileHandle, &iostatus, &fileInfo, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation ); ULONG len = strlen(myString); if( NT_SUCCESS(status) ) { LARGE_INTEGER ByteOffset = fileInfo.EndOfFile; status = ZwWriteFile(fileHandle, NULL, NULL, NULL, &iostatus, myString, len, // Записываемая строка &ByteOffset, // a если NULL? см. ниже NULL); if( !NT_SUCCESS(status) || iostatus.Information != len ) { DbgPrint("Error on writing. Status = %x.", status); } } ZwClose(fileHandle); break;
}
Следует заметить, что без магической комбинации "\\??\\" в начале имени файла, вызов ZwCreateFile непременно возвратит ошибку.
Этот префикс является обязательным для файлов на диске.
Таблица 7.40. Прототип вызова ZwWriteFile
NTSTATUS ZwWriteFile | IRQL == PASSIVE_LEVEL |
Параметры | Производит модификацию объекта (файла), указанного открытым дескриптором |
IN HANDLE FileHandle | Дескриптор открытого для модификации файлового объекта |
IN HANDLE Event OPTIONAL | Драйверы устройств и драйверы средних слоев должны установить этот параметр равным NULL |
IN PIO_APC_ROUTINE pApcRoutine OPTIONAL |
Драйверы устройств и драйверы средних слоев должны установить этот параметр равным NULL |
IN PVOID ApcContext OPTIONAL | Драйверы устройств и драйверы средних слоев должны установить этот параметр равным NULL |
OUT PIO_STATUS_BLOCK pIoStatusBlock | В поле pIoStatusBlock->Information по завершении вызова находится число реально записанных байт |
IN PVOID Buffer | Буфер с данными для записи |
IN ULONG Length | Размер записываемой порции данных |
IN PLARGE_INTEGER pByteOffset OPTIONAL | Указатель на переменную, где содержится смещение в файле (от начала), по которому следует производить запись данных |
IN PULONG Key OPTIONAL | Драйверы устройств и драйверы средних слоев должны установить этот параметр равным NULL |
Возвращаемое значение | STATUS_SUCCESS или код ошибки |
Рассмотрим подробнее некоторые функции, использованные в приведенном выше примере.
Макроопределение InitializeObjectAttributes используется для заполнения полей структуры OBJECT_ATTRIBUTES, что делает эту операцию компактнее. Это макроопределение вводится в заголовочном файле ntdef.h пакета DDK. Там же описана и внутренняя организация OBJECT_ATTRIBUTES.
Собственно запись в файл в приведенном выше примере выполняется системной функцией ZwWriteFile.
Способ вызова ZwReadFile во многом повторяет прототип для ZwWriteFile, приведенный в таблице 7.40.
Если параметр pByteOffset указан равным NULL, то в большинстве случаев это воспринимается как нулевое смещение, и запись была бы выполнена (скажем, в приведенном примере) с начала файла. Однако на это допущение лучше не полагаться. Например, если бы мы пытались произвести запись в упомянутое выше устройство PHDDebugPrint, то нас преследовали бы ошибки, пока значение *pByteOffset не было бы задано явно, то есть:
pByteOffset->QuadPart = 0i64; // 0 для LARGE_INTEGER.
Упоминания в документации DDK XР, по поводу того, что при наличии в параметре DesiredAccess (при вызове ZwCreateFile для получения доступа к файлу) флага FILE_APPEND_DATA запись всегда производится в конец файла, мягко говоря, не совсем справедливы. При наличии этого флага (среди параметров вызова ZwCreateFile) значение ByteOffset играет по-прежнему ту же роль в вызове ZwWriteFile, что и 6eз использования этого флага при открытии файла. |
Разумеется, для каждого типа вызова должна быть предоставлена своя структура, для чего предусмотрен предпоследний параметр, где при вызове указывается ее размер. То есть больше указанного таким образом размера вызов ZwQueryInformationFile
использовать не может. Недостаток места для записи всей информации может быть причиной неудачного завершения.