Haiku BPath: Lexical Normalization, Relative Bases, and Filesystem Identity
Use Haiku BPath to compose and normalize path strings while keeping lexical paths distinct from resolved entries, symlinks, and durable references.
BPath is a Support and Storage Kit value object for composing and inspecting filesystem path strings. It can be initialized from text, an entry_ref, a BEntry, or a BDirectory plus a relative leaf. It can append components, return a path or leaf, identify whether its string is absolute, and normalize path syntax. It does not prove that the path exists, resolve symlinks to their targets, or provide stable filesystem identity.
That boundary matters in code that mixes paths and entries. A BPath is a lexical name. A BEntry and entry_ref describe an entry in the mounted filesystem namespace. A node reference identifies a filesystem object for specific APIs. Use the right type for each boundary instead of treating all of them as interchangeable strings.
Initialize, check, then use the path
The default constructor leaves the object uninitialized. Constructors and SetTo() calls that accept a path can fail, including when the resulting path exceeds B_PATH_NAME_LENGTH. Always call InitCheck() after construction and inspect the status returned by SetTo() or Append(). Do not read Path() or Leaf() after a failed initialization.
BPath output;
status_t status = output.SetTo(baseDirectory, "cache/results.bin", true);
if (status != B_OK)
return status;
const char* fullPath = output.Path();
if (fullPath == NULL)
return B_NO_INIT;
Path() returns storage owned by the BPath object. Copy the string before mutating, unsetting, or destroying the path if it must outlive that object state. Do not free the returned pointer. Leaf() has the same borrowed-object lifetime concern.
Understand relative inputs and normalization
When a path string is relative, the documentation says it is based on the current working directory. A leaf argument must be relative and is appended to the base with a separator when needed. If the base is a BDirectory, that object establishes the base namespace for the constructed name. Prefer the directory overload when the operation already has a checked directory object, rather than rebuilding a string from a presumed current working directory.
Normalization can occur even when the normalize argument is false. The documentation lists relative pathnames, . or .. components, redundant slashes, and trailing slashes as cases that require normalization. Therefore, normalize=false should not be interpreted as a guarantee that the input bytes are preserved exactly. If the exact original spelling matters for audit or display, store that input separately.
Normalization is lexical: reducing . or .. components and redundant separators does not prove the resulting path points inside a security boundary. A path such as root/../outside can normalize to a name outside the intended tree. Validate containment against a resolved entry or directory policy, and defend against symlink and concurrent rename behavior separately.
Do not confuse normalization with existence or identity
BPath can hold a syntactically valid path whose target does not exist. It does not open the file or reserve its name. Another process can create, rename, or remove an entry after path construction. Use BEntry or BFile to perform the actual operation and handle its status. A successful path normalization is not an authorization check.
Likewise, BPath::operator== compares path values; it does not establish that two different strings resolve to the same node or that two equal strings still point to the same object. Symbolic links, mount points, case behavior, and concurrent namespace changes all affect the meaning of a name. Use entry_ref, node_ref, or an open node as appropriate for identity-sensitive logic.
Do not canonicalize user input and then assume traversal cannot escape an intended directory. Resolve components with APIs that preserve the required volume and symlink policy, and validate every relevant step. The check-then-open race remains if another process can alter the namespace between validation and use.
Compose paths without leaking state
Append() can add a path component and optionally normalize. Use it to build a path from known components, not to concatenate unchecked user input as though it were a safe child. Validate that user-provided leaves do not contain forbidden separators or parent traversal if the policy requires a single child name. The API can combine path components but cannot infer the product’s trust boundary.
For output paths, verify the parent directory separately, choose a collision policy, and create the file through a status-returning API. A BPath is useful for presenting the selected destination, but only successful creation or open proves that the current operation obtained the object. Preserve the previous file until the replacement has been written and validated when data loss is possible.
GetParent() writes the parent path into another BPath; check its status and do not assume every relative or root value has a meaningful parent for your workflow. Leaf() gives the last path component, not necessarily the ultimate target name behind a symlink. Label UI controls accurately so the user understands whether they are choosing a string, a link, or a resolved file.
For file chooser integrations, prefer the returned entry_ref for later Storage Kit operations and use BPath only when a textual path is actually needed. This avoids converting identity back into a path only to resolve it again. For configuration display, make a copy of the path and consider shortening only the presentation string; never mutate the actual path to fit a label. A display abbreviation must not be fed back into a filesystem operation.
If a path is part of a protocol or a preference file, define whether it is absolute, which volume namespace it belongs to, and how missing mount points are handled. A relative path depends on the process working directory, which can differ between launching from Tracker, a terminal, or a service. Resolve relative inputs against an explicit BDirectory when reproducibility matters.
Flattening and message exchange
BPath implements BFlattenable and can participate in BMessage data exchange. That is useful for passing a path value between local Haiku components. It does not make the path portable to another operating system, preserve a file handle, or grant access to the target. A recipient should still validate and resolve the received path according to its own policy.
For long-lived preferences, consider storing both the user’s intended path and a separately validated entry_ref when appropriate. The textual path can be re-resolved after a restart, while the reference may detect a specific directory entry. Neither alone provides recovery across arbitrary volume changes. Ask the user to repair stale references instead of silently choosing a similarly named target.
Failure-oriented verification
Test absolute and relative paths, a relative leaf, . and .., redundant separators, trailing slash, empty strings, overlong paths, missing parents, symlinks, and a base directory that is renamed after initialization. Verify InitCheck(), every operation status, and pointer lifetime after mutation. Confirm path equality is not used as file identity.
Run tests from different current working directories so relative path assumptions are visible. Include a volume path and a network/removable volume if the application supports them. The same string can resolve differently when the mount table or current working directory changes; log enough context to reproduce the resolution without exposing private paths.
Add a round-trip check for paths sent through a BMessage: flatten and unflatten into a fresh BPath, compare the lexical value, then independently test whether the referenced entry resolves. These are separate assertions. A path can round-trip byte-for-byte and still point to a missing file after the receiving process starts.
Acceptance criteria
Accept a BPath workflow when initialization and length failures are checked, normalization is treated as lexical cleanup only, relative bases are explicit, returned strings are copied when needed, and filesystem operations independently validate the resulting entry. Keep original user spelling when audit requirements need it.
BPath is a convenient path-string value type. It does not resolve a secure canonical target, eliminate namespace races, or replace entry and node identity APIs.
Related:
- Haiku find_directory and BPathFinder: Resolve Paths at Runtime
- Haiku entry_ref: Directory Identity, Stale Names, and Revalidation
Sources: