Skip to content
Haiku OSDeep Dive Published Updated 7 min readViews unavailable

Haiku find_directory and BPathFinder: Resolve Paths at Runtime

Resolve Haiku user, volume, and package paths through Storage Kit APIs, using find_directory only for the cases it still serves well.

Haiku applications should ask the Storage Kit where a file belongs instead of assembling paths from assumptions about /boot, a home directory name, or an installation layout. The supported path APIs distinguish user settings, per-volume directories, installed add-ons, package-owned resources, and all matching installation locations. That distinction is increasingly important as Haiku’s package system permits files to be resolved from package images and multiple installation scopes rather than one fixed directory tree.

The older find_directory() function remains useful, but current Haiku documentation explicitly deprecates it for several common patterns. In particular, code collecting add-ons or data from all installation locations should use find_paths(), find_paths_etc(), or BPathFinder::FindPaths(). A file shipped in the same package as an application should be found with find_path() or BPathFinder::FindPath(). Hardcoding a legacy directory constant for those cases can silently miss files when package layouts change.

Classify the path before choosing an API

Start with ownership and scope, not the desired string:

  • A user’s mutable preferences belong in that user’s settings area.
  • A cache can be deleted and regenerated, so it belongs in a cache location rather than a settings file that must survive cleanup.
  • A Trash directory is volume-specific, not a global user folder.
  • A resource installed with the calling application should be resolved relative to that application’s image/package.
  • A shared add-on or data provider may have files in multiple system and user installation locations; enumerate all relevant paths rather than choosing the first hardcoded directory.
  • A system-wide administrator-managed configuration file differs from a per-user preference, even when both use text formats.

This classification prevents applications from writing generated files into package-managed content or choosing a system directory merely because it is writable in a development build. It also makes multiple users behave predictably: B_USER_SETTINGS_DIRECTORY is interpreted for the calling user, while a volume-local directory requires the relevant volume context.

Use find_directory for the cases it still models

The C++ find_directory() overload accepts a directory_which, a BPath*, an optional create flag, and an optional BVolume*. For example, a settings directory can be resolved without embedding the home path:

BPath settingsPath;
status_t status = find_directory(B_USER_SETTINGS_DIRECTORY,
    &settingsPath, true);
if (status != B_OK)
    return status;

// Append the application's own filename only after validating its name.
status = settingsPath.Append("ExampleApp/settings.json");
if (status != B_OK)
    return status;

The API call can create the requested directory when createIt is true; this is not a request to create every parent file or to overwrite an existing settings file. The application must still handle permissions, disk-full errors, and the possibility that a later open fails. Use a stable application-specific subdirectory so unrelated applications do not collide.

For Trash or Desktop paths associated with a particular volume, pass the volume context rather than assuming the boot volume is the destination. A path valid for one mounted volume may not exist on another. If a removable volume disappears between lookup and use, expect the later operation to fail and recover without corrupting user state.

Avoid legacy B_BEOS_* directory constants in new code. The current public header marks them obsolete; use the supported B_SYSTEM_*, B_USER_*, or global path mechanisms according to the actual scope. Do not confuse a Preferences application directory with a settings-data directory: executable tools and configuration files have different storage semantics.

Use find_path for files that belong to the application

When an application needs a resource packaged alongside itself, find_path() resolves a path using the calling code image and a base directory such as data, documentation, or libraries. BPathFinder offers object-oriented overloads and can be initialized from a code pointer, an image path, or an entry reference. This avoids reconstructing an installation path from a known executable directory and a relative string.

The distinction is especially important under packagefs. An installed package can present files through a package image and activation links; a resource may not be an ordinary writable sibling of the executable. A computed BPath is a lookup result, not permission to modify the package. Treat application resources as read-only inputs. Store user-created state in an appropriate user directory.

If a resource is optional, handle B_ENTRY_NOT_FOUND as a supported absence when the feature allows it. If it is required, surface a clear diagnostic that names the logical resource rather than exposing an internal package-layout guess. Do not copy a resource from a developer checkout into a system directory as a runtime workaround; that hides packaging defects.

Enumerate providers with find_paths and BPathFinder

When the application supports add-ons or shared data contributed from several installation locations, use the plural APIs. find_paths() and find_paths_etc() return a list of candidates; BPathFinder::FindPaths() fills a BStringList. These APIs let the system account for current and future installation locations, and find_paths_etc() exposes architecture and scope flags for more specific queries.

Enumeration order should not become an undocumented precedence contract. If the app loads an add-on by name from several scopes, define whether user overrides system, whether the first match wins, or whether all providers can be loaded. Validate each candidate’s type, ownership expectations, ABI/version, and signature as appropriate. A filename collision should be visible in logs and tests instead of being resolved by accidental directory ordering.

Do not retain an enumerated path forever as proof the file remains available. Packages can be activated or removed, and volumes can unmount. Reopen by a stable reference when appropriate and handle races. If an add-on is loaded into the process, unload rules and object lifetimes are separate from path lookup.

Path strings are not file identities

A BPath is a convenient representation of a pathname; it is not a durable object identity or a guarantee that the referenced file still exists. Between lookup and open, the path may be replaced, the volume may disappear, or permissions may change. When an operation must act on the same object that was inspected, use Storage Kit entry/reference or open-file APIs with appropriate checks and minimize the time between validation and use.

Do not concatenate untrusted file names into a path without checking separators, traversal components, and the destination policy. BPath::Append() helps compose paths but does not replace validation of an untrusted leaf or guarantee that a symlink cannot escape an intended tree. If the app writes user-controlled data, use a dedicated directory, safe temporary-file creation, and a commit strategy that does not overwrite an unrelated file.

Unicode and case behavior belong to the filesystem and API contract, not a hand-written lowercase comparison. Preserve the exact returned path and let the Storage Kit resolve it. Avoid presenting an internal packagefs mount path as a portable identifier in configuration or logs.

Test path behavior across users, packages, and volumes

Test a normal user settings lookup, a first-run directory creation, an existing settings file, a read-only destination, and a missing optional resource. Test an application resource when run from a package and from a development image. Test add-on discovery with both user and system candidates, including a conflicting name, and confirm the precedence rule is deliberate.

For per-volume paths, use two mounted volumes and verify the result is associated with the selected volume. Unmount one between discovery and use and confirm the failure is handled. Run tests under a non-administrator account so accidental reliance on writable system directories is exposed.

When investigating a path issue, log the API used, logical directory enum or base directory, caller image, volume context, returned status, and final path. Avoid logging private document names or complete user data. This evidence quickly distinguishes a wrong path API from a packaging error or a later open/permission failure.

The practical rule is concise: use find_directory() for user and volume locations it still models, use find_path() for resources owned by the current package, and use plural path discovery for providers installed in multiple scopes. Choosing by ownership and scope keeps Haiku software resilient to package activation, multiple users, removable volumes, and future layout changes.

Related:

Sources:

Comments