⛏️ index : haiku.git

/*
 * Copyright 2004, François Revol.
 * Copyright 2007-2010, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2011, Oliver Tappe, zooey@hirschkaefer.de.
 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
 *
 * Distributed under the terms of the MIT license.
 */

// TODO: this call is currently compiled for the kernel and libroot separately;
//		they may not always return the same directory right now!

#ifdef _KERNEL_MODE
#	include <vfs.h>
#else
#	include <syscalls.h>
#endif

#include <directories.h>
#include <FindDirectory.h>
#include <fs_info.h>
#include <StackOrHeapArray.h>

#include <errno.h>
#include <pwd.h>
#include <string.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <unistd.h>

#include <architecture_private.h>
#include <errno_private.h>
#include <find_directory_private.h>
#include <stdlib_private.h>
#include <symbol_versioning.h>
#include <user_group.h>

#include <AutoDeleter.h>

#include "PathBuffer.h"


/* use pwents to find home */
#define USE_PWENTS


/*
 * If you change any of the directories below, please have a look at
 * headers/private/libroot/directories.h and adjust that accordingly!
 */

#define SYSTEM "system"
#define COMMON "system/data/empty"
#define NON_PACKAGED "/non-packaged"

enum {
	// obsolete common directories
	B_COMMON_DIRECTORY					= 2000,
	B_COMMON_SYSTEM_DIRECTORY,
	B_COMMON_ADDONS_DIRECTORY,
	B_COMMON_BOOT_DIRECTORY,
	B_COMMON_FONTS_DIRECTORY,
	B_COMMON_LIB_DIRECTORY,
	B_COMMON_SERVERS_DIRECTORY,
	B_COMMON_BIN_DIRECTORY,
	_B_COMMON_ETC_DIRECTORY,
	B_COMMON_DOCUMENTATION_DIRECTORY,
	_B_COMMON_SETTINGS_DIRECTORY,
	B_COMMON_DEVELOP_DIRECTORY,
	_B_COMMON_LOG_DIRECTORY,
	_B_COMMON_SPOOL_DIRECTORY,
	_B_COMMON_TEMP_DIRECTORY,
	_B_COMMON_VAR_DIRECTORY,
	B_COMMON_TRANSLATORS_DIRECTORY,
	B_COMMON_MEDIA_NODES_DIRECTORY,
	B_COMMON_SOUNDS_DIRECTORY,
	B_COMMON_DATA_DIRECTORY,
	_B_COMMON_CACHE_DIRECTORY,
	B_COMMON_PACKAGES_DIRECTORY,
	B_COMMON_HEADERS_DIRECTORY,
};


/* Haiku system directories */

static const char *kSystemDirectories[] = {
	SYSTEM,										// B_SYSTEM_DIRECTORY
	SYSTEM,										// B_BEOS_SYSTEM_DIRECTORY
	SYSTEM "/add-ons$a",
	SYSTEM "/boot",
	SYSTEM "/data/fonts",
	SYSTEM "/lib$a",
	SYSTEM "/servers",
	SYSTEM "/apps",
	SYSTEM "/bin$a",
	SYSTEM "/settings/etc",
	SYSTEM "/documentation",
	SYSTEM "/preferences",
	SYSTEM "/add-ons$a/Translators",
	SYSTEM "/add-ons$a/media",
	SYSTEM "/data/sounds",
	SYSTEM "/data",
	SYSTEM "/develop",
	SYSTEM "/packages",
	SYSTEM "/develop/headers$a",
	SYSTEM "/data/deskbar/menu",
};

/* Common directories, shared among users */

static const char *kCommonDirectories[] = {
	COMMON,									// B_COMMON_DIRECTORY
	COMMON,									// B_COMMON_SYSTEM_DIRECTORY
	COMMON "/add-ons$a",
	COMMON "/boot",
	COMMON "/data/fonts",
	COMMON "/lib$a",
	COMMON "/servers",
	COMMON "/bin$a",
	SYSTEM "/settings/etc",					// B_SYSTEM_ETC_DIRECTORY
	COMMON "/documentation",
	SYSTEM "/settings",						// B_SYSTEM_SETTINGS_DIRECTORY
	COMMON "/develop",
	SYSTEM "/var/log",						// B_SYSTEM_LOG_DIRECTORY
	SYSTEM "/var/spool",					// B_SYSTEM_SPOOL_DIRECTORY
	SYSTEM "/cache/tmp",					// B_SYSTEM_TEMP_DIRECTORY
	SYSTEM "/var",							// B_SYSTEM_VAR_DIRECTORY
	COMMON "/add-ons$a/Translators",
	COMMON "/add-ons$a/media",
	COMMON "/data/sounds",
	COMMON "/data",
	SYSTEM "/cache",						// B_SYSTEM_CACHE_DIRECTORY
	COMMON "/packages",
	COMMON "/develop/headers$a",
	SYSTEM NON_PACKAGED,
	SYSTEM NON_PACKAGED "/add-ons$a",
	SYSTEM NON_PACKAGED "/add-ons$a/Translators",
	SYSTEM NON_PACKAGED "/add-ons$a/media",
	SYSTEM NON_PACKAGED "/bin$a",
	SYSTEM NON_PACKAGED "/data",
	SYSTEM NON_PACKAGED "/data/fonts",
	SYSTEM NON_PACKAGED "/data/sounds",
	SYSTEM NON_PACKAGED "/documentation",
	SYSTEM NON_PACKAGED "/lib$a",
	SYSTEM NON_PACKAGED "/develop/headers$a",
	SYSTEM NON_PACKAGED "/develop",
};

/* User directories */

#define HOME "$h"
#define CONFIG "/config"

static const char *kUserDirectories[] = {
	HOME,									// B_USER_DIRECTORY
	HOME CONFIG,							// B_USER_CONFIG_DIRECTORY
	HOME CONFIG "/add-ons$a",
	HOME CONFIG "/settings/boot",
	HOME CONFIG "/data/fonts",
	HOME CONFIG "/lib$a",
	HOME CONFIG "/settings",
	HOME CONFIG "/settings/deskbar/menu",
	HOME CONFIG "/settings/printers",
	HOME CONFIG "/add-ons$a/Translators",
	HOME CONFIG "/add-ons$a/media",
	HOME CONFIG "/data/sounds",
	HOME CONFIG "/data",
	HOME CONFIG "/cache",
	HOME CONFIG "/packages",
	HOME CONFIG "/develop/headers$a",
	HOME CONFIG NON_PACKAGED,
	HOME CONFIG NON_PACKAGED "/add-ons$a",
	HOME CONFIG NON_PACKAGED "/add-ons$a/Translators",
	HOME CONFIG NON_PACKAGED "/add-ons$a/media",
	HOME CONFIG NON_PACKAGED "/bin$a",
	HOME CONFIG NON_PACKAGED "/data",
	HOME CONFIG NON_PACKAGED "/data/fonts",
	HOME CONFIG NON_PACKAGED "/data/sounds",
	HOME CONFIG NON_PACKAGED "/documentation",
	HOME CONFIG NON_PACKAGED "/lib$a",
	HOME CONFIG NON_PACKAGED "/develop/headers$a",
	HOME CONFIG NON_PACKAGED "/develop",
	HOME CONFIG "/develop",
	HOME CONFIG "/documentation",
	HOME CONFIG "/servers",
	HOME CONFIG "/apps",
	HOME CONFIG "/bin$a",
	HOME CONFIG "/preferences",
	HOME CONFIG "/settings/etc",
	HOME CONFIG "/var/log",
	HOME CONFIG "/var/spool",
	HOME CONFIG "/var",
};

#ifndef _LOADER_MODE
/*! make dir and its parents if needed */
static int
create_path(const char *path, mode_t mode)
{
	int pathLength;
	int i = 0;

	if (path == NULL || ((pathLength = strlen(path)) > B_PATH_NAME_LENGTH))
		return EINVAL;

	BStackOrHeapArray<char, 128> buffer(pathLength + 1);
	if (!buffer.IsValid())
		return B_NO_MEMORY;

	while (++i < pathLength) {
		char *slash = strchr(&path[i], '/');
		struct stat st;

		if (slash == NULL)
			i = pathLength;
		else if (i != slash - path)
			i = slash - path;
		else
			continue;

		strlcpy(buffer, path, i + 1);
		if (stat(buffer, &st) < 0) {
			__set_errno(0);
			if (mkdir(buffer, mode) < 0)
				return errno;
		}
	}

	return 0;
}


static size_t
get_user_home_path(char* buffer, size_t bufferSize)
{
	const char* home = NULL;
#ifndef _KERNEL_MODE
#ifdef USE_PWENTS
	uid_t user = geteuid();
	if (user == 0) {
		// TODO: this is a work-around as the launch_daemon, and the registrar
		// must not call getpwuid_r().
		return strlcpy(buffer, kUserDirectory, bufferSize);
	}

	struct passwd pwBuffer;
	char pwStringBuffer[MAX_PASSWD_BUFFER_SIZE];
	struct passwd* pw;

	if (getpwuid_r(user, &pwBuffer, pwStringBuffer,
			sizeof(pwStringBuffer), &pw) == 0
		&& pw != NULL) {
		home = pw->pw_dir;
	}
#endif	// USE_PWENTS
	if (home == NULL) {
		/* use env var */
		ssize_t result = __getenv_reentrant("HOME", buffer, bufferSize);
		if (result >= 0)
			return result;
	}
#endif	// !_KERNEL_MODE
	if (home == NULL)
		home = kUserDirectory;

	return strlcpy(buffer, home, bufferSize);
}


//	#pragma mark -


status_t
__find_directory(directory_which which, dev_t device, bool createIt,
	char *returnedPath, int32 _pathLength)
{
	if (_pathLength <= 0)
		return E2BIG;
	size_t pathLength = _pathLength;

	status_t err = B_OK;
	dev_t bootDevice = -1;
	struct fs_info fsInfo;
	struct stat st;
	const char *templatePath = NULL;

	/* as with the R5 version, no on-stack buffer */
	char *buffer = (char*)malloc(pathLength);
	if (buffer == NULL)
		return B_NO_MEMORY;
	MemoryDeleter bufferDeleter(buffer);

	memset(buffer, 0, pathLength);

	/* fiddle with non-boot volume for items that need it */
	switch (which) {
		case B_DESKTOP_DIRECTORY:
		case B_TRASH_DIRECTORY:
			bootDevice = dev_for_path("/boot");
			if (device <= 0)
				device = bootDevice;
			if (fs_stat_dev(device, &fsInfo) != B_OK)
				return ENODEV;
			if (device != bootDevice) {
#ifdef _KERNEL_MODE
				err = _user_entry_ref_to_path(device, fsInfo.root, /*"."*/
					NULL, buffer, pathLength);
#else
				err = _kern_entry_ref_to_path(device, fsInfo.root, /*"."*/
					NULL, buffer, pathLength);
#endif
				if (err != B_OK)
					return err;
			} else {
				/* use the user id to find the home folder */
				/* done later */
				strlcat(buffer, "/boot", pathLength);
			}
			break;
		case B_PACKAGE_LINKS_DIRECTORY:
			// this is a directory living in rootfs
			break;
		default:
			strlcat(buffer, "/boot", pathLength);
			break;
	}

	switch ((int)which) {
		/* Per volume directories */
		case B_DESKTOP_DIRECTORY:
			if (device == bootDevice || !strcmp(fsInfo.fsh_name, "bfs"))
				templatePath = "$h/Desktop";
			break;
		case B_TRASH_DIRECTORY:
			// TODO: eventually put that into the file system API?
			if (device == bootDevice || !strcmp(fsInfo.fsh_name, "bfs"))
				templatePath = "trash"; // TODO: add suffix for current user
			else if (!strcmp(fsInfo.fsh_name, "fat"))
				templatePath = "RECYCLED/_BEOS_";
			break;

		/* Haiku system directories */
		case B_SYSTEM_DIRECTORY:
		case B_BEOS_SYSTEM_DIRECTORY:
		case B_SYSTEM_ADDONS_DIRECTORY:
		case B_SYSTEM_BOOT_DIRECTORY:
		case B_SYSTEM_FONTS_DIRECTORY:
		case B_SYSTEM_LIB_DIRECTORY:
		case B_SYSTEM_SERVERS_DIRECTORY:
		case B_SYSTEM_APPS_DIRECTORY:
		case B_SYSTEM_BIN_DIRECTORY:
		case B_BEOS_ETC_DIRECTORY:
		case B_SYSTEM_DOCUMENTATION_DIRECTORY:
		case B_SYSTEM_PREFERENCES_DIRECTORY:
		case B_SYSTEM_TRANSLATORS_DIRECTORY:
		case B_SYSTEM_MEDIA_NODES_DIRECTORY:
		case B_SYSTEM_SOUNDS_DIRECTORY:
		case B_SYSTEM_DATA_DIRECTORY:
		case B_SYSTEM_DEVELOP_DIRECTORY:
		case B_SYSTEM_PACKAGES_DIRECTORY:
		case B_SYSTEM_HEADERS_DIRECTORY:
		case B_SYSTEM_DESKBAR_DIRECTORY:
			templatePath = kSystemDirectories[which - B_SYSTEM_DIRECTORY];
			break;

		/* Obsolete common directories and writable system directories */
		case B_COMMON_DIRECTORY:
		case B_COMMON_SYSTEM_DIRECTORY:
		case B_COMMON_ADDONS_DIRECTORY:
		case B_COMMON_BOOT_DIRECTORY:
		case B_COMMON_FONTS_DIRECTORY:
		case B_COMMON_LIB_DIRECTORY:
		case B_COMMON_SERVERS_DIRECTORY:
		case B_COMMON_BIN_DIRECTORY:
		case B_SYSTEM_ETC_DIRECTORY:
		case B_COMMON_DOCUMENTATION_DIRECTORY:
		case B_SYSTEM_SETTINGS_DIRECTORY:
		case B_COMMON_DEVELOP_DIRECTORY:
		case B_SYSTEM_LOG_DIRECTORY:
		case B_SYSTEM_SPOOL_DIRECTORY:
		case B_SYSTEM_TEMP_DIRECTORY:
		case B_SYSTEM_VAR_DIRECTORY:
		case B_COMMON_TRANSLATORS_DIRECTORY:
		case B_COMMON_MEDIA_NODES_DIRECTORY:
		case B_COMMON_SOUNDS_DIRECTORY:
		case B_COMMON_DATA_DIRECTORY:
		case B_SYSTEM_CACHE_DIRECTORY:
		case B_COMMON_PACKAGES_DIRECTORY:
		case B_COMMON_HEADERS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_TRANSLATORS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_MEDIA_NODES_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_BIN_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_DATA_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_FONTS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_SOUNDS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_DOCUMENTATION_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_LIB_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_HEADERS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_DEVELOP_DIRECTORY:
			templatePath = kCommonDirectories[which - B_COMMON_DIRECTORY];
			break;

		/* User directories */
		case B_USER_DIRECTORY:
		case B_USER_CONFIG_DIRECTORY:
		case B_USER_ADDONS_DIRECTORY:
		case B_USER_BOOT_DIRECTORY:
		case B_USER_FONTS_DIRECTORY:
		case B_USER_LIB_DIRECTORY:
		case B_USER_SETTINGS_DIRECTORY:
		case B_USER_DESKBAR_DIRECTORY:
		case B_USER_PRINTERS_DIRECTORY:
		case B_USER_TRANSLATORS_DIRECTORY:
		case B_USER_MEDIA_NODES_DIRECTORY:
		case B_USER_SOUNDS_DIRECTORY:
		case B_USER_DATA_DIRECTORY:
		case B_USER_CACHE_DIRECTORY:
		case B_USER_PACKAGES_DIRECTORY:
		case B_USER_HEADERS_DIRECTORY:
		case B_USER_DEVELOP_DIRECTORY:
		case B_USER_DOCUMENTATION_DIRECTORY:
		case B_USER_NONPACKAGED_DIRECTORY:
		case B_USER_NONPACKAGED_ADDONS_DIRECTORY:
		case B_USER_NONPACKAGED_TRANSLATORS_DIRECTORY:
		case B_USER_NONPACKAGED_MEDIA_NODES_DIRECTORY:
		case B_USER_NONPACKAGED_BIN_DIRECTORY:
		case B_USER_NONPACKAGED_DATA_DIRECTORY:
		case B_USER_NONPACKAGED_FONTS_DIRECTORY:
		case B_USER_NONPACKAGED_SOUNDS_DIRECTORY:
		case B_USER_NONPACKAGED_DOCUMENTATION_DIRECTORY:
		case B_USER_NONPACKAGED_LIB_DIRECTORY:
		case B_USER_NONPACKAGED_HEADERS_DIRECTORY:
		case B_USER_NONPACKAGED_DEVELOP_DIRECTORY:
		case B_USER_SERVERS_DIRECTORY:
		case B_USER_APPS_DIRECTORY:
		case B_USER_BIN_DIRECTORY:
		case B_USER_PREFERENCES_DIRECTORY:
		case B_USER_ETC_DIRECTORY:
		case B_USER_LOG_DIRECTORY:
		case B_USER_SPOOL_DIRECTORY:
		case B_USER_VAR_DIRECTORY:
			templatePath = kUserDirectories[which - B_USER_DIRECTORY];
			break;

		/* Global directories */
		case B_APPS_DIRECTORY:
		case B_UTILITIES_DIRECTORY:
			templatePath = SYSTEM "/apps";
			break;
		case B_PREFERENCES_DIRECTORY:
			templatePath = SYSTEM "/preferences";
			break;
		case B_PACKAGE_LINKS_DIRECTORY:
			templatePath = "packages";
			break;

		default:
			return EINVAL;
	}

	if (templatePath == NULL)
		return ENOENT;

	PathBuffer pathBuffer(buffer, pathLength, strlen(buffer));

	// resolve "$h" placeholder to the user's home directory
	if (!strncmp(templatePath, "$h", 2)) {
		if (bootDevice > -1 && device != bootDevice) {
			pathBuffer.Append("/home");
		} else {
			size_t length = get_user_home_path(buffer, pathLength);
			if (length >= pathLength)
				return E2BIG;
			pathBuffer.SetTo(buffer, pathLength, length);
		}
		templatePath += 2;
	} else if (templatePath[0] != '\0')
		pathBuffer.Append('/');

	// resolve "$a" placeholder to the architecture subdirectory, if not
	// primary
	if (char* dollar = strchr(templatePath, '$')) {
		if (dollar[1] == 'a') {
			pathBuffer.Append(templatePath, dollar - templatePath);
#ifndef _KERNEL_MODE
			const char* architecture = __get_architecture();
			if (strcmp(architecture, __get_primary_architecture()) != 0) {
				pathBuffer.Append('/');
				pathBuffer.Append(architecture);
			}
#endif
			templatePath = dollar + 2;
		}
	}

	// append (remainder of) template path
	pathBuffer.Append(templatePath);

	if (pathBuffer.Length() >= pathLength)
		return E2BIG;

	if (createIt && stat(buffer, &st) < 0) {
		err = create_path(buffer, 0755);
		if (err != B_OK)
			return err;
	}

	strlcpy(returnedPath, buffer, pathLength);
	return B_OK;
}


