⛏️ index : haiku.git

/*
 * Copyright 2015, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2013, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include <find_directory_private.h>

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>

#include <algorithm>

#include <fs_attr.h>

#include <architecture_private.h>
#include <AutoDeleter.h>
#include <directories.h>
#include <syscalls.h>

#include "PathBuffer.h"


static size_t kHomeInstallationLocationIndex = 1;

static const path_base_directory kArchitectureSpecificBaseDirectories[] = {
	B_FIND_PATH_ADD_ONS_DIRECTORY,
	B_FIND_PATH_BIN_DIRECTORY,
	B_FIND_PATH_DEVELOP_LIB_DIRECTORY,
	B_FIND_PATH_HEADERS_DIRECTORY,
};

static size_t kArchitectureSpecificBaseDirectoryCount =
	sizeof(kArchitectureSpecificBaseDirectories)
		/ sizeof(kArchitectureSpecificBaseDirectories[0]);


namespace {


struct InstallationLocations {
public:
	static const size_t	kCount = 4;

public:
	InstallationLocations()
		:
		fReferenceCount(1)
	{
		fLocations[0] = kUserNonpackagedDirectory;
		fLocations[1] = kUserConfigDirectory;
		fLocations[2] = kSystemNonpackagedDirectory;
		fLocations[3] = kSystemDirectory;
	}

	InstallationLocations(const char* home)
		:
		fReferenceCount(1)
	{
		static const char* const kNonPackagedSuffix = "/non-packaged";
		char* homeNonPackaged
			= (char*)malloc(strlen(home) + strlen(kNonPackagedSuffix) + 1);
		fLocations[0] = homeNonPackaged;
		if (homeNonPackaged != NULL) {
			strcpy(homeNonPackaged, home);
			strcat(homeNonPackaged, kNonPackagedSuffix);
		}

		fLocations[1] = strdup(home);

		fLocations[2] = kSystemNonpackagedDirectory;
		fLocations[3] = kSystemDirectory;
	}

	~InstallationLocations()
	{
		free(const_cast<char*>(fLocations[0]));
		free(const_cast<char*>(fLocations[1]));
	}

	bool IsValid() const
	{
		return fLocations[0] != NULL && fLocations[1] != NULL;
	}

	bool IsUserIndex(size_t index) const
	{
		return index==0 || index==1;
	}

	bool IsSystemIndex(size_t index) const
	{
		return index==2 || index==3;
	}

	static InstallationLocations* Default()
	{
		static char sBuffer[sizeof(InstallationLocations)];
		static InstallationLocations* sDefaultLocations
			= new(&sBuffer) InstallationLocations;
		return sDefaultLocations;
	}

	static InstallationLocations* Get()
	{
		InstallationLocations* defaultLocations = Default();

		// Get the home config installation location and create a new object,
		// if it differs from the default.
		char homeInstallationLocation[B_PATH_NAME_LENGTH];
		if (__find_directory(B_USER_CONFIG_DIRECTORY, -1, false,
					homeInstallationLocation, sizeof(homeInstallationLocation))
				== B_OK) {
			_kern_normalize_path(homeInstallationLocation, true,
				homeInstallationLocation);
				// failure is OK
			if (strcmp(homeInstallationLocation,
						defaultLocations->At(kHomeInstallationLocationIndex))
					!= 0) {
				InstallationLocations* locations
					= new(std::nothrow) InstallationLocations(
						homeInstallationLocation);
				if (locations != NULL && locations->IsValid())
					return locations;
				delete locations;
			}
		}

		atomic_add(&defaultLocations->fReferenceCount, 1);
		return defaultLocations;
	}

	void Put()
	{
		if (atomic_add(&fReferenceCount, -1) == 1)
			delete this;
	}

	const char* At(size_t index) const
	{
		return fLocations[index];
	}

	const char* LocationFor(const char* path, size_t& _index)
	{
		for (size_t i = 0; i < kCount; i++) {
			size_t length = strlen(fLocations[i]);
			if (strncmp(path, fLocations[i], length) == 0
				&& (path[length] == '/' || path[length] == '\0')) {
				_index = i;
				return fLocations[i];
			}
		}

		return NULL;
	}

private:
	int32		fReferenceCount;
	const char*	fLocations[kCount];
};


}	// unnamed namespace


/*!	Returns the installation location relative path for the given base directory
	constant and installation location index. A '%' in the returned path must be
	replaced by "" for the primary architecture and by "/<arch>" for a secondary
	architecture.
 */
static const char*
get_relative_directory_path(size_t installationLocationIndex,
	path_base_directory baseDirectory)
{
	switch (baseDirectory) {
		case B_FIND_PATH_INSTALLATION_LOCATION_DIRECTORY:
			return "";
		case B_FIND_PATH_ADD_ONS_DIRECTORY:
			return "/add-ons%";
		case B_FIND_PATH_APPS_DIRECTORY:
			return "/apps";
		case B_FIND_PATH_BIN_DIRECTORY:
			return "/bin%";
		case B_FIND_PATH_BOOT_DIRECTORY:
			return "/boot";
		case B_FIND_PATH_CACHE_DIRECTORY:
			return "/cache";
		case B_FIND_PATH_DATA_DIRECTORY:
			return "/data";
		case B_FIND_PATH_DEVELOP_DIRECTORY:
			return "/develop";
		case B_FIND_PATH_DEVELOP_LIB_DIRECTORY:
			return "/develop/lib%";
		case B_FIND_PATH_DOCUMENTATION_DIRECTORY:
			return "/documentation";
		case B_FIND_PATH_ETC_DIRECTORY:
			return "/settings/etc";
		case B_FIND_PATH_FONTS_DIRECTORY:
			return "/data/fonts";
		case B_FIND_PATH_HEADERS_DIRECTORY:
			return "/develop/headers%";
		case B_FIND_PATH_LIB_DIRECTORY:
			return "/lib%";
		case B_FIND_PATH_LOG_DIRECTORY:
			return "/log";
		case B_FIND_PATH_MEDIA_NODES_DIRECTORY:
			return "/add-ons%/media";
		case B_FIND_PATH_PACKAGES_DIRECTORY:
			return "/packages";
		case B_FIND_PATH_PREFERENCES_DIRECTORY:
			return "/preferences";
		case B_FIND_PATH_SERVERS_DIRECTORY:
			return "/servers";
		case B_FIND_PATH_SETTINGS_DIRECTORY:
			return "/settings";
		case B_FIND_PATH_SOUNDS_DIRECTORY:
			return "/data/sounds";
		case B_FIND_PATH_SPOOL_DIRECTORY:
			return "/var/spool";
		case B_FIND_PATH_TRANSLATORS_DIRECTORY:
			return "/add-ons%/Translators";
		case B_FIND_PATH_VAR_DIRECTORY:
			return "/var";

		case B_FIND_PATH_IMAGE_PATH:
		case B_FIND_PATH_PACKAGE_PATH:
		default:
			return NULL;
	}
}


static status_t
create_directory(char* path)
{
	// find the first directory that doesn't exist
	char* slash = path;
	bool found = false;
	while (!found && (slash = strchr(slash + 1, '/')) != NULL) {
		*slash = '\0';
		struct stat st;
		if (lstat(path, &st) != 0)
			break;
		*slash = '/';
	}

	if (found)
		return B_OK;

	// create directories
	while (slash != NULL) {
		*slash = '\0';
		bool created = mkdir(path, 0755);
		*slash = '/';

		if (!created)
			return errno;

		slash = strchr(slash + 1, '/');
	}

	return B_OK;
}


static bool
is_in_range(const void* pointer, const void* base, size_t size)
{
	return pointer >= base && (addr_t)pointer < (addr_t)base + size;
}


static status_t
find_image(const void* codePointer, image_info& _info)
{
	int32 cookie = 0;

	while (get_next_image_info(B_CURRENT_TEAM, &cookie, &_info) == B_OK) {
		if (codePointer == NULL ? _info.type == B_APP_IMAGE
				: (is_in_range(codePointer, _info.text, _info.text_size)
					|| is_in_range(codePointer, _info.data, _info.data_size))) {
			return B_OK;
		}
	}

	return B_ENTRY_NOT_FOUND;
}


