Подтвердить что ты не робот

Что происходит внутри, когда путь к файлу превышает ок. 32767 символов в Windows?

В Windows (предположим, начиная с 2000 года) длина файла может составлять не более 32767 символов. Это ограничение существует из-за внутренней обработки с UNICODE_STRING в собственном API (также со стороны ядра, в драйверах и т.д.). Все идет нормально. Я знаю теорию этой части.

Причиной ограничения является то, что члены Length и MaximumLength UNICODE_STRING подсчитывают количество байтов в Buffer, но представляют собой 16-битные беззнаковые целые числа.

Я также знаю, почему предел является приближением, а не установленным пределом. Это происходит главным образом из-за того, как ваше имя файла (например, \\.\C:\boot.ini) разрешается в его родной форме (например, \??\C:\boot.ini), а затем на то, что префикс имени фактического имени устройства, а затем путь по отношению к этому тому, например \Device\HarddiskVolume2\boot.ini.

Кроме того, из Проводника Windows известный симптом при достижении предела ( "ANSI" ) MAX_PATH заключается в том, чтобы притворяться, что файл или папка не существует в некоторых версиях Windows (возможно, это было исправлено в какой-то момент).

Но что происходит в диспетчере объектов, диспетчере ввода-вывода и уровне драйверов файловой системы, соответственно, когда я вызываю CreateFile() с контуром, который выглядит как \\.\C:\...\filename.ext, и весь путь не превышает предел, но достигает его, в моем обращении к kernel32.dll CreateFile(), а затем расширяется?...

Ни SDK, ни WDK не кажутся особенно болтливыми по этой теме. Или я смотрел в неправильные разделы?

4b9b3361

Ответ 1

Поскольку я ленив, я не писал тестовую программу, но тестировал ее с помощью превосходного Far Manager, который обрабатывает такие вещи, как длинные пути (длиннее MAX_PATH) или специальные имена файлов (con, prn и т.д.) просто отлично.

Я создал строку из 255 символов ( "12345678901234... 012345" ) и начал создавать вложенные каталоги. К счастью, функция Far "Make Directory" принимает строку, разделенную на слэш, чтобы означать "создавать вложенные каталоги", поэтому я смог сделать это всего за несколько шагов, подготовив строку во внутреннем редакторе с помощью некоторой копии и пасты.

Самый длинный путь, который я смог создать, длился 32739, считая от "C: \" (т.е. он не включает "\\? \", добавленный Far). Ошибка, которую я получаю при попытке создать каталог или файл только с одним дополнительным символом, - " Имя файла или расширение слишком длинное.". Если я попытаюсь войти в этот каталог, я получаю ту же ошибку.

EDIT: провел некоторое время в отладчике, и вот что происходит на уровне API Win32:

  • Я пытаюсь создать файл с одним символом выше предела
  • Дальний вызов CreateFileW со строкой "\\?\C:\123 [...] 012345" который длиннее 32744. (не считая нулевого окончания).
  • CreateFileW выполняет некоторые дополнительные проверки, преобразует строку с завершающим нулем в UNICODE_STRING (Length = 65488, MaximumLength = 65490) и подготавливает структуру OBJECT_ATTRIBUTES.
  • CreateFileW затем вызывает NtCreateFile в ntdll.dll, который является всего лишь оберткой вокруг инструкции syscall.
  • NtCreateFile возвращает 0xC0000106 (STATUS_NAME_TOO_LONG).
  • Затем это значение состояния преобразуется (используя RtlNtStatusToDosError) в ошибку Win32 206 (ERROR_FILENAME_EXCED_RANGE).

Я не стал проверять, что происходит в ядре, но я думаю, что я тоже мог бы посмотреть на это.

EDIT2: я запустил WinObj и обнаружил, что в моей системе C: символическая ссылка на \Device\HarddiskVolume1. Длина этой строки 23 символа. Если мы заменим \C: на строку, переданную ей на NtCreateFile, мы получим 32744 - 3 + 23 = 32764. Вместе с завершающим нулем для этого требуется 65530 байт. Все еще не хватает предела (0xFFFF = 65535), поэтому я предполагаю, что добавляется что-то дополнительное, например, имя сеанса или имени пространства имен.

EDIT3: после прохождения ядра:

  • NtCreateFile вызывает IopCreateFile
  • IopCreateFile вызывает ObOpenObjectByName
  • ObOpenObjectByName вызывает ObpLookupObjectName
  • ObpLookupObjectName проверяет ObpDosDevicesShortNamePrefix ("\??\") → успех
  • он пропускает префикс и разбивает оставшуюся часть на "C:" и "\1234..."
  • он разрешает "C:" с вызовом ObpLookupDirectoryEntry
  • он вызывает ObpParseSymbolicLink, передавая ему просматриваемую запись каталога (_OBJECT_SYMBOLIC_LINK с LinkTarget == "\Device\HarddiskVolume1" и DosDeviceDriveIndex == 3), а оставшаяся часть имени.
  • Затем он делает что-то подобное (верно воспроизведенное ReactOS):

    TargetPath = &SymlinkObject->LinkTarget;
    TempLength = TargetPath->Length;
    TotalLength = TempLength + RemainingName->Length;
    if (LengthUsed > 0xFFF0)
        return STATUS_NAME_TOO_LONG;
    

    В нашем случае 46 + 65476 = 65522 (0xfff2), который находится чуть выше предела.

    Итак, тайна решена (надеюсь!).

P.S. все протестировано под Windows 7 x64 SP1.