Расширенные атрибуты файлов в Windows

Расширенные атрибуты NTFS и FAT16




Расширенные атрибуты файлов — дополнительный набор атрибутов, поддерживаемый Windows в файловых системах NTFS, FAT16 и HPFS. Расширенные атрибуты (extended attributes, EA) поддерживаются начиная с Windows NT и во всех последующих операционных системах на ядре NT. Поддержка расширенных атрибутов была добавлена в Windows для совместимости с операционной системой OS/2, в которой они широко использовались. В Windows эти атрибуты почти не используются программным обеспечением, но, тем не менее, их поддержка не была убрана и присутствует даже в Windows 7.

Каждый расширенный атрибут имеет строку названия, максимальный размер которой 255 символов, и байт флагов атрибута. Максимальный размер данных расширенного атрибута — 64 килобайта, причём это ограничение распространяется не на один отдельный атрибут, а на все атрибуты целиком.

В NTFS расширенные атрибуты прицеплены к файлу в виде потока ::$EA. Увидеть этот поток у файла можно с помошью программы NTFS Stream Explorer. Она предназначена для работы с альтернативными файловыми потоками, но может отображать и наличие потока EA. На иллюстрации видно, что у файла test.dat нулевой длины есть поток расширенных атрибутов размером 23 байта.

Расширенные атрибуты
Буфер расширенных атрибутов

Прочитать содержимое потока ::$EA напрямую нельзя. Это приведёт к выводу такого сообщения:

E:\CODE\ea\bin>more < test.dat::$EA
Отказано в доступе.

В FAT16 для расширенных атрибутов система создаёт в корне диска системный файл «EA DATA. SF», там и хранится их содержимое. А вот в FAT32 поддержка расширенных атрибутов отсутствует, при копировании файла на эту файловую систему EA теряются.

EA DATA. SF
Файл EA DATA. SF

При копировании файла с расширенными атрибутами на раздел FAT32 информация EA молча теряется. Windows не выводит никаких сообщений о потере информации. Это отличает дополнительные атрибуты от схожих по концепции альтернативных файловых потоков, поддержка которых есть в NTFS. Например в Windows 7 при попытке скопировать с NTFS на FAT32 файл, содержащий в себе альтернативные потоки NTFS, выводится сообщение «Действительно скопировать файл без его свойств?». Но если копировать туда же файл с EA, никаких сообщений не будет.

скопировать файл без его свойств
Сообщение при потери потока

Стандартных WinAPI функций специально для работы с расширенными атрибутами нет. Отсутствуют также стандартные утилиты Windows для работы с ними. Даже утилита fsutil, которая умеет делать много интересных операций над файлами, не поможет при работе с EA. Этим объясняется то, что данная технология почти никем не используется в Windows. Чтение расширенных атрибутов возможно через использование функции BackupRead (способ описан тут, но в примере описывается чтение потоков NTFS, а не EA), или через использование NT Native API (недокументированных функций библиотеки ntdll.dll).

Я написал консольное приложение EA.EXE, способное читать, писать, удалять расширенные атрибуты и выводить список EA. Программа использует функции из ntdll для работы с EA.

Имена расширенных атрибутов

Имя расширенного атрибута состоит из ASCII-символов. Латинские буквы приводятся к верхнему регистру, регистр символов не различается при обращении к атрибуту. Набор символов имени файла не должен содержать следующие запрещённые символы: значения ASCII 0x00 - 0x1F, символы \ / : * ? " < > | , + = [ ] ;. Максимальная длина имени 255 символов.

API расширенных атрибутов

В ntdll.dll содержатся две функции, с помощью которых реализуется доступ к расширенным атрибутам. Их прототипы:

NTSYSCALLAPI
NTSTATUS
NTAPI
NtSetEaFile(
    IN HANDLE FileHandle,
    IN PIO_STATUS_BLOCK IoStatusBlock,
    PVOID EaBuffer,
    ULONG EaBufferSize
);

NTSYSCALLAPI
NTSTATUS
NTAPI
NtQueryEaFile(
    IN HANDLE FileHandle,
    OUT PIO_STATUS_BLOCK IoStatusBlock,
    OUT PVOID Buffer,
    IN ULONG Length,
    IN BOOLEAN ReturnSingleEntry,
    IN PVOID EaList OPTIONAL,
    IN ULONG EaListLength,
    IN PULONG EaIndex OPTIONAL,
    IN BOOLEAN RestartScan
);

