* 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();
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);
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];
};
}
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)
{
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;
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;
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;
for (;;) {
struct stat st;
if (lstat(buffer, &st) == 0)
break;
char* lastSlash = strrchr(buffer, '/');
if (lastSlash == NULL || strcmp(lastSlash + 1, "..") == 0)
return B_ENTRY_NOT_FOUND;
*lastSlash = '\0';
}
size_t prefixLength = strlen(buffer);
status_t error = normalize_path(buffer, buffer, bufferSize);
if (error != B_OK)
return error;
const char* bufferEnd = buffer + bufferSize;
char* end = buffer + strlen(buffer);
const char* remainder = path + prefixLength + 1;
while (*remainder != '\0') {
if (*remainder == '/') {
remainder++;
continue;
}
const char* componentEnd = strchr(remainder, '/');
if (componentEnd == NULL)
componentEnd = remainder + strlen(remainder);
size_t componentLength = componentEnd - remainder;
if (componentLength == 1 && *remainder == '.') {
remainder++;
continue;
}
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;
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)
{
PathBuffer buffer(pathBuffer, bufferSize);
buffer.Append(installationLocation);
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);
if (subPath != NULL) {
buffer.Append("/", 1);
buffer.Append(subPath);
}
size_t totalLength = buffer.Length();
if (totalLength >= bufferSize)
return B_BUFFER_OVERFLOW;
char* path = pathBuffer;
status_t error = B_OK;
if ((flags & B_FIND_PATH_CREATE_DIRECTORY) != 0) {
error = create_directory(path);
} else if ((flags & B_FIND_PATH_CREATE_PARENT_DIRECTORY) != 0) {
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) {
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;
char packageName[B_FILE_NAME_LENGTH];
if (dependency != NULL) {
status_t error = get_file_attribute(referencePath, "SYS:PACKAGE",
packageName, sizeof(packageName));
if (error != B_OK)
return error;
char normalizedDependency[B_FILE_NAME_LENGTH];
error = normalize_dependency(dependency, normalizedDependency,
sizeof(normalizedDependency));
if (error != B_OK)
return error;
if (snprintf(referencePath, referencePathSize,
kSystemPackageLinksDirectory "/%s/%s", packageName,
normalizedDependency)
>= (ssize_t)referencePathSize) {
return B_BUFFER_OVERFLOW;
}
}
if (baseDirectory == B_FIND_PATH_IMAGE_PATH)
return copy_path(referencePath, pathBuffer, bufferSize);
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;
}
status_t error = normalize_path(referencePath, referencePath,
referencePathSize);
if (error != B_OK)
return error;
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;
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;
}
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;
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;
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;
InstallationLocations* installationLocations = InstallationLocations::Get();
MethodDeleter<InstallationLocations, void, &InstallationLocations::Put>
installationLocationsDeleter(installationLocations);
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;
}
char** paths = (char**)malloc(sizeof(char*) * InstallationLocations::kCount
+ totalSize);
if (paths == NULL)
return B_NO_MEMORY;
MemoryDeleter pathsDeleter(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)
{
char prefix[B_PATH_NAME_LENGTH];
if (normalize_longest_existing_path_prefix(path, prefix, sizeof(prefix))
!= B_OK) {
return NULL;
}
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;
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;
size_t prefixLength = placeholder - basePath;
if (strncmp(relativePath, basePath, prefixLength) != 0
|| relativePath[prefixLength] != '/') {
continue;
}
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);