Skip to main content
The FileNode class represents individual files and directories in WinFsp-MemFs-Extended. Each node contains file metadata, security information, and a reference to the file’s data sectors.

FileNode structure

A FileNode combines several pieces of information:
class FileNode {
public:
    std::wstring fileName;          // Full file path
    FSP_FSCTL_FILE_INFO fileInfo;   // WinFsp metadata
    
    DynamicStruct<SECURITY_DESCRIPTOR> fileSecurity;
    DynamicStruct<byte> reparseData;
    
private:
    SectorNode sectors;             // File data
    volatile long refCount{0};      // Reference counter
    FileNode* mainFileNode{};       // For named streams
    std::optional<FileNodeEaMap> eaMap; // Extended attributes
};
See nodes.h:12-54 for the full class definition.

File metadata

The fileInfo member is an FSP_FSCTL_FILE_INFO structure that contains:
  • File attributes: Directory, hidden, system, readonly, etc.
  • Timestamps: Creation, access, write, and change times
  • File sizes: Allocation size and actual file size
  • Index number: Unique identifier for the file
  • EA size: Total size of extended attributes
When you create a new FileNode, all timestamps are initialized to the current system time and a unique index number is assigned:
FileNode::FileNode(const std::wstring& fileName) : fileName(fileName) {
    const uint64_t now = Utils::GetSystemTime();
    this->fileInfo.CreationTime =
        this->fileInfo.LastAccessTime =
        this->fileInfo.LastWriteTime =
        this->fileInfo.ChangeTime = now;
    
    this->fileInfo.IndexNumber = IndexNumber++;
}
See nodes.cpp:13-23 for the initialization logic.

Reference counting

FileNodes use reference counting for lifecycle management. When the reference count reaches zero, the node deletes itself automatically.

Reference operations

You manage references through three methods:

Reference()

Increments the reference count when opening or referencing a file:
void FileNode::Reference() {
    InterlockedIncrement(&this->refCount);
}

Dereference()

Decrements the reference count and self-deletes if it reaches zero:
void FileNode::Dereference(const bool toZero) {
    volatile long newRefCount = InterlockedDecrement(&this->refCount);
    
    if (newRefCount == 0ULL) {
        delete this; // Automatic cleanup
    }
}

GetReferenceCount()

Reads the current reference count (thread-safe by default):
long FileNode::GetReferenceCount(const bool withInterlock = true);
See nodes.cpp:31-69 for the reference counting implementation.

Reference lifecycle

A typical file’s reference count changes as follows:
  1. Creation: Reference count is 0
  2. Insert into FileNodeMap: Reference count becomes 1 (map holds a reference)
  3. Each file open: Reference count increases by 1
  4. Each file close: Reference count decreases by 1
  5. Remove from FileNodeMap: Reference count decreases by 1
  6. Deletion: When count reaches 0, the node self-deletes
This ensures that:
  • Files remain in memory while open or in the file map
  • Nodes are automatically cleaned up when no longer needed
  • No manual memory management is required

FileNodeMap integration

The FileNodeMap is a case-aware map that stores all nodes:
using FileNodeMap = std::map<std::wstring, FileNode*, Utils::FileLess>;
The custom comparator FileLess respects the file system’s case sensitivity setting:
  • Case-insensitive mode: “File.txt” and “file.txt” are the same
  • Case-sensitive mode: “File.txt” and “file.txt” are different
You can look up files by path using MemFs::FindFile():
std::refoptional<FileNode> MemFs::FindFile(const std::wstring_view& fileName);
See memfs.h:25 and memfs.h:55 for the map definition and lookup method.

Named streams

NTFS supports alternate data streams (named streams) on files. WinFsp-MemFs-Extended represents these as separate FileNodes that reference a main node:
FileNode* mainFileNode{}; // nullptr for main nodes

Main node vs. stream node

You can check if a node is a main file or a named stream:
bool FileNode::IsMainNode() const {
    return this->mainFileNode == nullptr;
}

Stream node relationships

Named streams:
  • Store their own fileName (e.g., “file.txt:stream”)
  • Have their own SectorNode for data storage
  • Reference the main node’s metadata for attributes
  • Share extended attributes with the main node
When you query file information from a stream:
void FileNode::CopyFileInfo(FSP_FSCTL_FILE_INFO* fileInfoDest) const {
    if (this->IsMainNode()) {
        *fileInfoDest = this->fileInfo;
    } else {
        // Copy main node's info but use stream's size
        *fileInfoDest = mainFileNode->fileInfo;
        fileInfoDest->AllocationSize = this->fileInfo.AllocationSize;
        fileInfoDest->FileSize = this->fileInfo.FileSize;
    }
}
See nodes.cpp:71-83 for the metadata copying logic.

Extended attributes

Extended attributes (EA) are name-value pairs associated with files. They’re stored in an optional map:
std::optional<FileNodeEaMap> eaMap;
The map type is:
using FileNodeEaMap = std::map<std::string, 
                                DynamicStruct<FILE_FULL_EA_INFORMATION>, 
                                Utils::EaLess>;

Lazy initialization

The EA map is only created when needed:
FileNodeEaMap& FileNode::GetEaMap() {
    if (!this->eaMap.has_value()) {
        this->eaMap = FileNodeEaMap();
    }
    return this->eaMap.value();
}
This saves memory for files that don’t use extended attributes (the common case). See nodes.h:10 and nodes.cpp:97-107 for the EA map implementation.

Security descriptors

Each FileNode can have a security descriptor that defines access control:
DynamicStruct<SECURITY_DESCRIPTOR> fileSecurity;
The DynamicStruct template provides:
  • Dynamic sizing: Allocates exactly the required space
  • Type safety: Ensures correct structure layout
  • Automatic cleanup: Frees memory on destruction
See nodes.h:17 for the security descriptor member.

Reparse points

Reparse points (symbolic links, junctions, etc.) store their data in:
DynamicStruct<byte> reparseData;
The reparse data buffer contains:
  • Reparse tag (e.g., IO_REPARSE_TAG_SYMLINK)
  • Target path information
  • Additional reparse-specific data
See nodes.h:18 for the reparse data member.

Sector data access

You can access a node’s sector data through:
SectorNode& FileNode::GetSectorNode() {
    return this->sectors;
}
This provides access to the file’s actual data storage. The SectorNode is owned by the FileNode and is automatically freed when the node is destroyed. See nodes.cpp:180-182 for the accessor implementation.

Build docs developers (and LLMs) love