Ответ 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.