Последняя редакция: 31.07.07

Длина имен файлов в Windows 2000 / XP / 2003 / VISTA

Эта статья посвящена исследованию максимальных длин имен объектов, поддерживаемых 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




Размещение статьи на других сайтах - только с разрешения автора
Запрещено использование исходного кода с этого сайта в коммерческом ПО с закрытым кодом
Все вопросы по данному материалу можно задать по почте: ntinside [at] yandex.ru

Copyright (C) Fur, 2007
Хостинг от uCoz