Attribute | Description |
Standard Information | What you're used to thinking of as a file's attributes. These are flags such as read-only, hidden, and system. It also includes the creation, accessed, and modified time stamps. A file's hard link count is also maintained here. |
Name | The name of the file/directory in Unicode. A file can have multiple name attributes if it also has a short (8.3) name or if it has hard links. |
Security Descriptor | The security data structure associated with the file that governs a user's access to the file. |
Data | This is the contents of the file. More specifically, this attribute identifies the data in this file's unnamed stream. NTFS treats a file's data simply as another attribute of the file itself, which allows a file to have multiple streams. Note that directories do not have an associated Data attribute. |
Named Data | This optional attribute identifies an additional named data stream associated with the file. Note that a single file or a directory can have 0 or more Named Data attributes. |
Index Root, Index Allocation, and Bitmap | These three attributes are used to implement file name indexes for large directories. These attributes are only associated with directories, not files. |
Reparse | The data stored in this attribute identifies a file system filter that executes when this file or directory is accessed. |
Figure 2 FileStreams.cpp
/******************************************************************************
Module name: FileStreams.cpp
Written by: Jeffrey Richter
Notices: 1998 Jeffrey Richter
******************************************************************************/
#define STRICT
#include <windows.h>
///////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain (HINSTANCE hinstExe, HINSTANCE hinstPrev,
LPSTR pszCmdLine, int nCmdShow) {
LPCTSTR pszFile = __TEXT("D:\\StreamTest.tst");
LPCTSTR pszFirstStream = __TEXT("D:\\StreamTest.tst:FirstStream");
LPCTSTR pszCopyStream = __TEXT("D:\\StreamTest.tst:CopyStream");
LPCTSTR pszRenameStream = __TEXT("D:\\StreamTest.tst:RenameStream");
LPCTSTR pszMoveStream = __TEXT("D:\\StreamTest.tst:MoveStream");
char szDataToWrite[] = "This is some data";
char szDataToRead[100] = { 0 };
HANDLE hfile;
DWORD cb;
// NOTE: In a real application, you do not have to open and
// close each stream's handle repeatedly as I've done below.
// Create a file with no data in its unnamed stream and no named streams
hfile = CreateFile(pszFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
CloseHandle(hfile);
// TEST: DIR (file should exist)
// Add a named stream to the file
// (NOTE: Step above does NOT have to execute first)
hfile = CreateFile(pszFirstStream, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
CloseHandle(hfile);
// TEST: MORE < C:\StreamTest.txt (nothing should be displayed)
// TEST: MORE < C:\StreamTest.txt:FirstStream (nothing should be displayed)
// Put some data in the named stream
hfile = CreateFile(pszFirstStream, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN, NULL);
WriteFile(hfile, (PVOID) szDataToWrite, strlen(szDataToWrite), &cb, NULL);
CloseHandle(hfile);
// TEST: MORE < C:\StreamTest.txt (nothing should be displayed)
// TEST: MORE < C:\StreamTest.txt:FirstStream (text should be displayed)
// Get the size of the named stream
hfile = CreateFile(pszFirstStream, 0, 0, NULL, OPEN_EXISTING, 0, NULL);
DWORD dwSize = GetFileSize(hfile, NULL);
CloseHandle(hfile);
// TEST: dwSize should be the correct number of bytes
// Read the contents of the named stream
hfile = CreateFile(pszFirstStream, GENERIC_READ, 0, NULL, OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN, NULL);
ReadFile(hfile, (PVOID) szDataToRead, sizeof(szDataToRead), &cb, NULL);
CloseHandle(hfile);
// TEST: szDataToRead should contain "This is some data"
// Make a copy of the named stream to another named stream
CopyFile(pszFirstStream, pszCopyStream, FALSE);
// TEST: MORE < C:\StreamTest.txt:CopyStream (text should be displayed)
// NOTE: CopyFile doesn't always behave as expected; see below
// 1st param 2nd param Result
// ----------- ----------- --------------------------------------------
// UnnamedStrm UnnamedStrm Complete file copy with all streams
// UnnamedStrm NamedStrm UnnamedStrm copied to NamedStrm
// NamedStrm UnnamedStrm File deleted; NamedStrm copied to UnnamedStrm
// NamedStrm NamedStrm NamedStrm copied to NamedStrm
// Delete all the data in a stream
hfile = CreateFile(pszCopyStream, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0,
NULL);
SetFilePointer(hfile, 0, NULL, FILE_BEGIN);
SetEndOfFile(hfile);
CloseHandle(hfile);
// TEST: MORE < C:\StreamTest.txt:CopyStream (nothing displayed)
// Delete the first named stream
DeleteFile(pszFirstStream);
// TEST: MORE < C:\StreamTest.txt:FirstStream (error should occur)
// TEST: DIR (file should exist)
// Delete the contents of the unnamed stream
hfile = CreateFile(pszFile, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
SetFilePointer(hfile, 0, NULL, FILE_BEGIN);
SetEndOfFile(hfile);
CloseHandle(hfile);
// TEST: MORE < C:\StreamTest.txt (nothing should display)
// TEST: DIR (file should exist)
// Delete the file and all of its streams
DeleteFile(pszFile);
// TEST: MORE < C:\StreamTest.txt (error should occur)
// TEST: DIR (file should NOT exist)
// Unfortunately, the Win32 function MoveFile does not support the
// moving/renaming of streams. This function only works on complete files.
// There is no documented way to move/rename a stream.
// The Win32 Backup functions can be used to enumerate the streams within a
// file. But they are very hard to work with and their performance is poor
// because the function also reads the stream's data.
return(0);
}
Figure 3 FileLink.cpp
/************************************************************
Module name: FileLink.cpp
Written by: Jeffrey Richter
Notices 1998 Jeffrey Richter
************************************************************/
#define STRICT
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <tchar.h>
/////////////////////////////////////////////////////////////
int WINAPI WinMain (HINSTANCE, HINSTANCE, LPSTR, int) {
if (__argc != 3) {
TCHAR sz[200];
wsprintf(sz,
__TEXT("FileLink creates a hard link to an existing file.\n")
__TEXT("Usage: %s (ExistingFile) (NewFileName)"),\
__targv[0]);
MessageBox(NULL, sz,
__TEXT("FileLink by Jeffrey Richter"),
MB_ICONINFORMATION | MB_OK);
return(0);
}
if (!CreateHardLink(__targv[2], __targv[1], NULL)) {
MessageBox(NULL, __TEXT("The FileLink couldn't be created.\n"),
__TEXT("FileLink by Jeffrey Richter"),
MB_ICONINFORMATION | MB_OK);
}
return(0);
}
Figure 4 Compression Example
StreamOffset | NumberOfClusters | Compressed | Reason |
0 | 10 | Yes | 10 clusters is less than a compression unit. |
32768 | 16 | No | Equal to a compression unit. |
65536 | 12 | Yes | 12 clusters is less than a compression unit. |
98304 | 12 | No | Although 12 clusters is less than a compression unit, this is the end of the stream. |
Figure 5 SparseFile.cpp
/******************************************************************************
Module name: SparseFile.cpp
Written by: Jeffrey Richter
Notices: 1998 Jeffrey Richter
******************************************************************************/
#define STRICT
#define _WIN32_WINNT 0x0500
#include <Windows.h>
#include <WinIoCtl.h>
///////////////////////////////////////////////////////////////////////////////
class CSparseStream {
public:
static BOOL DoesFileSystemSupportSparseStreams(LPCTSTR pszVolume);
static BOOL DoesFileContainAnySparseStreams(LPCTSTR pszPathname);
public:
CSparseStream(HANDLE hstream) { m_hstream = hstream; m_nReadOffset = 0; }
~CSparseStream() { }
public:
operator HANDLE() const { return(m_hstream); }
public:
BOOL IsStreamSparse() const;
BOOL MakeSparse();
BOOL DecommitPortionOfStream(__int64 qwFileOffsetStart,
__int64 qwFileOffsetEnd);
FILE_ALLOCATED_RANGE_BUFFER* QueryAllocatedRanges(PDWORD pdwNumEntries);
BOOL FreeAllocatedRanges(FILE_ALLOCATED_RANGE_BUFFER* pfarb);
BOOL AppendQueueEntry(PVOID pvEntry, DWORD cbEntry);
PVOID ExtractQueueEntry(PDWORD pcbEntry = NULL);
BOOL FreeExtractedQueueEntry(PVOID pvEntry);
private:
HANDLE m_hstream;
__int64 m_nReadOffset;
private:
static BOOL AreFlagsSet(DWORD fdwFlagBits, DWORD fFlagsToCheck) {
return((fdwFlagBits & fFlagsToCheck) == fFlagsToCheck);
}
};
///////////////////////////////////////////////////////////////////////////////
BOOL CSparseStream::DoesFileSystemSupportSparseStreams(LPCTSTR pszVolume) {
DWORD dwFileSystemFlags = 0;
BOOL fOk = GetVolumeInformation(pszVolume, NULL, 0, NULL, NULL,
&dwFileSystemFlags, NULL, 0);
fOk = fOk && AreFlagsSet(dwFileSystemFlags, FILE_SUPPORTS_SPARSE_FILES);
return(fOk);
}
///////////////////////////////////////////////////////////////////////////////
BOOL CSparseStream::IsStreamSparse() const {
BY_HANDLE_FILE_INFORMATION bhfi;
GetFileInformationByHandle(m_hstream, &bhfi);
return(AreFlagsSet(bhfi.dwFileAttributes, FILE_ATTRIBUTE_SPARSE_FILE));
}
///////////////////////////////////////////////////////////////////////////////
BOOL CSparseStream::MakeSparse() {
DWORD dw;
return(DeviceIoControl(m_hstream, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dw,
NULL));
}
///////////////////////////////////////////////////////////////////////////////
BOOL CSparseStream::AppendQueueEntry(PVOID pvEntry, DWORD cbEntry) {
// Always write new entries to the end of the queue
SetFilePointer(m_hstream, 0, NULL, FILE_END);
DWORD cb;
// Write the size of the entry
BOOL fOk = WriteFile(m_hstream, &cbEntry, sizeof(cbEntry), &cb, NULL);
// Write the entry itself
fOk = fOk && WriteFile(m_hstream, pvEntry, cbEntry, &cb, NULL);
return(fOk);
}
///////////////////////////////////////////////////////////////////////////////
PVOID CSparseStream::ExtractQueueEntry(PDWORD pcbEntry) {
DWORD cbEntry, cb;
PVOID pvEntry = NULL;
LARGE_INTEGER liOffset;
liOffset.QuadPart = m_nReadOffset;
// Position to the next place to read from
SetFilePointer(m_hstream, liOffset.LowPart,
&liOffset.HighPart, FILE_BEGIN);
if (pcbEntry == NULL) pcbEntry = &cbEntry;
// Read the size of the entry
BOOL fOk = ReadFile(m_hstream, pcbEntry, sizeof(*pcbEntry), &cb, NULL);
// Allocate memory for the queue entry
fOk = fOk && ((pvEntry = HeapAlloc(GetProcessHeap(), 0, *pcbEntry)) != NULL);
// Read the queue entry into the allocated memory
fOk = fOk && ReadFile(m_hstream, pvEntry, *pcbEntry, &cb, NULL);
if (fOk) {
m_nReadOffset += sizeof(*pcbEntry) + *pcbEntry;
// Decommit the storage occupied the extracted queue entries
fOk = fOk && DecommitPortionOfStream(0, m_nReadOffset);
}
return(pvEntry); // Return the queue entry's allocated memory
}
///////////////////////////////////////////////////////////////////////////////
BOOL CSparseStream::FreeExtractedQueueEntry(PVOID pvEntry) {
// Free the queue entry's allocated memory
return(HeapFree(GetProcessHeap(), 0, pvEntry));
}
///////////////////////////////////////////////////////////////////////////////
BOOL CSparseStream::DecommitPortionOfStream(__int64 qwFileOffsetStart,
__int64 qwFileOffsetEnd) {
DWORD dw;
FILE_ZERO_DATA_INFORMATION fzdi;
fzdi.FileOffset.QuadPart = qwFileOffsetStart;
fzdi.BeyondFinalZero.QuadPart = qwFileOffsetEnd;
return(DeviceIoControl(m_hstream, FSCTL_SET_ZERO_DATA, (LPVOID) &fzdi,
sizeof(fzdi), NULL, 0, &dw, NULL));
}
///////////////////////////////////////////////////////////////////////////////
BOOL CSparseStream::DoesFileContainAnySparseStreams(LPCTSTR pszPathname) {
DWORD dw = GetFileAttributes(pszPathname);
return((dw == 0xfffffff) ? FALSE : AreFlagsSet(
dw, FILE_ATTRIBUTE_SPARSE_FILE));
}
///////////////////////////////////////////////////////////////////////////////
FILE_ALLOCATED_RANGE_BUFFER* CSparseStream::QueryAllocatedRanges(
PDWORD pdwNumEntries) {
FILE_ALLOCATED_RANGE_BUFFER farb;
farb.FileOffset.QuadPart = 0;
farb.Length.LowPart = GetFileSize(m_hstream, (PDWORD) &farb.Length.HighPart);
// There is no way to determine the correct memory
// block size prior to attempting to collect this data,
// so I just picked 1000 * sizeof(*pfarb)
DWORD cb = 100 * sizeof(FILE_ALLOCATED_RANGE_BUFFER);
FILE_ALLOCATED_RANGE_BUFFER* pfarb = (FILE_ALLOCATED_RANGE_BUFFER*)
HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cb);
BOOL fOk = DeviceIoControl(m_hstream, FSCTL_QUERY_ALLOCATED_RANGES,
&farb, sizeof(farb), pfarb, cb, &cb, NULL);
GetLastError();
*pdwNumEntries = cb / sizeof(*pfarb);
return(pfarb);
}
///////////////////////////////////////////////////////////////////////////////
BOOL CSparseStream::FreeAllocatedRanges(FILE_ALLOCATED_RANGE_BUFFER* pfarb) {
// Free the queue entry's allocated memory
return(HeapFree(GetProcessHeap(), 0, pfarb));
}
///////////////////////////////////////////////////////////////////////////////
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int){
TCHAR szPathName[] = __TEXT("D:\\SparseFile");
if (!CSparseStream::DoesFileSystemSupportSparseStreams("D:\\")) {
// run "ChkNtfs /e"
MessageBox(NULL, "File system doesn't support Sparse Files", NULL,
MB_OK);
return(0);
}
HANDLE hstream = CreateFile(szPathName, GENERIC_READ | GENERIC_WRITE,
0, NULL, CREATE_ALWAYS, 0, NULL);
CSparseStream ss(hstream);
BOOL f = ss.MakeSparse();
f = ss.IsStreamSparse();
DWORD dwNumEntries, cb;
SetFilePointer(ss, 50 * 1024 * 1024, NULL, FILE_BEGIN);
WriteFile(ss, "A", 1, &cb, NULL);
cb = GetFileSize(ss, NULL);
cb = GetCompressedFileSize(szPathName, NULL);
FILE_ALLOCATED_RANGE_BUFFER* pfarb = ss.QueryAllocatedRanges(&dwNumEntries);
ss.FreeAllocatedRanges(pfarb);
ss.DecommitPortionOfStream(0, 60 * 1024 * 1024);
pfarb = ss.QueryAllocatedRanges(&dwNumEntries);
ss.FreeAllocatedRanges(pfarb);
cb = GetFileSize(ss, NULL);
cb = GetCompressedFileSize(szPathName, NULL);
SetFilePointer(ss, 0, NULL, FILE_BEGIN);
SetEndOfFile(ss);
// Put a bunch of entries in the end of the queue
BYTE bEntry[32 * 1024 - 4]; // 100KB
for (int x = 0; x < 7; x++) ss.AppendQueueEntry(bEntry, sizeof(bEntry));
pfarb = ss.QueryAllocatedRanges(&dwNumEntries);
ss.FreeAllocatedRanges(pfarb);
// Read a bunch of entries from the beginning of the queue
for (x = 0; x < 7; x++) {
PVOID pvEntry = ss.ExtractQueueEntry(&cb);
ss.FreeExtractedQueueEntry(pvEntry);
cb = GetFileSize(ss, NULL);
cb = GetCompressedFileSize(szPathName, NULL);
pfarb = ss.QueryAllocatedRanges(&dwNumEntries);
ss.FreeAllocatedRanges(pfarb);
}
CloseHandle(hstream);
DeleteFile(szPathName);
return(0);
}
Figure 6 Sparse Stream Example
StreamOffset | NumberOfClusters | Notes |
0 | 10 | This unit is compressed because 10 clusters are less than a compression unit. This compression unit is not sparse because there is at least 1 cluster in use. |
32768 | 0 | This compression unit is sparse because there are no clusters allocated for this unit. |
65536 | 12 | This unit is compressed because 12 clusters are less than a compression unit. |
98304 | 12 | This compression unit is not compressed and is not sparse because this is the end of the stream (even though 12 clusters are less than a compression unit and all the bytes are zeroes). |
Figure 9 Disk Quota COM Interfaces
Interface | Description |
IDiskQuotaControl | Provides methods for controlling how NTFS tracks quotas and logs quota events. Also allows you to add and remove user SIDs to the quota database. |
IDiskQuotaEvents | You must implement this interface if you want to receive quota-related event notifications. |
IDiskQuotaUser | Allows the modification of a specific user's quota information. |
IDiskQuotaUserBatch | Allows multiple IDiskQuotaUser objects to be added to the quota database in a single call, thus improving performance. |
IEnumDiskQuotaUsers | Enumerates user quota entries on a volume. |