diff --git a/Zend/zend_API.h b/Zend/zend_API.h index c1ccbf13666a..028e99f43c4d 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -238,9 +238,11 @@ typedef struct _zend_fcall_info_cache { #define ZEND_MODULE_GLOBALS_CTOR_D(module) void ZEND_MODULE_GLOBALS_CTOR_N(module)(zend_##module##_globals *module##_globals) #define ZEND_MODULE_GLOBALS_DTOR_D(module) void ZEND_MODULE_GLOBALS_DTOR_N(module)(zend_##module##_globals *module##_globals) +#define ZEND_MODULE_ENTRY(name) (&name##_module_entry) + #define ZEND_GET_MODULE(name) \ BEGIN_EXTERN_C()\ - ZEND_DLEXPORT zend_module_entry *get_module(void) { return &name##_module_entry; }\ + ZEND_DLEXPORT zend_module_entry *get_module(void) { return ZEND_MODULE_ENTRY(name); }\ END_EXTERN_C() #define ZEND_BEGIN_MODULE_GLOBALS(module_name) \ diff --git a/Zend/zend_extensions.c b/Zend/zend_extensions.c index a4e5a38f90d8..84c1f8190304 100644 --- a/Zend/zend_extensions.c +++ b/Zend/zend_extensions.c @@ -19,6 +19,8 @@ #include "zend_extensions.h" #include "zend_system_id.h" +#include "zend_API.h" +#include "zend_modules.h" ZEND_API zend_llist zend_extensions; ZEND_API uint32_t zend_extension_flags = 0; @@ -26,6 +28,41 @@ ZEND_API int zend_op_array_extension_handles = 0; ZEND_API int zend_internal_function_extension_handles = 0; static int last_resource_number; +// TODO: revisit this name if we want to make it ZEND_API. +static zend_result zend_try_register_hybrid_module(zend_extension *extension) +{ + zend_module_entry *module = extension->module_entry; + + if (!module) { + return SUCCESS; + } + + if (module->zend_api != ZEND_MODULE_API_NO) { + zend_error(E_CORE_WARNING, + "Hybrid module \"%s\" from Zend extension \"%s\" cannot be initialized: " + "Module API=%d, PHP API=%d", + module->name ? module->name : "", + extension->name ? extension->name : "", + module->zend_api, ZEND_MODULE_API_NO); + return FAILURE; + } + + if (!module->build_id || strcmp(module->build_id, ZEND_MODULE_BUILD_ID)) { + zend_error(E_CORE_WARNING, + "Hybrid module \"%s\" from Zend extension \"%s\" cannot be initialized: " + "Module build ID=%s, PHP build ID=%s", + module->name ? module->name : "", + extension->name ? extension->name : "", + module->build_id ? module->build_id : "", + ZEND_MODULE_BUILD_ID); + return FAILURE; + } + + // The handle is owned by the zend_extension for hybrid extensions. + module->handle = NULL; + return zend_register_module_ex(module, MODULE_PERSISTENT) ? SUCCESS : FAILURE; +} + zend_result zend_load_extension(const char *path) { #if ZEND_EXTENSIONS_SUPPORT @@ -134,6 +171,14 @@ zend_result zend_load_extension_handle(DL_HANDLE handle, const char *path) return FAILURE; } + // The module_entry is registered before the extension is registered + // because zend_register_extension() returns void and is ZEND_API, so + // operations which can fail need to be performed before it. + if (zend_try_register_hybrid_module(new_extension) != SUCCESS) { + DL_UNLOAD(handle); + return FAILURE; + } + zend_register_extension(new_extension, handle); return SUCCESS; #else diff --git a/Zend/zend_extensions.h b/Zend/zend_extensions.h index 4de8ad5414c2..fad81050b603 100644 --- a/Zend/zend_extensions.h +++ b/Zend/zend_extensions.h @@ -54,6 +54,7 @@ typedef struct _zend_extension_version_info { #define ZEND_EXTENSION_BUILD_ID "API" ZEND_TOSTR(ZEND_EXTENSION_API_NO) ZEND_BUILD_TS ZEND_BUILD_DEBUG ZEND_BUILD_SYSTEM ZEND_BUILD_EXTRA typedef struct _zend_extension zend_extension; +typedef struct _zend_module_entry zend_module_entry; /* Typedef's for zend_extension function pointers */ typedef int (*startup_func_t)(zend_extension *extension); @@ -101,7 +102,19 @@ struct _zend_extension { int (*build_id_check)(const char* build_id); op_array_persist_calc_func_t op_array_persist_calc; op_array_persist_func_t op_array_persist; - void *reserved5; + + /* Setting a module_entry indicates a hybrid extension, meaning an + * extension which is also a module. Such extensions can be loaded with + * either "zend_extension=" or "extension=" by INI. + * + * The symbol "get_module" must _not_ be exported, i.e. don't call + * ZEND_GET_MODULE(), and instead use ZEND_MODULE_ENTRY() to assign a value + * to `.module_entry`. + * + * The DL_HANDLE is owned by the zend_extension for hybrid extensions, so + * the handle should be null for the module entry. + */ + zend_module_entry *module_entry; void *reserved6; void *reserved7; void *reserved8; diff --git a/ext/standard/dl.c b/ext/standard/dl.c index 31adbceac8c2..cf6726ddf2b9 100644 --- a/ext/standard/dl.c +++ b/ext/standard/dl.c @@ -21,6 +21,8 @@ #include "php_globals.h" #include "php_ini.h" #include "ext/standard/info.h" +#include "Zend/zend_API.h" +#include "Zend/zend_extensions.h" #include "SAPI.h" @@ -106,6 +108,28 @@ PHPAPI void *php_load_shlib(const char *path, char **errp) } /* }}} */ +/* This helper handles the hybrid zend_{extension,module_entry} fallback path. + * It unloads the handle on failure but it does not free libpath. */ +static zend_result php_try_load_hybrid_zend_extension( + DL_HANDLE handle, const char *filename, char *libpath, int error_type) +{ + zend_extension *zend_extension_entry = (zend_extension *) DL_FETCH_SYMBOL(handle, "zend_extension_entry"); + if (!zend_extension_entry) { + zend_extension_entry = (zend_extension *) DL_FETCH_SYMBOL(handle, "_zend_extension_entry"); + } + if (zend_extension_entry && zend_extension_entry->module_entry) { + return zend_load_extension_handle(handle, libpath); + } + if (zend_extension_entry) { + php_error_docref(NULL, error_type, "Invalid library (appears to be a Zend Extension, try loading using zend_extension=%s from php.ini)", filename); + DL_UNLOAD(handle); + return FAILURE; + } + php_error_docref(NULL, error_type, "Invalid library (maybe not a PHP library) '%s'", filename); + DL_UNLOAD(handle); + return FAILURE; +} + /* {{{ php_load_extension */ PHPAPI int php_load_extension(const char *filename, int type, int start_now) { @@ -173,13 +197,12 @@ PHPAPI int php_load_extension(const char *filename, int type, int start_now) efree(orig_libpath); efree(err1); } - efree(libpath); - #ifdef PHP_WIN32 if (!php_win32_image_compatible(handle, &err1)) { php_error_docref(NULL, error_type, "%s", err1); efree(err1); DL_UNLOAD(handle); + efree(libpath); return FAILURE; } #endif @@ -195,15 +218,13 @@ PHPAPI int php_load_extension(const char *filename, int type, int start_now) } if (!get_module) { - if (DL_FETCH_SYMBOL(handle, "zend_extension_entry") || DL_FETCH_SYMBOL(handle, "_zend_extension_entry")) { - DL_UNLOAD(handle); - php_error_docref(NULL, error_type, "Invalid library (appears to be a Zend Extension, try loading using zend_extension=%s from php.ini)", filename); - return FAILURE; - } - DL_UNLOAD(handle); - php_error_docref(NULL, error_type, "Invalid library (maybe not a PHP library) '%s'", filename); - return FAILURE; + // If get_module still isn't found, maybe it's a zend_extension with a module entry. + zend_result result = php_try_load_hybrid_zend_extension(handle, filename, libpath, error_type); + efree(libpath); + return result; } + efree(libpath); + module_entry = get_module(); if (zend_hash_str_exists(&module_registry, module_entry->name, strlen(module_entry->name))) { zend_error(E_CORE_WARNING, "Module \"%s\" is already loaded", module_entry->name);