blob: 85df70611ccc4fc4103577b7f6198b70f4026471 [file] [log] [blame]
/*
*
* Copyright (c) 2018 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "dlopen_fuchsia.h"
#include <fcntl.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/io.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <threads.h>
#include <zircon/dlfcn.h>
#include <zircon/syscalls.h>
#include <mutex>
#include <unordered_map>
#include <unordered_set>
#include "loader_service.h"
static char g_error[256] = {};
const char *dlerror_fuchsia(void) { return g_error; }
namespace fio = fuchsia_io;
struct FuchsiaLoaderData {
std::mutex table_lock;
// Keep a cache of ICD shared libraries to avoid loading the same library
// multiple times. dlclose doesn't currently do anything on Fuchsia, so we never
// bother calling it.
std::unordered_map<std::string, void *> icd_dso_map __attribute__((guarded_by(table_lock)));
std::unordered_set<void *> icd_dso_set __attribute__((guarded_by(table_lock)));
};
static FuchsiaLoaderData *fuchsia_loader_data;
void fuchsia_initialize() { fuchsia_loader_data = new FuchsiaLoaderData; }
void fuchsia_teardown() { delete fuchsia_loader_data; }
static void append_to_error(const char *format, ...) __attribute__((format(printf, 1, 2)));
static void append_to_error(const char *format, ...) {
// Size of an individual error message. Multiple can be concatenated.
constexpr size_t kErrorSize = 128;
char local_error[kErrorSize];
va_list args;
va_start(args, format);
vsnprintf(local_error, sizeof(local_error), format, args);
va_end(args);
// Subtract 1 because the NUL-terminator isn't counted.
strncat(g_error, local_error, sizeof(g_error) - strlen(g_error) - 1);
// Set last byte to 0 in case there wasn't enough room in g_error to fit the entire string.
g_error[sizeof(g_error) - 1] = '\0';
}
// Some VMOs may be loaded from directories outside the package which the shared library loader doesn't
// have access to. They can only be loaded by opening the file and using dlopen_vmo.
static void *dlopen_using_vmo(const char *name, int mode) {
int fd;
zx_status_t status = fdio_open_fd(
name, static_cast<uint32_t>(fio::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kRightExecutable), &fd);
if (status != ZX_OK) {
append_to_error("libvulkan.so:dlopen_using_vmo: couldn't open fd, %d\n", status);
return nullptr;
}
zx::vmo vmo;
status = fdio_get_vmo_exec(fd, vmo.reset_and_get_address());
close(fd);
if (status != ZX_OK) {
append_to_error("libvulkan.so:dlopen_using_vmo: couldn't get vmo exec: %d\n", status);
return nullptr;
}
void *result = dlopen_vmo(vmo.get(), mode);
if (!result) {
append_to_error("%s", dlerror());
}
return result;
}
static void *dlopen_from_vulkan_loader(const char *name, int mode, bool *retry_out) {
// Connect to the vulkan loader service to request this library.
// If loading fails at these early points, the ICD may just not be available
// through the loader service, so retry using plain dlopen.
*retry_out = true;
std::lock_guard<std::mutex> lock(fuchsia_loader_data->table_lock);
auto it = fuchsia_loader_data->icd_dso_map.find(name);
if (it != fuchsia_loader_data->icd_dso_map.end()) {
return it->second;
}
auto &loader_svc = get_vulkan_loader_service();
if (!loader_svc.is_valid()) {
append_to_error("libvulkan.so:dlopen_fuchsia: no connection to loader svc\n");
return nullptr;
}
auto get_result = loader_svc->Get(fidl::StringView::FromExternal(name));
if (!get_result.ok()) {
append_to_error("libvulkan.so:dlopen_fuchsia: Get() failed: %d\n", get_result.status());
return nullptr;
}
if (!get_result->lib) {
append_to_error("libvulkan.so:dlopen_fuchsia: Get() returned invalid vmo\n");
return nullptr;
}
// If we've got a valid VMO matching the name then that VMO is expected to work.
*retry_out = false;
void *result = dlopen_vmo(get_result->lib.get(), mode);
if (!result) {
append_to_error("%s", dlerror());
} else {
fuchsia_loader_data->icd_dso_map[name] = result;
fuchsia_loader_data->icd_dso_set.insert(result);
}
return result;
}
void *dlopen_fuchsia(const char *name, int mode, bool driver) {
// All errors are appended to g_error, so reset it to empty.
g_error[0] = '\0';
void *result;
if (driver) {
// ICDs generally should by loaded from the loader service, so try that first.
bool retry{};
result = dlopen_from_vulkan_loader(name, mode, &retry);
if (result || !retry) {
return result;
}
}
result = dlopen(name, mode);
if (result != nullptr) {
return result;
}
return dlopen_using_vmo(name, mode);
}
void dlclose_fuchsia(void *dso) {
{
std::lock_guard<std::mutex> lock(fuchsia_loader_data->table_lock);
if (fuchsia_loader_data->icd_dso_set.count(dso)) {
// dlclose doesn't actually do anything on Fuchsia, so it's better
// to cache the shared object forever rather than leak it.
return;
}
}
// It's fine to dlclose normal shared objects with sonames because the
// library loader caches them by soname.
dlclose(dso);
}