Эта статья посвящена исследованию максимальных длин имен объектов,
поддерживаемых API Windows и проблем, которые могут вызвать длинные
имена объектов.
Создание каталогов и файлов возможно с помощью документированной в Platform SDK
функции CreateFile:
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
В описании функции в MSDN есть замечание про первый параметр:
ANSI-версия функции (т.е.
CreateFileA) поддерживает максимальную
длину имени объекта равную
MAX_PATH (260).
UNICODE-версия функции (т.е.
CreateFileW) поддерживает максимальную
длину имени объекта 32 767 символов и функцию следует вызывать с префиксом
в пути файла следующий строки: "\\?\", т.е. например для создания или открытия
файла C:\WINDOWS\system32\kernel32.dll имя следует указать как UNICODE-строку:
"\\?\C:\WINDOWS\system32\kernel32.dll".
В замечаниях также указано, что собственное имя объекта (без полного пути к нему)
может составлять до 255 символов.
Чтобы выяснить максимальную длину собственного имени файла можно попробовать
создать его на разных типах файловых систем с помощью следующей функции:
#define CREATELP_I_USINGLONG_PREFIX L"\\\\?\\"
DWORD
CreatelpTestLongFile(
IN WCHAR *szDir,
IN BOOL bDelete
)
/*++
Описание:
Функция создает файл с максимально возможным длинным именем
в указанном каталоге
Аргументы:
szDir - каталог для создания файла. Наличие '\' в конце пути
не имеет значения для функции (обрабатываются любые пути)
bDelete - флаг, указывающий что следует удалить файл при выходе
из функции
Возвращаемое значение:
Собственная длина в символах имени файла.
0 - если создать файл не удалось
--*/
{
WCHAR szFileName[500 + 1];
INT i;
WCHAR *szFullName;
INT iFileLen;
BOOL bBackSlashNeed = FALSE;
if ( szDir[wcslen(szDir) - 1 ] != L'\\' )
bBackSlashNeed = TRUE;
for( i = 0; i < 500; i++ )
szFileName[i] = L'a';
for( iFileLen = 500; iFileLen >= 1; iFileLen-- )
{
HANDLE hFile;
BOOL bRet = FALSE;
DWORD dwAdditionalFlag = 0;
if ( bDelete )
dwAdditionalFlag |= FILE_FLAG_DELETE_ON_CLOSE;
szFullName = (WCHAR*) LocalAlloc( LMEM_ZEROINIT,
(wcslen( CREATELP_I_USINGLONG_PREFIX ) +
wcslen( szDir ) + bBackSlashNeed +
iFileLen + 1) * sizeof(WCHAR) );
wcscpy( szFullName, CREATELP_I_USINGLONG_PREFIX );
wcscat( szFullName, szDir );
if ( bBackSlashNeed )
wcscat( szFullName, L"\\" );
wcsncat( szFullName, szFileName, iFileLen );
hFile = CreateFileW( szFullName, GENERIC_ALL, 0, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | dwAdditionalFlag, NULL );
if ( hFile != INVALID_HANDLE_VALUE )
{
CloseHandle( hFile );
bRet = TRUE;
}
LocalFree( szFullName );
if ( bRet )
return ((DWORD) iFileLen );
}
return 0;
}
Действительно, на Windows XP Professional SP2, собственная длина имени файла в
любом каталоге диска составляет
255 символов (и на FAT32, и на NTFS).
Интересен тот факт, что если в параметре bDelete в функции передавать FALSE
(т.е. не удалять файл при завершении функции), то могут возникнуть небольшие
трудности с удалением файла, если он размещается внутри каталога на диске, а не
в корневом каталоге диска (например, Проводник Windows не реагирует на команду
удаления файла, а файловый менеджер FAR выводит следующее сообщение:
"Буфер, размещенный под имя файла слишком мал.
Требуется 267 байт, а имеется только 261".
Теперь, когда размер собственного имени объекта выяснен, можно провести эксперимент
по созданию длинной структуры каталогов, т.е. создать такой файл, длина полного пути
к которому гораздо больше, чем
MAX_PATH.
Это можно сделать используя, например, следующую функцию:
BOOL
CreatelpTestLongPath(
IN WCHAR *szDir,
IN WCHAR *szDirSelfName,
IN INT iCreateSubdirs,
IN WCHAR *szFileSelfName,
IN BOOL bDelete
)
/*++
Описание:
Функция создает в каталоге szDir глубокую структуру вложенных друг в друга
каталогов с именами szDirSelfName (количество уровней вложенности равно
iCreateSubdirs). В последнем каталоге создается файл с именем szFileSelfName.
В результате выполнения функции будет создан файл, полный путь к которому:
szDir \ szDirSelfName \ szDirSelfName \ ... \ szDirSelfName \ szFileSelfName
Аргументы:
szDir - каталог для создания структуры. Наличие '\' в конце пути
не имеет значения для функции (обрабатываются любые пути)
szDirSelfName - собственное имя каталога для создания длинной структуры
вложенности
iCreateSubdirs - количество вложенных друг в друга каталогов с именами
szDirSelfName
szFileSelfName - собственное имя файла, который следует создать на самом
глубоком уровне вложенности
bDelete - флаг, указывающий, что следует удалить созданную структуру
Возвращаемое значение:
BOOL
--*/
{
WCHAR *szFullName;
HANDLE hObject;
BOOL bBackSlashNeed = FALSE;
DWORD dwAdditionalFlag = 0;
if ( bDelete )
dwAdditionalFlag |= FILE_FLAG_DELETE_ON_CLOSE;
if ( szDir[wcslen(szDir) - 1 ] != L'\\' )
bBackSlashNeed = TRUE;
if ( iCreateSubdirs == 0 )
{
szFullName = (WCHAR*) LocalAlloc( LMEM_ZEROINIT,
(wcslen(CREATELP_I_USINGLONG_PREFIX) +
wcslen(szDir) + bBackSlashNeed + wcslen(szFileSelfName) + 1) * sizeof(WCHAR) );
wcscpy( szFullName, CREATELP_I_USINGLONG_PREFIX );
wcscat( szFullName, szDir );
if ( bBackSlashNeed )
wcscat( szFullName, L"\\" );
wcscat( szFullName, szFileSelfName );
hObject = CreateFileW( szFullName, GENERIC_ALL, 0, NULL, OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL | dwAdditionalFlag, NULL );
if ( hObject != INVALID_HANDLE_VALUE )
{
LocalFree( szFullName );
CloseHandle( hObject );
return TRUE;
}
else
{
LocalFree( szFullName );
_tprintf( _T("-Error creation file. PathLength = %d\n"), wcslen( szFullName ) );
return FALSE;
}
}
else
{
BOOL bRet;
szFullName = (WCHAR*) LocalAlloc( LMEM_ZEROINIT,
(wcslen(CREATELP_I_USINGLONG_PREFIX) +
wcslen(szDir) + bBackSlashNeed + wcslen(szDirSelfName) + 1) * sizeof(WCHAR) );
wcscpy( szFullName, CREATELP_I_USINGLONG_PREFIX );
wcscat( szFullName, szDir );
if ( bBackSlashNeed )
wcscat( szFullName, L"\\" );
wcscat( szFullName, szDirSelfName );
if ( !CreateDirectoryW( szFullName, NULL ) )
{
DWORD dwErr = GetLastError();
if ( dwErr != ERROR_ALREADY_EXISTS )
{
_tprintf( _T("-Error creation dir. PathLength = %d\n"), wcslen( szFullName ) );
LocalFree( szFullName );
return FALSE;
}
}
bRet = CreatelpTestLongPath( szFullName + wcslen(CREATELP_I_USINGLONG_PREFIX),
szDirSelfName, iCreateSubdirs - 1, szFileSelfName, bDelete );
if ( bDelete )
RemoveDirectoryW( szFullName );
LocalFree( szFullName );
return bRet;
}
}
С помощью функции можно выяснить, например, максимальную длину строки с именем
объекта, который можно указывать в качестве входного параметра в функции. Для
этого ее можно вызывать следующим образом:
CreatelpTestLongPath( L"D:\\",
L"d", 17000,
L"f", TRUE );
Функция будет создавать вложенные каталоги, пока CreateDirectoryW не вернет
ошибку. При этом функция распечатает в консоль длину пути к объекту, который
не удалось создать. Этим числом является 15424 (а не 32767, как указывает
разработчик ОС фирма Microsoft в MSDN). С учетом того, что в строке указанной
длины записаны следующие данные:
\\?\D:\d\d\d\d\....\d
то можно вычислить максимальное число вложенных каталогов, поддерживаемых ОС:
(15424 - (4 + 2 + 2)) / 2
где:
4 - длина "\\?\"
2 - длина "D:"
2 - длина "\d" - каталога, который не удалось создать
деление на 2 требуется, т.к. каталог записан с backslash в начале - 1 символ, а
1 символ входит в имя каталога
таким образом, максимальный уровень вложенности, поддерживаемый Windows -
7708 каталогов.
Примечательно, что полученное значение распространяется не только на файловую
систему NTFS, но и FAT32.
Даже если создать с помощью функции не очень глубокую структуру, например в 500
каталогов и не удалять ее после завершения функции следующим вызовом:
CreatelpTestLongPath( L"D:\\",
L"d", 500,
L"f", FALSE );
могут возникнуть проблемы с просмотром и удалением созданной структуры с помощью
файловых менеджеров. Например, при попытке зайти внутрь структуры каталогов, уже
на 128 уровне вложенности Проводник Windows сообщит об ошибке:
"Папка недоступа. Указан слишком длинный путь". При попытке удаления структуры,
Проводник сообщает о следующей ошибке: "Не удается удалить папку. Указано
неправильное или слишком длинное имя. Задайте другое имя". При попытке удалить
структуру с помощью файлового менеджера FAR, программа сообщит об ошибке:
"Имя файла или его расширение имеет слишком большую длину.
Ошибка удаления папки".
Наличие в файловой системе таких длинных вложенных структур может не позволить
нормально функционировать утилитам, выполняющим сканирование диска,
таким, как антивирусы, файловые менеджеры, анализаторы настроек безопасности и
др.
Проблема обоснована тем, что разработчики ПО не учитывают указанных особенностей,
касающихся длин имен объектов. Учитывайте, что имена файлов и пути к ним могут
быть гораздо больше
MAX_PATH.
Для уничтожения структур каталогов и файлов, которые нельзя удалить используя
Проводник Windows и файловые менеджера, предлагается воспользоваться утилитой
XDEL,
разработанной мной при написании этой статьи.
Полученные значения:
Максимальная длина собственного имени файла (FAT32, NTFS) =
255 символов
Максимальная длина имени объекта, включая родительские каталоги и разделители backslash (FAT32, NTFS) =
15422 символов
Максимальный уровень вложенности (FAT32, NTFS) =
7708 каталогов
Исходники:
Тестирование максимальной собственной длины имен файлов и полных путей:
createlong.c
Утилиты:
Удаление файлов с длинными именами и каталогов, содержащих глубоко вложенные
подкаталоги:
xdel
Ссылки:
CreateFile
Naming a File