FILE_OPEN and AllocationSize Semantics

I am experiencing strange behavior with the semantics of the AllocationSize parameter of NtCreateFile on NTFS. According to the documentation this parameter is used when the file is created, overwritten or superseded. However it seems that the AllocationSize of the file can be changed by a simple FILE_OPEN with FILE_READ_ATTRIBUTES.

Specifically it seems that opening the file resets the AllocationSize to be just greater than the current FileSize (EndOfFile); if the FileSize is 0, then the AllocationSize becomes 0; if the FileSize is 5120, then the AllocationSize becomes 8192.

I include a test that I cobbled together to test this. Apologies for the crudeness of this test:
<<
void create_allocation_dotest(ULONG Flags, PWSTR Prefix)
{
//void *memfs = memfs_start(Flags);

HANDLE DirHandle, FileHandle;
NTSTATUS Result;
BOOLEAN Success;
WCHAR FilePath[MAX_PATH];
WCHAR UnicodePathBuf[MAX_PATH] = L"file2";
UNICODE_STRING UnicodePath;
OBJECT_ATTRIBUTES Obja;
IO_STATUS_BLOCK Iosb;
LARGE_INTEGER AllocationSize;
FILE_STANDARD_INFO StandardInfo;

StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\dir1",
Prefix ? L"" : L"\\?\GLOBALROOT", Prefix ? Prefix : L""/*memfs_volumename(memfs)*/);

Success = CreateDirectoryW(FilePath, 0);
ASSERT(Success);

DirHandle = CreateFileW(FilePath,
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE, 0);
ASSERT(INVALID_HANDLE_VALUE != DirHandle);

AllocationSize.QuadPart = 65536;
UnicodePath.Length = (USHORT)wcslen(UnicodePathBuf) * sizeof(WCHAR);
UnicodePath.MaximumLength = sizeof UnicodePathBuf;
UnicodePath.Buffer = UnicodePathBuf;
InitializeObjectAttributes(&Obja, &UnicodePath, 0, DirHandle, 0);
Result = NtCreateFile(&FileHandle,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,
&AllocationSize, FILE_ATTRIBUTE_NORMAL, 0,
FILE_CREATE, 0, 0, 0);
ASSERT(STATUS_SUCCESS == Result);

Success = GetFileInformationByHandleEx(FileHandle, FileStandardInfo, &StandardInfo, sizeof StandardInfo);
ASSERT(Success);
ASSERT(65536 == StandardInfo.AllocationSize.QuadPart);

CloseHandle(FileHandle);

AllocationSize.QuadPart = 0;
UnicodePath.Length = (USHORT)wcslen(UnicodePathBuf) * sizeof(WCHAR);
UnicodePath.MaximumLength = sizeof UnicodePathBuf;
UnicodePath.Buffer = UnicodePathBuf;
InitializeObjectAttributes(&Obja, &UnicodePath, 0, DirHandle, 0);
Result = NtCreateFile(&FileHandle,
FILE_READ_ATTRIBUTES, &Obja, &Iosb,
&AllocationSize, FILE_ATTRIBUTE_NORMAL, 0,
FILE_OPEN, 0, 0, 0);
ASSERT(STATUS_SUCCESS == Result);

Success = GetFileInformationByHandleEx(FileHandle, FileStandardInfo, &StandardInfo, sizeof StandardInfo);
ASSERT(Success);
ASSERT(0 == StandardInfo.AllocationSize.QuadPart);

CloseHandle(FileHandle);

AllocationSize.QuadPart = 0;
UnicodePath.Length = (USHORT)wcslen(UnicodePathBuf) * sizeof(WCHAR);
UnicodePath.MaximumLength = sizeof UnicodePathBuf;
UnicodePath.Buffer = UnicodePathBuf;
InitializeObjectAttributes(&Obja, &UnicodePath, 0, DirHandle, 0);
Result = NtCreateFile(&FileHandle,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,
&AllocationSize, FILE_ATTRIBUTE_NORMAL, 0,
FILE_OPEN, FILE_DELETE_ON_CLOSE, 0, 0);
ASSERT(STATUS_SUCCESS == Result);

Success = GetFileInformationByHandleEx(FileHandle, FileStandardInfo, &StandardInfo, sizeof StandardInfo);
ASSERT(Success);
ASSERT(0 == StandardInfo.AllocationSize.QuadPart);

CloseHandle(FileHandle);

CloseHandle(DirHandle);

//memfs_stop(memfs);
}

>

I am not certain if this should be considered NTFS specific behavior or whether all FSD’s on Windows are expected to behave like this. I tend to think the former (i.e. that this is NTFS specific behavior) and I am not currently planning to change my FSD to replicate this behavior.

I first discovered this problem when testing my FSD against IFSTEST. Specifically the CloseCleanupDelete.TruncateOnCloseTest fails with the following message on my FSD:
<<
Test :TruncateOnCloseTest
Group :CloseCleanupDelete
Status :C0000014 (IFSTEST_TEST_ALLOCATION_SIZE_ERROR)
LastGeneralValue :00010000
ExpectedGeneralValue :00001400
Description :{Msg# CloseDel!tronc!14} The allocation size queried
from the file using standard information was not
correct. The file was created with 65536 allocation
size specified. The file was then written with 5120
bytes. The handle was closed. The file was then
reopened with a NULL allocation size specified. The
standard information was queried and 65536 was returned
for the current allocation size. The allocation size
should be 5120.

>

The fact that this is checked by IFSTEST makes me wonder whether this is indeed the expected behavior of all file systems that implement allocation size. [I have not tested what happens on FastFat yet.]

As always I am seeking the wisdom of the NTFSD list.

Bill

A quick update on this.

Some further testing shows that the AllocationSize is reset to “envelop” the FileSize at Cleanup time. Thus the documentation is correct (AllocationSize is *not* changed during FILE_OPEN) and my assumptions on how AllocationSize operates on Windows are incorrect (at least in the NTFS case).

My current understanding is this: AllocationSize is a *temporary* attribute of a file. It can be set/changed during IRP_MJ_CREATE and IRP_MJ_SET_INFORMATION, but it remains in effect only while the handle that was used for this operation has not been IRP_MJ_CLEANUP’ed yet. Once the cleanup happens the AllocationSize gets reset to “envelop” the file size: (FileSize + AllocationUnit - 1) / AllocationUnit * AllocationUnit

My previous understanding was that AllocationSize is a permanent attribute of a file, i.e. that once set it remains in effect until changed by IRP_MJ_SET_INFORMATION regardless of handle cleanup/close. In this view one could open the file, set the allocation size, close the file and then reopen it to find the allocation size unchanged. That is not how NTFS operates however.

It is unclear to me whether Windows mandates a particular behavior, especially since AllocationSize is not exposed through Win32.