static status_t
copy_path(const char* path, char* buffer, size_t bufferSize)
{
	if (strlcpy(buffer, path, bufferSize) >= bufferSize)
		return B_BUFFER_OVERFLOW;
	return B_OK;
}


static status_t
normalize_path(const char* path, char* buffer, size_t bufferSize)
{
	status_t error;
	if (bufferSize >= B_PATH_NAME_LENGTH) {
		error = _kern_normalize_path(path, true, buffer);
	} else {
		char normalizedPath[B_PATH_NAME_LENGTH];
		error = _kern_normalize_path(path, true, normalizedPath);
		if (error == B_OK)
			error = copy_path(path, buffer, bufferSize);
	}

	if (error != B_OK)
		return error;

	// make sure the path exists
	struct stat st;
	if (lstat(buffer, &st) != 0)
		return errno;

	return B_OK;
}


static status_t
normalize_longest_existing_path_prefix(const char* path, char* buffer,
	size_t bufferSize)
{
	if (strlcpy(buffer, path, bufferSize) >= bufferSize)
		return B_NAME_TOO_LONG;

	// Until we have an existing path, chop off leaf components.
	for (;;) {
		struct stat st;
		if (lstat(buffer, &st) == 0)
			break;

		// Chop off the leaf, but fail, it it's "..", since then we'd actually
		// construct a subpath.
		char* lastSlash = strrchr(buffer, '/');
		if (lastSlash == NULL || strcmp(lastSlash + 1, "..") == 0)
			return B_ENTRY_NOT_FOUND;

		*lastSlash = '\0';
	}

	// normalize the existing prefix path
	size_t prefixLength = strlen(buffer);
	status_t error = normalize_path(buffer, buffer, bufferSize);
	if (error != B_OK)
		return error;

	// Re-append the non-existent suffix. Remove duplicate slashes and "."
	// components.
	const char* bufferEnd = buffer + bufferSize;
	char* end = buffer + strlen(buffer);
	const char* remainder = path + prefixLength + 1;
	while (*remainder != '\0') {
		// find component start
		if (*remainder == '/') {
			remainder++;
			continue;
		}

		// find component end
		const char* componentEnd = strchr(remainder, '/');
		if (componentEnd == NULL)
			componentEnd = remainder + strlen(remainder);

		// skip "." components
		size_t componentLength = componentEnd - remainder;
		if (componentLength == 1 && *remainder == '.') {
			remainder++;
			continue;
		}

		// append the component
		if (end + 1 + componentLength >= bufferEnd)
			return B_BUFFER_OVERFLOW;

		*end++ = '/';
		memcpy(end, remainder, componentLength);
		end += componentLength;
		remainder += componentLength;
	}

	*end = '\0';
	return B_OK;
}


static status_t
get_file_attribute(const char* path, const char* attribute, char* nameBuffer,
	size_t bufferSize)
{
	int fd = fs_open_attr(path, attribute, B_STRING_TYPE,
		O_RDONLY | O_NOTRAVERSE);
	if (fd < 0)
		return errno;

	status_t error = B_OK;
	ssize_t bytesRead = read(fd, nameBuffer, bufferSize - 1);
	if (bytesRead < 0)
		error = bytesRead;
	else if (bytesRead == 0)
		error = B_ENTRY_NOT_FOUND;
	else
		nameBuffer[bytesRead] = '\0';

	fs_close_attr(fd);

	return error;
}


static status_t
normalize_dependency(const char* dependency, char* buffer, size_t bufferSize)
{
	if (strlcpy(buffer, dependency, bufferSize) >= bufferSize)
		return B_NAME_TOO_LONG;

	// replace all ':' with '~'
	char* colon = buffer - 1;
	while ((colon = strchr(colon + 1, ':')) != NULL)
		*colon = '~';

	return B_OK;
}


static ssize_t
process_path(const char* installationLocation, const char* architecture,
	const char* relativePath, const char* subPath, uint32 flags,
	char* pathBuffer, size_t bufferSize)
{
	// copy the installation location
	PathBuffer buffer(pathBuffer, bufferSize);
	buffer.Append(installationLocation);

	// append the relative path, expanding the architecture placeholder
	if (const char* placeholder = strchr(relativePath, '%')) {
		buffer.Append(relativePath, placeholder - relativePath);

		if (architecture != NULL) {
			buffer.Append("/", 1);
			buffer.Append(architecture);
		}

		buffer.Append(placeholder + 1);
	} else
		buffer.Append(relativePath);

	// append subpath, if given
	if (subPath != NULL) {
		buffer.Append("/", 1);
		buffer.Append(subPath);
	}

	size_t totalLength = buffer.Length();
	if (totalLength >= bufferSize)
		return B_BUFFER_OVERFLOW;

	// handle the flags
	char* path = pathBuffer;

	status_t error = B_OK;
	if ((flags & B_FIND_PATH_CREATE_DIRECTORY) != 0) {
		// create the directory
		error = create_directory(path);
	} else if ((flags & B_FIND_PATH_CREATE_PARENT_DIRECTORY) != 0) {
		// create the parent directory
		char* lastSlash = strrchr(path, '/');
		*lastSlash = '\0';
		error = create_directory(path);
		*lastSlash = '/';
	}

	if (error != B_OK)
		return error;

	if ((flags & B_FIND_PATH_EXISTING_ONLY) != 0) {
		// check if the entry exists
		struct stat st;
		if (lstat(path, &st) != 0)
			return 0;
	}

	return totalLength + 1;
}


status_t
internal_path_for_path(char* referencePath, size_t referencePathSize,
	const char* dependency, const char* architecture,
	path_base_directory baseDirectory, const char* subPath, uint32 flags,
	char* pathBuffer, size_t bufferSize)
{
	if (strcmp(architecture, __get_primary_architecture()) == 0)
		architecture = NULL;

	// resolve dependency
	char packageName[B_FILE_NAME_LENGTH];
		// Temporarily used here, permanently used below where
		// B_FIND_PATH_PACKAGE_PATH is handled.
	if (dependency != NULL) {
		// get the versioned package name
		status_t error = get_file_attribute(referencePath, "SYS:PACKAGE",
			packageName, sizeof(packageName));
		if (error != B_OK)
			return error;

		// normalize the dependency name
		char normalizedDependency[B_FILE_NAME_LENGTH];
		error = normalize_dependency(dependency, normalizedDependency,
			sizeof(normalizedDependency));
		if (error != B_OK)
			return error;

		// Compute the path of the dependency symlink. This will yield the
		// installation location path when normalized.
		if (snprintf(referencePath, referencePathSize,
					kSystemPackageLinksDirectory "/%s/%s", packageName,
					normalizedDependency)
				>= (ssize_t)referencePathSize) {
			return B_BUFFER_OVERFLOW;
		}
	}

	// handle B_FIND_PATH_IMAGE_PATH
	if (baseDirectory == B_FIND_PATH_IMAGE_PATH)
		return copy_path(referencePath, pathBuffer, bufferSize);

	// Handle B_FIND_PATH_PACKAGE_PATH: get the package file name and
	// simply adjust our arguments to look the package file up in the packages
	// directory.
	if (baseDirectory == B_FIND_PATH_PACKAGE_PATH) {
		status_t error = get_file_attribute(referencePath, "SYS:PACKAGE_FILE",
			packageName, sizeof(packageName));
		if (error != B_OK)
			return error;

		dependency = NULL;
		subPath = packageName;
		baseDirectory = B_FIND_PATH_PACKAGES_DIRECTORY;
		flags = B_FIND_PATH_EXISTING_ONLY;
	}

	// normalize
	status_t error = normalize_path(referencePath, referencePath,
		referencePathSize);
	if (error != B_OK)
		return error;

	// get the installation location
	InstallationLocations* installationLocations = InstallationLocations::Get();
	MethodDeleter<InstallationLocations, void, &InstallationLocations::Put>
		installationLocationsDeleter(installationLocations);

	size_t installationLocationIndex;
	const char* installationLocation = installationLocations->LocationFor(
		referencePath, installationLocationIndex);
	if (installationLocation == NULL)
		return B_ENTRY_NOT_FOUND;

	// get base dir and process the path
	const char* relativePath = get_relative_directory_path(
		installationLocationIndex, baseDirectory);
	if (relativePath == NULL)
		return B_BAD_VALUE;

	ssize_t pathSize = process_path(installationLocation, architecture,
		relativePath, subPath, flags, pathBuffer, bufferSize);
	if (pathSize <= 0)
		return pathSize == 0 ? B_ENTRY_NOT_FOUND : pathSize;
	return B_OK;
}


// #pragma mark -


status_t
__find_path(const void* codePointer, path_base_directory baseDirectory,
	const char* subPath, char* pathBuffer, size_t bufferSize)
{
	return __find_path_etc(codePointer, NULL, NULL, baseDirectory, subPath, 0,
		pathBuffer, bufferSize);
}


status_t
__find_path_etc(const void* codePointer, const char* dependency,
	const char* architecture, path_base_directory baseDirectory,
	const char* subPath, uint32 flags, char* pathBuffer, size_t bufferSize)
{
	if (pathBuffer == NULL)
		return B_BAD_VALUE;

	// resolve codePointer to image info
	image_info imageInfo;
	status_t error = find_image(codePointer, imageInfo);
	if (error != B_OK)
		return error;

	if (architecture == NULL)
		architecture = __get_architecture();

	return internal_path_for_path(imageInfo.name, sizeof(imageInfo.name),
		dependency, architecture, baseDirectory, subPath, flags, pathBuffer,
		bufferSize);
}


status_t
__find_path_for_path(const char* path, path_base_directory baseDirectory,
	const char* subPath, char* pathBuffer, size_t bufferSize)
{
	return __find_path_for_path_etc(path, NULL, NULL, baseDirectory, subPath, 0,
		pathBuffer, bufferSize);
}


status_t
__find_path_for_path_etc(const char* path, const char* dependency,
	const char* architecture, path_base_directory baseDirectory,
	const char* subPath, uint32 flags, char* pathBuffer, size_t bufferSize)
{
	if (baseDirectory == B_FIND_PATH_IMAGE_PATH)
		return B_BAD_VALUE;

	char referencePath[B_PATH_NAME_LENGTH];
	if (strlcpy(referencePath, path, sizeof(referencePath))
			>= sizeof(referencePath)) {
		return B_NAME_TOO_LONG;
	}

	if (architecture == NULL)
		architecture = __guess_architecture_for_path(path);

	return internal_path_for_path(referencePath, sizeof(referencePath),
		dependency, architecture, baseDirectory, subPath, flags, pathBuffer,
		bufferSize);
}


status_t
__find_paths(path_base_directory baseDirectory, const char* subPath,
	char*** _paths, size_t* _pathCount)
{
	return __find_paths_etc(NULL, baseDirectory, subPath, 0, _paths,
		_pathCount);
}


status_t
__find_paths_etc(const char* architecture, path_base_directory baseDirectory,
	const char* subPath, uint32 flags, char*** _paths, size_t* _pathCount)
{
	if (_paths == NULL || _pathCount == NULL)
		return B_BAD_VALUE;

	// Analyze architecture. If NULL, use the caller's architecture. If the
	// effective architecture is the primary one, set architecture to NULL to
	// indicate that we don't need to insert an architecture subdirectory
	// component.
	if (architecture == NULL)
		architecture = __get_architecture();
	if (strcmp(architecture, __get_primary_architecture()) == 0)
		architecture = NULL;
	size_t architectureSize = architecture != NULL
		? strlen(architecture) + 1 : 0;

	size_t subPathLength = subPath != NULL ? strlen(subPath) + 1 : 0;

	// get the installation locations
	InstallationLocations* installationLocations = InstallationLocations::Get();
	MethodDeleter<InstallationLocations, void, &InstallationLocations::Put>
		installationLocationsDeleter(installationLocations);

	// Get the relative paths and compute the total size to allocate.
	const char* relativePaths[InstallationLocations::kCount];
	size_t totalSize = 0;

	for (size_t i = 0; i < InstallationLocations::kCount; i++) {
		if (((flags & B_FIND_PATHS_USER_ONLY) != 0
				&& !installationLocations->IsUserIndex(i))
			|| ((flags & B_FIND_PATHS_SYSTEM_ONLY) != 0
				&& !installationLocations->IsSystemIndex(i)))
			continue;

		relativePaths[i] = get_relative_directory_path(i, baseDirectory);
		if (relativePaths[i] == NULL)
			return B_BAD_VALUE;

		totalSize += strlen(installationLocations->At(i))
			+ strlen(relativePaths[i]) + subPathLength + 1;
		if (strchr(relativePaths[i], '%') != NULL)
			totalSize += architectureSize - 1;
	}

	// allocate storage
	char** paths = (char**)malloc(sizeof(char*) * InstallationLocations::kCount
		+ totalSize);
	if (paths == NULL)
		return B_NO_MEMORY;
	MemoryDeleter pathsDeleter(paths);

	// construct and process the paths
	size_t count = 0;
	char* pathBuffer = (char*)(paths + InstallationLocations::kCount);
	const char* pathBufferEnd = pathBuffer + totalSize;
	for (size_t i = 0; i < InstallationLocations::kCount; i++) {
		if (((flags & B_FIND_PATHS_USER_ONLY) != 0
				&& !installationLocations->IsUserIndex(i))
			|| ((flags & B_FIND_PATHS_SYSTEM_ONLY) != 0
				&& !installationLocations->IsSystemIndex(i)))
			continue;

		ssize_t pathSize = process_path(installationLocations->At(i),
			architecture, relativePaths[i], subPath, flags, pathBuffer,
			pathBufferEnd - pathBuffer);
		if (pathSize < 0)
			return pathSize;
		if (pathSize > 0) {
			paths[count++] = pathBuffer;
			pathBuffer += pathSize;
		}
	}

	if (count == 0)
		return B_ENTRY_NOT_FOUND;

	*_paths = paths;
	*_pathCount = count;
	pathsDeleter.Detach();

	return B_OK;
}


const char*
__guess_secondary_architecture_from_path(const char* path,
	const char* const* secondaryArchitectures,
	size_t secondaryArchitectureCount)
{
	// Get the longest existing prefix path and normalize it.
	char prefix[B_PATH_NAME_LENGTH];
	if (normalize_longest_existing_path_prefix(path, prefix, sizeof(prefix))
			!= B_OK) {
		return NULL;
	}

	// get an installation location relative path
	InstallationLocations* installationLocations = InstallationLocations::Get();
	MethodDeleter<InstallationLocations, void, &InstallationLocations::Put>
		installationLocationsDeleter(installationLocations);

	size_t installationLocationIndex;
	const char* installationLocation = installationLocations->LocationFor(
		prefix, installationLocationIndex);
	if (installationLocation == NULL)
		return NULL;

	const char* relativePath = prefix + strlen(installationLocation);
	if (relativePath[0] != '/')
		return NULL;

	// Iterate through the known paths that would indicate a secondary
	// architecture and try to match them with our given path.
	for (size_t i = 0; i < kArchitectureSpecificBaseDirectoryCount; i++) {
		const char* basePath = get_relative_directory_path(
			installationLocationIndex, kArchitectureSpecificBaseDirectories[i]);
		const char* placeholder = strchr(basePath, '%');
		if (placeholder == NULL)
			continue;

		// match the part up to the architecture placeholder
		size_t prefixLength = placeholder - basePath;
		if (strncmp(relativePath, basePath, prefixLength) != 0
			|| relativePath[prefixLength] != '/') {
			continue;
		}

		// match the architecture
		const char* architecturePart = relativePath + prefixLength + 1;
		for (size_t k = 0; k < secondaryArchitectureCount; k++) {
			const char* architecture = secondaryArchitectures[k];
			size_t architectureLength = strlen(architecture);
			if (strncmp(architecturePart, architecture, architectureLength) == 0
				&& (architecturePart[architectureLength] == '/'
					|| architecturePart[architectureLength] == '\0')) {
				return architecture;
			}
		}
	}

	return NULL;
}


B_DEFINE_WEAK_ALIAS(__find_path, find_path);
B_DEFINE_WEAK_ALIAS(__find_path_etc, find_path_etc);
B_DEFINE_WEAK_ALIAS(__find_path_for_path, find_path_for_path);
B_DEFINE_WEAK_ALIAS(__find_path_for_path_etc, find_path_for_path_etc);
B_DEFINE_WEAK_ALIAS(__find_paths, find_paths);
B_DEFINE_WEAK_ALIAS(__find_paths_etc, find_paths_etc);