diff --git a/CHANGES b/CHANGES index 9a223fdd120a42bc83e66f4f12f2d27756786f70..f7078ea79177d32664af4c6072b8de822f9d94bb 100644 --- a/CHANGES +++ b/CHANGES @@ -63,6 +63,9 @@ Build System Please see https://wiki.asterisk.org/wiki/x/J4GLAQ for more information on configuring and installing PJSIP for usage with Asterisk. + * Optional API was re-implemented to be more portable, and no longer requires + weak reference support from the compiler. The build option OPTIONAL_API may + be disabled to disable Optional API support. Applications ------------------ diff --git a/build_tools/cflags.xml b/build_tools/cflags.xml index c4d15a67ba03c6692121a4c50cdc6927a0b35eb7..e7bec1672ece735620b93c28e00de062b338da56 100644 --- a/build_tools/cflags.xml +++ b/build_tools/cflags.xml @@ -1,4 +1,4 @@ - <category name="MENUSELECT_CFLAGS" displayname="Compiler Flags" positive_output="yes" remove_on_change=".lastclean"> +<category name="MENUSELECT_CFLAGS" displayname="Compiler Flags" positive_output="yes" remove_on_change=".lastclean"> <member name="DONT_OPTIMIZE" displayname="Disable Optimizations by the Compiler"> <support_level>core</support_level> </member> @@ -30,6 +30,13 @@ --> <support_level>extended</support_level> </member> + <member name="OPTIONAL_API" displayname="Enable the optional API"> + <!-- Added to manually disable the optional API, since + it's now supported on all systems. + --> + <defaultenabled>yes</defaultenabled> + <support_level>extended</support_level> + </member> <member name="BETTER_BACKTRACES" displayname="Use libbfd (GPL) to generate better inline backtraces"> <depend>BFD</depend> <depend>DLADDR</depend> diff --git a/channels/chan_sip.c b/channels/chan_sip.c index e6d9cffc7f8151d7b21ba356b843fa6258ec358d..55b1ed5506cad359fa5e14a8670d2dc9badc9a82 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -298,6 +298,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/stasis_system.h" #include "asterisk/stasis_channels.h" #include "asterisk/features_config.h" +#include "asterisk/http_websocket.h" /*** DOCUMENTATION <application name="SIPDtmfMode" language="en_US"> diff --git a/channels/sip/include/sip.h b/channels/sip/include/sip.h index 724581b71f7967111d071052db55b3d90f6b3cfd..c0704ec79f29076b2f726a7f360970af3e49700e 100644 --- a/channels/sip/include/sip.h +++ b/channels/sip/include/sip.h @@ -35,7 +35,6 @@ #include "asterisk/indications.h" #include "asterisk/security_events.h" #include "asterisk/features.h" -#include "asterisk/http_websocket.h" #include "asterisk/rtp_engine.h" #include "asterisk/netsock2.h" #include "asterisk/features_config.h" @@ -769,6 +768,8 @@ struct sip_settings { int default_max_forwards; /*!< Default max forwards (SIP Anti-loop) */ }; +struct ast_websocket; + /*! \brief The SIP socket definition */ struct sip_socket { enum ast_transport type; /*!< UDP, TCP or TLS */ diff --git a/configure b/configure index 2d04b6d11d89e80b6220bd04120491231ee4c87d..3dff95f606454574b432db5122b889f3b90cc7cf 100755 --- a/configure +++ b/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.ac Revision: 395985 . +# From configure.ac Revision: 397868 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.69 for asterisk trunk. # @@ -684,6 +684,7 @@ PKG_CONFIG_PATH PKG_CONFIG PBX_DLADDR PBX_IP_MTU_DISCOVER +PBX_RTLD_NOLOAD PBX_GLOB_BRACE PBX_GLOB_NOMAGIC AST_RPATH @@ -696,7 +697,6 @@ AST_TRAMPOLINES AST_DECLARATION_AFTER_STATEMENT GC_LDFLAGS GC_CFLAGS -PBX_WEAKREF PBX_PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP PBX_PTHREAD_RWLOCK_INITIALIZER AST_ASTERISKSSL @@ -16564,28 +16564,17 @@ CFLAGS="$saved_CFLAGS" -# Support weak symbols on a platform specific basis. The Mac OS X -# (Darwin) support must be isolated from the other platforms because -# it has caused other platforms to fail. -# -case "${OSARCH}" in - darwin*) - # Allow weak symbol support on Darwin platforms only because there - # is active community support for it. - # However, Darwin seems to break weak symbols for each new version. - # - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiler 'attribute weak_import' support" >&5 -$as_echo_n "checking for compiler 'attribute weak_import' support... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiler 'attribute may_alias' support" >&5 +$as_echo_n "checking for compiler 'attribute may_alias' support... " >&6; } saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -Wall -Wno-unused -Werror" -PBX_WEAKREF=0 + if test "x" = "x" then cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ - void __attribute__((weak_import)) *test(void *muffin, ...) {return (void *) 0;} + void __attribute__((may_alias)) *test(void *muffin, ...) {return (void *) 0;} int main () { @@ -16597,10 +16586,10 @@ _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - PBX_WEAKREF=1 + cat >>confdefs.h <<_ACEOF -#define HAVE_ATTRIBUTE_weak_import 1 +#define HAVE_ATTRIBUTE_may_alias 1 _ACEOF else @@ -16624,10 +16613,10 @@ _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - PBX_WEAKREF=1 + cat >>confdefs.h <<_ACEOF -#define HAVE_ATTRIBUTE_weak_import 1 +#define HAVE_ATTRIBUTE_may_alias 1 _ACEOF else @@ -16643,21 +16632,17 @@ CFLAGS="$saved_CFLAGS" - # Several other platforms including Linux have GCC versions that - # define the weak attribute. However, this attribute is only - # setup for use in the code by Darwin. - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiler 'attribute weak' support" >&5 -$as_echo_n "checking for compiler 'attribute weak' support... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiler 'attribute constructor' support" >&5 +$as_echo_n "checking for compiler 'attribute constructor' support... " >&6; } saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -Wall -Wno-unused -Werror" -PBX_WEAKREF=0 + if test "x" = "x" then cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ - void __attribute__((weak)) *test(void *muffin, ...) {return (void *) 0;} + void __attribute__((constructor)) *test(void *muffin, ...) {return (void *) 0;} int main () { @@ -16669,10 +16654,10 @@ _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - PBX_WEAKREF=1 + cat >>confdefs.h <<_ACEOF -#define HAVE_ATTRIBUTE_weak 1 +#define HAVE_ATTRIBUTE_constructor 1 _ACEOF else @@ -16696,82 +16681,10 @@ _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - PBX_WEAKREF=1 - -cat >>confdefs.h <<_ACEOF -#define HAVE_ATTRIBUTE_weak 1 -_ACEOF - -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -fi - - -CFLAGS="$saved_CFLAGS" - - - ;; - linux-gnu) - # Primarily support weak symbols on Linux platforms. - # - -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiler 'attribute weakref' support" >&5 -$as_echo_n "checking for compiler 'attribute weakref' support... " >&6; } -saved_CFLAGS="$CFLAGS" -CFLAGS="$CFLAGS -Wall -Wno-unused -Werror" -PBX_WEAKREF=0 - -if test "xweakref("foo")" = "x" -then -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -static void __attribute__((weakref)) *test(void *muffin, ...) {return (void *) 0;} -int -main () -{ - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - PBX_WEAKREF=1 cat >>confdefs.h <<_ACEOF -#define HAVE_ATTRIBUTE_weakref 1 -_ACEOF - -else - { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 -$as_echo "no" >&6; } - -fi -rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext -else -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ -static void __attribute__((weakref("foo"))) *test(void *muffin, ...) {return (void *) 0;} -int -main () -{ - - ; - return 0; -} -_ACEOF -if ac_fn_c_try_compile "$LINENO"; then : - { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 -$as_echo "yes" >&6; } - PBX_WEAKREF=1 - -cat >>confdefs.h <<_ACEOF -#define HAVE_ATTRIBUTE_weakref 1 +#define HAVE_ATTRIBUTE_constructor 1 _ACEOF else @@ -16786,24 +16699,18 @@ fi CFLAGS="$saved_CFLAGS" - ;; - *) - # Allow weak symbols on other platforms. However, any problems - # with this feature on other platforms must be fixed by the - # community. - # -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiler 'attribute weakref' support" >&5 -$as_echo_n "checking for compiler 'attribute weakref' support... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for compiler 'attribute destructor' support" >&5 +$as_echo_n "checking for compiler 'attribute destructor' support... " >&6; } saved_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -Wall -Wno-unused -Werror" -PBX_WEAKREF=0 -if test "xweakref("foo")" = "x" + +if test "x" = "x" then cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -static void __attribute__((weakref)) *test(void *muffin, ...) {return (void *) 0;} + void __attribute__((destructor)) *test(void *muffin, ...) {return (void *) 0;} int main () { @@ -16815,10 +16722,10 @@ _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - PBX_WEAKREF=1 + cat >>confdefs.h <<_ACEOF -#define HAVE_ATTRIBUTE_weakref 1 +#define HAVE_ATTRIBUTE_destructor 1 _ACEOF else @@ -16830,7 +16737,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -static void __attribute__((weakref("foo"))) *test(void *muffin, ...) {return (void *) 0;} + void __attribute__(()) *test(void *muffin, ...) {return (void *) 0;} int main () { @@ -16842,10 +16749,10 @@ _ACEOF if ac_fn_c_try_compile "$LINENO"; then : { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - PBX_WEAKREF=1 + cat >>confdefs.h <<_ACEOF -#define HAVE_ATTRIBUTE_weakref 1 +#define HAVE_ATTRIBUTE_destructor 1 _ACEOF else @@ -16860,8 +16767,6 @@ fi CFLAGS="$saved_CFLAGS" - ;; -esac { $as_echo "$as_me:${as_lineno-$LINENO}: checking for -ffunction-sections support" >&5 $as_echo_n "checking for -ffunction-sections support... " >&6; } @@ -17454,6 +17359,53 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + if test "x${PBX_RTLD_NOLOAD}" != "x1"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for RTLD_NOLOAD in dlfcn.h" >&5 +$as_echo_n "checking for RTLD_NOLOAD in dlfcn.h... " >&6; } + saved_cppflags="${CPPFLAGS}" + if test "x${RTLD_NOLOAD_DIR}" != "x"; then + RTLD_NOLOAD_INCLUDE="-I${RTLD_NOLOAD_DIR}/include" + fi + CPPFLAGS="${CPPFLAGS} ${RTLD_NOLOAD_INCLUDE}" + + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + #include <dlfcn.h> +int +main () +{ +#if defined(RTLD_NOLOAD) + int foo = 0; + #else + int foo = bar; + #endif + 0 + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + PBX_RTLD_NOLOAD=1 + +$as_echo "#define HAVE_RTLD_NOLOAD 1" >>confdefs.h + + + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + CPPFLAGS="${saved_cppflags}" + fi + + + + if test "x${PBX_IP_MTU_DISCOVER}" != "x1"; then { $as_echo "$as_me:${as_lineno-$LINENO}: checking for IP_MTU_DISCOVER in netinet/in.h" >&5 $as_echo_n "checking for IP_MTU_DISCOVER in netinet/in.h... " >&6; } diff --git a/configure.ac b/configure.ac index 69228b517277dec62a7644354656b88bb13bb0b3..e409327fc1914b79b5a51304d56a454c9a16a656 100644 --- a/configure.ac +++ b/configure.ac @@ -957,37 +957,9 @@ AST_GCC_ATTRIBUTE(always_inline) AST_GCC_ATTRIBUTE(deprecated) AST_GCC_ATTRIBUTE(sentinel) AST_GCC_ATTRIBUTE(warn_unused_result) - -# Support weak symbols on a platform specific basis. The Mac OS X -# (Darwin) support must be isolated from the other platforms because -# it has caused other platforms to fail. -# -case "${OSARCH}" in - darwin*) - # Allow weak symbol support on Darwin platforms only because there - # is active community support for it. - # However, Darwin seems to break weak symbols for each new version. - # - AST_GCC_ATTRIBUTE(weak_import, [], [], PBX_WEAKREF) - - # Several other platforms including Linux have GCC versions that - # define the weak attribute. However, this attribute is only - # setup for use in the code by Darwin. - AST_GCC_ATTRIBUTE(weak, [], [], PBX_WEAKREF) - ;; - linux-gnu) - # Primarily support weak symbols on Linux platforms. - # - AST_GCC_ATTRIBUTE(weakref, [weakref("foo")], static, PBX_WEAKREF) - ;; - *) - # Allow weak symbols on other platforms. However, any problems - # with this feature on other platforms must be fixed by the - # community. - # - AST_GCC_ATTRIBUTE(weakref, [weakref("foo")], static, PBX_WEAKREF) - ;; -esac +AST_GCC_ATTRIBUTE(may_alias) +AST_GCC_ATTRIBUTE(constructor) +AST_GCC_ATTRIBUTE(destructor) AC_MSG_CHECKING(for -ffunction-sections support) saved_CFLAGS="${CFLAGS}" @@ -1181,6 +1153,8 @@ AST_C_DEFINE_CHECK([GLOB_NOMAGIC], [GLOB_NOMAGIC], [glob.h]) AST_C_DEFINE_CHECK([GLOB_BRACE], [GLOB_BRACE], [glob.h]) +AST_C_DEFINE_CHECK([RTLD_NOLOAD], [RTLD_NOLOAD], [dlfcn.h]) + AST_C_DEFINE_CHECK([IP_MTU_DISCOVER], [IP_MTU_DISCOVER], [netinet/in.h]) AC_CHECK_HEADER([libkern/OSAtomic.h], diff --git a/include/asterisk/ari.h b/include/asterisk/ari.h index 4f2954000ca9af2087d516261e224f7aad5d6002..dfeef513cf54d2dd0e48a2c99a1c66a1f898b5e6 100644 --- a/include/asterisk/ari.h +++ b/include/asterisk/ari.h @@ -21,7 +21,7 @@ /*! \file * - * \brief Stasis RESTful API hooks. + * \brief Asterisk RESTful API hooks. * * This header file is used mostly as glue code between generated declarations * and res_ari.c. @@ -31,7 +31,14 @@ #include "asterisk/http.h" #include "asterisk/json.h" -#include "asterisk/http_websocket.h" + +/* Forward-declare websocket structs. This avoids including http_websocket.h, + * which causes optional_api stuff to happen, which makes optional_api more + * difficult to debug. */ + +struct ast_websocket_server; + +struct ast_websocket; /*! * \brief Configured encoding format for JSON output. diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in index 1796a3f689e6965837ac34bccfe92c32c854363f..87a769ed072df7a3077867b1ac3834d5f43da83f 100644 --- a/include/asterisk/autoconfig.h.in +++ b/include/asterisk/autoconfig.h.in @@ -82,12 +82,21 @@ /* Define to 1 if your GCC C compiler supports the 'const' attribute. */ #undef HAVE_ATTRIBUTE_const +/* Define to 1 if your GCC C compiler supports the 'constructor' attribute. */ +#undef HAVE_ATTRIBUTE_constructor + /* Define to 1 if your GCC C compiler supports the 'deprecated' attribute. */ #undef HAVE_ATTRIBUTE_deprecated +/* Define to 1 if your GCC C compiler supports the 'destructor' attribute. */ +#undef HAVE_ATTRIBUTE_destructor + /* Define to 1 if your GCC C compiler supports the 'malloc' attribute. */ #undef HAVE_ATTRIBUTE_malloc +/* Define to 1 if your GCC C compiler supports the 'may_alias' attribute. */ +#undef HAVE_ATTRIBUTE_may_alias + /* Define to 1 if your GCC C compiler supports the 'pure' attribute. */ #undef HAVE_ATTRIBUTE_pure @@ -101,15 +110,6 @@ attribute. */ #undef HAVE_ATTRIBUTE_warn_unused_result -/* Define to 1 if your GCC C compiler supports the 'weak' attribute. */ -#undef HAVE_ATTRIBUTE_weak - -/* Define to 1 if your GCC C compiler supports the 'weak_import' attribute. */ -#undef HAVE_ATTRIBUTE_weak_import - -/* Define to 1 if your GCC C compiler supports the 'weakref' attribute. */ -#undef HAVE_ATTRIBUTE_weakref - /* Define to 1 if you have the Debug symbol decoding library. */ #undef HAVE_BFD @@ -720,6 +720,9 @@ /* Define to 1 if you have the `roundl' function. */ #undef HAVE_ROUNDL +/* Define if your system has the RTLD_NOLOAD headers. */ +#undef HAVE_RTLD_NOLOAD + /* Define to 1 if your system has /sbin/launchd. */ #undef HAVE_SBIN_LAUNCHD diff --git a/include/asterisk/compiler.h b/include/asterisk/compiler.h index 91112dbfe9bf2d33b393a0d4bd0c467c9c27f592..77b5de40e7ceafe13aeadcd2d3415afc9c5099f1 100644 --- a/include/asterisk/compiler.h +++ b/include/asterisk/compiler.h @@ -71,6 +71,12 @@ #define attribute_warn_unused_result #endif +#ifdef HAVE_ATTRIBUTE_may_alias +#define attribute_may_alias __attribute__((may_alias)) +#else +#define attribute_may_alias +#endif + /* Some older version of GNU gcc (3.3.5 on OpenBSD 4.3 for example) dont like 'NULL' as sentinel */ #define SENTINEL ((char *)NULL) diff --git a/include/asterisk/http_websocket.h b/include/asterisk/http_websocket.h index 82c7ad8b66427211aaa848d57865a9222fbd2e16..10cb9a023230ebad8e5d8893af700f1b64282719 100644 --- a/include/asterisk/http_websocket.h +++ b/include/asterisk/http_websocket.h @@ -22,6 +22,8 @@ #include "asterisk/http.h" #include "asterisk/optional_api.h" +#include <errno.h> + /*! * \file http_websocket.h * \brief Support for WebSocket connections within the Asterisk HTTP server. @@ -71,7 +73,7 @@ typedef void (*ast_websocket_callback)(struct ast_websocket *session, struct ast * \retval \c NULL on error * \since 12 */ -struct ast_websocket_server *ast_websocket_server_create(void); +AST_OPTIONAL_API(struct ast_websocket_server *, ast_websocket_server_create, (void), { return NULL; }); /*! * \brief Callback suitable for use with a \ref ast_http_uri. @@ -79,7 +81,7 @@ struct ast_websocket_server *ast_websocket_server_create(void); * Set the data field of the ast_http_uri to \ref ast_websocket_server. * \since 12 */ -int ast_websocket_uri_cb(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers); +AST_OPTIONAL_API(int, ast_websocket_uri_cb, (struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers), { return -1; }); /*! * \brief Add a sub-protocol handler to the default /ws server @@ -141,7 +143,7 @@ AST_OPTIONAL_API(int, ast_websocket_server_remove_protocol, (struct ast_websocke * * \note Once an AST_WEBSOCKET_OPCODE_CLOSE opcode is received the socket will be closed */ -AST_OPTIONAL_API(int, ast_websocket_read, (struct ast_websocket *session, char **payload, uint64_t *payload_len, enum ast_websocket_opcode *opcode, int *fragmented), {return -1;}); +AST_OPTIONAL_API(int, ast_websocket_read, (struct ast_websocket *session, char **payload, uint64_t *payload_len, enum ast_websocket_opcode *opcode, int *fragmented), { errno = ENOSYS; return -1;}); /*! * \brief Construct and transmit a WebSocket frame @@ -154,7 +156,7 @@ AST_OPTIONAL_API(int, ast_websocket_read, (struct ast_websocket *session, char * * \retval 0 if successfully written * \retval -1 if error occurred */ -AST_OPTIONAL_API(int, ast_websocket_write, (struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t actual_length), {return -1;}); +AST_OPTIONAL_API(int, ast_websocket_write, (struct ast_websocket *session, enum ast_websocket_opcode opcode, char *payload, uint64_t actual_length), { errno = ENOSYS; return -1;}); /*! * \brief Close a WebSocket session by sending a message with the CLOSE opcode and an optional code @@ -165,7 +167,7 @@ AST_OPTIONAL_API(int, ast_websocket_write, (struct ast_websocket *session, enum * \retval 0 if successfully written * \retval -1 if error occurred */ -AST_OPTIONAL_API(int, ast_websocket_close, (struct ast_websocket *session, uint16_t reason), {return -1;}); +AST_OPTIONAL_API(int, ast_websocket_close, (struct ast_websocket *session, uint16_t reason), { errno = ENOSYS; return -1;}); /*! * \brief Enable multi-frame reconstruction up to a certain number of bytes @@ -207,7 +209,7 @@ AST_OPTIONAL_API(void, ast_websocket_unref, (struct ast_websocket *session), {re * * \note You must *not* directly read from or write to this file descriptor. It should only be used for polling. */ -AST_OPTIONAL_API(int, ast_websocket_fd, (struct ast_websocket *session), {return -1;}); +AST_OPTIONAL_API(int, ast_websocket_fd, (struct ast_websocket *session), { errno = ENOSYS; return -1;}); /*! * \brief Get the remote address for a WebSocket connected session. @@ -222,7 +224,7 @@ AST_OPTIONAL_API(struct ast_sockaddr *, ast_websocket_remote_address, (struct as * \retval 0 if unsecure * \retval 1 if secure */ -AST_OPTIONAL_API(int, ast_websocket_is_secure, (struct ast_websocket *session), {return -1;}); +AST_OPTIONAL_API(int, ast_websocket_is_secure, (struct ast_websocket *session), { errno = ENOSYS; return -1;}); /*! * \brief Set the socket of a WebSocket session to be non-blocking. @@ -230,6 +232,6 @@ AST_OPTIONAL_API(int, ast_websocket_is_secure, (struct ast_websocket *session), * \retval 0 on success * \retval -1 on failure */ -AST_OPTIONAL_API(int, ast_websocket_set_nonblock, (struct ast_websocket *session), {return -1;}); +AST_OPTIONAL_API(int, ast_websocket_set_nonblock, (struct ast_websocket *session), { errno = ENOSYS; return -1;}); #endif diff --git a/include/asterisk/optional_api.h b/include/asterisk/optional_api.h index cc31ce0a6e77d707b68eaa00d2c6c5a99b37fedf..7d66d2e47689e641357e07d112d41a0851b4d8d6 100644 --- a/include/asterisk/optional_api.h +++ b/include/asterisk/optional_api.h @@ -1,7 +1,7 @@ /* * Asterisk -- An open source telephony toolkit. * - * Copyright (C) 2008, Digium, Inc. + * Copyright (C) 2008-2013, Digium, Inc. * * Kevin P. Fleming <kpfleming@digium.com> * @@ -30,19 +30,6 @@ * have only a part of their functionality dependent on the APIs, and can * provide the remainder even if the APIs are not available. * - * To accomodate this situation, the AST_OPTIONAL_API macro allows an API - * function to be declared in a special way, if Asterisk being built on a - * platform that supports special compiler and dynamic linker attributes. - * If so the API function will actually be a weak symbol, which means if the - * provider of the API is not loaded, the symbol can still be referenced (unlike a - * strong symbol, which would cause an immediate fault if not defined when - * referenced), but it will return NULL signifying the linker/loader was - * not able to resolve the symbol. In addition, the macro defines a hidden - * 'stub' version of the API call, using a provided function body, and uses - * various methods to make the API function symbol actually resolve to - * that hidden stub, but only when the *real* provider of the symbol has - * not been found. - * * An example can be found in agi.h: * * \code @@ -74,19 +61,6 @@ * apply special aliases to the function prototype; this can be done * by defining AST_API_MODULE just before including the header file * containing the AST_OPTIONAL_API macro calls. - * - * \note If the platform does not provide adequate resources, - * then the AST_OPTIONAL_API macro will result in a non-optional function - * definition; this means that any consumers of the API functions so - * defined will require that the provider of the API functions be - * loaded before they can reference the symbols. - * - * WARNING WARNING WARNING WARNING WARNING - * - * You MUST add the AST_MODFLAG_GLOBAL_SYMBOLS to the module for which you - * are enabling optional_api functionality, or it will fail to work. - * - * WARNING WARNING WARNING WARNING WARNING */ /*! @@ -99,150 +73,185 @@ */ #define AST_OPTIONAL_API_UNAVAILABLE INT_MIN - -#if defined(HAVE_ATTRIBUTE_weak_import) || defined(HAVE_ATTRIBUTE_weak) - -/* - * This is the Darwin (Mac OS/X) implementation, that only provides the 'weak' - * or 'weak_import' compiler attribute for weak symbols. On this platform, - * - * - The module providing the API will only provide a '__' prefixed version - * of the API function to other modules (this will be hidden from the other - * modules by the macros), so any modules compiled against older versions - * of the module that provided a non-prefixed version of the API function - * will fail to link at runtime. - * - In the API module itself, access to the API function without using a - * prefixed name is provided by a static pointer variable that holds the - * function address. - * - 'Consumer' modules of the API will use a combination of a weak_import or - * weak symbol, a local stub function, a pointer variable and a constructor - * function (which initializes that pointer variable as the module is being - * loaded) to provide safe, optional access to the API function without any - * special code being required. +/*! + * \def AST_OPTIONAL_API_NAME(name) + * \brief Expands to the name of the implementation function. */ -#if defined(HAVE_ATTRIBUTE_weak_import) -#define __default_attribute weak_import /* pre-Lion */ -#else -#define __default_attribute weak /* Lion-onwards */ -#endif - -#define AST_OPTIONAL_API_NAME(name) __##name - -#if defined(AST_API_MODULE) +/*! + * \def AST_OPTIONAL_API(result, name, proto, stub) + * \brief Declare an optional API function + * + * \param result The type of result the function returns + * \param name The name of the function + * \param proto The prototype (arguments) of the function + * \param stub The code block that will be used by the hidden stub when needed + * + * Example usage: + * \code + * AST_OPTIONAL_API(int, ast_agi_register, (struct ast_module *mod, agi_command *cmd), + * { return AST_OPTIONAL_API_UNAVAILABLE; }); + * \endcode + */ -#define AST_OPTIONAL_API(result, name, proto, stub) \ - result AST_OPTIONAL_API_NAME(name) proto; \ - static attribute_unused typeof(AST_OPTIONAL_API_NAME(name)) * const name = AST_OPTIONAL_API_NAME(name); +/*! + * \def AST_OPTIONAL_API_ATTR(result, attr, name, proto, stub) + * \brief Declare an optional API function with compiler attributes + * + * \param result The type of result the function returns + * \param attr Any compiler attributes to be applied to the function (without the __attribute__ wrapper) + * \param name The name of the function + * \param proto The prototype (arguments) of the function + * \param stub The code block that will be used by the hidden stub when needed + */ -#define AST_OPTIONAL_API_ATTR(result, attr, name, proto, stub) \ - result __attribute__((attr)) AST_OPTIONAL_API_NAME(name) proto; \ - static attribute_unused typeof(AST_OPTIONAL_API_NAME(name)) * const name = AST_OPTIONAL_API_NAME(name); +#if defined(OPTIONAL_API) -#else +#if !defined(HAVE_ATTRIBUTE_constructor) || !defined(HAVE_ATTRIBUTE_constructor) +#error OPTIONAL_API requires compiler constructor/destructor support +#endif -#define AST_OPTIONAL_API(result, name, proto, stub) \ - static result __stub__##name proto stub; \ - __attribute__((__default_attribute)) typeof(__stub__##name) AST_OPTIONAL_API_NAME(name); \ - static attribute_unused typeof(__stub__##name) * name; \ - static void __attribute__((constructor)) __init__##name(void) { name = AST_OPTIONAL_API_NAME(name) ? : __stub__##name; } +/*! + * \internal + * \brief Function pointer to an optional API function. + * + * Functions that are declared as optional may have any signature they want; + * they are cast to this type as needed. We don't use a \c void pointer, because + * technically data and function pointers are incompatible. + * + * \note + * The may_alias attribute is to avoid type punning/strict aliasing warnings + * with older GCC's. + */ +typedef void (*ast_optional_fn)(void) attribute_may_alias; -#define AST_OPTIONAL_API_ATTR(result, attr, name, proto, stub) \ - static __attribute__((attr)) result __stub__##name proto stub; \ - __attribute__((attr, __default_attribute)) typeof(__stub__##name) AST_OPTIONAL_API_NAME(name); \ - static attribute_unused __attribute__((attr)) typeof(__stub__##name) * name; \ - static void __attribute__((constructor)) __init__##name(void) { name = AST_OPTIONAL_API_NAME(name) ? : __stub__##name; } +/*! + * \internal + * \brief Provide an implementation of an optional API. + * + * Any declared usages of this function are linked. + * + * \param symname Name of the provided function. + * \param impl Function pointer to the implementation function. + */ +void ast_optional_api_provide(const char *symname, ast_optional_fn impl); -#endif +/*! + * \internal + * \brief Remove an implementation of an optional API. + * + * Any declared usages of this function are unlinked. + * + * \param symname Name of the provided function. + * \param impl Function pointer to the implementation function. + */ +void ast_optional_api_unprovide(const char *symname, ast_optional_fn impl); -/* End of Darwin (Mac OS/X) implementation */ +/*! + * \internal + * \brief Define a usage of an optional API. + * + * If the API has been provided, it will be linked into \a optional_ref. + * Otherwise, it will be linked to \a stub until an implementation is provided. + * + * \param symname Name of the function to use. + * \param optional_ref Pointer-to-function-pointer to link to impl/stub. + * \param stub Stub function to link to when impl is not available. + * \param module Name of the module requesting the API. + */ +void ast_optional_api_use(const char *symname, ast_optional_fn *optional_ref, + ast_optional_fn stub, const char *module); -#elif defined(HAVE_ATTRIBUTE_weakref) +/*! + * \internal + * \brief Remove a usage of an optional API. + * + * The \a optional_ref will be linked to the \a stub provided at use time, + * will no longer be updated if the API is provided/removed. + * + * \param symname Name of the function to use. + * \param optional_ref Pointer-to-function-pointer to link to impl/stub. + * \param module Name of the module requesting the API. + */ +void ast_optional_api_unuse(const char *symname, ast_optional_fn *optional_ref, + const char *module); -/* - * This is the generic GCC implementation, used when the 'weakref' - * compiler attribute is available. On these platforms: - * - * - The module providing the API will provide a '__' prefixed version - * of the API function to other modules (this will be hidden from the other - * modules by the macros), and also a non-prefixed alias so that modules - * compiled against older versions of the module that provided a non-prefixed - * version of the API function will continue to link properly. - * - In the API module itself, access to the API function without using a - * prefixed name is provided by the non-prefixed alias described above. - * - 'Consumer' modules of the API will use a combination of a weakref - * symbol, a local stub function, a pointer variable and a constructor function - * (which initializes that pointer variable as the module is being loaded) - * to provide safe, optional access to the API function without any special - * code being required. +/*! + * \brief Call at exit to clean up optional_api internals. + * + * Since the optional_api code might run before main() starts, it can't safely + * register its own cleanup handlers. That has to be done within main(). */ +void optional_api_cleanup(void); #define AST_OPTIONAL_API_NAME(name) __##name #if defined(AST_API_MODULE) - -#define AST_OPTIONAL_API(result, name, proto, stub) \ - result AST_OPTIONAL_API_NAME(name) proto; \ - static __attribute__((alias(__stringify(AST_OPTIONAL_API_NAME(name))))) typeof(AST_OPTIONAL_API_NAME(name)) name; - -#define AST_OPTIONAL_API_ATTR(result, attr, name, proto, stub) \ - result __attribute__((attr)) AST_OPTIONAL_API_NAME(name) proto; \ - static __attribute__((alias(__stringify(AST_OPTIONAL_API_NAME(name))))) typeof(AST_OPTIONAL_API_NAME(name)) name; +/* Module defining the API */ + +#define AST_OPTIONAL_API_IMPL_INIT(name) \ + static void __attribute__((constructor)) __init__##name##_impl(void) { \ + ast_optional_api_provide(#name, \ + (ast_optional_fn)AST_OPTIONAL_API_NAME(name)); \ + } \ + static void __attribute__((destructor)) __dtor__##name##_impl(void) { \ + ast_optional_api_unprovide(#name, \ + (ast_optional_fn)AST_OPTIONAL_API_NAME(name)); \ + } + +#define AST_OPTIONAL_API(result, name, proto, stub) \ + result AST_OPTIONAL_API_NAME(name) proto; \ + static attribute_unused typeof(AST_OPTIONAL_API_NAME(name)) * const \ + name = AST_OPTIONAL_API_NAME(name); \ + AST_OPTIONAL_API_IMPL_INIT(name) + +#define AST_OPTIONAL_API_ATTR(result, attr, name, proto, stub) \ + result __attribute__((attr)) AST_OPTIONAL_API_NAME(name) proto; \ + static attribute_unused typeof(AST_OPTIONAL_API_NAME(name)) * const \ + name = AST_OPTIONAL_API_NAME(name); \ + AST_OPTIONAL_API_IMPL_INIT(name) #else +/* Module using the API */ -#define AST_OPTIONAL_API(result, name, proto, stub) \ - static result __stub__##name proto stub; \ - static __attribute__((weakref(__stringify(AST_OPTIONAL_API_NAME(name))))) typeof(__stub__##name) __ref__##name; \ - static attribute_unused typeof(__stub__##name) * name; \ - static void __attribute__((constructor)) __init__##name(void) { name = __ref__##name ? : __stub__##name; } +#define AST_OPTIONAL_API_INIT(name) \ + static void __attribute__((constructor)) __init__##name(void) { \ + ast_optional_api_use(#name, (ast_optional_fn *)&name, \ + (ast_optional_fn)__stub__##name, \ + AST_MODULE); \ + } \ + static void __attribute__((destructor)) __dtor__##name(void) { \ + ast_optional_api_unuse(#name, (ast_optional_fn *)&name, \ + AST_MODULE); \ + } -#define AST_OPTIONAL_API_ATTR(result, attr, name, proto, stub) \ - static __attribute__((attr)) result __stub__##name proto stub; \ - static __attribute__((attr, weakref(__stringify(AST_OPTIONAL_API_NAME(name))))) typeof(__stub__##name) __ref__##name; \ - static attribute_unused __attribute__((attr)) typeof(__stub__##name) * name; \ - static void __attribute__((constructor)) __init__##name(void) { name = __ref__##name ? : __stub__##name; } +#define AST_OPTIONAL_API(result, name, proto, stub) \ + static result __stub__##name proto stub; \ + static attribute_unused \ + typeof(__stub__##name) * name; \ + AST_OPTIONAL_API_INIT(name) -#endif +#define AST_OPTIONAL_API_ATTR(result, attr, name, proto, stub) \ + static __attribute__((attr)) result __stub__##name proto stub; \ + static attribute_unused __attribute__((attr)) \ + typeof(__stub__##name) * name; \ + AST_OPTIONAL_API_INIT(name) -/* End of GCC implementation */ +#endif /* defined(AST_API_MODULE) */ -#else +#else /* defined(OPTIONAL_API) */ -/* This is the non-optional implementation. */ +/* Non-optional API */ #define AST_OPTIONAL_API_NAME(name) name -/*! - * \brief Define an optional API function - * - * \param result The type of result the function returns - * \param name The name of the function - * \param proto The prototype (arguments) of the function - * \param stub The code block that will be used by the hidden stub when needed - * - * Example usage: - * \code - * AST_OPTIONAL_API(int, ast_agi_register, (struct ast_module *mod, agi_command *cmd), - * { return AST_OPTIONAL_API_UNAVAILABLE; }); - * \endcode - */ -#define AST_OPTIONAL_API(result, name, proto, stub) result AST_OPTIONAL_API_NAME(name) proto +#define AST_OPTIONAL_API(result, name, proto, stub) \ + result AST_OPTIONAL_API_NAME(name) proto -/*! - * \brief Define an optional API function with compiler attributes - * - * \param result The type of result the function returns - * \param attr Any compiler attributes to be applied to the function (without the __attribute__ wrapper) - * \param name The name of the function - * \param proto The prototype (arguments) of the function - * \param stub The code block that will be used by the hidden stub when needed - */ -#define AST_OPTIONAL_API_ATTR(result, attr, name, proto, stub) result __attribute__((attr)) AST_OPTIONAL_API_NAME(name) proto - -/* End of non-optional implementation */ +#define AST_OPTIONAL_API_ATTR(result, attr, name, proto, stub) \ + result __attribute__((attr)) AST_OPTIONAL_API_NAME(name) proto -#endif +#endif /* defined(OPTIONAL_API) */ #undef AST_API_MODULE diff --git a/main/asterisk.c b/main/asterisk.c index 7e5ddac581466078d1ab382283a3225608b5cfa6..df5561860ff13dba7c194de9774f702f252681d3 100644 --- a/main/asterisk.c +++ b/main/asterisk.c @@ -247,6 +247,7 @@ int daemon(int, int); /* defined in libresolv of all places */ #include "asterisk/stasis_endpoints.h" #include "asterisk/stasis_system.h" #include "asterisk/security_events.h" +#include "asterisk/optional_api.h" #include "../defaults.h" @@ -4165,6 +4166,10 @@ int main(int argc, char *argv[]) ast_el_read_history(filename); } +#if defined(OPTIONAL_API) + ast_register_cleanup(optional_api_cleanup); +#endif + ast_json_init(); ast_ulaw_init(); ast_alaw_init(); diff --git a/main/loader.c b/main/loader.c index ddfbffe9fe066d91f884ca68e598bf395b96d082..4065043aa8190afb62a0f0b2b6daa615bf189551 100644 --- a/main/loader.c +++ b/main/loader.c @@ -185,6 +185,8 @@ void ast_module_register(const struct ast_module_info *info) mod = resource_being_loaded; } + ast_verb(5, "Registering module %s\n", info->name); + mod->info = info; AST_LIST_HEAD_INIT(&mod->users); @@ -230,6 +232,7 @@ void ast_module_unregister(const struct ast_module_info *info) AST_LIST_UNLOCK(&module_list); if (mod) { + ast_verb(5, "Unregistering module %s\n", info->name); AST_LIST_HEAD_DESTROY(&mod->users); ast_free(mod); } @@ -403,16 +406,83 @@ static struct ast_module *find_resource(const char *resource, int do_lock) } #ifdef LOADABLE_MODULES + +/*! + * \brief dlclose(), with failure logging. + */ +static void logged_dlclose(const char *name, void *lib) +{ + if (dlclose(lib) != 0) { + ast_log(LOG_WARNING, "Failed to unload %s: %s\n", + name, dlerror()); + } +} + +#if defined(HAVE_RTLD_NOLOAD) +/*! + * \brief Check to see if the given resource is loaded. + * + * \param resource_name Name of the resource, including .so suffix. + * \return False (0) if module is not loaded. + * \return True (non-zero) if module is loaded. + */ +static int is_module_loaded(const char *resource_name) +{ + char fn[PATH_MAX] = ""; + void *lib; + + ast_verb(10, "Checking if %s is loaded\n", resource_name); + + snprintf(fn, sizeof(fn), "%s/%s", ast_config_AST_MODULE_DIR, + resource_name); + + lib = dlopen(fn, RTLD_LAZY | RTLD_NOLOAD); + + if (lib) { + ast_verb(10, " %s loaded\n", resource_name); + logged_dlclose(resource_name, lib); + return 1; + } + + ast_verb(10, " %s not loaded\n", resource_name); + return 0; +} +#endif + static void unload_dynamic_module(struct ast_module *mod) { + char *name = ast_strdupa(ast_module_name(mod)); void *lib = mod->lib; /* WARNING: the structure pointed to by mod is going to disappear when this operation succeeds, so we can't dereference it */ - if (lib) - while (!dlclose(lib)); + if (!lib) { + return; + } + + logged_dlclose(name, lib); + + /* There are several situations where the module might still be resident + * in memory. + * + * If somehow there was another dlopen() on the same module (unlikely, + * since that all is supposed to happen in loader.c). + * + * Or the lazy resolution of a global symbol (very likely, since that is + * how we load all of our modules that export global symbols). + * + * Avoid the temptation of repeating the dlclose(). The other code that + * dlopened the module still has its module reference, and should close + * it itself. In other situations, dlclose() will happily return success + * for as many times as you wish to call it. + */ +#if defined(HAVE_RTLD_NOLOAD) + if (is_module_loaded(name)) { + ast_log(LOG_WARNING, "Module '%s' could not be completely unloaded\n", name); + } +#endif } static enum ast_module_load_result load_resource(const char *resource_name, unsigned int global_symbols_only, struct ast_heap *resource_heap, int required); @@ -461,7 +531,7 @@ static struct ast_module *load_dynamic_module(const char *resource_in, unsigned if (resource_being_loaded != (mod = AST_LIST_LAST(&module_list))) { ast_log(LOG_WARNING, "Module '%s' did not register itself during load\n", resource_in); /* no, it did not, so close it and return */ - while (!dlclose(lib)); + logged_dlclose(resource_in, lib); /* note that the module's destructor will call ast_module_unregister(), which will free the structure we allocated in resource_being_loaded */ return NULL; @@ -472,32 +542,11 @@ static struct ast_module *load_dynamic_module(const char *resource_in, unsigned /* if we are being asked only to load modules that provide global symbols, and this one does not, then close it and return */ if (global_symbols_only && !wants_global) { - while (!dlclose(lib)); + logged_dlclose(resource_in, lib); return NULL; } - /* This section is a workaround for a gcc 4.1 bug that has already been - * fixed in later versions. Unfortunately, some distributions, such as - * RHEL/CentOS 5, distribute gcc 4.1, so we're stuck with having to deal - * with this issue. This basically ensures that optional_api modules are - * loaded before any module which requires their functionality. */ -#if !defined(HAVE_ATTRIBUTE_weak_import) && !defined(HAVE_ATTRIBUTE_weakref) - if (!ast_strlen_zero(mod->info->nonoptreq)) { - /* Force any required dependencies to load */ - char *each, *required_resource = ast_strdupa(mod->info->nonoptreq); - while ((each = strsep(&required_resource, ","))) { - struct ast_module *dependency; - each = ast_strip(each); - dependency = find_resource(each, 0); - /* Is it already loaded? */ - if (!dependency) { - load_resource(each, global_symbols_only, resource_heap, 1); - } - } - } -#endif - - while (!dlclose(lib)); + logged_dlclose(resource_in, lib); resource_being_loaded = NULL; /* start the load process again */ @@ -523,6 +572,7 @@ static struct ast_module *load_dynamic_module(const char *resource_in, unsigned return AST_LIST_LAST(&module_list); } + #endif void ast_module_shutdown(void) diff --git a/main/optional_api.c b/main/optional_api.c new file mode 100644 index 0000000000000000000000000000000000000000..f48fe11a11995112aa2755580d3a7cd74d6962a8 --- /dev/null +++ b/main/optional_api.c @@ -0,0 +1,365 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * David M. Lee, II <dlee@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/optional_api.h" +#include "asterisk/utils.h" + +#if defined(OPTIONAL_API) + +/* + * \file Optional API innards. + * + * The calls to ast_optional_api_*() happen implicitly from \c __constructor__ + * calls which are defined in header files. This means that some number of them + * happen before main() is called. This makes calling most Asterisk APIs + * dangerous, since we could be called before they are initialized. This + * includes things like AO2, malloc debug, and static mutexes. + * + * Another limitation is that most functions are called from the midst of + * dlopen() or dlclose(), and there is no opportunity to return a failure code. + * The best we can do is log an error, and call ast_do_crash(). + * + * Fortunately, there are some constraints that help us out. The \c + * ast_optional_api_*() are called during module loads, which happens either + * before main(), or during dlopen() calls. These are already serialized, so we + * don't have to lock ourselves. + */ + +/*! \brief A user of an optional API */ +struct optional_api_user { + /*! Pointer to function pointer to link */ + ast_optional_fn *optional_ref; + /*! Stub to use when impl is unavailable */ + ast_optional_fn stub; + /*! Name of the module using the API */ + char module[]; +}; + +/*! \brief An optional API */ +struct optional_api { + /*! Pointer to the implementation function; could be null */ + ast_optional_fn impl; + /*! Variable length array of users of this API */ + struct optional_api_user **users; + /*! Allocated size of the \a users array */ + size_t users_maxlen; + /*! Number of entries in the \a users array */ + size_t users_len; + /*! Name of the optional API function */ + char symname[]; +}; + +/*! + * \brief Free an \ref optional_api_user. + * + * \param user User struct to free. + */ +static void optional_api_user_destroy(struct optional_api_user *user) +{ + *user->optional_ref = user->stub; + free(user); +} + +/*! + * \brief Create an \ref optional_api_user. + * + * \param optional_ref Pointer-to-function-pointer to link to impl/stub. + * \param stub Stub function to link to when impl is not available. + * \param module Name of the module requesting the API. + * + * \return New \ref optional_api_user. + * \return \c NULL on error. + */ +static struct optional_api_user *optional_api_user_create( + ast_optional_fn *optional_ref, ast_optional_fn stub, const char *module) +{ + struct optional_api_user *user; + size_t size = sizeof(*user) + strlen(module) + 1; + + user = calloc(1, size); + if (!user) { + return NULL; + } + + user->optional_ref = optional_ref; + user->stub = stub; + strcpy(user->module, module); /* SAFE */ + + return user; +} + +/*! + * \brief Free an \ref optional_api. + * + * \param api API struct to free. + */ +static void optional_api_destroy(struct optional_api *api) +{ + while (api->users_len--) { + optional_api_user_destroy(api->users[api->users_len]); + } + free(api->users); + api->users = NULL; + api->users_maxlen = 0; + free(api); +} + +/*! + * \brief Create an \ref optional_api. + * + * \param symname Name of the optional function. + * \return New \ref optional_api. + * \return \c NULL on error. + */ +static struct optional_api *optional_api_create(const char *symname) +{ + struct optional_api *api; + size_t size; + + ast_verb(6, "%s: building api object\n", symname); + size = sizeof(*api) + strlen(symname) + 1; + api = calloc(1, size); + if (!api) { + ast_log(LOG_ERROR, "Failed to allocate api\n"); + return NULL; + } + + strcpy(api->symname, symname); /* SAFE */ + + return api; +} + +/*! Array of \ref optional_api functions */ +struct { + /*! Variable length array of API's */ + struct optional_api **list; + /*! Allocated size of the \a list array */ + size_t maxlen; + /*! Number of entries in the \a list array */ + size_t len; +} apis; + +void optional_api_cleanup(void) +{ + while (apis.len--) { + optional_api_destroy(apis.list[apis.len]); + } + free(apis.list); + apis.list = NULL; + apis.maxlen = 0; +} + +/*! + * \brief Gets (or creates) the \ref optional_api for the give function. + * + * \param sysname Name of the function to look up. + * \return Corresponding \ref optional_api. + * \return \c NULL on error. + */ +static struct optional_api *get_api(const char *symname) +{ + struct optional_api *api; + size_t i; + + /* Find one, if we already have it */ + for (i = 0; i < apis.len; ++i) { + if (strcmp(symname, apis.list[i]->symname) == 0) { + return apis.list[i]; + } + } + + /* API not found. Build one */ + api = optional_api_create(symname); + + /* Grow the list, if needed */ + if (apis.len + 1 > apis.maxlen) { + size_t new_maxlen = apis.maxlen ? 2 * apis.maxlen : 1; + struct optional_api **new_list = + realloc(apis.list, new_maxlen * sizeof(*new_list)); + + if (!new_list) { + optional_api_destroy(api); + ast_log(LOG_ERROR, "Failed to allocate api list\n"); + return NULL; + } + + apis.maxlen = new_maxlen; + apis.list = new_list; + } + + apis.list[apis.len++] = api; + + return api; +} + +/*! + * \brief Re-links a given \a user against its associated \a api. + * + * If the \a api has an implementation, the \a user is linked to that + * implementation. Otherwise, the \a user is linked to its \a stub. + * + * \param user \ref optional_api_user to link. + * \param api \ref optional_api to link. + */ +static void optional_api_user_relink(struct optional_api_user *user, + struct optional_api *api) +{ + if (api->impl && *user->optional_ref != api->impl) { + ast_verb(4, "%s: linking for %s\n", api->symname, user->module); + *user->optional_ref = api->impl; + } else if (!api->impl && *user->optional_ref != user->stub) { + ast_verb(4, "%s: stubbing for %s\n", api->symname, + user->module); + *user->optional_ref = user->stub; + } +} + +/*! + * \brief Sets the implementation function pointer for an \a api. + * + * \param api API to implement/stub out. + * \param impl Pointer to implementation function. Can be 0 to remove + * implementation. + */ +static void optional_api_set_impl(struct optional_api *api, + ast_optional_fn impl) +{ + size_t i; + + api->impl = impl; + + /* re-link all users */ + for (i = 0; i < api->users_len; ++i) { + optional_api_user_relink(api->users[i], api); + } +} + +void ast_optional_api_provide(const char *symname, ast_optional_fn impl) +{ + struct optional_api *api; + + ast_verb(4, "%s: providing\n", symname); + + api = get_api(symname); + if (!api) { + ast_log(LOG_ERROR, "%s: Allocation failed\n", symname); + ast_do_crash(); + return; + } + + optional_api_set_impl(api, impl); +} + +void ast_optional_api_unprovide(const char *symname, ast_optional_fn impl) +{ + struct optional_api *api; + + ast_verb(4, "%s: un-providing\n", symname); + + api = get_api(symname); + if (!api) { + ast_log(LOG_ERROR, "%s: Could not find api\n", symname); + ast_do_crash(); + return; + } + + optional_api_set_impl(api, 0); +} + +void ast_optional_api_use(const char *symname, ast_optional_fn *optional_ref, + ast_optional_fn stub, const char *module) +{ + struct optional_api_user *user; + struct optional_api *api; + + + api = get_api(symname); + if (!api) { + ast_log(LOG_ERROR, "%s: Allocation failed\n", symname); + ast_do_crash(); + return; + } + + user = optional_api_user_create(optional_ref, stub, module); + if (!user) { + ast_log(LOG_ERROR, "%s: Allocation failed\n", symname); + ast_do_crash(); + return; + } + + /* Add user to the API */ + if (api->users_len + 1 > api->users_maxlen) { + size_t new_maxlen = api->users_maxlen ? + 2 * api->users_maxlen : 1; + struct optional_api_user **new_list = + realloc(api->users, new_maxlen * sizeof(*new_list)); + + if (!new_list) { + optional_api_user_destroy(user); + ast_log(LOG_ERROR, "Failed to allocate api list\n"); + ast_do_crash(); + return; + } + + api->users_maxlen = new_maxlen; + api->users = new_list; + } + + api->users[api->users_len++] = user; + + optional_api_user_relink(user, api); +} + +void ast_optional_api_unuse(const char *symname, ast_optional_fn *optional_ref, + const char *module) +{ + struct optional_api *api; + size_t i; + + api = get_api(symname); + if (!api) { + ast_log(LOG_ERROR, "%s: Could not find api\n", symname); + ast_do_crash(); + return; + } + + for (i = 0; i < api->users_len; ++i) { + struct optional_api_user *user = api->users[i]; + if (user->optional_ref == optional_ref) { + if (*user->optional_ref != user->stub) { + ast_verb(4, "%s: stubbing for %s\n", symname, + module); + *user->optional_ref = user->stub; + } + + /* Remove from the list */ + api->users[i] = api->users[--api->users_len]; + + optional_api_user_destroy(user); + return; + } + } + + ast_log(LOG_ERROR, "%s: Could not find user %s\n", symname, module); +} + +#endif /* defined(OPTIONAL_API) */ diff --git a/res/ari/ari_websockets.c b/res/ari/ari_websockets.c index a34e0f691e3e3890acdcc8e5b604e11aeeb7193b..20639e12e8c13b980577899103de155c82ea8479 100644 --- a/res/ari/ari_websockets.c +++ b/res/ari/ari_websockets.c @@ -20,8 +20,10 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") -#include "asterisk/astobj2.h" #include "asterisk/ari.h" +#include "asterisk/astobj2.h" +#include "asterisk/http_websocket.h" +#include "internal.h" /*! \file * @@ -163,3 +165,15 @@ int ast_ari_websocket_session_write(struct ast_ari_websocket_session *session, return ast_websocket_write(session->ws_session, AST_WEBSOCKET_OPCODE_TEXT, str, strlen(str)); } + +void ari_handle_websocket(struct ast_websocket_server *ws_server, + struct ast_tcptls_session_instance *ser, const char *uri, + enum ast_http_method method, struct ast_variable *get_params, + struct ast_variable *headers) +{ + struct ast_http_uri fake_urih = { + .data = ws_server, + }; + ast_websocket_uri_cb(ser, &fake_urih, uri, method, get_params, + headers); +} diff --git a/res/ari/internal.h b/res/ari/internal.h index ffacd493945edc843dd1d9f9fbd3726619e1f57c..8453747f19f094d7585f924f27aa55a9934277bb 100644 --- a/res/ari/internal.h +++ b/res/ari/internal.h @@ -25,7 +25,9 @@ * \author David M. Lee, II <dlee@digium.com> */ +#include "asterisk/http.h" #include "asterisk/json.h" +#include "asterisk/stringfields.h" /*! @{ */ @@ -139,5 +141,25 @@ struct ast_ari_conf_user *ast_ari_config_validate_user(const char *username, /*! @} */ +/* Forward-declare websocket structs. This avoids including http_websocket.h, + * which causes optional_api stuff to happen, which makes optional_api more + * difficult to debug. */ + +struct ast_websocket_server; + +/*! + * \brief Wrapper for invoking the websocket code for an incoming connection. + * + * \param ws_server WebSocket server to invoke. + * \param ser HTTP session. + * \param uri Requested URI. + * \param method Requested HTTP method. + * \param get_params Parsed query parameters. + * \param headers Parsed HTTP headers. + */ +void ari_handle_websocket(struct ast_websocket_server *ws_server, + struct ast_tcptls_session_instance *ser, const char *uri, + enum ast_http_method method, struct ast_variable *get_params, + struct ast_variable *headers); #endif /* ARI_INTERNAL_H_ */ diff --git a/res/res_ari.c b/res/res_ari.c index 4777bb7c209f1abf66e76e06661239d28ea1a28d..5475efce932d933614f73e6355639b343d362512 100644 --- a/res/res_ari.c +++ b/res/res_ari.c @@ -124,11 +124,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") +#include "ari/internal.h" +#include "asterisk/ari.h" #include "asterisk/astobj2.h" #include "asterisk/module.h" #include "asterisk/paths.h" -#include "asterisk/ari.h" -#include "ari/internal.h" #include <string.h> #include <sys/stat.h> @@ -522,11 +522,8 @@ void ast_ari_invoke(struct ast_tcptls_session_instance *ser, if (handler->ws_server && method == AST_HTTP_GET) { /* WebSocket! */ - struct ast_http_uri fake_urih = { - .data = handler->ws_server, - }; - ast_websocket_uri_cb(ser, &fake_urih, uri, method, get_params, - headers); + ari_handle_websocket(handler->ws_server, ser, uri, method, + get_params, headers); /* Since the WebSocket code handles the connection, we shouldn't * do anything else; setting no_response */ response->no_response = 1; diff --git a/res/res_ari_events.c b/res/res_ari_events.c index 567167f14c4f44abe036d3c0fd7de3447972b24f..17337bbc8356dd2a8249b7db782250a7cfa4cdac 100644 --- a/res/res_ari_events.c +++ b/res/res_ari_events.c @@ -48,6 +48,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #if defined(AST_DEVMODE) #include "ari/ari_model_validators.h" #endif +#include "asterisk/http_websocket.h" #define MAX_VALS 128 diff --git a/res/res_http_websocket.c b/res/res_http_websocket.c index 129e92c192b17037fca7d23b81962ecd771be0a9..b03745e6f764986928a15b8bc544bdb219d20139 100644 --- a/res/res_http_websocket.c +++ b/res/res_http_websocket.c @@ -107,18 +107,24 @@ struct ast_websocket_server { struct ao2_container *protocols; /*!< Container for registered protocols */ }; -static void websocket_server_dtor(void *obj) +static void websocket_server_internal_dtor(void *obj) { struct ast_websocket_server *server = obj; ao2_cleanup(server->protocols); server->protocols = NULL; } -struct ast_websocket_server *ast_websocket_server_create(void) +static void websocket_server_dtor(void *obj) +{ + websocket_server_internal_dtor(obj); + ast_module_unref(ast_module_info->self); +} + +static struct ast_websocket_server *websocket_server_create_impl(void (*dtor)(void *)) { RAII_VAR(struct ast_websocket_server *, server, NULL, ao2_cleanup); - server = ao2_alloc(sizeof(*server), websocket_server_dtor); + server = ao2_alloc(sizeof(*server), dtor); if (!server) { return NULL; } @@ -132,6 +138,17 @@ struct ast_websocket_server *ast_websocket_server_create(void) return server; } +static struct ast_websocket_server *websocket_server_internal_create(void) +{ + return websocket_server_create_impl(websocket_server_internal_dtor); +} + +struct ast_websocket_server *AST_OPTIONAL_API_NAME(ast_websocket_server_create)(void) +{ + ast_module_ref(ast_module_info->self); + return websocket_server_create_impl(websocket_server_dtor); +} + /*! \brief Destructor function for sessions */ static void session_destroy_fn(void *obj) { @@ -512,7 +529,7 @@ static struct websocket_protocol *one_protocol( return ao2_callback(server->protocols, OBJ_NOLOCK, NULL, NULL); } -int ast_websocket_uri_cb(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers) +int AST_OPTIONAL_API_NAME(ast_websocket_uri_cb)(struct ast_tcptls_session_instance *ser, const struct ast_http_uri *urih, const char *uri, enum ast_http_method method, struct ast_variable *get_vars, struct ast_variable *headers) { struct ast_variable *v; char *upgrade = NULL, *key = NULL, *key1 = NULL, *key2 = NULL, *protos = NULL, *requested_protocols = NULL, *protocol = NULL; @@ -521,6 +538,8 @@ int ast_websocket_uri_cb(struct ast_tcptls_session_instance *ser, const struct a struct ast_websocket *session; struct ast_websocket_server *server; + SCOPED_MODULE_USE(ast_module_info->self); + /* Upgrade requests are only permitted on GET methods */ if (method != AST_HTTP_GET) { ast_http_error(ser, 501, "Not Implemented", "Attempt to use unimplemented / unsupported method"); @@ -674,7 +693,7 @@ int ast_websocket_uri_cb(struct ast_tcptls_session_instance *ser, const struct a } static struct ast_http_uri websocketuri = { - .callback = ast_websocket_uri_cb, + .callback = AST_OPTIONAL_API_NAME(ast_websocket_uri_cb), .description = "Asterisk HTTP WebSocket", .uri = "ws", .has_subtree = 0, @@ -719,7 +738,7 @@ end: ast_websocket_unref(session); } -int AST_OPTIONAL_API_NAME(ast_websocket_add_protocol)(const char *name, ast_websocket_callback callback) +static int websocket_add_protocol_internal(const char *name, ast_websocket_callback callback) { struct ast_websocket_server *ws_server = websocketuri.data; if (!ws_server) { @@ -728,7 +747,16 @@ int AST_OPTIONAL_API_NAME(ast_websocket_add_protocol)(const char *name, ast_webs return ast_websocket_server_add_protocol(ws_server, name, callback); } -int AST_OPTIONAL_API_NAME(ast_websocket_remove_protocol)(const char *name, ast_websocket_callback callback) +int AST_OPTIONAL_API_NAME(ast_websocket_add_protocol)(const char *name, ast_websocket_callback callback) +{ + int res = websocket_add_protocol_internal(name, callback); + if (res == 0) { + ast_module_ref(ast_module_info->self); + } + return res; +} + +static int websocket_remove_protocol_internal(const char *name, ast_websocket_callback callback) { struct ast_websocket_server *ws_server = websocketuri.data; if (!ws_server) { @@ -737,14 +765,23 @@ int AST_OPTIONAL_API_NAME(ast_websocket_remove_protocol)(const char *name, ast_w return ast_websocket_server_remove_protocol(ws_server, name, callback); } +int AST_OPTIONAL_API_NAME(ast_websocket_remove_protocol)(const char *name, ast_websocket_callback callback) +{ + int res = websocket_remove_protocol_internal(name, callback); + if (res == 0) { + ast_module_unref(ast_module_info->self); + } + return res; +} + static int load_module(void) { - websocketuri.data = ast_websocket_server_create(); + websocketuri.data = websocket_server_internal_create(); if (!websocketuri.data) { return AST_MODULE_LOAD_FAILURE; } ast_http_uri_link(&websocketuri); - ast_websocket_add_protocol("echo", websocket_echo_callback); + websocket_add_protocol_internal("echo", websocket_echo_callback); return 0; } diff --git a/rest-api-templates/res_ari_resource.c.mustache b/rest-api-templates/res_ari_resource.c.mustache index e6b2a88f4203e15d8b9bd72a0983fd326354c1b3..8e043e68215845ac36e67654b9028b86a34cbedd 100644 --- a/rest-api-templates/res_ari_resource.c.mustache +++ b/rest-api-templates/res_ari_resource.c.mustache @@ -53,6 +53,13 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #if defined(AST_DEVMODE) #include "ari/ari_model_validators.h" #endif +{{^has_websocket}} +{{! Only include http_websocket if necessary. Otherwise we'll do a lot of + * unnecessary optional_api intialization, which makes optional_api harder + * to debug + }} +#include "asterisk/http_websocket.h" +{{/has_websocket}} #define MAX_VALS 128 diff --git a/rest-api-templates/swagger_model.py b/rest-api-templates/swagger_model.py index aa065b342bf708531cbe7d8356950b846ed5a063..af52266b409b398d1de0ed7a1ae1aaf75d21aa5b 100644 --- a/rest-api-templates/swagger_model.py +++ b/rest-api-templates/swagger_model.py @@ -632,6 +632,8 @@ class ApiDeclaration(Stringify): api_json = api_decl_json.get('apis') or [] self.apis = [ Api().load(j, processor, context) for j in api_json] + self.has_websocket = filter(lambda api: api.has_websocket, + self.apis) == [] models = api_decl_json.get('models').items() or [] self.models = [Model().load(id, json, processor, context) for (id, json) in models] diff --git a/tests/test_optional_api.c b/tests/test_optional_api.c new file mode 100644 index 0000000000000000000000000000000000000000..841dd5f868a63605d1e5e4ecfbc20c4fc39faf18 --- /dev/null +++ b/tests/test_optional_api.c @@ -0,0 +1,187 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2013, Digium, Inc. + * + * David M. Lee, II <dlee@digium.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file \brief Test optional API. + * + * This tests exercise the underlying implementation functions. Acutal usage + * won't look anything like this; it would use the wrapper macros. + * + * \author\verbatim David M. Lee, II <dlee@digium.com> \endverbatim + * + * \ingroup tests + */ + +/*** MODULEINFO + <depend>TEST_FRAMEWORK</depend> + <depend>OPTIONAL_API</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_FILE_VERSION(__FILE__, "$Revision$") + +#include "asterisk/module.h" +#include "asterisk/optional_api.h" +#include "asterisk/test.h" + +#define CATEGORY "/main/optional_api/" + +enum was_called { + NONE, + STUB, + IMPL +}; + +enum was_called was_called_result; + +ast_optional_fn test_optional_ref; + +static void test_optional_stub(void) +{ + was_called_result = STUB; +} + +static void test_optional_impl(void) +{ + was_called_result = IMPL; +} + +static void test_optional(void) +{ + was_called_result = NONE; + if (test_optional_ref) { + test_optional_ref(); + } +} + +#define SYMNAME "test_option" + +AST_TEST_DEFINE(test_provide_first) +{ + enum ast_test_result_state res; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = CATEGORY; + info->summary = "Test optional API publishing."; + info->description = "Test optional API publishing."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + res = AST_TEST_FAIL; + test_optional_ref = 0; + + ast_optional_api_provide(SYMNAME, test_optional_impl); + + ast_optional_api_use(SYMNAME, &test_optional_ref, test_optional_stub, + AST_MODULE); + + test_optional(); + + if (was_called_result != IMPL) { + ast_test_status_update(test, "Expected %d, was %d", + IMPL, was_called_result); + goto done; + } + + res = AST_TEST_PASS; + + done: + ast_optional_api_unuse(SYMNAME, &test_optional_ref, AST_MODULE); + ast_optional_api_unprovide(SYMNAME, test_optional_impl); + return res; +} + +AST_TEST_DEFINE(test_provide_last) +{ + enum ast_test_result_state res; + + switch (cmd) { + case TEST_INIT: + info->name = __func__; + info->category = CATEGORY; + info->summary = "Test optional API publishing."; + info->description = "Test optional API publishing."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + res = AST_TEST_FAIL; + test_optional_ref = 0; + + ast_optional_api_use(SYMNAME, &test_optional_ref, test_optional_stub, + AST_MODULE); + + test_optional(); + if (was_called_result != STUB) { + ast_test_status_update(test, "Expected %d, was %d", + STUB, was_called_result); + goto done; + } + + ast_optional_api_provide(SYMNAME, test_optional_impl); + + test_optional(); + if (was_called_result != IMPL) { + ast_test_status_update(test, "Expected %d, was %d", + IMPL, was_called_result); + ast_optional_api_unprovide(SYMNAME, test_optional_impl); + goto done; + } + + ast_optional_api_unprovide(SYMNAME, test_optional_impl); + + test_optional(); + if (was_called_result != STUB) { + ast_test_status_update(test, "Expected %d, was %d", + STUB, was_called_result); + ast_optional_api_unprovide(SYMNAME, test_optional_impl); + goto done; + } + + res = AST_TEST_PASS; + + done: + ast_optional_api_unuse(SYMNAME, &test_optional_ref, AST_MODULE); + return res; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(test_provide_first); + AST_TEST_UNREGISTER(test_provide_last); + return 0; +} + +static int load_module(void) +{ + AST_TEST_REGISTER(test_provide_first); + AST_TEST_REGISTER(test_provide_last); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "ARI testing", + .load = load_module, + .unload = unload_module, + );