Функция NtSetEaFile служит для записи информации в расширенные атрибуты, а также для удаления атрибутов. Функция NtQueryEaFile предназначена для чтения данных EA или перечисления расширенных атрибутов у файла. Эти функции работают со структурой FILE_FULL_EA_INFORMATION, имеющей следующий формат:

typedef struct _FILE_FULL_EA_INFORMATION
{
    ULONG NextEntryOffset;
    UCHAR Flags;
    UCHAR EaNameLength;
    USHORT EaValueLength;
    CHAR EaName[1];
} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;

Поле NextEntryOffset содержит смещение следующей структуры в буфере данных. Поле нужно при использовании NtQueryEaFile, когда она возвращает в буфере сразу несколько атрибутов. Поле Flags содержит флаги атрибутов. По-умолчанию поле флагов имеет значение 0, допустимо также значение FILE_NEED_EA, равное 0x80, которое означает, что имеющий этот флаг атрибут важен для обработки данных файла.

Поля EaNameLength и EaValueLength это размеры имени атрибута и данных атрибута. EaName это указатель на начало имени атрибута. Сразу после имени атрибута начинаются данные. Общий размер структуры FILE_FULL_EA_INFORMATION не может превышать 64 кб. При наличии в буфере нескольких структур их общий размер также не может превышать этот размер.

Запись расширенного атрибута

Определим в константе EA_BUF_LEN максимальный размер буфера EA, равный 0xFFFF. Тогда максимальный размер данных атрибута MAX_EA_DATA_LEN равен размеру буфера, от которого нужно отнять 8 (заголовок структуры) и ещё отнять 2 (минимально короткое имя атрибута, 1 буква + нулевой символ). Перед записью нужно убедиться, что записываемые данные не превышают этого значения.

Файл для записи EA данных нужно открыть с флагом FILE_FLAG_BACKUP_SEMANTICS. Далее следует сформировать структуру FILE_FULL_EA_INFORMATION, правильно указать в ней размеры имени и данных, выставить флаги и скопировать в структуру строку имени и данные атрибута по правильным смещениям.

Затем вызывается функция NtSetEaFile и анализируется её возвращаемое значение NTSTATUS. В результате ошибки попытки записи расширенных атрибутов функция может вернуть следующие коды ошибок:

// Неправильное имя расширенного атрибута 
#define STATUS_INVALID_EA_NAME           ((NTSTATUS)0x80000013L)
// Структура EA сформирована неправильно
#define STATUS_EA_LIST_INCONSISTENT      ((NTSTATUS)0x80000014L)
// Структура EA превышает максимальный размер
#define STATUS_EA_TOO_LARGE              ((NTSTATUS)0xC0000050L)
// Расширенные атрибуты не поддерживаются
#define STATUS_EAS_NOT_SUPPORTED         ((NTSTATUS)0xC000004FL)
// Структура EA повреждена
#define STATUS_EA_CORRUPT_ERROR          ((NTSTATUS)0xC0000053L)

Код ошибки STATUS_EAS_NOT_SUPPORTED может означать не только отсутствие поддержки расширенных атрибутов у файловой системы, но и невозможность использовать EA у конкретного файла. Если у файла есть reparse данные, то есть файл является точкой повторной обработки (например, симлинком), то у такого файла расширенные атрибуты не поддерживаются. В файловой системе NTFS файл не может одновременно являться и reparse point'ом, и содержать расширенные атрибуты EA, только что-то одно.

