diff --git a/configure.ac b/configure.ac index ee0ef71d..e1ee1ae8 100644 --- a/configure.ac +++ b/configure.ac @@ -253,7 +253,7 @@ AC_CHECK_HEADERS([netinet/in.h sys/un.h netpacket/packet.h net/if.h net/if_dl.h #include #include ]) AC_CHECK_HEADERS([sys/stropts.h sys/macstat.h linux/ethtool.h linux/sockios.h]) -AC_CHECK_HEADERS([ffi.h libunwind.h quadmath.h crt_externs.h uci.h]) +AC_CHECK_HEADERS([ffi.h libunwind.h quadmath.h crt_externs.h uci.h libmemcached/memcached.h]) dnl check data types dnl AC_CHECK_TYPE([wchar_t], @@ -384,6 +384,14 @@ AC_CHECK_LIB([uci], [uci_alloc_context], [UCI_LIBS="-luci"]) AC_SUBST(UCI_LIBS) AM_CONDITIONAL(HAVE_LIBUCI, test "x${ac_cv_lib_uci_uci_alloc_context}" = "xyes" -a "x${ac_cv_header_uci_h}" = "xyes") +dnl libmemcachd (optional) +AC_CHECK_LIB([memcached], [memcached], + [ + MEMCACHED_LIBS="-lmemcached" + AC_DEFINE([HAVE_MEMCACHED_LIB], [1], [libmemcached library is available]) + ]) +AC_SUBST(MEMCACHED_LIBS) + AC_MSG_CHECKING([for va_copy]) AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ va_list x, y; @@ -1059,6 +1067,30 @@ then fi AM_CONDITIONAL(ENABLE_MOD_UCI, test "${enable_mod_uci_is}" = "yes") +dnl ===== enable-mod-memc ===== +AC_ARG_ENABLE([mod-memc], + [AS_HELP_STRING([--enable-mod-memc],[enable mod/memc. one of auto, yes, no (default. auto)])], + enable_mod_memc_is=$enableval, + enable_mod_memc_is=auto +) +if test "x${enable_mod_memc_is}" = "xauto" +then + if test "x${ac_cv_header_libmemcached_memcached_h}" != "xyes" + then + enable_mod_memc_is="no" + elif test "x${ac_cv_lib_memcached_memcached}" != "xyes" + then + enable_mod_memc_is="no" + else + enable_mod_memc_is="yes" + fi +fi +if test "x${enable_mod_memc_is}" = "xyes" +then + AC_DEFINE([HAWK_ENABLE_MOD_MEMC],[1],[build mod/memc]) +fi +AM_CONDITIONAL(ENABLE_MOD_MEMC, test "${enable_mod_memc_is}" = "yes") + dnl ==== include pthread options to the default flags ==== dnl keep this as the last option as it changes the default compile flags. dnl otherwise, other tests may get affected if this option is on. @@ -1112,5 +1144,6 @@ echo " Math library: ${LIBM}" echo " Socket library: ${SOCKET_LIBS}" echo " Native function call library: ${FFI_LIBS}" echo " Thraed library: ${PTHREAD_LIBS}" +echo " Memcached library: ${MEMCACHED_LIBS}" echo "-------------------------------------------------------------------------" ] diff --git a/lib/Makefile.am b/lib/Makefile.am index 949509fb..71076ce3 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -236,6 +236,10 @@ if ENABLE_MOD_UCI libhawk_la_LIBADD += ../mod/libhawk-uci.la endif +if ENABLE_MOD_MEMC +libhawk_la_LIBADD += ../mod/libhawk-memc.la +endif + else ################################################## # DYNAMIC MODULES diff --git a/lib/parse.c b/lib/parse.c index c8db72ee..19446db4 100644 --- a/lib/parse.c +++ b/lib/parse.c @@ -7475,6 +7475,10 @@ int hawk_putsrcoochars (hawk_t* hawk, const hawk_ooch_t* str, hawk_oow_t len) #include "../mod/mod-uci.h" #endif +#if defined(HAWK_ENABLE_MOD_MEMC) +#include "../mod/mod-memc.h" +#endif + /* * if modules are linked statically into the main hawk module, * this table is used to find the entry point of the modules. @@ -7503,6 +7507,9 @@ static struct #if defined(HAWK_ENABLE_MOD_UCI) { HAWK_T("uci"), hawk_mod_uci } #endif +#if defined(HAWK_ENABLE_MOD_MEMC) + { HAWK_T("memc"), hawk_mod_memc } +#endif }; #endif diff --git a/mod/Makefile.am b/mod/Makefile.am index 8f0a9c74..de4cd1f5 100644 --- a/mod/Makefile.am +++ b/mod/Makefile.am @@ -37,6 +37,10 @@ if ENABLE_MOD_UCI noinst_LTLIBRARIES += libhawk-uci.la endif +if ENABLE_MOD_MEMC +noinst_LTLIBRARIES += libhawk-memc.la +endif + ################################################## else ################################################## @@ -66,6 +70,10 @@ if ENABLE_MOD_UCI pkgmodexec_LTLIBRARIES += libhawk-uci.la endif +if ENABLE_MOD_MEMC +pkgmodexec_LTLIBRARIES += libhawk-memc.la +endif + ################################################## endif ################################################## @@ -105,3 +113,11 @@ libhawk_uci_la_CFLAGS = $(CFLAGS_COMMON) libhawk_uci_la_LDFLAGS = $(LDFLAGS_COMMON) libhawk_uci_la_LIBADD = $(LIBADD_COMMON) $(UCI_LIBS) endif + +if ENABLE_MOD_MEMC +libhawk_memc_la_SOURCES = mod-memc.c mod-memc.h +libhawk_memc_la_CPPFLAGS = $(CPPFLAGS_COMMON) $(MEMCACHED_CFLAGS) +libhawk_memc_la_CFLAGS = $(CFLAGS_COMMON) +libhawk_memc_la_LDFLAGS = $(LDFLAGS_COMMON) $(MEMCACHED_LDFLAGS) +libhawk_memc_la_LIBADD = $(LIBADD_COMMON) $(MEMCACHED_LIBS) +endif diff --git a/mod/mod-memc.c b/mod/mod-memc.c new file mode 100644 index 00000000..f4635cb6 --- /dev/null +++ b/mod/mod-memc.c @@ -0,0 +1,462 @@ +/* + * $Id$ + * + Copyright (c) 2006-2020 Chung, Hyung-Hwan. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "mod-memc.h" +#include "../lib/hawk-prv.h" + +#include + +#define __IDMAP_NODE_T_DATA memcached_st *memc; +#define __IDMAP_LIST_T_DATA memcached_return_t rc; hawk_ooch_t errmsg[256]; +#define __IDMAP_LIST_T memc_list_t +#define __IDMAP_NODE_T memc_node_t +#define __INIT_IDMAP_LIST __init_memc_list +#define __FINI_IDMAP_LIST __fini_memc_list +#define __MAKE_IDMAP_NODE __new_memc_node +#define __FREE_IDMAP_NODE __free_memc_node +#include "../lib/idmap-imp.h" + +struct rtx_data_t +{ + memc_list_t memc_list; +}; +typedef struct rtx_data_t rtx_data_t; + +/* ------------------------------------------------------------------------ */ + +static memc_node_t* new_memc_node (hawk_rtx_t* rtx, memc_list_t* memc_list) +{ + memc_node_t* memc_node; + + memc_node = __new_memc_node(rtx, memc_list); + if (!memc_node) return HAWK_NULL; + + memc_node->memc = HAWK_NULL; + return memc_node; +} + +static void free_memc_node (hawk_rtx_t* rtx, memc_list_t* memc_list, memc_node_t* memc_node) +{ + if (memc_node->memc != HAWK_NULL) { + memcached_free (memc_node->memc); + memc_node->memc = HAWK_NULL; + } + + __free_memc_node (rtx, memc_list, memc_node); +} + +static HAWK_INLINE memc_list_t* rtx_to_memc_list (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi) +{ + hawk_rbt_pair_t* pair; + rtx_data_t* data; + + pair = hawk_rbt_search((hawk_rbt_t*)fi->mod->ctx, &rtx, HAWK_SIZEOF(rtx)); + HAWK_ASSERT (pair != HAWK_NULL); + data = (rtx_data_t*)HAWK_RBT_VPTR(pair); + return &data->memc_list; +} + +static void set_error_on_memc_list (hawk_rtx_t* rtx, memc_list_t* memc_list, const hawk_ooch_t* errfmt, ...) +{ + if (errfmt) + { + va_list ap; + va_start (ap, errfmt); + hawk_rtx_vfmttooocstr (rtx, memc_list->errmsg, HAWK_COUNTOF(memc_list->errmsg), errfmt, ap); + va_end (ap); + } + else + { + hawk_copy_oocstr (memc_list->errmsg, HAWK_COUNTOF(memc_list->errmsg), hawk_rtx_geterrmsg(rtx)); + } +} + +static HAWK_INLINE memc_node_t* get_memc_list_node (memc_list_t* memc_list, hawk_int_t id) +{ + if (id < 0 || id >= memc_list->map.high || !memc_list->map.tab[id]) return HAWK_NULL; + return memc_list->map.tab[id]; +} + +static memc_node_t* get_memc_list_node_with_arg (hawk_rtx_t* rtx, memc_list_t* memc_list, hawk_val_t* arg) +{ + hawk_int_t id; + memc_node_t* memc_node; + + if (hawk_rtx_valtoint(rtx, arg, &id) <= -1) + { + set_error_on_memc_list (rtx, memc_list, HAWK_T("illegal instance id")); + return HAWK_NULL; + } + else if (!(memc_node = get_memc_list_node(memc_list, id))) + { + set_error_on_memc_list (rtx, memc_list, HAWK_T("invalid instance id - %zd"), (hawk_oow_t)id); + return HAWK_NULL; + } + + return memc_node; +} + +/* ------------------------------------------------------------------------ */ + +static int fnc_new (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi) +{ + memc_list_t* memc_list; + memc_node_t* memc_node = HAWK_NULL; + hawk_int_t ret = -1; + hawk_val_t* retv; + + memc_list = rtx_to_memc_list(rtx, fi); + + memc_node = new_memc_node(rtx, memc_list); + if (memc_node) ret = memc_node->id; + else set_error_on_memc_list (rtx, memc_list, HAWK_NULL); + + /* ret may not be a statically managed number. + * error checking is required */ + retv = hawk_rtx_makeintval(rtx, ret); + if (retv == HAWK_NULL) + { + if (memc_node) free_memc_node (rtx, memc_list, memc_node); + return -1; + } + + hawk_rtx_setretval (rtx, retv); + return 0; +} + +static int fnc_connect (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi) +{ + memc_list_t* memc_list; + memc_node_t* memc_node; + hawk_val_t* a0; + hawk_bch_t* conf = HAWK_NULL; + hawk_oow_t conf_len = 0; + int ret = -1, take_rtx_err = 0; + + memc_list = rtx_to_memc_list(rtx, fi); + memc_node = get_memc_list_node_with_arg(rtx, memc_list, hawk_rtx_getarg(rtx, 0)); + if (memc_node) + { + a0 = hawk_rtx_getarg(rtx, 0); + if (!(conf = hawk_rtx_getvalbcstr(rtx, a0, &conf_len))) + { + take_rtx_err = 1; + goto done; + } + + memc_node->memc = memcached (conf, conf_len); + if (memc_node->memc == HAWK_NULL) + { + set_error_on_memc_list (rtx, memc_list, HAWK_T("unable to connect to %hs"), conf); + goto done; + } + + ret = 0; + } + +done: + if (take_rtx_err) set_error_on_memc_list (rtx, memc_list, HAWK_NULL); + if (conf) hawk_rtx_freevalbcstr (rtx, a0, conf); + + hawk_rtx_setretval (rtx, hawk_rtx_makeintval(rtx, ret)); + return 0; +} + +static int fnc_close (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi) +{ + memc_list_t* memc_list; + memc_node_t* memc_node; + int ret = -1; + + memc_list = rtx_to_memc_list(rtx, fi); + memc_node = get_memc_list_node_with_arg(rtx, memc_list, hawk_rtx_getarg(rtx, 0)); + if (memc_node) + { + free_memc_node (rtx, memc_list, memc_node); + ret = 0; + } + + hawk_rtx_setretval (rtx, hawk_rtx_makeintval(rtx, ret)); + return 0; +} + +static int fnc_errmsg (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi) +{ + memc_list_t* memc_list; + hawk_val_t* retv; + + memc_list = rtx_to_memc_list(rtx, fi); + retv = hawk_rtx_makestrvalwithoocstr(rtx, memc_list->errmsg); + if (!retv) return -1; + + hawk_rtx_setretval (rtx, retv); + return 0; +} + +static int fnc_get (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi) +{ + memc_list_t* memc_list; + memc_node_t* memc_node; + int take_rtx_err = 0; + + hawk_val_t* a1, * a2; + hawk_bch_t* key = HAWK_NULL; + hawk_bch_t* def_val = HAWK_NULL; + hawk_bch_t* rv = HAWK_NULL; + hawk_oow_t key_len = 0; + hawk_oow_t def_val_len = 0; + hawk_oow_t rv_len = 0; + char* val = HAWK_NULL; + size_t val_len = 0; + + memc_list = rtx_to_memc_list(rtx, fi); + memc_node = get_memc_list_node_with_arg(rtx, memc_list, hawk_rtx_getarg(rtx, 0)); + if (!memc_node) + { + goto done; + } + else + { + uint32_t flags; + memcached_return_t rc; + + hawk_oow_t nargs; + nargs = hawk_rtx_getnargs(rtx); + + a1 = hawk_rtx_getarg(rtx, 1); + if (!(key = hawk_rtx_getvalbcstr(rtx, a1, &key_len))) + { + take_rtx_err = 1; + goto done; + } + + if (nargs >= 3) + { + a2 = hawk_rtx_getarg(rtx, 2); + if (!(def_val = hawk_rtx_getvalbcstr(rtx, a2, &def_val_len))) + { + take_rtx_err = 1; + goto done; + } + } + + val = memcached_get(memc_node->memc, key, key_len, &val_len, &flags, &rc); + if (val == HAWK_NULL) goto done; + + rv = val; + rv_len = val_len; + } + +done: + if (rv == HAWK_NULL) + { + if (def_val != HAWK_NULL) rv = ""; + else + { + rv = def_val; + rv_len = def_val_len; + } + } + hawk_rtx_setretval (rtx, hawk_rtx_makestrvalwithbchars(rtx, rv, rv_len)); + + if (take_rtx_err) set_error_on_memc_list (rtx, memc_list, HAWK_NULL); + if (key) hawk_rtx_freevalbcstr (rtx, a1, key); + if (def_val) hawk_rtx_freevalbcstr (rtx, a2, def_val); + if (val) free(val); + + return 0; +} + +static int fnc_set (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi) +{ + memc_list_t* memc_list; + memc_node_t* memc_node; + int ret = -1; + int take_rtx_err = 0; + + hawk_val_t* a1, * a2, * a3, * a4; + hawk_bch_t* key = HAWK_NULL; + hawk_bch_t* val = HAWK_NULL; + hawk_oow_t key_len = 0; + hawk_oow_t val_len = 0; + hawk_int_t ttl = 0; + hawk_int_t flag = 0; + + memc_list = rtx_to_memc_list(rtx, fi); + memc_node = get_memc_list_node_with_arg(rtx, memc_list, hawk_rtx_getarg(rtx, 0)); + if (!memc_node) + { + goto done; + } + else + { + hawk_oow_t nargs; + memcached_return_t rc; + + a1 = hawk_rtx_getarg(rtx, 1); + a2 = hawk_rtx_getarg(rtx, 2); + a3 = hawk_rtx_getarg(rtx, 3); + + if (!(key = hawk_rtx_getvalbcstr(rtx, a1, &key_len)) || + !(val = hawk_rtx_getvalbcstr(rtx, a2, &val_len)) || + (hawk_rtx_valtoint(rtx, a3, &ttl) <= -1)) + { + take_rtx_err = 1; + goto done; + } + + nargs = hawk_rtx_getnargs(rtx); + if (nargs >= 5) { + a4 = hawk_rtx_getarg(rtx, 4); + if (hawk_rtx_valtoint(rtx, a4, &flag) <= -1) + { + take_rtx_err = 1; + goto done; + } + } + + rc = memcached_set(memc_node->memc, key, key_len, val, val_len, ttl, flag); + if (rc != MEMCACHED_SUCCESS) { + goto done; + } + + ret = 0; + } + +done: + if (take_rtx_err) set_error_on_memc_list (rtx, memc_list, HAWK_NULL); + if (key) hawk_rtx_freevalbcstr (rtx, a1, key); + if (val) hawk_rtx_freevalbcstr (rtx, a2, val); + + hawk_rtx_setretval (rtx, hawk_rtx_makeintval(rtx, ret)); + return 0; +} + +/* ------------------------------------------------------------------------ */ + +static hawk_mod_fnc_tab_t fnctab[] = +{ + /* keep this table sorted for binary search in query(). */ + { HAWK_T("close"), { { 1, 1, HAWK_NULL }, fnc_close, 0 } }, + { HAWK_T("connect"), { { 1, 1, HAWK_NULL }, fnc_connect, 0 } }, + { HAWK_T("errmsg"), { { 0, 0, HAWK_NULL }, fnc_errmsg, 0 } }, + { HAWK_T("get"), { { 2, 3, HAWK_NULL }, fnc_get, 0 } }, + { HAWK_T("new"), { { 0, 0, HAWK_NULL }, fnc_new, 0 } }, + { HAWK_T("set"), { { 4, 5, HAWK_NULL }, fnc_set, 0 } }, +}; + +static int query (hawk_mod_t* mod, hawk_t* hawk, const hawk_ooch_t* name, hawk_mod_sym_t* sym) +{ + return hawk_findmodsymfnc(hawk, fnctab, HAWK_COUNTOF(fnctab), name, sym); +} + +static int init (hawk_mod_t* mod, hawk_rtx_t* rtx) +{ + hawk_rbt_t* rbt; + rtx_data_t data, * datap; + hawk_rbt_pair_t* pair; + + rbt = (hawk_rbt_t*)mod->ctx; + + HAWK_MEMSET (&data, 0, HAWK_SIZEOF(data)); + pair = hawk_rbt_insert(rbt, &rtx, HAWK_SIZEOF(rtx), &data, HAWK_SIZEOF(data)); + if (HAWK_UNLIKELY(!pair)) return -1; + + datap = (rtx_data_t*)HAWK_RBT_VPTR(pair); + __init_memc_list (rtx, &datap->memc_list); + + return 0; +} + +static void fini (hawk_mod_t* mod, hawk_rtx_t* rtx) +{ + hawk_rbt_t* rbt; + hawk_rbt_pair_t* pair; + + rbt = (hawk_rbt_t*)mod->ctx; + + /* garbage clean-up */ + pair = hawk_rbt_search(rbt, &rtx, HAWK_SIZEOF(rtx)); + if (pair) + { + rtx_data_t* data; + + data = (rtx_data_t*)HAWK_RBT_VPTR(pair); + __fini_memc_list (rtx, &data->memc_list); + hawk_rbt_delete (rbt, &rtx, HAWK_SIZEOF(rtx)); + } +} + +static void unload (hawk_mod_t* mod, hawk_t* hawk) +{ + hawk_rbt_t* rbt; + + rbt = (hawk_rbt_t*)mod->ctx; + + HAWK_ASSERT (HAWK_RBT_SIZE(rbt) == 0); + hawk_rbt_close (rbt); +} + +int hawk_mod_memc (hawk_mod_t* mod, hawk_t* hawk) +{ + hawk_rbt_t* rbt; + + mod->query = query; + mod->unload = unload; + + mod->init = init; + mod->fini = fini; + + rbt = hawk_rbt_open(hawk_getgem(hawk), 0, 1, 1); + if (HAWK_UNLIKELY(!rbt)) return -1; + + hawk_rbt_setstyle (rbt, hawk_get_rbt_style(HAWK_RBT_STYLE_INLINE_COPIERS)); + mod->ctx = rbt; + + return 0; +} + +/* ------------------------------------------------------------------------ */ +/* +BEGIN { + conn = memc::new(); + if (memc::connect("--SERVER=localhost") <= -1) + { + print "connect error -", memc::errmsg(); + return -1; + } + + rc = memc::set(conn, "key", "val", 900); + if (rc <= -1) { + print "store result error - ", memc::errmsg(); + } + + val = memc::get(conn, "key"); print val; + val = memc::get(conn, "key", "default"); print val; + + memc::close(conn); +} +*/ diff --git a/mod/mod-memc.h b/mod/mod-memc.h new file mode 100644 index 00000000..d481885a --- /dev/null +++ b/mod/mod-memc.h @@ -0,0 +1,43 @@ +/* + * $Id$ + * + Copyright (c) 2006-2020 Chung, Hyung-Hwan. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _HAWK_MOD_MEMC_H_ +#define _HAWK_MOD_MEMC_H_ + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +HAWK_EXPORT int hawk_mod_memc (hawk_mod_t* mod, hawk_t* hawk); + +#if defined(__cplusplus) +} +#endif + +#endif +