Struct распаковать на win32file.DeviceIoControl
Я пытаюсь понять и работать с win32file. Мне нужно взять USN Journals и с трудом разобраться в фрагментах кода, которые я нашел в Интернете. Это фрагмент кода, который я нашел -
format = 'qqqqqLLLLqqqqq'
length = struct.calcsize(format)
out_buffer = win32file.DeviceIoControl(volh, winioctlcon.FSCTL_GET_NTFS_VOLUME_DATA, None, length)
data = struct.unpack(format, out_buffer)
Теперь я действительно ржавый, когда дело доходит до C и его структур. На данный момент я понял, что format
буфер 96 байтов, и он получит вывод из DeviceIoControl
Поэтому я попытался изменить формат на 'QQQQQQQQQQQQQQQQQQQ'
чтобы увидеть, что происходит (чтобы увидеть, потому что я вроде ничего не понимаю, что может на самом деле произойти), и оказывается, что я получил больше out_buffer
этот раз. Поэтому я подумал распаковать его -
struct.unpack(format, out_buffer)
И неожиданность для меня, я получил -
struct.error: unpack requires a string argument of length 152
Поэтому я добавил еще один "Q" для увеличения размера и получил тот же результат. Я не понимаю, почему "qqqqqLLLLqqqqq" работает, а "QQQQQQQQQQQQQQQQQQQ" - нет. Так что мои вопросы -
Насколько я понимаю, мы можем распаковать, если буфер был больше, чем вывод, так почему не работает распаковка?
Придется ли мне запоминать эти форматы каждый раз, когда я хочу получить что-то от DeviceIoControl?
Указание мне на ресурсы также было бы дополнительным бонусом, так как мне нужно использовать код для чтения журналов USN, и я не думаю, что использование метода "попробуй и попробуй" меня куда-нибудь приведет
1 ответ
Давайте разделим проблемы на более мелкие части и рассмотрим каждую из них по очереди.
Модуль win32file является частью [GitHub]: Python для Windows (pywin32) Расширения, которые являются оболочкой Python поверх WinAPI.
К сожалению, у него нет официальной страницы с документами (или я ее не знаю), поэтому ниже - лучшее, что я смог найти (я использовал это годами). Альтернативный метод, который никогда не выходит из строя (но он менее привлекателен), смотрит прямо на код
[ActiveState]: win32file.DeviceIoControl - это оболочка над [MS.Docs]: функция DeviceIoControl
DeviceIoControl действует по-разному, в зависимости от dwIoControlCode (2- й аргумент). Для FSCTL_GET_NTFS_VOLUME_DATA он заполняет буфер данными, специфичными для тома. Из [MSDN]: управляющий код FSCTL_GET_NTFS_VOLUME_DATA:
lpOutBuffer
Указатель на выходной буфер, структура NTFS_VOLUME_DATA_BUFFER (@CristiFati: !!! Broken URL!!!). Запись файла, связанная с идентификатором файла, указанным во входном буфере, возвращается в этот буфер. Обратитесь к разделу "Примечания" документации для структуры NTFS_VOLUME_DATA_BUFFER для получения конкретной информации о том, как определить правильный размер этого буфера.Вот альтернатива вышеприведенному неработающему URL: [MSDN]: структура NTFS_VOLUME_DATA_BUFFER. Поскольку я не уверен, как долго он будет действителен, я вставляю приведенное ниже определение структуры (из Windows Kits 8.1: winioctl.h (строка # 4987)):
typedef struct { LARGE_INTEGER VolumeSerialNumber; LARGE_INTEGER NumberSectors; LARGE_INTEGER TotalClusters; LARGE_INTEGER FreeClusters; LARGE_INTEGER TotalReserved; DWORD BytesPerSector; DWORD BytesPerCluster; DWORD BytesPerFileRecordSegment; DWORD ClustersPerFileRecordSegment; LARGE_INTEGER MftValidDataLength; LARGE_INTEGER MftStartLcn; LARGE_INTEGER Mft2StartLcn; LARGE_INTEGER MftZoneStart; LARGE_INTEGER MftZoneEnd; } NTFS_VOLUME_DATA_BUFFER, *PNTFS_VOLUME_DATA_BUFFER;
[Python 3]: struct - интерпретировать байты как модуль упакованных двоичных данных, используется для преобразования двоичных и "обычных" данных. Он содержит все значения символов формата (q, Q, L,...) и многое другое. Вы также можете взглянуть на поведение [SO]: Python struct.pack() для более (практических) деталей
Изучив вышеперечисленные материалы, все должно проясниться.
Пара заметок:
- Если кто-то не знает, что делает (возвращает) функция, он, вероятно, не должен ее использовать (конечно, не читая руководство). Хотя в настоящее время как Win (который всегда имел множество ограничений для обычного пользователя), так и Ux "защищают пользователей от себя" (например: вход в систему root больше не разрешен, защита от записи % SystemDrive%)
- Попытки (методом проб и ошибок) показывают некоторый недостаток опыта (вероятно, каждый делает это в какой-то момент, ключ не в том, чтобы полагаться исключительно на это)
- " Придется ли мне запоминать эти форматы каждый раз, когда я хочу получить что-то от DeviceIoControl "?
- Опять же, если вы не знаете, что делает функция, в чем причина ее вызова? Если вы имели в виду изучение NTFS_VOLUME_DATA_BUFFER наизусть, это определенно не тот случай. Вы должны знать его структуру только при его использовании (и, как вы заметили, есть некоторые места, из которых вы можете получить его, включая этот пост:))
- " Насколько я понимаю, мы можем распаковать, если буфер был больше, чем вывод, так почему же распаковка не работает? "
- Ваше понимание верно. Но win32file.DeviceIoControl иногда (вероятно, при достижении 1- го NULL после 96 байт) обрезает выходной буфер при передаче значения, большего, чем ожидаемое (через аргумент длины). При прохождении меньшего, он потерпит неудачу (как и ожидалось)
Я также подготовил фиктивный пример Python.
code.py:
#!/usr/bin/env python3
import sys
import struct
import win32file
import win32api
import win32con
import winioctlcon
VOLUME_LETTER = "E"
FILE_READ_ATTRIBUTES = 0x0080
FILE_EXECUTE = 0x0020
vol_data_buf_fmt = "qqqqqLLLLqqqqq" # This is the format that matches NTFS_VOLUME_DATA_BUFFER definition (96 bytes). Note: Instead of each 'q' you could also use 'Ll' as 'LARGE_INTEGER' is an union
BINARY_FORMAT_LIST = [
vol_data_buf_fmt,
"QQQQQQQQQQQQQQQQQQQ",
]
def print_formats(): # Dummy func
print("Formats and lengths:")
for format in BINARY_FORMAT_LIST:
print(" {:s}: {:d}".format(format, struct.calcsize(format)))
def main():
#print_formats()
vol_unc_name = "\\\\.\\{:s}:".format(VOLUME_LETTER)
print("volume: ", vol_unc_name)
access_flags = FILE_READ_ATTRIBUTES | FILE_EXECUTE # Apparently, doesn't work without FILE_EXECUTE
share_flags = win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE # Doesn't work withou FILE_SHARE_WRITE
creation_flags = win32con.OPEN_EXISTING
attributes_flags = win32con.FILE_ATTRIBUTE_NORMAL
vol_handle = win32file.CreateFile(vol_unc_name, access_flags, share_flags, None, creation_flags, attributes_flags, None)
buf_len = struct.calcsize(vol_data_buf_fmt)
for i in [buf_len]:
print(" Passing a buffer size of: {:d}".format(i))
buf = win32file.DeviceIoControl(vol_handle, winioctlcon.FSCTL_GET_NTFS_VOLUME_DATA, None, i)
print(" DeviceIocontrol returned a {:d} bytes long {:}".format(len(buf), type(buf)))
out = struct.unpack_from(vol_data_buf_fmt, buf)
print("\n NumberSectors: {:}\n TotalClusters: {:}\n BytesPerCluster: {:}".format(out[1], out[2], out[6]))
win32api.CloseHandle(vol_handle)
if __name__ == "__main__":
print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
main()
Выход:
(py35x64_test) e:\Work\Dev\Stackru\q053318932>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" ./code.py Python 3.5.4 (v3.5.4:3f56838, Aug 8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32 volume: \\.\E: Passing a buffer size of: 96 DeviceIocontrol returned a 96 bytes long <class 'bytes'> NumberSectors: 494374911 TotalClusters: 61796863 BytesPerCluster: 4096
Само собой разумеется, что, умножая TotalClusters на BytesPerCluster, я получаю правильное число байтов (как сообщает Win) для моего диска E:.