BOOL WriteEA(LPCSTR file_name, LPCSTR ascii_caps_name, 
            IN PVOID buf, UINT buf_len, UCHAR flags)
{
  IO_STATUS_BLOCK iosb;
  CHAR ea_buf[EA_BUF_LEN];
  PFILE_FULL_EA_INFORMATION ffei 
    = (PFILE_FULL_EA_INFORMATION) ea_buf;
  HANDLE FileHandle;  
  NTSTATUS ntst;
  
  if (buf_len > MAX_EA_DATA_LEN)
  {
    return FALSE;
  }

  ZeroMemory(&ea_buf, EA_BUF_LEN);
  FileHandle = CreateFile(file_name, GENERIC_READ | GENERIC_WRITE,
    0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

  if (INVALID_HANDLE_VALUE == FileHandle) 
  {
    return FALSE;
  }

  ffei->EaNameLength = strnlen(ascii_caps_name, MAX_EA_NAME_LEN);
  ffei->EaValueLength = buf_len;
  ffei->Flags = flags;

  memcpy(ffei->EaName, ascii_caps_name, ffei->EaNameLength);
  memcpy(ffei->EaName + ffei->EaNameLength + 1, 
    buf, ffei->EaValueLength);
    
  ntst = NtSetEaFile(FileHandle, &iosb, ffei, EA_BUF_LEN);

  return NT_SUCCESS(ntst);
}

Чтение расширенного атрибута

Для чтения расширенного атрибута следует получить EA-буфер полностью и найти в буфере ту структуру, которая содержит атрибут с искомым именем (EaName). Буфер читается с помощью функции NtQueryEaFile. Если параметр ReturnSingleEntry установлен в TRUE, функция с каждым вызовом возвращает только одну структуру, если FALSE — в буфер пишутся сразу все структуры, а переход между ними осуществляется через поле NextEntryOffset.

Данные читаются из буфера по смещению EaName + EaNameLength + 1. Параметр RestartScan перезапускает выдачу атрибутов сначала (если используется ReturnSingleEntry). Параметр EaList может содержать указатель на дополнительный список структур FILE_GET_EA_INFORMATION, содержащий список EA, которые нужно получить. Тем самым можно получить в буфере не все EA, а какой-то определённый набор. Параметр EaIndex позволяет обращаться к атрибуту по индексу.

В результате ошибки попытки чтения расширенных атрибутов, функция NtQueryEaFile может вернуть следующие коды ошибок:

// Доступ запрещён
#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022)
// Файл не содержит расширенных атрибутов
#define STATUS_NO_EAS_ON_FILE ((NTSTATUS)0xC0000052)  
// Буфер слишком мал
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023)  
// Переполнение буфера
#define STATUS_BUFFER_OVERFLOW ((NTSTATUS)0x80000005)

Код функции для чтения расширенных атрибутов:

BOOL ReadEA(LPCSTR file_name, LPCSTR ascii_caps_name, 
            OUT PVOID buf, OUT PUINT buf_len)
{
  IO_STATUS_BLOCK iosb;
  CHAR ea_buf[EA_BUF_LEN];
  PFILE_FULL_EA_INFORMATION ffei 
    = (PFILE_FULL_EA_INFORMATION) ea_buf;
  HANDLE FileHandle;
  NTSTATUS ntst;

  ZeroMemory(&ea_buf, EA_BUF_LEN);
  FileHandle = CreateFile(file_name, 
    GENERIC_READ | GENERIC_WRITE,
    0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

  if (INVALID_HANDLE_VALUE == FileHandle) 
  {
    return FALSE;
  }

  while (TRUE)
  {
    ntst =  NtQueryEaFile(FileHandle, &iosb, ffei,
      EA_BUF_LEN, TRUE, NULL, NULL, NULL, FALSE);    
    if (!NT_SUCCESS(ntst))
    {
      break;
    }
    if (0 == _strnicmp(ffei->EaName, ascii_caps_name, 
                      MAX_EA_NAME_LEN))
    {
      memcpy(buf, ffei->EaName + ffei->EaNameLength + 1, 
                  ffei->EaValueLength);
      *buf_len = ffei->EaValueLength;
      return TRUE;
    }
  }
  
  return FALSE;
}

Удаление расширенного атрибута

Удаление расширенного атрибута из файла реализуется через вызов NtSetEaFile, со структурой, в которой поле EaValueLength равно NULL, а EaName содержит только имя удаляемого атрибута.

// Удалить атрибут attribute из файла filename
WriteEA("filename", "attribute", NULL, 0, 0);

Исходный код

Скачать консольную программу EA.EXE для работы с расширенными атрибутами. В архиве содержится программа и её исходный код на Си.

Кроме того, моя программа NTFS Stream Explorer, которая имеет графический интерфейс, теперь тоже поддерживает редактирование расширенных атрибутов файлов.



Автор: амдф
Дата: 10.02.2011


При копировании материалов хорошим тоном будет указание авторства и ссылка на сайт.