Haiku BDirectory Mutations: Create, Open, and Revalidate Entries
Use Haiku BDirectory for scoped file creation and lookup while handling relative paths, status codes, symlinks, and races with concurrent changes.
BDirectory is more than a directory iterator. It is a BNode-backed handle that can resolve entries relative to an opened directory and create child directories, files, and symbolic links. Using a directory object as the base for a workflow makes the path scope clearer than repeatedly assembling absolute strings. It does not make a sequence of filesystem operations atomic: entries can be renamed, removed, or replaced by another process between lookup and use.
For mutation workflows, check the directory’s initialization status, make path interpretation explicit, and check every returned status_t. Keep the parent directory object alive for the operation and treat output parameters as valid only after the call succeeds. The iterator methods share state with BEntryList; this article focuses on safe lookup and mutation rather than traversal.
Open a directory and establish the path base
BDirectory can be initialized from a path, an entry_ref, a node_ref, a BEntry, or another directory plus a relative path. Constructors do not replace error handling. Call InitCheck() before using the object, or call SetTo() and inspect its status. If a directory path comes from user input, resolve it once and retain the opened handle instead of recalculating the base differently in each helper.
BDirectory parent("/boot/home/project-data");
status_t status = parent.InitCheck();
if (status != B_OK)
return status;
BEntry existing;
status = parent.FindEntry("input", &existing, false);
if (status == B_OK) {
// The entry was found without following a symbolic link.
} else if (status != B_ENTRY_NOT_FOUND) {
return status;
}
This pattern distinguishes a missing child from an invalid directory or another lookup failure. FindEntry() accepts a traverse argument; the conservative false shown here resolves the entry itself instead of following a symbolic link. If the application intentionally follows links, define what happens when the link points outside the expected tree and how cycles are detected.
Relative paths should be kept relative to the directory object deliberately. A leading slash is not a child name; it changes how the path is interpreted. Validate caller-provided path components and reject paths that escape an intended output directory. Do not treat a BDirectory base as a sandbox or an authorization boundary: filesystem permissions and concurrent path changes still apply.
Create directories and files with explicit collision policy
CreateDirectory() creates a child directory and can return a BDirectory for it. Check the result before using that output object. If the directory already exists, treat that as a separate branch: inspect that it is the expected type and identity, and do not blindly continue as though the call created a new private workspace.
CreateFile() can initialize a BFile output and accepts failIfExists. Decide whether an existing file is an error, a reusable target, or something to replace under an explicit user-visible policy. A default that truncates or reopens an existing file can destroy data if a generated name collides. Use a unique staging name when creating outputs, then validate and promote it through a separate, reviewed step.
BDirectory work(parent, "run-042");
status_t status = parent.CreateDirectory("run-042", &work);
if (status != B_OK)
return status;
BFile output;
status = work.CreateFile("result.tmp", &output, true);
if (status != B_OK)
return status;
The example expresses a fail-on-collision policy. Production code should distinguish “already exists” from permissions, full disk, invalid parent, and I/O failures and report the path involved. It should also check output.InitCheck() if the API contract or surrounding code leaves uncertainty about the output handle. Do not continue after a failed create merely because the object variable exists.
File creation is not a multi-step transaction. If a later write or flush fails, the file may remain partially populated. Write into a known temporary location, check write and close status, and only mark the output complete after validating its expected size or content. Cleanup should remove only a file the operation itself created, not a pre-existing path that happened to share its name.
Create symbolic links without confusing link and target
CreateSymLink() creates a symbolic link entry whose target path is supplied separately. A successful call establishes the link object, not that the target exists, is readable, or stays at the same location. Relative link targets are interpreted in relation to the link’s directory when resolved. Preserve whether the application intended a relative or absolute target and test resolution from the resulting directory.
Do not pass an arbitrary link target from untrusted input without applying the application’s path policy. A link can lead outside a workspace or refer back into a parent and create loops for recursive tools. When inspecting entries, decide whether to traverse symlinks and maintain visited identities or a traversal limit if following them. When removing an entry, make sure the operation targets the link itself or the desired target according to the API being used; those are not interchangeable actions.
Treat Contains() and FindEntry() as observations
Contains() and FindEntry() are useful for checking expected state, but their result is not a lease on the filesystem. Another process can rename or replace an entry after the check. Avoid a check-then-open security assumption such as “it existed and was safe a moment ago, so the later open must refer to the same object.” Open or mutate the intended object and handle the returned status. When identity matters, retain an appropriate BEntry or reference and revalidate immediately before an operation.
For tools that create a hierarchy, make each step idempotent and distinguish an already-created parent from a conflicting file or link. Log the full relative path and returned status without exposing unrelated user data. If the parent directory is replaced or becomes unavailable, stop the operation rather than reconstructing a path with an assumed location.
Validation and failure cases
Test a missing parent, a read-only directory, an existing file collision, an existing directory, a dangling link, a link that points outside the tree, a path with whitespace, and a failure after output creation. Confirm each method’s status and that cleanup does not remove pre-existing data. For a recursive operation, include a symlink loop and a concurrent rename to verify that it terminates and reports stale entries.
When a test runs on BFS and another filesystem, record the volume type and Haiku revision. Do not infer transactional rename, atomic creation, or metadata behavior that the APIs do not promise. For durability-sensitive output, check write/close status and test the application’s recovery behavior after an interrupted operation.
For a save workflow, write into a temporary sibling when the file format permits it, close and validate the completed file, then rename into the final name. That pattern limits the window in which readers can see a partially written result, but it does not make a whole directory tree transactionally atomic. If a later step fails, report which nodes were created and which were not. A cleanup routine should remove only objects the operation can prove it created; deleting a pre-existing directory after a collision would turn a recoverable error into data loss.
Keep recursive work bounded. Record the current entry_ref or other stable identity for each visited node, cap maximum depth and total entries, and define whether permission-denied children stop the whole operation or are reported as partial results. A cancellation request should be checked between entries, not only before traversal begins. If the app creates a manifest or progress log, write it outside the tree being traversed so it cannot accidentally be processed as another input.
BDirectory provides a useful scope for child lookup and creation, while BEntry and BFile represent individual objects and I/O. A reliable workflow checks initialization and operation status, chooses a collision policy, keeps symlink traversal explicit, and assumes that the filesystem can change between calls.
Related:
- Haiku BEntryList: Directory Iteration, Cursor State, and Mutation Races
- Haiku BFile: Open Modes, Truncation Safety, and Reliable I/O
Sources: