/* * 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 #include #include #include #include #include #include #include #include #include #include #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(fLocations[0])); free(const_cast(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 "/" 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 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 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 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);