extern "C" status_t
__find_directory_alpha4(directory_which which, dev_t device, bool createIt,
	char *returnedPath, int32 pathLength)
{
	return __find_directory(which, device, createIt, returnedPath, pathLength);
}


DEFINE_LIBROOT_KERNEL_SYMBOL_VERSION("__find_directory_alpha4",
	"find_directory@", "BASE");

DEFINE_LIBROOT_KERNEL_SYMBOL_VERSION("__find_directory", "find_directory@@",
	"1_ALPHA5");
#else // _LOADER_MODE
status_t
__find_directory(directory_which which, dev_t device, bool createIt,
	char *returnedPath, int32 _pathLength)
{
	if (_pathLength <= 0)
		return E2BIG;
	size_t pathLength = _pathLength;

	const char *templatePath = NULL;

	/* as with the R5 version, no on-stack buffer */
	char *buffer = (char*)malloc(pathLength);
	if (buffer == NULL)
		return B_NO_MEMORY;
	MemoryDeleter bufferDeleter(buffer);

	memset(buffer, 0, pathLength);

	strlcat(buffer, "/boot", pathLength);

	switch ((int)which) {
		/* Haiku system directories */
		case B_SYSTEM_DIRECTORY:
		case B_BEOS_SYSTEM_DIRECTORY:
		case B_SYSTEM_ADDONS_DIRECTORY:
		case B_SYSTEM_BOOT_DIRECTORY:
		case B_SYSTEM_FONTS_DIRECTORY:
		case B_SYSTEM_LIB_DIRECTORY:
		case B_SYSTEM_SERVERS_DIRECTORY:
		case B_SYSTEM_APPS_DIRECTORY:
		case B_SYSTEM_BIN_DIRECTORY:
		case B_BEOS_ETC_DIRECTORY:
		case B_SYSTEM_DOCUMENTATION_DIRECTORY:
		case B_SYSTEM_PREFERENCES_DIRECTORY:
		case B_SYSTEM_TRANSLATORS_DIRECTORY:
		case B_SYSTEM_MEDIA_NODES_DIRECTORY:
		case B_SYSTEM_SOUNDS_DIRECTORY:
		case B_SYSTEM_DATA_DIRECTORY:
		case B_SYSTEM_DEVELOP_DIRECTORY:
		case B_SYSTEM_PACKAGES_DIRECTORY:
		case B_SYSTEM_HEADERS_DIRECTORY:
		case B_SYSTEM_DESKBAR_DIRECTORY:
			templatePath = kSystemDirectories[which - B_SYSTEM_DIRECTORY];
			break;

		/* Obsolete common directories and writable system directories */
		case B_COMMON_DIRECTORY:
		case B_COMMON_SYSTEM_DIRECTORY:
		case B_COMMON_ADDONS_DIRECTORY:
		case B_COMMON_BOOT_DIRECTORY:
		case B_COMMON_FONTS_DIRECTORY:
		case B_COMMON_LIB_DIRECTORY:
		case B_COMMON_SERVERS_DIRECTORY:
		case B_COMMON_BIN_DIRECTORY:
		case B_SYSTEM_ETC_DIRECTORY:
		case B_COMMON_DOCUMENTATION_DIRECTORY:
		case B_SYSTEM_SETTINGS_DIRECTORY:
		case B_COMMON_DEVELOP_DIRECTORY:
		case B_SYSTEM_LOG_DIRECTORY:
		case B_SYSTEM_SPOOL_DIRECTORY:
		case B_SYSTEM_TEMP_DIRECTORY:
		case B_SYSTEM_VAR_DIRECTORY:
		case B_COMMON_TRANSLATORS_DIRECTORY:
		case B_COMMON_MEDIA_NODES_DIRECTORY:
		case B_COMMON_SOUNDS_DIRECTORY:
		case B_COMMON_DATA_DIRECTORY:
		case B_SYSTEM_CACHE_DIRECTORY:
		case B_COMMON_PACKAGES_DIRECTORY:
		case B_COMMON_HEADERS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_ADDONS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_TRANSLATORS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_MEDIA_NODES_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_BIN_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_DATA_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_FONTS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_SOUNDS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_DOCUMENTATION_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_LIB_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_HEADERS_DIRECTORY:
		case B_SYSTEM_NONPACKAGED_DEVELOP_DIRECTORY:
			templatePath = kCommonDirectories[which - B_COMMON_DIRECTORY];
			break;

		/* User directories */
		case B_USER_DIRECTORY:
		case B_USER_CONFIG_DIRECTORY:
		case B_USER_ADDONS_DIRECTORY:
		case B_USER_BOOT_DIRECTORY:
		case B_USER_FONTS_DIRECTORY:
		case B_USER_LIB_DIRECTORY:
		case B_USER_SETTINGS_DIRECTORY:
		case B_USER_DESKBAR_DIRECTORY:
		case B_USER_PRINTERS_DIRECTORY:
		case B_USER_TRANSLATORS_DIRECTORY:
		case B_USER_MEDIA_NODES_DIRECTORY:
		case B_USER_SOUNDS_DIRECTORY:
		case B_USER_DATA_DIRECTORY:
		case B_USER_CACHE_DIRECTORY:
		case B_USER_PACKAGES_DIRECTORY:
		case B_USER_HEADERS_DIRECTORY:
		case B_USER_DEVELOP_DIRECTORY:
		case B_USER_DOCUMENTATION_DIRECTORY:
		case B_USER_NONPACKAGED_DIRECTORY:
		case B_USER_NONPACKAGED_ADDONS_DIRECTORY:
		case B_USER_NONPACKAGED_TRANSLATORS_DIRECTORY:
		case B_USER_NONPACKAGED_MEDIA_NODES_DIRECTORY:
		case B_USER_NONPACKAGED_BIN_DIRECTORY:
		case B_USER_NONPACKAGED_DATA_DIRECTORY:
		case B_USER_NONPACKAGED_FONTS_DIRECTORY:
		case B_USER_NONPACKAGED_SOUNDS_DIRECTORY:
		case B_USER_NONPACKAGED_DOCUMENTATION_DIRECTORY:
		case B_USER_NONPACKAGED_LIB_DIRECTORY:
		case B_USER_NONPACKAGED_HEADERS_DIRECTORY:
		case B_USER_NONPACKAGED_DEVELOP_DIRECTORY:
		case B_USER_SERVERS_DIRECTORY:
		case B_USER_APPS_DIRECTORY:
		case B_USER_BIN_DIRECTORY:
		case B_USER_PREFERENCES_DIRECTORY:
		case B_USER_ETC_DIRECTORY:
		case B_USER_LOG_DIRECTORY:
		case B_USER_SPOOL_DIRECTORY:
		case B_USER_VAR_DIRECTORY:
			templatePath = kUserDirectories[which - B_USER_DIRECTORY];
			break;

		default:
			return EINVAL;
	}

	if (templatePath == NULL)
		return ENOENT;

	PathBuffer pathBuffer(buffer, pathLength, strlen(buffer));

	// resolve "$h" placeholder to the user's home directory
	if (!strncmp(templatePath, "$h", 2)) {
		pathBuffer.Append("/home");
		templatePath += 2;
	} else if (templatePath[0] != '\0')
		pathBuffer.Append('/');

	// resolve "$a" placeholder to the architecture subdirectory, if not
	// primary
	if (char* dollar = strchr(templatePath, '$')) {
		if (dollar[1] == 'a') {
			pathBuffer.Append(templatePath, dollar - templatePath);
			templatePath = dollar + 2;
		}
	}

	// append (remainder of) template path
	pathBuffer.Append(templatePath);

	if (pathBuffer.Length() >= pathLength)
		return E2BIG;

	strlcpy(returnedPath, buffer, pathLength);
	return B_OK;
}
#endif // _LOADER_MODE