BackupSeek неправильно пропускает разреженный блок
Во время разработки следующей версии программы NTFS Stream Explorer я столкнулся с багом в WinAPI-функции BackupSeek. Для чтения потоков NTFS, содержащихся в файле можно использовать две стандартные функции BackupRead и BackupSeek. Эти функции перечисляют все его содержащиеся в нём системные потоки. Информация о каждом потоке содержится в структуре WIN32_STEAM_ID. Я написал вспомогательную утилиту NTFS BackupRead Dumper, которая умеет делать дамп всех потоков файла при помощи BackupRead и восстанавливать файл из дампа.
typedef struct _WIN32_STREAM_ID { DWORD dwStreamId ; DWORD dwStreamAttributes ; LARGE_INTEGER Size ; DWORD dwStreamNameSize ; WCHAR cStreamName[ ANYSIZE_ARRAY ] ; } WIN32_STREAM_ID, *LPWIN32_STREAM_ID ;
Три самых главных поля в структуре WIN32_STREAM_ID это dwStreamId, Size и dwStreamNameSize. Анализ файла происходит следующим образом: с помощью BackupRead читается структура WIN32_STREAM_ID, по полю dwStreamId выясняется, что за поток перед нами, далее его можно либо прочитать с помощью той же BackupRead, либо пропустить его с помощью BackupSeek и перейти к следующему. Если мы решили читать поток, то в первую очередь надо проверить, именованый он или неименованый. Среднестатистический файл содержит несколько системных потоков, причём все из них безымянные, соответственно dwStreamNameSize равно 0. Некоторые файлы содержат именованные потоки типа BACKUP_ALTERNATE_DATA, это те самые «альтернативные файловые потоки», о которых много информации в сети. Если dwStreamNameSize не равно нулю, считываем имя потока, если Size тоже не равно нулю, значит поток не пустой, и его содержимое можно прочитать.
Для примера был создан пустой файл размером 64 Кб, ему был присвоен разреженный атрибут и задан диапазон разреженности 0-65536. У файла был создан альтернативный поток, названный STREAM с содержимым AAAA. После этого файлу был присвоен один расширенный атрибут, названный ATTR и содержащий BBBB. Расширенный атрибут был присвоен с помошью утилиты EA.EXE, а альтернативный поток был создан прямо из командной строки.
D:\TMP>fsutil file createnew test.dat 65536 Файл D:\TMP\test.dat создан D:\TMP>fsutil sparse setflag test.dat D:\TMP>fsutil sparse setrange test.dat 0 65536 D:\TMP>echo AAAA > test.dat:stream D:\TMP>ea set test.dat ATTR BBBB
На картинке показано, как BackupRead видит файл, содержащий все эти атрибуты. Цветами выделены заголовки атрибутов, соответствующие структуре WIN32_STREAM_ID.
Прежде чем решить, с каким потоком взаимодействовать, нужно получить полный список потоков файла, чтобы знать, какие из них есть в наличии, а какие отсутствуют. Перечисление всех потоков упрощённо выглядит так:
#define BackupClose(x,y) BackupRead(x, NULL, 0, NULL, TRUE, FALSE, y) WIN32_STREAM_ID sid; LPVOID lpContext = NULL; while (TRUE) { BackupRead(hFile, (LPBYTE)&sid, dwStreamHeaderSize, &dwRead, FALSE, TRUE, &lpContext); // Условие выхода из цикла if (0 == dwRead) { break; } // Тут можно вывести какую-либо информацию о потоке, взятую // из WIN32_STREAM_ID // Если поток именованный if (sid.dwStreamNameSize > 0) { // с помощью BackupRead можно прочитать имя потока } // Если поток имеет не нулевой размер if (sid.Size.QuadPart > 0) { // с помощью BackupRead можно прочитать содержимое потока // или, если это не требуется, переместиться к следующему потоку BackupSeek(hFile, sid.Size.LowPart, sid.Size.HighPart, &dw1, &dw2, &lpContext); } //Очищаем структуру перед следующим проходом цикла ZeroMemory(&sid, sizeof(WIN32_STREAM_ID)); } BackupClose(hFile, &lpContext);
Как выяснилось, функция BackupSeek в таком алгоритме прекрасно работает со всеми видами потоков, за исключением потока типа BACKUP_SPARSE_BLOCK. Такие потоки существуют у разреженных файлов NTFS. Баг в функции BackupSeek приводит к тому, что указатель для чтения перемещается в некорректную позицию. Из-за этого следующий в цикле вызов BackupRead прочитает не структуру WIN32_STREAM_ID, а часть содержимого потока разреженного файла. Соотвественно, алгоритм не может распознать в структуре тип потока и не может далее производить их перечисление. Все потоки, стоящие в файле после BACKUP_SPARSE_BLOCK становятся невидимыми для программы, которая занимается перечислением потоков. Саму операционную систему Windows это не затрагивает, так как она, разумеется, обрабатывает внутренности файла далеко не с помощью функций резервного копирования.
Вывод: функция BackupSeek не может корректно пропустить поток, если он имеет тип BACKUP_SPARSE_BLOCK.
Решение проблемы
Чтобы осуществить корректное перечисление потоков, приходится добавить в алгоритм дополнительное условие.
if (BACKUP_SPARSE_BLOCK == sid.dwStreamId) { // Баг в BackupSeek не позволяет пропустить разреженный блок. // Читаем его, не обрабатывая. sparse_buf = (LPBYTE) GlobalAlloc(GPTR, BUF_SIZE); BackupRead(hFile, sparse_buf, sid.Size.LowPart, &dwRead, FALSE, TRUE, &lpContext); GlobalFree(sparse_buf); } else { //Перемещаемся к следующему потоку BackupSeek(hFile, sid.Size.LowPart, sid.Size.HighPart, &dw1, &dw2, &lpContext); }
В отличие от BackupSeek, функция BackupRead читает именно столько байт, сколько указано в sid.Size, и после операции чтения указатель в файле оказывается на правильной позиции. Это позволяет перечислять потоки дальше.
В итоге в бета-версии NTFS Stream Explorer 1.03 сделано корректное отображение потоков, даже если открыт разреженный файл.
Кратко
Ошибка:
Неправильная работа функции BackupSeek при обработке разреженных файлов
Применение:
Провоцирование ошибок в программах, использующих функцию для перечисления потоков NTFS, а также
в программах, осуществляющих резервное копирование данных.
Операционная система:
ОС Windows семейства NT
Компонент:
Библиотека kernel32.dll, функция BackupSeek
По теме разреженных файлов также есть следующее:
- NTFS Stream Explorer 2.00 Программа поддерживает редактирование разреженности файлов.
- Разреженные файлы NTFS — внутреннее устройство.
Автор: амдф
Дата: 12.03.2011
Избранное
Остальное
По вопросам сотрудничества и другим вопросам по работе сайта пишите на cleogroup[собака]yandex.ru