Merge "res_pjsip/config_transport: Allow reloading transports."
authorJoshua Colp <jcolp@digium.com>
Sat, 27 Feb 2016 16:18:26 +0000 (10:18 -0600)
committerGerrit Code Review <gerrit2@gerrit.digium.api>
Sat, 27 Feb 2016 16:18:26 +0000 (10:18 -0600)
CHANGES
UPGRADE.txt
channels/chan_sip.c
configs/samples/pjproject.conf.sample [new file with mode: 0644]
configs/samples/sip.conf.sample
include/asterisk/rtp_engine.h
res/res_config_sqlite3.c
res/res_pjproject.c
res/res_pjsip_config_wizard.c
res/res_sorcery_memory_cache.c

diff --git a/CHANGES b/CHANGES
index 260aa2f..91e170f 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -81,6 +81,11 @@ chan_sip
  * DTLS related configuration options can now be set at a general level.
    Enabling DTLS support, though, requires enabling it at the user
    or peer level.
+ * Added the possibility to set the From: header through the the SIP dial
+   string (populating the fromuser/fromdomain fields), complementing the
+   [!dnid] option for the To: header that has existed since 1.6.0 (1d6b192).
+   NOTE: This is again separated by an exclamation mark, so the To: header may
+   not contain one of those.
 
 chan_pjsip
 ------------------
@@ -210,6 +215,12 @@ Queue
 --- Functionality changes from Asterisk 13.7.0 to Asterisk 13.8.0 ------------
 ------------------------------------------------------------------------------
 
+res_pjsip_config_wizard
+------------------
+ * A new command (pjsip export config_wizard primitives) has been added that
+   will export all the pjsip objects it created to the console or a file
+   suitable for reuse in a pjsip.conf file.
+
 app_confbridge
 ------------------
  * Added CONFBRIDGE_INFO(muted,) for querying the muted conference state.
@@ -229,6 +240,14 @@ res_pjproject
    This displays the compiled-in options of the pjproject installation
    Asterisk is currently running against.
 
+ * Another feature of this module is the ability to map pjproject log levels
+   to Asterisk log levels, or to suppress the pjproject log messages
+   altogether.  Many of the messages emitted by pjproject itself are the result
+   of errors which Asterisk will ultimately handle so the messages can be
+   misleading or just noise.  A new config file (pjproject.conf) has been added
+   to configure the mapping and a new CLI command (pjproject show log mappings)
+   has been added to display the mappings currently in use.
+
 res_pjsip
 ------------------
  * Transports are now reloadable.  In testing, no in-progress calls were
index 6fb82c4..131ce6c 100644 (file)
@@ -31,6 +31,11 @@ chan_dahdi:
    ring-ring-ring pattern would exceed the pattern limits and stop
    Caller-ID detection.
 
+chan_sip:
+ - The SIP dial string has been extended past the [!dnid] option by another
+   exclamation mark: [!dnid[!fromuri].  An exclamation mark in the To-URI
+   will now mean changes to the From-URI.
+
 Core:
  - The REF_DEBUG compiler flag is now used to enable refdebug by default.
    The setting can be overridden in asterisk.conf by setting refdebug in
index aaf0b6d..6250f4e 100644 (file)
@@ -13447,7 +13447,7 @@ static enum sip_result add_sdp(struct sip_request *resp, struct sip_pvt *p, int
 
                ast_str_append(&m_modem, 0, "m=image %d udptl t38\r\n", ast_sockaddr_port(&udptldest));
 
-               if (ast_sockaddr_cmp(&udptldest, &dest)) {
+               if (ast_sockaddr_cmp_addr(&udptldest, &dest)) {
                        ast_str_append(&m_modem, 0, "c=IN %s %s\r\n",
                                        (ast_sockaddr_is_ipv6(&udptldest) && !ast_sockaddr_is_ipv4_mapped(&udptldest)) ?
                                        "IP6" : "IP4", ast_sockaddr_stringify_addr_remote(&udptldest));
@@ -29680,7 +29680,8 @@ static int sip_devicestate(const char *data)
  *     or      SIP/devicename/extension/IPorHost
  *     or      SIP/username@domain//IPorHost
  *     and there is an optional [!dnid] argument you can append to alter the
- *     To: header.
+ *     To: header. And after that, a [![fromuser][@fromdomain]] argument.
+ *     Leave those blank to use the defaults.
  * \endverbatim
  */
 static struct ast_channel *sip_request_call(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *dest, int *cause)
@@ -29752,11 +29753,49 @@ static struct ast_channel *sip_request_call(const char *type, struct ast_format_
        /* Save the destination, the SIP dial string */
        ast_copy_string(tmp, dest, sizeof(tmp));
 
-       /* Find DNID and take it away */
+       /* Find optional DNID (SIP to-uri) and From-CLI (SIP from-uri)
+        * and strip it from the dial string:
+        *   [!touser[@todomain][![fromuser][@fromdomain]]]
+        * For historical reasons, the touser@todomain is passed as dnid
+        * while fromuser@fromdomain are split immediately. Passing a
+        * todomain without touser will create an invalid SIP message. */
        dnid = strchr(tmp, '!');
        if (dnid != NULL) {
+               char *fromuser_and_domain;
+
                *dnid++ = '\0';
-               ast_string_field_set(p, todnid, dnid);
+               if ((fromuser_and_domain = strchr(dnid, '!'))) {
+                       char *forward_compat;
+                       char *fromdomain;
+
+                       *fromuser_and_domain++ = '\0';
+
+                       /* Cut it at a trailing NUL or trailing '!' for
+                        * forward compatibility with extra arguments
+                        * in the future. */
+                       if ((forward_compat = strchr(fromuser_and_domain, '!'))) {
+                               /* Ignore the rest.. */
+                               *forward_compat = '\0';
+                       }
+
+                       if ((fromdomain = strchr(fromuser_and_domain, '@'))) {
+                               *fromdomain++ = '\0';
+                               /* Set fromdomain. */
+                               if (!ast_strlen_zero(fromdomain)) {
+                                       ast_string_field_set(p, fromdomain, fromdomain);
+                               }
+                       }
+
+                       /* Set fromuser. */
+                       if (!ast_strlen_zero(fromuser_and_domain)) {
+                               ast_string_field_set(p, fromuser, fromuser_and_domain);
+                       }
+               }
+
+               /* Set DNID (touser/todomain). */
+               if (!ast_strlen_zero(dnid)) {
+                       ast_string_field_set(p, todnid, dnid);
+               }
        }
 
        /* Divvy up the items separated by slashes */
diff --git a/configs/samples/pjproject.conf.sample b/configs/samples/pjproject.conf.sample
new file mode 100644 (file)
index 0000000..97af734
--- /dev/null
@@ -0,0 +1,28 @@
+; Common pjproject options
+;
+
+;========================LOG_MAPPINGS SECTION OPTIONS===============================
+;[log_mappings]
+;  SYNOPSIS: Provides pjproject to Asterisk log level mappings.
+;  NOTES: The name of this section in the pjproject.conf configuration file must
+;         remain log_mappings or the configuration will not be applied.
+;         The defaults mentioned below only apply if this file or the 'log_mappings'
+;         object can'tbe found.  If the object is found, there are no defaults. If
+;         you don't specify an entry, nothing will be logged for that level.
+;
+;asterisk_error =    ; A comma separated list of pjproject log levels to map to
+                     ; Asterisk errors.
+                     ; (default: "0,1")
+;asterisk_warning =  ; A comma separated list of pjproject log levels to map to
+                     ; Asterisk warnings.
+                     ; (default: "2")
+;asterisk_notice =   ; A comma separated list of pjproject log levels to map to
+                     ; Asterisk notices.
+                     ; (default: "")
+;asterisk_verbose =  ; A comma separated list of pjproject log levels to map to
+                     ; Asterisk verbose.
+                     ; (default: "")
+;asterisk_debug =    ; A comma separated list of pjproject log levels to map to
+                     ; Asterisk debug
+                     ; (default: "3,4,5")
+;type=               ; Must be of type log_mappings (default: "")
index fe68514..d89a2a1 100644 (file)
@@ -24,6 +24,9 @@
 ;        SIP/devicename/extension
 ;        SIP/devicename/extension/IPorHost
 ;        SIP/username@domain//IPorHost
+; And to alter the To: or the From: header, you can additionally append
+; the following to any of the above strings:
+;        [![touser[@todomain]][![fromuser][@fromdomain]]]
 ;
 ;
 ; Devicename
 ;
 ;         SIP/sales@mysipproxy!sales@edvina.net
 ;
+; (Specifying only @todomain without touser will create an invalid SIP
+; request.)
+;
+; Similarly, you can specify the From header as well, after a second
+; exclamation mark:
+;
+;         SIP/customer@mysipproxy!!customersupport@wearespindle.com
+;
 ; A new feature for 1.8 allows one to specify a host or IP address to use
 ; when routing the call. This is typically used in tandem with func_srv if
 ; multiple methods of reaching the same domain exist. The host or IP address
index 0f6ec7a..6734033 100644 (file)
@@ -935,7 +935,7 @@ int ast_rtp_instance_set_requested_target_address(struct ast_rtp_instance *insta
  * \since 1.8
  */
 #define ast_rtp_instance_set_remote_address(instance, address) \
-       ast_rtp_instance_set_requested_target_address((instance), (address));
+       ast_rtp_instance_set_requested_target_address((instance), (address))
 
 /*!
  * \brief Set the address that we are expecting to receive RTP on
@@ -1047,7 +1047,7 @@ void ast_rtp_instance_get_requested_target_address(struct ast_rtp_instance *inst
  * \since 1.8
  */
 #define ast_rtp_instance_get_remote_address(instance, address) \
-       ast_rtp_instance_get_incoming_source_address((instance), (address));
+       ast_rtp_instance_get_incoming_source_address((instance), (address))
 
 /*!
  * \brief Get the requested target address of the remote endpoint and
@@ -1083,7 +1083,7 @@ int ast_rtp_instance_get_and_cmp_requested_target_address(struct ast_rtp_instanc
  * \since 1.8
  */
 #define ast_rtp_instance_get_and_cmp_remote_address(instance, address) \
-       ast_rtp_instance_get_and_cmp_requested_target_address((instance), (address));
+       ast_rtp_instance_get_and_cmp_requested_target_address((instance), (address))
 
 /*!
  * \brief Set the value of an RTP instance extended property
index 0b0a78c..a306123 100644 (file)
@@ -127,8 +127,14 @@ static inline const char *sqlite3_escape_string_helper(struct ast_threadstorage
         * add two quotes, and convert NULL pointers to the word "NULL", but we
         * don't allow those anyway. Just going to use %q for now. */
        struct ast_str *buf = ast_str_thread_get(ts, maxlen);
-       char *tmp = ast_str_buffer(buf);
        char q = ts == &escape_value_buf ? '\'' : '"';
+       char *tmp;
+
+       if (ast_str_size(buf) < maxlen) {
+               /* realloc if buf is too small */
+               ast_str_make_space(&buf, maxlen);
+       }
+       tmp = ast_str_buffer(buf);
 
        ast_str_reset(buf);
        *tmp++ = q; /* Initial quote */
@@ -160,9 +166,15 @@ static const char *sqlite3_escape_column_op(const char *param)
 {
        size_t maxlen = strlen(param) * 2 + sizeof("\"\" =");
        struct ast_str *buf = ast_str_thread_get(&escape_column_buf, maxlen);
-       char *tmp = ast_str_buffer(buf);
+       char *tmp;
        int space = 0;
 
+       if (ast_str_size(buf) < maxlen) {
+               /* realloc if buf is too small */
+               ast_str_make_space(&buf, maxlen);
+       }
+       tmp = ast_str_buffer(buf);
+
        ast_str_reset(buf);
        *tmp++ = '"';
        while ((*tmp++ = *param++)) {
index 9e08bf3..9ed3d57 100644 (file)
        <support_level>core</support_level>
  ***/
 
+/*** DOCUMENTATION
+       <configInfo name="res_pjproject" language="en_US">
+               <synopsis>pjproject common configuration</synopsis>
+               <configFile name="pjproject.conf">
+                       <configObject name="log_mappings">
+                               <synopsis>PJPROJECT to Asterisk Log Level Mapping</synopsis>
+                               <description><para>Warnings and errors in the pjproject libraries are generally handled
+                                       by Asterisk.  In many cases, Asterisk wouldn't even consider them to
+                                       be warnings or errors so the messages emitted by pjproject directly
+                                       are either superfluous or misleading.  The 'log_mappings'
+                                       object allows mapping the pjproject levels to Asterisk levels, or nothing.
+                                       </para>
+                                       <note><para>The id of this object, as well as its type, must be
+                                       'log_mappings' or it won't be found.</para></note>
+                               </description>
+                               <configOption name="type">
+                                       <synopsis>Must be of type 'log_mappings'.</synopsis>
+                               </configOption>
+                               <configOption name="asterisk_error" default="0,1">
+                                       <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_ERROR.</synopsis>
+                               </configOption>
+                               <configOption name="asterisk_warning" default="2">
+                                       <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_WARNING.</synopsis>
+                               </configOption>
+                               <configOption name="asterisk_notice" default="">
+                                       <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_NOTICE.</synopsis>
+                               </configOption>
+                               <configOption name="asterisk_debug" default="3,4,5">
+                                       <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_DEBUG.</synopsis>
+                               </configOption>
+                               <configOption name="asterisk_verbose" default="">
+                                       <synopsis>A comma separated list of pjproject log levels to map to Asterisk LOG_VERBOSE.</synopsis>
+                               </configOption>
+                       </configObject>
+               </configFile>
+       </configInfo>
+ ***/
+
 #include "asterisk.h"
 
 ASTERISK_REGISTER_FILE()
@@ -51,7 +89,9 @@ ASTERISK_REGISTER_FILE()
 #include "asterisk/cli.h"
 #include "asterisk/res_pjproject.h"
 #include "asterisk/vector.h"
+#include "asterisk/sorcery.h"
 
+static struct ast_sorcery *pjproject_sorcery;
 static pj_log_func *log_cb_orig;
 static unsigned decor_orig;
 
@@ -70,6 +110,66 @@ static struct pjproject_log_intercept_data pjproject_log_intercept = {
        .fd = -1,
 };
 
+struct log_mappings {
+       /*! Sorcery object details */
+       SORCERY_OBJECT(details);
+       /*! These are all comma-separated lists of pjproject log levels */
+       AST_DECLARE_STRING_FIELDS(
+               /*! pjproject log levels mapped to Asterisk ERROR */
+               AST_STRING_FIELD(asterisk_error);
+               /*! pjproject log levels mapped to Asterisk WARNING */
+               AST_STRING_FIELD(asterisk_warning);
+               /*! pjproject log levels mapped to Asterisk NOTICE */
+               AST_STRING_FIELD(asterisk_notice);
+               /*! pjproject log levels mapped to Asterisk VERBOSE */
+               AST_STRING_FIELD(asterisk_verbose);
+               /*! pjproject log levels mapped to Asterisk DEBUG */
+               AST_STRING_FIELD(asterisk_debug);
+       );
+};
+
+static struct log_mappings *default_log_mappings;
+
+static struct log_mappings *get_log_mappings(void)
+{
+       struct log_mappings *mappings;
+
+       mappings = ast_sorcery_retrieve_by_id(pjproject_sorcery, "log_mappings", "log_mappings");
+       if (!mappings) {
+               return ao2_bump(default_log_mappings);
+       }
+
+       return mappings;
+}
+
+#define __LOG_SUPPRESS -1
+
+static int get_log_level(int pj_level)
+{
+       RAII_VAR(struct log_mappings *, mappings, get_log_mappings(), ao2_cleanup);
+       unsigned char l;
+
+       if (!mappings) {
+               return __LOG_ERROR;
+       }
+
+       l = '0' + fmin(pj_level, 9);
+
+       if (strchr(mappings->asterisk_error, l)) {
+               return __LOG_ERROR;
+       } else if (strchr(mappings->asterisk_warning, l)) {
+               return __LOG_WARNING;
+       } else if (strchr(mappings->asterisk_notice, l)) {
+               return __LOG_NOTICE;
+       } else if (strchr(mappings->asterisk_verbose, l)) {
+               return __LOG_VERBOSE;
+       } else if (strchr(mappings->asterisk_debug, l)) {
+               return __LOG_DEBUG;
+       }
+
+       return __LOG_SUPPRESS;
+}
+
 static void log_forwarder(int level, const char *data, int len)
 {
        int ast_level;
@@ -89,25 +189,19 @@ static void log_forwarder(int level, const char *data, int len)
                return;
        }
 
-       /* Lower number indicates higher importance */
-       switch (level) {
-       case 0: /* level zero indicates fatal error, according to docs */
-       case 1: /* 1 seems to be used for errors */
-               ast_level = __LOG_ERROR;
-               break;
-       case 2: /* 2 seems to be used for warnings and errors */
-               ast_level = __LOG_WARNING;
-               break;
-       default:
-               ast_level = __LOG_DEBUG;
+       ast_level = get_log_level(level);
+
+       if (ast_level == __LOG_SUPPRESS) {
+               return;
+       }
 
+       if (ast_level == __LOG_DEBUG) {
                /* For levels 3 and up, obey the debug level for res_pjproject */
                mod_level = ast_opt_dbg_module ?
                        ast_debug_get_by_module("res_pjproject") : 0;
                if (option_debug < level && mod_level < level) {
                        return;
                }
-               break;
        }
 
        /* PJPROJECT uses indention to indicate function call depth. We'll prepend
@@ -201,14 +295,105 @@ static char *handle_pjproject_show_buildopts(struct ast_cli_entry *e, int cmd, s
        return CLI_SUCCESS;
 }
 
+static void mapping_destroy(void *object)
+{
+       struct log_mappings *mappings = object;
+
+       ast_string_field_free_memory(mappings);
+}
+
+static void *mapping_alloc(const char *name)
+{
+       struct log_mappings *mappings = ast_sorcery_generic_alloc(sizeof(*mappings), mapping_destroy);
+       if (!mappings) {
+               return NULL;
+       }
+       ast_string_field_init(mappings, 128);
+
+       return mappings;
+}
+
+static char *handle_pjproject_show_log_mappings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct ast_variable *objset;
+       struct ast_variable *i;
+       struct log_mappings *mappings;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "pjproject show log mappings";
+               e->usage =
+                       "Usage: pjproject show log mappings\n"
+                       "       Show pjproject to Asterisk log mappings\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       ast_cli(a->fd, "PJPROJECT to Asterisk log mappings:\n");
+       ast_cli(a->fd, "Asterisk Level   : PJPROJECT log levels\n");
+
+       mappings = get_log_mappings();
+       if (!mappings) {
+               ast_log(LOG_ERROR, "Unable to retrieve pjproject log_mappings\n");
+               return CLI_SUCCESS;
+       }
+
+       objset = ast_sorcery_objectset_create(pjproject_sorcery, mappings);
+       if (!objset) {
+               ao2_ref(mappings, -1);
+               return CLI_SUCCESS;
+       }
+
+       for (i = objset; i; i = i->next) {
+               ast_cli(a->fd, "%-16s : %s\n", i->name, i->value);
+       }
+       ast_variables_destroy(objset);
+
+       ao2_ref(mappings, -1);
+       return CLI_SUCCESS;
+}
+
 static struct ast_cli_entry pjproject_cli[] = {
        AST_CLI_DEFINE(handle_pjproject_show_buildopts, "Show the compiled config of the pjproject in use"),
+       AST_CLI_DEFINE(handle_pjproject_show_log_mappings, "Show pjproject to Asterisk log mappings"),
 };
 
 static int load_module(void)
 {
        ast_debug(3, "Starting PJPROJECT logging to Asterisk logger\n");
 
+       if (!(pjproject_sorcery = ast_sorcery_open())) {
+               ast_log(LOG_ERROR, "Failed to open SIP sorcery failed to open\n");
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       ast_sorcery_apply_default(pjproject_sorcery, "log_mappings", "config", "pjproject.conf,criteria=type=log_mappings");
+       if (ast_sorcery_object_register(pjproject_sorcery, "log_mappings", mapping_alloc, NULL, NULL)) {
+               ast_log(LOG_WARNING, "Failed to register pjproject log_mappings object with sorcery\n");
+               ast_sorcery_unref(pjproject_sorcery);
+               pjproject_sorcery = NULL;
+               return AST_MODULE_LOAD_DECLINE;
+       }
+
+       ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "type", "", OPT_NOOP_T, 0, 0);
+       ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_debug", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_debug));
+       ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_error", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_error));
+       ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_warning", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_warning));
+       ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_notice", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_notice));
+       ast_sorcery_object_field_register(pjproject_sorcery, "log_mappings", "asterisk_verbose", "",  OPT_STRINGFIELD_T, 0, STRFLDSET(struct log_mappings, asterisk_verbose));
+
+       default_log_mappings = ast_sorcery_alloc(pjproject_sorcery, "log_mappings", "log_mappings");
+       if (!default_log_mappings) {
+               ast_log(LOG_ERROR, "Unable to allocate memory for pjproject log_mappings\n");
+               return AST_MODULE_LOAD_DECLINE;
+       }
+       ast_string_field_set(default_log_mappings, asterisk_error, "0,1");
+       ast_string_field_set(default_log_mappings, asterisk_warning, "2");
+       ast_string_field_set(default_log_mappings, asterisk_debug, "3,4,5");
+
+       ast_sorcery_load(pjproject_sorcery);
+
        pj_init();
 
        decor_orig = pj_log_get_decor();
@@ -247,12 +432,27 @@ static int unload_module(void)
 
        pj_shutdown();
 
+       ao2_cleanup(default_log_mappings);
+       default_log_mappings = NULL;
+
+       ast_sorcery_unref(pjproject_sorcery);
+
        return 0;
 }
 
+static int reload_module(void)
+{
+       if (pjproject_sorcery) {
+               ast_sorcery_reload(pjproject_sorcery);
+       }
+
+       return AST_MODULE_LOAD_SUCCESS;
+}
+
 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "PJPROJECT Log and Utility Support",
        .support_level = AST_MODULE_SUPPORT_CORE,
        .load = load_module,
        .unload = unload_module,
+       .reload = reload_module,
        .load_pri = AST_MODPRI_CHANNEL_DEPEND - 6,
 );
index 9d85a46..cf09a54 100644 (file)
@@ -45,6 +45,7 @@ ASTERISK_REGISTER_FILE()
 #include <pjsip.h>
 
 #include "asterisk/astobj2.h"
+#include "asterisk/cli.h"
 #include "asterisk/res_pjsip.h"
 #include "asterisk/module.h"
 #include "asterisk/pbx.h"
@@ -276,7 +277,7 @@ struct object_type_wizard {
        struct ast_config *last_config;
        char object_type[];
 };
-static AST_VECTOR(object_type_wizards, struct object_type_wizard *) object_type_wizards;
+static AST_VECTOR_RW(object_type_wizards, struct object_type_wizard *) object_type_wizards;
 
 /*! \brief Callbacks for vector deletes */
 #define NOT_EQUALS(a, b) (a != b)
@@ -304,12 +305,15 @@ static struct object_type_wizard *find_wizard(const char *object_type)
 {
        int idx;
 
+       AST_VECTOR_RW_RDLOCK(&object_type_wizards);
        for(idx = 0; idx < AST_VECTOR_SIZE(&object_type_wizards); idx++) {
                struct object_type_wizard *otw = AST_VECTOR_GET(&object_type_wizards, idx);
                if (!strcmp(otw->object_type, object_type)) {
+                       AST_VECTOR_RW_UNLOCK(&object_type_wizards);
                        return otw;
                }
        }
+       AST_VECTOR_RW_UNLOCK(&object_type_wizards);
 
        return NULL;
 }
@@ -1137,7 +1141,9 @@ static void wizard_mapped_observer(const char *name, struct ast_sorcery *sorcery
                otw->wizard_data = wizard_data;
                otw->last_config = NULL;
                strcpy(otw->object_type, object_type); /* Safe */
+               AST_VECTOR_RW_WRLOCK(&object_type_wizards);
                AST_VECTOR_APPEND(&object_type_wizards, otw);
+               AST_VECTOR_RW_UNLOCK(&object_type_wizards);
                ast_debug(1, "Wizard mapped for object_type '%s'\n", object_type);
        }
 }
@@ -1177,19 +1183,118 @@ static void instance_destroying_observer(const char *name, struct ast_sorcery *s
        ast_module_unref(ast_module_info->self);
 }
 
+static char *handle_export_primitives(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
+{
+       struct ast_sorcery *sorcery;
+       int idx;
+       FILE *f = NULL;
+       const char *fn = NULL;
+
+       switch (cmd) {
+       case CLI_INIT:
+               e->command = "pjsip export config_wizard primitives [to]";
+               e->usage =
+                       "Usage: pjsip export config_wizard primitives [ to <filename ]\n"
+                       "       Export the config_wizard objects as pjsip primitives to\n"
+                       "       the console or to <filename>\n";
+               return NULL;
+       case CLI_GENERATE:
+               return NULL;
+       }
+
+       if (a->argc > 5) {
+               char date[256]="";
+               time_t t;
+               fn = a->argv[5];
+
+               time(&t);
+               ast_copy_string(date, ctime(&t), sizeof(date));
+               f = fopen(fn, "w");
+               if (!f) {
+                       ast_log(LOG_ERROR, "Unable to write %s (%s)\n", fn, strerror(errno));
+                       return CLI_FAILURE;
+               }
+
+               fprintf(f, ";!\n");
+               fprintf(f, ";! Automatically generated configuration file\n");
+               fprintf(f, ";! Filename: %s\n", fn);
+               fprintf(f, ";! Generator: %s\n", "'pjsip export config_wizard primitives'");
+               fprintf(f, ";! Creation Date: %s", date);
+               fprintf(f, ";!\n");
+       }
+
+       sorcery = ast_sip_get_sorcery();
+
+       AST_VECTOR_RW_RDLOCK(&object_type_wizards);
+       for(idx = 0; idx < AST_VECTOR_SIZE(&object_type_wizards); idx++) {
+               struct object_type_wizard *otw = AST_VECTOR_GET(&object_type_wizards, idx);
+               struct ao2_container *container;
+               struct ao2_iterator i;
+               void *o;
+
+               container = ast_sorcery_retrieve_by_fields(sorcery, otw->object_type, AST_RETRIEVE_FLAG_MULTIPLE, NULL);
+               if (!container) {
+                       continue;
+               }
+
+               i = ao2_iterator_init(container, 0);
+               while ((o = ao2_iterator_next(&i))) {
+                       struct ast_variable *vars;
+                       struct ast_variable *v;
+
+                       vars = ast_sorcery_objectset_create(sorcery, o);
+                       if (vars && ast_variable_find_in_list(vars, "@pjsip_wizard")) {
+                               if (f) {
+                                       fprintf(f, "\n[%s]\ntype = %s\n", ast_sorcery_object_get_id(o), otw->object_type);
+                               } else {
+                                       ast_cli(a->fd, "\n[%s]\ntype = %s\n", ast_sorcery_object_get_id(o), otw->object_type);
+                               }
+                               for (v = vars; v; v = v->next) {
+                                       if (!ast_strlen_zero(v->value)) {
+                                               if (f) {
+                                                       fprintf(f, "%s = %s\n", v->name, v->value);
+                                               } else {
+                                                       ast_cli(a->fd, "%s = %s\n", v->name, v->value);
+                                               }
+                                       }
+                               }
+                       }
+                       ast_variables_destroy(vars);
+                       ao2_ref(o, -1);
+               }
+               ao2_iterator_destroy(&i);
+               ao2_cleanup(container);
+       }
+       AST_VECTOR_RW_UNLOCK(&object_type_wizards);
+
+       if (f) {
+               fclose(f);
+               ast_cli(a->fd, "Wrote configuration to %s\n", fn);
+       }
+
+
+       return CLI_SUCCESS;
+}
+
+static struct ast_cli_entry config_wizard_cli[] = {
+       AST_CLI_DEFINE(handle_export_primitives, "Export config wizard primitives"),
+};
+
 static int load_module(void)
 {
-       AST_VECTOR_INIT(&object_type_wizards, 12);
+       AST_VECTOR_RW_INIT(&object_type_wizards, 12);
        ast_sorcery_global_observer_add(&global_observer);
+       ast_cli_register_multiple(config_wizard_cli, ARRAY_LEN(config_wizard_cli));
 
        return AST_MODULE_LOAD_SUCCESS;
 }
 
 static int unload_module(void)
 {
+       ast_cli_unregister_multiple(config_wizard_cli, ARRAY_LEN(config_wizard_cli));
        ast_sorcery_global_observer_remove(&global_observer);
        AST_VECTOR_REMOVE_CMP_UNORDERED(&object_type_wizards, NULL, NOT_EQUALS, OTW_DELETE_CB);
-       AST_VECTOR_FREE(&object_type_wizards);
+       AST_VECTOR_RW_FREE(&object_type_wizards);
 
        return 0;
 }
index 0421d81..704372e 100644 (file)
@@ -1832,7 +1832,7 @@ static char *sorcery_memory_cache_expire(struct ast_cli_entry *e, int cmd, struc
                }
        }
 
-       if (a->argc > 6) {
+       if (a->argc < 5 || a->argc > 6) {
                return CLI_SHOWUSAGE;
        }
 
@@ -1886,7 +1886,7 @@ static char *sorcery_memory_cache_stale(struct ast_cli_entry *e, int cmd, struct
                }
        }
 
-       if (a->argc > 6) {
+       if (a->argc < 5 || a->argc > 6) {
                return CLI_SHOWUSAGE;
        }
 
@@ -1945,7 +1945,7 @@ static char *sorcery_memory_cache_populate(struct ast_cli_entry *e, int cmd, str
                }
        }
 
-       if (a->argc > 5) {
+       if (a->argc != 5) {
                return CLI_SHOWUSAGE;
        }