diff --git a/src/mod/applications/mod_memcache/Makefile b/src/mod/applications/mod_memcache/Makefile new file mode 100644 index 0000000000..68f88cfe73 --- /dev/null +++ b/src/mod/applications/mod_memcache/Makefile @@ -0,0 +1,31 @@ +MEMCACHED=libmemcached-0.27 +switch_srcdir=../../../.. + +WANT_CURL=yes + +MEMCACHED_DIR=$(switch_srcdir)/libs/$(MEMCACHED) + +MEMCACHEDLA=$(MEMCACHED_DIR)/libmemcached/libmemcached.la + +LOCAL_CFLAGS=-I$(MEMCACHED_DIR) +LOCAL_LIBADD=$(MEMCACHEDLA) + +include $(switch_srcdir)/build/modmake.rules + +DEFAULT_ARGS=--prefix=$(PREFIX) --disable-shared --with-pic + +$(LOCAL_OBJS): $(LOCAL_SOURCES) + +$(MEMCACHED_DIR): + $(GETLIB) $(MEMCACHED).tar.gz + +$(MEMCACHED_DIR)/Makefile: $(MEMCACHED_DIR) + cd $(MEMCACHED_DIR) && CFLAGS=$(AM_CFLAGS) CC=$(CC) CXX=$(CXX) ./configure --disable-shared --with-pic CPPFLAGS= LDFLAGS= + $(TOUCH_TARGET) + +$(MEMCACHEDLA): $(MEMCACHED_DIR)/Makefile + cd $(MEMCACHED_DIR) && $(MAKE) + $(TOUCH_TARGET) + + + diff --git a/src/mod/applications/mod_memcache/mod_memcache.c b/src/mod/applications/mod_memcache/mod_memcache.c new file mode 100755 index 0000000000..a1f6752c71 --- /dev/null +++ b/src/mod/applications/mod_memcache/mod_memcache.c @@ -0,0 +1,391 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005-2009, Anthony Minessale II + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Rupa Schomaker + * Anthony Minessale II + * Neal Horman + * + * + * mod_memcache.c -- API for memcache + * + */ +#include +#include + +/* Prototypes */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_memcache_shutdown); +SWITCH_MODULE_RUNTIME_FUNCTION(mod_memcache_runtime); +SWITCH_MODULE_LOAD_FUNCTION(mod_memcache_load); + +/* SWITCH_MODULE_DEFINITION(name, load, shutdown, runtime) + * Defines a switch_loadable_module_function_table_t and a static const char[] modname + */ +SWITCH_MODULE_DEFINITION(mod_memcache, mod_memcache_load, mod_memcache_shutdown, NULL); + +static char *SYNTAX = "memcache [expiration [flags]]\n" + "memcache \n" + "memcache \n" + "memcache [offset]\n" + "memcache \n" + "memcache [verbose]\n"; + +static struct { + memcached_st *memcached; + char *memcached_str; +} globals; + +static switch_status_t config_callback_memcached(switch_xml_config_item_t *data, switch_config_callback_type_t callback_type, switch_bool_t changed) +{ + memcached_server_st *memcached_server = NULL; + memcached_st *newmemcached = NULL; + memcached_st *oldmemcached = NULL; + char *memcached_str = NULL; + memcached_return rc; + unsigned int servercount; + + if ((callback_type == CONFIG_LOAD || callback_type == CONFIG_RELOAD) && changed) { + memcached_str = *((char**)data->ptr); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "memcached data: %s\n", memcached_str); + + /* initialize main ptr */ + memcached_server = memcached_servers_parse(memcached_str); + if (!memcached_server) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Unable to initialize memcached data structure (server_list).\n"); + goto error; + } + + if ((servercount = memcached_server_list_count(memcached_server)) == 0) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "No memcache servers defined. Server string: %s.\n", memcached_str); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%u servers defined.\n", servercount); + } + + /* setup memcached */ + newmemcached = memcached_create(NULL); + if (!newmemcached) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Unable to initialize memcached data structure (memcached_st).\n"); + goto error; + } + rc = memcached_server_push(newmemcached, memcached_server); + if (rc != MEMCACHED_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memcache error adding server list: %s\n", memcached_strerror(newmemcached, rc)); + goto error; + } + /* memcached_behavior_set(newmemcached, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1); */ + + /* swap pointers */ + oldmemcached = globals.memcached; + globals.memcached = newmemcached; + newmemcached = NULL; + } + + if (memcached_server) { + memcached_server_list_free(memcached_server); + } + if (newmemcached) { + memcached_free(newmemcached); + } + if (oldmemcached) { + memcached_free(oldmemcached); + } + return SWITCH_STATUS_SUCCESS; + +error: + if (memcached_server) { + memcached_server_list_free(memcached_server); + } + if (newmemcached) { + memcached_free(newmemcached); + } + if (oldmemcached) { + memcached_free(oldmemcached); + } + return SWITCH_STATUS_GENERR; +} + +static switch_xml_config_string_options_t config_opt_memcache_servers = {NULL, 0, ".*"}; /* anything is ok here */ + +static switch_xml_config_item_t instructions[] = { + /* parameter name type reloadable pointer default value options structure */ + SWITCH_CONFIG_ITEM_CALLBACK("memcache-servers", SWITCH_CONFIG_STRING, CONFIG_REQUIRED | CONFIG_RELOAD, &globals.memcached_str, NULL, config_callback_memcached, &config_opt_memcache_servers, + "host,host:port,host", "List of memcached servers."), + SWITCH_CONFIG_ITEM_END() +}; + +static switch_status_t do_config(switch_bool_t reload) +{ + switch_xml_t cfg, xml, settings; + + memset(&globals, 0, sizeof(globals)); + + if (!(xml = switch_xml_open_cfg("memcache.conf", &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Could not open memcache.conf\n"); + return SWITCH_STATUS_FALSE; + } + + if ((settings = switch_xml_child(cfg, "settings"))) { + if (switch_xml_config_parse(switch_xml_child(settings, "param"), 0, instructions) == SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE,"Config parsed ok!\n"); + } + } + + if (xml) { + switch_xml_free(xml); + } + + return SWITCH_STATUS_SUCCESS; +} + +SWITCH_STANDARD_API(memcache_function) +{ + char *argv[5] = { 0 }; + int argc; + char *subcmd = NULL; + char *key = NULL; + char *val = NULL; + char *expires_str = NULL; + char *flags_str = NULL; + char *mydata = NULL; + size_t string_length = 0; + time_t expires = 0; + uint32_t flags = 0; + unsigned int server_count = 0; + + memcached_return rc; + memcached_st *memcached = NULL; + memcached_stat_st *stat = NULL; + memcached_server_st *server_list; + + if (switch_strlen_zero(cmd)) { + goto usage; + } + + mydata = strdup(cmd); + if ((argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))))) { + if (argc < 1) { + goto usage; + } + + /* clone memcached struct so we're thread safe */ + memcached = memcached_clone(NULL, globals.memcached); + if (!memcached) { + stream->write_function(stream, "-ERR Error cloning memcached object\n"); + } + + subcmd = argv[0]; + + if ((!strcasecmp(subcmd, "set") || !strcasecmp(subcmd, "replace") || !strcasecmp(subcmd, "add")) && argc > 2) { + key = argv[1]; + val = argv[2]; + + if(argc > 3) { + expires_str = argv[3]; + expires = (time_t)strtol(expires_str, NULL, 10); + } + if(argc > 4) { + flags_str = argv[4]; + flags = (uint32_t)strtol(flags_str, NULL, 16); + } + if (!strcasecmp(subcmd, "set")) { + rc = memcached_set(memcached, key, strlen(key), val, strlen(val), expires, flags); + } else if (!strcasecmp(subcmd, "replace")) { + rc = memcached_replace(memcached, key, strlen(key), val, strlen(val), expires, flags); + } else if (!strcasecmp(subcmd, "add")) { + rc = memcached_add(memcached, key, strlen(key), val, strlen(val), expires, flags); + } + + if (rc == MEMCACHED_SUCCESS) { + stream->write_function(stream, "+OK\n"); + } else { + stream->write_function(stream, "-ERR Error while running command %s: %s\n", subcmd, memcached_strerror(memcached, rc)); + } + } else if (!strcasecmp(subcmd, "get") && argc > 1) { + key = argv[1]; + + val = memcached_get(memcached, key, strlen(key), &string_length, &flags, &rc); + if (rc == MEMCACHED_SUCCESS) { + stream->write_function(stream, "%.*s", (int)string_length, val); + } else { + stream->write_function(stream, "-ERR Error while running command %s: %s\n", subcmd, memcached_strerror(memcached, rc)); + } + } else if (!strcasecmp(subcmd, "getflags") && argc > 1) { + key = argv[1]; + + val = memcached_get(memcached, key, strlen(key), &string_length, &flags, &rc); + if (rc == MEMCACHED_SUCCESS) { + stream->write_function(stream, "%x", flags); + } else { + stream->write_function(stream, "-ERR Error while running command %s: %s\n", subcmd, memcached_strerror(memcached, rc)); + } + } else if ((!strcasecmp(subcmd, "increment") || !strcasecmp(subcmd, "decrement")) && argc > 1) { + key = argv[1]; + uint64_t ivalue; + unsigned int offset = 1; + if(argc > 2) { + offset = (unsigned int)strtol(argv[2], NULL, 10); + } + if (!strcasecmp(subcmd, "increment")) { + rc = memcached_increment(memcached, key, strlen(key), offset, &ivalue); + } else if (!strcasecmp(subcmd, "decrement")) { + rc = memcached_decrement(memcached, key, strlen(key), offset, &ivalue); + } + if (rc == MEMCACHED_SUCCESS) { + stream->write_function(stream, "%ld", ivalue); + } else { + stream->write_function(stream, "-ERR Error while running command %s %s: %s\n", subcmd, key, memcached_strerror(memcached, rc)); + } + } else if (!strcasecmp(subcmd, "delete") && argc > 1) { + key = argv[1]; + if(argc > 2) { + expires_str = argv[3]; + expires = (time_t)strtol(expires_str, NULL, 10); + } + rc = memcached_delete(memcached, key, strlen(key), expires); + if (rc == MEMCACHED_SUCCESS) { + stream->write_function(stream, "+OK\n", key); + } else { + stream->write_function(stream, "-ERR Error while running command %s %s: %s\n", subcmd, key, memcached_strerror(memcached, rc)); + } + } else if (!strcasecmp(subcmd, "flush")) { + if(argc > 1) { + expires_str = argv[3]; + expires = (time_t)strtol(expires_str, NULL, 10); + } + rc = memcached_flush(memcached, expires); + if (rc == MEMCACHED_SUCCESS) { + stream->write_function(stream, "+OK\n", key); + } else { + stream->write_function(stream, "-ERR Error while running command %s : %s\n", subcmd, memcached_strerror(memcached, rc)); + } + } else if (!strcasecmp(subcmd, "status")) { + switch_bool_t verbose = SWITCH_FALSE; + + if (argc > 1) { + if (!strcasecmp(argv[1], "verbose")) { + verbose = SWITCH_TRUE; + } + } + + stream->write_function(stream, "Lib version: %s\n", memcached_lib_version()); + stat = memcached_stat(memcached, NULL, &rc); + if (rc != MEMCACHED_SUCCESS && rc != MEMCACHED_SOME_ERRORS) { + stream->write_function(stream, "-ERR Error communicating with servers (%s)\n", memcached_strerror(memcached, rc)); + } + server_list = memcached_server_list(memcached); + server_count = memcached_server_count(memcached); + stream->write_function(stream, "Servers: %d\n", server_count); + for (int x=0; x < server_count; x++) { + stream->write_function(stream, " %s (%u)\n", memcached_server_name(memcached, server_list[x]), memcached_server_port(memcached, server_list[x])); + if (verbose == SWITCH_TRUE) { + char **list; + char **ptr; + char *value; + memcached_return rc2; + + list = memcached_stat_get_keys(memcached, &stat[x], &rc); + for (ptr = list; *ptr; ptr++) { + value = memcached_stat_get_value(memcached, &stat[x], *ptr, &rc2); + stream->write_function(stream, " %s: %s\n", *ptr, value); + switch_safe_free(value); + } + switch_safe_free(list); + stream->write_function(stream, "\n"); + } + } + } else { + goto usage; + } + } + + if (memcached) { + memcached_quit(memcached); + memcached_free(memcached); + } + switch_safe_free(mydata); + switch_safe_free(stat); + return SWITCH_STATUS_SUCCESS; +usage: + if (memcached) { + memcached_quit(memcached); + memcached_free(memcached); + } + switch_safe_free(mydata); + stream->write_function(stream, "-ERR\n%s\n", SYNTAX); + return SWITCH_STATUS_SUCCESS; +} + +/* Macro expands to: switch_status_t mod_memcache_load(switch_loadable_module_interface_t **module_interface, switch_memory_pool_t *pool) */ +SWITCH_MODULE_LOAD_FUNCTION(mod_memcache_load) +{ + switch_api_interface_t *api_interface; + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + + do_config(SWITCH_FALSE); + + SWITCH_ADD_API(api_interface, "memcache", "Memcache API", memcache_function, "syntax"); + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_SUCCESS; +} + +/* + Called when the system shuts down + Macro expands to: switch_status_t mod_memcache_shutdown() */ +SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_memcache_shutdown) +{ + /* Cleanup dynamically allocated config settings */ + switch_xml_config_cleanup(instructions); + if (globals.memcached) { + memcached_free(globals.memcached); + } + return SWITCH_STATUS_SUCCESS; +} + + +/* + If it exists, this is called in it's own thread when the module-load completes + If it returns anything but SWITCH_STATUS_TERM it will be called again automatically + Macro expands to: switch_status_t mod_memcache_runtime() +SWITCH_MODULE_RUNTIME_FUNCTION(mod_memcache_runtime) +{ + while(looping) + { + switch_cond_next(); + } + return SWITCH_STATUS_TERM; +} +*/ + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:t + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 + */