evmc/lib/loader/loader.c

324 lines
9.6 KiB
C
Raw Normal View History

/* EVMC: Ethereum Client-VM Connector API.
* Copyright 2018-2019 The EVMC Authors.
* Licensed under the Apache License, Version 2.0.
*/
#include <evmc/loader.h>
2018-08-28 14:26:38 +00:00
#include <evmc/evmc.h>
2018-08-20 13:46:31 +00:00
#include <evmc/helpers.h>
2019-04-16 18:12:15 +00:00
#include <stdarg.h>
#include <stdint.h>
2019-04-16 18:12:15 +00:00
#include <stdio.h>
#include <string.h>
#if defined(EVMC_LOADER_MOCK)
#include "../../test/unittests/loader_mock.h"
#elif _WIN32
2018-07-05 11:01:20 +00:00
#include <Windows.h>
#define DLL_HANDLE HMODULE
#define DLL_OPEN(filename) LoadLibrary(filename)
#define DLL_CLOSE(handle) FreeLibrary(handle)
#define DLL_GET_CREATE_FN(handle, name) (evmc_create_fn)(uintptr_t) GetProcAddress(handle, name)
#define DLL_GET_ERROR_MSG() NULL
2018-07-05 11:01:20 +00:00
#else
#include <dlfcn.h>
2018-07-05 11:01:20 +00:00
#define DLL_HANDLE void*
#define DLL_OPEN(filename) dlopen(filename, RTLD_LAZY)
#define DLL_CLOSE(handle) dlclose(handle)
#define DLL_GET_CREATE_FN(handle, name) (evmc_create_fn)(uintptr_t) dlsym(handle, name)
#define DLL_GET_ERROR_MSG() dlerror()
2018-07-05 11:01:20 +00:00
#endif
#ifdef __has_attribute
#if __has_attribute(format)
#define ATTR_FORMAT(archetype, string_index, first_to_check) \
__attribute__((format(archetype, string_index, first_to_check)))
#endif
#endif
#ifndef ATTR_FORMAT
#define ATTR_FORMAT(...)
#endif
/*
* Limited variant of strcpy_s().
*/
2019-05-30 13:30:30 +00:00
#if !defined(EVMC_LOADER_MOCK)
static
#endif
int
strcpy_sx(char* dest, size_t destsz, const char* src)
2018-07-05 11:01:20 +00:00
{
size_t len = strlen(src);
if (len >= destsz)
{
// The input src will not fit into the dest buffer.
// Set the first byte of the dest to null to make it effectively empty string
// and return error.
dest[0] = 0;
return 1;
}
2018-07-05 11:01:20 +00:00
memcpy(dest, src, len);
dest[len] = 0;
return 0;
2018-07-05 11:01:20 +00:00
}
#define PATH_MAX_LENGTH 4096
static const char* last_error_msg = NULL;
2019-04-16 18:12:15 +00:00
#define LAST_ERROR_MSG_BUFFER_SIZE 511
// Buffer for formatted error messages.
// It has one null byte extra to avoid buffer read overflow during concurrent access.
static char last_error_msg_buffer[LAST_ERROR_MSG_BUFFER_SIZE + 1];
ATTR_FORMAT(printf, 2, 3)
2019-04-16 18:12:15 +00:00
static enum evmc_loader_error_code set_error(enum evmc_loader_error_code error_code,
const char* format,
...)
{
va_list args;
va_start(args, format);
if (vsnprintf(last_error_msg_buffer, LAST_ERROR_MSG_BUFFER_SIZE, format, args) <
LAST_ERROR_MSG_BUFFER_SIZE)
last_error_msg = last_error_msg_buffer;
va_end(args);
return error_code;
}
evmc_create_fn evmc_load(const char* filename, enum evmc_loader_error_code* error_code)
{
last_error_msg = NULL; // Reset last error.
enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS;
evmc_create_fn create_fn = NULL;
if (!filename)
{
2019-04-16 18:12:15 +00:00
ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: file name cannot be null");
goto exit;
}
const size_t length = strlen(filename);
2019-04-16 18:12:15 +00:00
if (length == 0)
{
ec = set_error(EVMC_LOADER_INVALID_ARGUMENT, "invalid argument: file name cannot be empty");
goto exit;
}
else if (length > PATH_MAX_LENGTH)
{
2019-04-16 18:12:15 +00:00
ec = set_error(EVMC_LOADER_INVALID_ARGUMENT,
"invalid argument: file name is too long (%d, maximum allowed length is %d)",
(int)length, PATH_MAX_LENGTH);
goto exit;
}
2018-07-05 11:01:20 +00:00
DLL_HANDLE handle = DLL_OPEN(filename);
if (!handle)
{
// Get error message if available.
last_error_msg = DLL_GET_ERROR_MSG();
2019-04-16 18:12:15 +00:00
if (last_error_msg)
ec = EVMC_LOADER_CANNOT_OPEN;
else
ec = set_error(EVMC_LOADER_CANNOT_OPEN, "cannot open %s", filename);
goto exit;
}
2018-07-05 12:16:54 +00:00
// Create name buffer with the prefix.
const char prefix[] = "evmc_create_";
const size_t prefix_length = strlen(prefix);
char prefixed_name[sizeof(prefix) + PATH_MAX_LENGTH];
strcpy_sx(prefixed_name, sizeof(prefixed_name), prefix);
2018-07-05 12:16:54 +00:00
// Find filename in the path.
const char* sep_pos = strrchr(filename, '/');
2018-07-10 09:08:33 +00:00
#if _WIN32
// On Windows check also Windows classic path separator.
const char* sep_pos_windows = strrchr(filename, '\\');
sep_pos = sep_pos_windows > sep_pos ? sep_pos_windows : sep_pos;
#endif
const char* name_pos = sep_pos ? sep_pos + 1 : filename;
2018-07-05 12:16:54 +00:00
// Skip "lib" prefix if present.
const char lib_prefix[] = "lib";
const size_t lib_prefix_length = strlen(lib_prefix);
if (strncmp(name_pos, lib_prefix, lib_prefix_length) == 0)
name_pos += lib_prefix_length;
char* base_name = prefixed_name + prefix_length;
strcpy_sx(base_name, PATH_MAX_LENGTH, name_pos);
2018-07-05 12:16:54 +00:00
// Trim the file extension.
char* ext_pos = strrchr(prefixed_name, '.');
if (ext_pos)
*ext_pos = 0;
2018-07-05 12:16:54 +00:00
// Replace all "-" with "_".
char* dash_pos = base_name;
while ((dash_pos = strchr(dash_pos, '-')) != NULL)
*dash_pos++ = '_';
// Search for the built function name.
while ((create_fn = DLL_GET_CREATE_FN(handle, prefixed_name)) == NULL)
{
// Shorten the base name by skipping the `word_` segment.
const char* shorter_name_pos = strchr(base_name, '_');
if (!shorter_name_pos)
break;
memmove(base_name, shorter_name_pos + 1, strlen(shorter_name_pos) + 1);
}
if (!create_fn)
create_fn = DLL_GET_CREATE_FN(handle, "evmc_create");
if (!create_fn)
{
2018-07-05 11:01:20 +00:00
DLL_CLOSE(handle);
2019-04-16 18:12:15 +00:00
ec = set_error(EVMC_LOADER_SYMBOL_NOT_FOUND, "EVMC create function not found in %s",
filename);
}
exit:
if (error_code)
*error_code = ec;
return create_fn;
}
const char* evmc_last_error_msg()
{
const char* m = last_error_msg;
last_error_msg = NULL;
return m;
}
2018-08-28 14:26:38 +00:00
struct evmc_instance* evmc_load_and_create(const char* filename,
enum evmc_loader_error_code* error_code)
{
// First load the DLL. This also resets the last_error_msg;
evmc_create_fn create_fn = evmc_load(filename, error_code);
if (!create_fn)
return NULL;
enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS;
struct evmc_instance* instance = create_fn();
if (!instance)
{
ec = set_error(EVMC_LOADER_INSTANCE_CREATION_FAILURE,
"creating EVMC instance of %s has failed", filename);
goto exit;
}
2018-08-20 13:46:31 +00:00
if (!evmc_is_abi_compatible(instance))
{
ec = set_error(EVMC_LOADER_ABI_VERSION_MISMATCH,
"EVMC ABI version %d of %s mismatches the expected version %d",
instance->abi_version, filename, EVMC_ABI_VERSION);
evmc_destroy(instance);
instance = NULL;
goto exit;
}
exit:
if (error_code)
*error_code = ec;
return instance;
}
/// Gets the token delimited by @p delim character of the string pointed by the @p str_ptr.
/// If the delimiter is not found, the whole string is returned.
/// The @p str_ptr is also slided after the delimiter or to the string end
/// if the delimiter is not found (in this case the @p str_ptr points to an empty string).
static char* get_token(char** str_ptr, char delim)
{
char* str = *str_ptr;
char* delim_pos = strchr(str, delim);
if (delim_pos)
{
// If the delimiter is found, null it to get null-terminated prefix
// and slide the str_ptr after the delimiter.
*delim_pos = '\0';
*str_ptr = delim_pos + 1;
}
else
{
// Otherwise, slide the str_ptr to the end and return the whole string as the prefix.
*str_ptr += strlen(str);
}
return str;
}
struct evmc_instance* evmc_load_and_configure(const char* config,
enum evmc_loader_error_code* error_code)
{
enum evmc_loader_error_code ec = EVMC_LOADER_SUCCESS;
struct evmc_instance* instance = NULL;
char config_copy_buffer[PATH_MAX_LENGTH];
if (strcpy_sx(config_copy_buffer, sizeof(config_copy_buffer), config) != 0)
{
ec = set_error(EVMC_LOADER_INVALID_ARGUMENT,
"invalid argument: configuration is too long (maximum allowed length is %d)",
(int)sizeof(config_copy_buffer));
goto exit;
}
char* options = config_copy_buffer;
const char* path = get_token(&options, ',');
instance = evmc_load_and_create(path, error_code);
if (!instance)
return NULL;
if (instance->set_option == NULL && strlen(options) != 0)
{
ec = set_error(EVMC_LOADER_INVALID_OPTION_NAME, "%s (%s) does not support any options",
instance->name, path);
goto exit;
}
while (strlen(options) != 0)
{
char* option = get_token(&options, ',');
// Slit option into name and value by taking the name token.
// The option variable will have the value, can be empty.
const char* name = get_token(&option, '=');
enum evmc_set_option_result r = instance->set_option(instance, name, option);
switch (r)
{
case EVMC_SET_OPTION_SUCCESS:
break;
case EVMC_SET_OPTION_INVALID_NAME:
ec = set_error(EVMC_LOADER_INVALID_OPTION_NAME, "%s (%s): unknown option '%s'",
instance->name, path, name);
goto exit;
case EVMC_SET_OPTION_INVALID_VALUE:
ec = set_error(EVMC_LOADER_INVALID_OPTION_VALUE,
"%s (%s): unsupported value '%s' for option '%s'", instance->name, path,
option, name);
goto exit;
}
}
exit:
if (error_code)
*error_code = ec;
if (ec == EVMC_LOADER_SUCCESS)
return instance;
if (instance)
evmc_destroy(instance);
return NULL;
}