Figure 1   Standard File or Directory Attributes

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.