Haiku BNode Attributes: Typed Metadata Reads and Writes
Read and write Haiku file attributes through BNode with explicit type codes, byte counts, size checks, storage limitations, and index separation.
Haiku’s BNode exposes file-system attributes as named, typed data associated with a filesystem node. Attributes can carry compact metadata alongside a file’s ordinary byte stream, and BFS can index selected attributes for queries. These are related but separate capabilities: writing an attribute does not automatically create an index, and an API method being present does not prove that every mounted file system supports the same attribute behavior.
Attribute code should treat names, type codes, lengths, and byte counts as a durable storage contract. A wrong type can make data difficult for other applications to interpret; a failed short write can leave incomplete metadata; and a stale BNode can point to a different lifecycle than the UI assumes. Correctness comes from checking every status and verifying the destination file system’s support.
A node is not a path string
BNode can be constructed from a path, entry reference, entry, or directory-relative name. Check InitCheck() after construction or SetTo() before using it. A path is a lookup performed in the current namespace; it can change if a file is renamed or its volume goes offline. An entry_ref and node reference express different identity information, but neither means a later read cannot fail.
An attribute belongs to the file-system node, not necessarily to one particular directory name. This matters when a file is renamed or linked. Decide whether the metadata describes the underlying content, a directory entry, or an application relationship. If the distinction matters, store the key in the appropriate place and document how hard links and copies behave. Do not assume an attribute is copied, indexed, or preserved by every file-management operation unless tested on the target file system.
The Storage Kit exposes attributes for file-system entries while ordinary file data remains a byte stream handled by BFile and BPositionIO. Keep large payloads in the file or an application-managed data file; use attributes for compact metadata with explicit bounds. Attributes are not a replacement for a database transaction or a general key-value store.
Store a stable name and type
WriteAttr() takes a name, type_code, offset, data pointer, and length. The type code is stored with the attribute and can be queried through GetAttrInfo(). Choose a stable, application-specific name and use a documented Haiku type code appropriate to the value. Avoid reusing a name with an incompatible schema. If the representation must change, version the attribute name or include a schema version in its payload.
An attribute’s type code is metadata, not a validator for its bytes. A reader still needs to check exact length, encoding, version, range, and internal invariants. Never reinterpret untrusted bytes as a structure with compiler-dependent padding. Serialize fields using a defined representation and byte order, or use a supported typed message format with explicit bounds.
Use GetAttrInfo() to inspect the type and stored size before allocating a destination buffer. Place a strict maximum on accepted sizes, check conversions between the reported size and size_t, and reject absurd values. If the attribute is absent, decide whether to use a default, regenerate metadata, or report that the file is not yet indexed; do not treat every error as “attribute missing.”
Read and write counts are part of the result
ReadAttr() and WriteAttr() return ssize_t, not a Boolean success flag. A negative result is an error; a nonnegative result is the number of bytes transferred. Compare it with the requested length. A successful call that wrote fewer bytes than expected does not mean the whole record is present.
The API includes an offset argument. The current BNode implementation passes offset and type through to the file-system attribute operations; actual support and semantics depend on the underlying file system. Older API prose describes some implementations as ignoring offsets. If code depends on partial attribute updates, validate behavior on every supported file system and Haiku version. For a small structured attribute, writing the complete value at the documented start position is easier to reason about than a sequence of in-place updates.
The string helpers WriteAttrString() and ReadAttrString() simplify a BString value. They do not remove the need to check status, set a size bound, or version the string’s semantics. For binary records or data requiring explicit encoding, use ReadAttr() and WriteAttr() with a defined format.
An illustrative write is:
const char value[] = "approved";
ssize_t written = node.WriteAttr("application/x-vnd.example:state",
B_STRING_TYPE, 0, value, sizeof(value));
if (written < 0 || static_cast<size_t>(written) != sizeof(value))
return B_FILE_ERROR;
The example intentionally includes the terminating NUL as part of the stored byte sequence. A reader should validate the expected size and terminator before treating the bytes as a C string. In production, preserve the actual error code instead of replacing all failures with B_FILE_ERROR.
Attribute enumeration has mutable cursor state
GetNextAttrName() advances an attribute-name cursor, and RewindAttrs() resets it. Treat this as state on the BNode object, not as an independent iterator returned per call. Do not share one node object between two concurrent attribute enumerations and assume each traversal has a separate position. Use a separate BNode instance per traversal or protect the shared cursor.
Enumeration is a changing view, not an atomic directory snapshot. Another process can add, remove, or rename attributes during the pass. Handle end-of-list separately from an I/O error, and avoid using a fixed-size name buffer without checking the API’s required limit. When the complete set must be reconciled, compare a fresh scan with an application model and tolerate concurrent updates rather than claiming transactionality.
Locking and synchronization are not transactions
Lock() and Unlock() coordinate access to the node according to the Storage Kit contract; they do not turn several independent metadata and data writes into a database transaction. Keep critical sections short, use RAII for unlock paths, and do not hold a node lock while prompting the user or waiting on the network. Sync() requests synchronization for the node but does not make a multi-step schema change atomic.
If an attribute update must correspond to a file-data update, define failure recovery. A process can stop after writing one but before writing the other. A version or generation field can help readers detect incomplete state, but it is not a substitute for atomic file replacement when the operation requires all-or-nothing behavior. Write temporary content, validate it, then replace the destination through the appropriate file-system workflow when supported.
Indexing is a separate opt-in capability
BFS indexes are configured separately from attribute storage. An attribute can exist and be readable without a matching index. A live query or indexed search depends on a correctly registered index and consistent type conventions. Keep the same type code and value representation across the writer, index, and query. Test index availability and query behavior at runtime rather than assuming every BFS volume has your custom index installed.
On a non-BFS volume, attribute operations may fail or have different limits. Do not make core file access depend on an optional custom attribute unless the application can reconstruct it or explain its absence. A file may be copied to FAT or another file system and lose Haiku-specific metadata; that should not corrupt its ordinary data.
Validate schema and storage behavior
Test attribute missing, empty, normal, maximum-sized, truncated, wrong-type, and future-version cases. Exercise read-only volumes, a removed USB drive, disk-full errors, unsupported file systems, concurrent updates, and renames while the UI displays metadata. Confirm that every path distinguishes an absent attribute from an invalid node, permission error, and I/O failure.
Round-trip the exact byte representation and verify it from a second process or a separate BNode. Test indexed search only on a volume where the needed index is installed. Copy the file to another file system and check that the application still opens the main content when its attributes are gone. Run tests with attribute sizes just below and above configured limits.
Log the node identity, attribute name, expected and actual byte counts, type code, file-system type where known, and status. Avoid dumping user metadata to logs. The goal is to make a failed metadata update diagnosable without turning logs into a copy of the user’s file collection.
Haiku attributes are powerful because they let applications attach typed metadata to file-system nodes and let BFS index selected values. They remain a file-system capability with explicit limits, not an automatic database. BNode code is dependable when it validates the node, schema, actual byte counts, and target file-system behavior on every path.
Related:
- How to Create BFS Attributes and Indexes for Fast Haiku Queries
- BFS: How Haiku’s File System Doubles as a Database
Sources: