<p>George Joseph <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/c/asterisk/+/11156">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Kevin Harwell: Looks good to me, but someone else must approve
  Joshua Colp: Looks good to me, but someone else must approve
  George Joseph: Looks good to me, approved; Approved for Submit

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">sorcery.c: Sorcery enhancements for wizard management<br><br>Added ability to specifiy a wizard is read-only when applying<br>it to a specific object type.  This allows you to specify<br>create, update and delete callbacks for the wizard but limit<br>which object types can use them.<br><br>Added the ability to allow an object type to have multiple<br>wizards of the same type.  This is indicated when a wizard<br>is added to a specific object type.<br><br>Added 3 new sorcery wizard functions:<br><br>* ast_sorcery_object_type_insert_wizard which does the same thing<br>  as the existing ast_sorcery_insert_wizard_mapping function but<br>  accepts the new read-only and allot-duplicates flags and also<br>  returns the ast_sorcery_wizard structure used and it's internal<br>  data structure. This allows immediate use of the wizard's<br>  callbacks without having to register a "wizard mapped" observer.<br><br>* ast_sorcery_object_type_apply_wizard which does the same<br>  thing as the existing ast_sorcery_apply_wizard_mapping function<br>  but has the added capabilities of<br>  ast_sorcery_object_type_insert_wizard.<br><br>* ast_sorcery_object_type_remove_wizard which removes a wizard<br>  matching both its name and its original argument string.<br><br>* The original logic in __ast_sorcery_insert_wizard_mapping was moved<br>  to __ast_sorcery_object_type_insert_wizard and enhanced for the<br>  new capabilities, then __ast_sorcery_insert_wizard_mapping was<br>  refactored to just call __ast_sorcery_insert_wizard_mapping.<br><br>* Added a unit test to test_sorcery.c to test the read-only<br>  capability.<br><br>Change-Id: I40f35840252e4313d99e11dbd80e270a3aa10605<br>---<br>M include/asterisk/sorcery.h<br>M main/sorcery.c<br>M tests/test_sorcery.c<br>3 files changed, 336 insertions(+), 29 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/include/asterisk/sorcery.h b/include/asterisk/sorcery.h</span><br><span>index bafca5f..3a0e1b8 100644</span><br><span>--- a/include/asterisk/sorcery.h</span><br><span>+++ b/include/asterisk/sorcery.h</span><br><span>@@ -562,6 +562,169 @@</span><br><span>            (caching), (position))</span><br><span> </span><br><span> /*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Wizard Apply Flags</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * These flags apply only to a wizard/object-type combination.</span><br><span style="color: hsl(120, 100%, 40%);">+ * The same wizard may be applied to a different object-type with</span><br><span style="color: hsl(120, 100%, 40%);">+ * different flags and behavior.  If ALLOW_DUPLICATE is set</span><br><span style="color: hsl(120, 100%, 40%);">+ * the wizard could even be applied to the same object-type</span><br><span style="color: hsl(120, 100%, 40%);">+ * with different flags.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+enum ast_sorcery_wizard_apply_flags {</span><br><span style="color: hsl(120, 100%, 40%);">+        /*! Apply no special behavior */</span><br><span style="color: hsl(120, 100%, 40%);">+      AST_SORCERY_WIZARD_APPLY_NONE = (0 << 0),</span><br><span style="color: hsl(120, 100%, 40%);">+       /*! This wizard will cache this object type's entries */</span><br><span style="color: hsl(120, 100%, 40%);">+  AST_SORCERY_WIZARD_APPLY_CACHING = (1 << 0),</span><br><span style="color: hsl(120, 100%, 40%);">+    /*! This wizard won't allow Create, Update or Delete operations on this object type */</span><br><span style="color: hsl(120, 100%, 40%);">+    AST_SORCERY_WIZARD_APPLY_READONLY = (1 << 1),</span><br><span style="color: hsl(120, 100%, 40%);">+   /*! This wizard will allow other instances of itself on the same object type */</span><br><span style="color: hsl(120, 100%, 40%);">+       AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE = (1 << 2)</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Insert an additional object wizard mapping at a specific position</span><br><span style="color: hsl(120, 100%, 40%);">+ * in the wizard list returning wizard information</span><br><span style="color: hsl(120, 100%, 40%);">+ * \since 13.26.0</span><br><span style="color: hsl(120, 100%, 40%);">+ * \since 16.3.0</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param sorcery Pointer to a sorcery structure</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param object_type_name Name of the object type to apply to</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param module The name of the module, typically AST_MODULE</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param wizard_type_name Name of the wizard type to use</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param wizard_args Opaque string to be passed to the wizard</span><br><span style="color: hsl(120, 100%, 40%);">+ *             May be NULL but see note below</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param flags One or more of enum ast_sorcery_wizard_apply_flags</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param position An index to insert to or one of ast_sorcery_wizard_position</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] wizard A variable to receive a pointer to the ast_sorcery_wizard structure.</span><br><span style="color: hsl(120, 100%, 40%);">+ *             May be NULL if not needed.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] wizard_data A variable to receive a pointer to the wizard's internal data.</span><br><span style="color: hsl(120, 100%, 40%);">+ *             May be NULL if not needed.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return What occurred when applying the mapping</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note This should be called *after* applying default mappings</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note Although \ref wizard_args is an optional parameter it is highly</span><br><span style="color: hsl(120, 100%, 40%);">+ * recommended to supply one.  If you use the AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE</span><br><span style="color: hsl(120, 100%, 40%);">+ * flag, and you intend to ever remove a wizard mapping, you'll need wizard_args</span><br><span style="color: hsl(120, 100%, 40%);">+ * to remove specific instances of a wizard type.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+enum ast_sorcery_apply_result __ast_sorcery_object_type_insert_wizard(struct ast_sorcery *sorcery,</span><br><span style="color: hsl(120, 100%, 40%);">+  const char *object_type_name, const char *module, const char *wizard_type_name,</span><br><span style="color: hsl(120, 100%, 40%);">+       const char *wizard_args, enum ast_sorcery_wizard_apply_flags flags, int position,</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_sorcery_wizard **wizard, void **wizard_data);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Insert an additional object wizard mapping at a specific position</span><br><span style="color: hsl(120, 100%, 40%);">+ * in the wizard list returning wizard information</span><br><span style="color: hsl(120, 100%, 40%);">+ * \since 13.26.0</span><br><span style="color: hsl(120, 100%, 40%);">+ * \since 16.3.0</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param sorcery Pointer to a sorcery structure</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param object_type_name Name of the object type to apply to</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param wizard_type_name Name of the wizard type to use</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param wizard_args Opaque string to be passed to the wizard</span><br><span style="color: hsl(120, 100%, 40%);">+ *             May be NULL but see note below</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param flags One or more of enum ast_sorcery_wizard_apply_flags</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param position An index to insert to or one of ast_sorcery_wizard_position</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] wizard A variable to receive a pointer to the ast_sorcery_wizard structure.</span><br><span style="color: hsl(120, 100%, 40%);">+ *             May be NULL if not needed.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] wizard_data A variable to receive a pointer to the wizard's internal data.</span><br><span style="color: hsl(120, 100%, 40%);">+ *             May be NULL if not needed.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return What occurred when applying the mapping</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note This should be called *after* applying default mappings</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note Although \ref wizard_args is an optional parameter it is highly</span><br><span style="color: hsl(120, 100%, 40%);">+ * recommended to supply one.  If you use the AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE</span><br><span style="color: hsl(120, 100%, 40%);">+ * flag, and you intend to ever remove a wizard mapping, you'll need wizard_args</span><br><span style="color: hsl(120, 100%, 40%);">+ * to remove specific instances.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+#define ast_sorcery_object_type_insert_wizard(sorcery, \</span><br><span style="color: hsl(120, 100%, 40%);">+ object_type_name, wizard_type_name, wizard_args, flags, \</span><br><span style="color: hsl(120, 100%, 40%);">+     position, wizard, wizard_data) \</span><br><span style="color: hsl(120, 100%, 40%);">+      __ast_sorcery_object_type_insert_wizard((sorcery), \</span><br><span style="color: hsl(120, 100%, 40%);">+          (object_type_name), AST_MODULE, (wizard_type_name), (wizard_args), (flags), \</span><br><span style="color: hsl(120, 100%, 40%);">+         position, (wizard), (wizard_data))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Apply additional object wizard mappings returning wizard information</span><br><span style="color: hsl(120, 100%, 40%);">+ * \since 13.26.0</span><br><span style="color: hsl(120, 100%, 40%);">+ * \since 16.3.0</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param sorcery Pointer to a sorcery structure</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param object_type_name Name of the object type to apply to</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param wizard_type_name Name of the wizard type to use</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param wizard_args Opaque string to be passed to the wizard</span><br><span style="color: hsl(120, 100%, 40%);">+ *             May be NULL but see note below</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param flags One or more of enum ast_sorcery_wizard_apply_flags</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] wizard A variable to receive a pointer to the ast_sorcery_wizard structure.</span><br><span style="color: hsl(120, 100%, 40%);">+ *             May be NULL if not needed.</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param[out] wizard_data A variable to receive a pointer to the wizard's internal data.</span><br><span style="color: hsl(120, 100%, 40%);">+ *             May be NULL if not needed.</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \return What occurred when applying the mapping</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note This should be called *after* applying default mappings</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note Although \ref wizard_args is an optional parameter it is highly</span><br><span style="color: hsl(120, 100%, 40%);">+ * recommended to supply one.  If you use the AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE</span><br><span style="color: hsl(120, 100%, 40%);">+ * flag, and you intend to ever remove a wizard mapping, you'll need wizard_args</span><br><span style="color: hsl(120, 100%, 40%);">+ * to remove specific instances.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+#define ast_sorcery_object_type_apply_wizard(sorcery, \</span><br><span style="color: hsl(120, 100%, 40%);">+ object_type_name, wizard_type_name, wizard_args, flags, \</span><br><span style="color: hsl(120, 100%, 40%);">+     wizard, wizard_data) \</span><br><span style="color: hsl(120, 100%, 40%);">+        __ast_sorcery_object_type_insert_wizard((sorcery), \</span><br><span style="color: hsl(120, 100%, 40%);">+          (object_type_name), AST_MODULE, (wizard_type_name), (wizard_args), (flags), \</span><br><span style="color: hsl(120, 100%, 40%);">+         AST_SORCERY_WIZARD_POSITION_LAST, (wizard), (wizard_data))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Remove an object wizard mapping</span><br><span style="color: hsl(120, 100%, 40%);">+ * \since 13.26.0</span><br><span style="color: hsl(120, 100%, 40%);">+ * \since 16.3.0</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param sorcery Pointer to a sorcery structure</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param object_type_name Name of the object type to remove from</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param module The name of the module, typically AST_MODULE</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param wizard_type_name The name of the of the wizard type to remove</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param wizard_args Opaque string originally passed to the wizard</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 0 success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval -1 failure</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note If there were multiple instances of the same wizard type</span><br><span style="color: hsl(120, 100%, 40%);">+ * added to this object type without using \ref wizard_args, then</span><br><span style="color: hsl(120, 100%, 40%);">+ * only the first wizard matching wizard_type will be removed.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+int __ast_sorcery_object_type_remove_wizard(struct ast_sorcery *sorcery,</span><br><span style="color: hsl(120, 100%, 40%);">+     const char *object_type_name, const char *module, const char *wizard_type_name,</span><br><span style="color: hsl(120, 100%, 40%);">+       const char *wizard_args);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span style="color: hsl(120, 100%, 40%);">+ * \brief Remove an object wizard mapping</span><br><span style="color: hsl(120, 100%, 40%);">+ * \since 13.26.0</span><br><span style="color: hsl(120, 100%, 40%);">+ * \since 16.3.0</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param sorcery Pointer to a sorcery structure</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param object_type_name Name of the object type to remove from</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param wizard_type_name The name of the of the wizard type to remove</span><br><span style="color: hsl(120, 100%, 40%);">+ * \param wizard_args Opaque string originally passed to the wizard</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval 0 success</span><br><span style="color: hsl(120, 100%, 40%);">+ * \retval -1 failure</span><br><span style="color: hsl(120, 100%, 40%);">+ *</span><br><span style="color: hsl(120, 100%, 40%);">+ * \note If there were multiple instances of the same wizard type</span><br><span style="color: hsl(120, 100%, 40%);">+ * added to this object type without using \ref wizard_args, then</span><br><span style="color: hsl(120, 100%, 40%);">+ * only the first wizard matching wizard_type will be removed.</span><br><span style="color: hsl(120, 100%, 40%);">+ */</span><br><span style="color: hsl(120, 100%, 40%);">+#define ast_sorcery_object_type_remove_wizard(sorcery, object_type_name, \</span><br><span style="color: hsl(120, 100%, 40%);">+   wizard_type_name, wizard_args) \</span><br><span style="color: hsl(120, 100%, 40%);">+      __ast_sorcery_object_type_remove_wizard((sorcery), (object_type_name), \</span><br><span style="color: hsl(120, 100%, 40%);">+              AST_MODULE, (wizard_type_name), (wizard_args))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+/*!</span><br><span>  * \brief Remove an object wizard mapping</span><br><span>  *</span><br><span>  * \param sorcery Pointer to a sorcery structure</span><br><span>diff --git a/main/sorcery.c b/main/sorcery.c</span><br><span>index 8e14881..d837845 100644</span><br><span>--- a/main/sorcery.c</span><br><span>+++ b/main/sorcery.c</span><br><span>@@ -109,6 +109,15 @@</span><br><span> </span><br><span>         /*! \brief Wizard is acting as an object cache */</span><br><span>    unsigned int caching:1;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     /*! \brief Wizard is read_only */</span><br><span style="color: hsl(120, 100%, 40%);">+     unsigned int read_only:1;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   /*! \brief Wizard allows others of the same type */</span><br><span style="color: hsl(120, 100%, 40%);">+   unsigned int allow_duplicates:1;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    /*! \brief Wizard arguments */</span><br><span style="color: hsl(120, 100%, 40%);">+        char wizard_args[0];</span><br><span> };</span><br><span> </span><br><span> /*! \brief Interface for a sorcery object type wizards */</span><br><span>@@ -802,6 +811,35 @@</span><br><span>   return 0;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+int __ast_sorcery_object_type_remove_wizard(struct ast_sorcery *sorcery,</span><br><span style="color: hsl(120, 100%, 40%);">+  const char *object_type_name, const char *module, const char *wizard_type_name,</span><br><span style="color: hsl(120, 100%, 40%);">+       const char *wizard_args)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+   RAII_VAR(struct ast_sorcery_object_type *, object_type,</span><br><span style="color: hsl(120, 100%, 40%);">+               ao2_find(sorcery->types, object_type_name, OBJ_SEARCH_KEY), ao2_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+  int res = -1;</span><br><span style="color: hsl(120, 100%, 40%);">+ int i;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      if (!object_type) {</span><br><span style="color: hsl(120, 100%, 40%);">+           return res;</span><br><span style="color: hsl(120, 100%, 40%);">+   }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   AST_VECTOR_RW_WRLOCK(&object_type->wizards);</span><br><span style="color: hsl(120, 100%, 40%);">+   for (i = 0; i < AST_VECTOR_SIZE(&object_type->wizards); i++) {</span><br><span style="color: hsl(120, 100%, 40%);">+              struct ast_sorcery_object_wizard *wizard = AST_VECTOR_GET(&object_type->wizards, i);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+         if (strcmp(wizard->wizard->callbacks.name, wizard_type_name) == 0</span><br><span style="color: hsl(120, 100%, 40%);">+                       && strcmp(S_OR(wizard->wizard_args, ""), S_OR(wizard_args, "")) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+                        ao2_cleanup(AST_VECTOR_REMOVE_ORDERED(&object_type->wizards, i));</span><br><span style="color: hsl(120, 100%, 40%);">+                      res = 0;</span><br><span style="color: hsl(120, 100%, 40%);">+                      break;</span><br><span style="color: hsl(120, 100%, 40%);">+                }</span><br><span style="color: hsl(120, 100%, 40%);">+     }</span><br><span style="color: hsl(120, 100%, 40%);">+     AST_VECTOR_RW_UNLOCK(&object_type->wizards);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+ return res;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*! \brief Internal function removes a wizard mapping */</span><br><span> int __ast_sorcery_remove_wizard_mapping(struct ast_sorcery *sorcery,</span><br><span>                 const char *type, const char *module, const char *name)</span><br><span>@@ -822,29 +860,35 @@</span><br><span>      return res;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-/*! \brief Internal function which creates an object type and inserts a wizard mapping */</span><br><span style="color: hsl(0, 100%, 40%);">-enum ast_sorcery_apply_result __ast_sorcery_insert_wizard_mapping(struct ast_sorcery *sorcery,</span><br><span style="color: hsl(0, 100%, 40%);">-             const char *type, const char *module, const char *name, const char *data,</span><br><span style="color: hsl(0, 100%, 40%);">-               unsigned int caching, int position)</span><br><span style="color: hsl(120, 100%, 40%);">+enum ast_sorcery_apply_result __ast_sorcery_object_type_insert_wizard(struct ast_sorcery *sorcery,</span><br><span style="color: hsl(120, 100%, 40%);">+       const char *object_type_name, const char *module, const char *wizard_type_name,</span><br><span style="color: hsl(120, 100%, 40%);">+       const char *wizard_args, enum ast_sorcery_wizard_apply_flags flags, int position,</span><br><span style="color: hsl(120, 100%, 40%);">+     struct ast_sorcery_wizard **wizard, void **wizard_data)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-    RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, type, OBJ_KEY), ao2_cleanup);</span><br><span style="color: hsl(0, 100%, 40%);">-       RAII_VAR(struct ast_sorcery_internal_wizard *, wizard, ao2_find(wizards, name, OBJ_KEY), ao2_cleanup);</span><br><span style="color: hsl(0, 100%, 40%);">-  RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, ao2_alloc(sizeof(*object_wizard), sorcery_object_wizard_destructor), ao2_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+        RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, object_type_name, OBJ_KEY), ao2_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+ RAII_VAR(struct ast_sorcery_internal_wizard *, internal_wizard, ao2_find(wizards, wizard_type_name, OBJ_KEY), ao2_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+   RAII_VAR(struct ast_sorcery_object_wizard *, object_wizard, NULL, ao2_cleanup);</span><br><span>      int created = 0;</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+  object_wizard = ao2_alloc(sizeof(*object_wizard)</span><br><span style="color: hsl(120, 100%, 40%);">+              + (ast_strlen_zero(wizard_args) ? 0 : strlen(wizard_args) + 1),</span><br><span style="color: hsl(120, 100%, 40%);">+               sorcery_object_wizard_destructor);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span>         if (!object_wizard) {</span><br><span>                return AST_SORCERY_APPLY_FAIL;</span><br><span>       }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   if (!wizard || wizard->callbacks.module != ast_module_running_ref(wizard->callbacks.module)) {</span><br><span style="color: hsl(0, 100%, 40%);">-            ast_log(LOG_ERROR, "Wizard '%s' could not be applied to object type '%s' as it was not found\n",</span><br><span style="color: hsl(0, 100%, 40%);">-                      name, type);</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!internal_wizard</span><br><span style="color: hsl(120, 100%, 40%);">+          || internal_wizard->callbacks.module != ast_module_running_ref(internal_wizard->callbacks.module)) {</span><br><span style="color: hsl(120, 100%, 40%);">+            ast_log(LOG_ERROR,</span><br><span style="color: hsl(120, 100%, 40%);">+                    "Wizard '%s' could not be applied to object type '%s' as it was not found\n",</span><br><span style="color: hsl(120, 100%, 40%);">+                       wizard_type_name, object_type_name);</span><br><span>                 return AST_SORCERY_APPLY_FAIL;</span><br><span>       }</span><br><span> </span><br><span>        if (!object_type) {</span><br><span style="color: hsl(0, 100%, 40%);">-             if (!(object_type = sorcery_object_type_alloc(type, module))) {</span><br><span style="color: hsl(0, 100%, 40%);">-                 ast_module_unref(wizard->callbacks.module);</span><br><span style="color: hsl(120, 100%, 40%);">+                if (!(object_type = sorcery_object_type_alloc(object_type_name, module))) {</span><br><span style="color: hsl(120, 100%, 40%);">+                   ast_module_unref(internal_wizard->callbacks.module);</span><br><span>                      return AST_SORCERY_APPLY_FAIL;</span><br><span>               }</span><br><span>            created = 1;</span><br><span>@@ -855,29 +899,34 @@</span><br><span>                 struct ast_sorcery_object_wizard *found;</span><br><span> </span><br><span> #define WIZARD_COMPARE(a, b) ((a)->wizard == (b))</span><br><span style="color: hsl(0, 100%, 40%);">-            found = AST_VECTOR_GET_CMP(&object_type->wizards, wizard, WIZARD_COMPARE);</span><br><span style="color: hsl(120, 100%, 40%);">+             found = AST_VECTOR_GET_CMP(&object_type->wizards, internal_wizard, WIZARD_COMPARE);</span><br><span> #undef WIZARD_COMPARE</span><br><span style="color: hsl(0, 100%, 40%);">-             if (found) {</span><br><span style="color: hsl(120, 100%, 40%);">+          if (found</span><br><span style="color: hsl(120, 100%, 40%);">+                     && !((flags & AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE) || found->allow_duplicates)) {</span><br><span>                        ast_debug(1, "Wizard %s already applied to object type %s\n",</span><br><span style="color: hsl(0, 100%, 40%);">-                                 wizard->callbacks.name, object_type->name);</span><br><span style="color: hsl(120, 100%, 40%);">+                             internal_wizard->callbacks.name, object_type->name);</span><br><span>                   AST_VECTOR_RW_UNLOCK(&object_type->wizards);</span><br><span style="color: hsl(0, 100%, 40%);">-                     ast_module_unref(wizard->callbacks.module);</span><br><span style="color: hsl(120, 100%, 40%);">+                        ast_module_unref(internal_wizard->callbacks.module);</span><br><span>                      return AST_SORCERY_APPLY_DUPLICATE;</span><br><span>          }</span><br><span>    }</span><br><span> </span><br><span>        ast_debug(5, "Calling wizard %s open callback on object type %s\n",</span><br><span style="color: hsl(0, 100%, 40%);">-           name, object_type->name);</span><br><span style="color: hsl(0, 100%, 40%);">-    if (wizard->callbacks.open && !(object_wizard->data = wizard->callbacks.open(data))) {</span><br><span style="color: hsl(120, 100%, 40%);">+               wizard_type_name, object_type->name);</span><br><span style="color: hsl(120, 100%, 40%);">+      if (internal_wizard->callbacks.open && !(object_wizard->data = internal_wizard->callbacks.open(wizard_args))) {</span><br><span>             ast_log(LOG_WARNING, "Wizard '%s' failed to open mapping for object type '%s' with data: %s\n",</span><br><span style="color: hsl(0, 100%, 40%);">-                       name, object_type->name, S_OR(data, ""));</span><br><span style="color: hsl(120, 100%, 40%);">+                        wizard_type_name, object_type->name, S_OR(wizard_args, ""));</span><br><span>            AST_VECTOR_RW_UNLOCK(&object_type->wizards);</span><br><span style="color: hsl(0, 100%, 40%);">-             ast_module_unref(wizard->callbacks.module);</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_module_unref(internal_wizard->callbacks.module);</span><br><span>              return AST_SORCERY_APPLY_FAIL;</span><br><span>       }</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-   object_wizard->wizard = ao2_bump(wizard);</span><br><span style="color: hsl(0, 100%, 40%);">-    object_wizard->caching = caching;</span><br><span style="color: hsl(120, 100%, 40%);">+  object_wizard->wizard = ao2_bump(internal_wizard);</span><br><span style="color: hsl(120, 100%, 40%);">+ object_wizard->caching = !!(flags & AST_SORCERY_WIZARD_APPLY_CACHING);</span><br><span style="color: hsl(120, 100%, 40%);">+ object_wizard->read_only = !!(flags & AST_SORCERY_WIZARD_APPLY_READONLY);</span><br><span style="color: hsl(120, 100%, 40%);">+      if (wizard_args) {</span><br><span style="color: hsl(120, 100%, 40%);">+            strcpy(object_wizard->wizard_args, wizard_args); /* Safe */</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span> </span><br><span>        if (position == AST_SORCERY_WIZARD_POSITION_LAST) {</span><br><span>          position = AST_VECTOR_SIZE(&object_type->wizards);</span><br><span>@@ -895,17 +944,36 @@</span><br><span>    }</span><br><span> </span><br><span>        NOTIFY_INSTANCE_OBSERVERS(sorcery->observers, wizard_mapped,</span><br><span style="color: hsl(0, 100%, 40%);">-         sorcery->module_name, sorcery, type, &wizard->callbacks, data, object_wizard->data);</span><br><span style="color: hsl(120, 100%, 40%);">+             sorcery->module_name, sorcery, object_type_name, &internal_wizard->callbacks, wizard_args,</span><br><span style="color: hsl(120, 100%, 40%);">+          object_wizard->data);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    if (wizard) {</span><br><span style="color: hsl(120, 100%, 40%);">+         *wizard = &internal_wizard->callbacks;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+     if (wizard_data) {</span><br><span style="color: hsl(120, 100%, 40%);">+            *wizard_data = object_wizard->data;</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span> </span><br><span>        return AST_SORCERY_APPLY_SUCCESS;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+/*! \brief Internal function which creates an object type and inserts a wizard mapping */</span><br><span style="color: hsl(120, 100%, 40%);">+enum ast_sorcery_apply_result __ast_sorcery_insert_wizard_mapping(struct ast_sorcery *sorcery,</span><br><span style="color: hsl(120, 100%, 40%);">+         const char *object_type_name, const char *module, const char *wizard_type_name,</span><br><span style="color: hsl(120, 100%, 40%);">+               const char *wizard_args, unsigned int caching, int position)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+       return __ast_sorcery_object_type_insert_wizard(sorcery, object_type_name, module, wizard_type_name,</span><br><span style="color: hsl(120, 100%, 40%);">+           wizard_args, caching ? AST_SORCERY_WIZARD_APPLY_CACHING : AST_SORCERY_WIZARD_APPLY_NONE,</span><br><span style="color: hsl(120, 100%, 40%);">+              position, NULL, NULL);</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> /*! \brief Internal function which creates an object type and adds a wizard mapping */</span><br><span> enum ast_sorcery_apply_result __ast_sorcery_apply_wizard_mapping(struct ast_sorcery *sorcery,</span><br><span style="color: hsl(0, 100%, 40%);">-          const char *type, const char *module, const char *name, const char *data, unsigned int caching)</span><br><span style="color: hsl(120, 100%, 40%);">+               const char *object_type_name, const char *module, const char *wizard_type_name,</span><br><span style="color: hsl(120, 100%, 40%);">+               const char *wizard_args, unsigned int caching)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-     return __ast_sorcery_insert_wizard_mapping(sorcery, type, module, name, data,</span><br><span style="color: hsl(0, 100%, 40%);">-           caching, AST_SORCERY_WIZARD_POSITION_LAST);</span><br><span style="color: hsl(120, 100%, 40%);">+   return __ast_sorcery_insert_wizard_mapping(sorcery, object_type_name, module, wizard_type_name,</span><br><span style="color: hsl(120, 100%, 40%);">+               wizard_args, caching, AST_SORCERY_WIZARD_POSITION_LAST);</span><br><span> }</span><br><span> </span><br><span> enum ast_sorcery_apply_result  __ast_sorcery_apply_config(struct ast_sorcery *sorcery, const char *name, const char *module)</span><br><span>@@ -1904,7 +1972,7 @@</span><br><span> /*! \brief Internal function which returns if the wizard has created the object */</span><br><span> static int sorcery_wizard_create(const struct ast_sorcery_object_wizard *object_wizard, const struct sorcery_details *details)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-  if (!object_wizard->wizard->callbacks.create) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!object_wizard->wizard->callbacks.create || object_wizard->read_only) {</span><br><span>                 ast_debug(5, "Sorcery wizard '%s' does not support creation\n", object_wizard->wizard->callbacks.name);</span><br><span>              return 0;</span><br><span>    }</span><br><span>@@ -2015,7 +2083,7 @@</span><br><span> /*! \brief Internal function which returns if a wizard has updated the object */</span><br><span> static int sorcery_wizard_update(const struct ast_sorcery_object_wizard *object_wizard, const struct sorcery_details *details)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-    if (!object_wizard->wizard->callbacks.update) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!object_wizard->wizard->callbacks.update || object_wizard->read_only) {</span><br><span>                 ast_debug(5, "Sorcery wizard '%s' does not support updating\n", object_wizard->wizard->callbacks.name);</span><br><span>              return 0;</span><br><span>    }</span><br><span>@@ -2103,7 +2171,7 @@</span><br><span> /*! \brief Internal function which returns if a wizard has deleted the object */</span><br><span> static int sorcery_wizard_delete(const struct ast_sorcery_object_wizard *object_wizard, const struct sorcery_details *details)</span><br><span> {</span><br><span style="color: hsl(0, 100%, 40%);">-    if (!object_wizard->wizard->callbacks.delete) {</span><br><span style="color: hsl(120, 100%, 40%);">+ if (!object_wizard->wizard->callbacks.delete || object_wizard->read_only) {</span><br><span>                 ast_debug(5, "Sorcery wizard '%s' does not support deletion\n", object_wizard->wizard->callbacks.name);</span><br><span>              return 0;</span><br><span>    }</span><br><span>diff --git a/tests/test_sorcery.c b/tests/test_sorcery.c</span><br><span>index 0662336..9f5cc7e 100644</span><br><span>--- a/tests/test_sorcery.c</span><br><span>+++ b/tests/test_sorcery.c</span><br><span>@@ -3505,6 +3505,13 @@</span><br><span>              ast_sorcery_insert_wizard_mapping(sorcery, "test_object_type", "test2", "test2data", 0, 0) != 0);</span><br><span> </span><br><span>  ast_test_validate(test,</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_sorcery_object_type_insert_wizard(sorcery, "test_object_type", "test2", "test2data2",</span><br><span style="color: hsl(120, 100%, 40%);">+                       AST_SORCERY_WIZARD_APPLY_ALLOW_DUPLICATE, 0, NULL, NULL) == 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     ast_test_validate(test,</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_sorcery_object_type_remove_wizard(sorcery, "test_object_type", "test2", "test2data2") == 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+      ast_test_validate(test,</span><br><span>              ast_sorcery_get_wizard_mapping(sorcery, "test_object_type", 0, &wizard, &data) == 0);</span><br><span>      ast_test_validate(test, strcmp("test2", wizard->name) == 0);</span><br><span>    ast_test_validate(test, strcmp("test2data", data) == 0);</span><br><span>@@ -3554,6 +3561,73 @@</span><br><span>  return AST_TEST_PASS;</span><br><span> }</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+static struct ast_sorcery_wizard test_read_only_wizard = {</span><br><span style="color: hsl(120, 100%, 40%);">+    .name = "test-read-only",</span><br><span style="color: hsl(120, 100%, 40%);">+   .retrieve_id = sorcery_test_retrieve_id,</span><br><span style="color: hsl(120, 100%, 40%);">+};</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+AST_TEST_DEFINE(wizard_read_only)</span><br><span style="color: hsl(120, 100%, 40%);">+{</span><br><span style="color: hsl(120, 100%, 40%);">+  RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);</span><br><span style="color: hsl(120, 100%, 40%);">+     RAII_VAR(struct ast_sorcery_wizard *, wizard_read_only, &test_read_only_wizard, ast_sorcery_wizard_unregister);</span><br><span style="color: hsl(120, 100%, 40%);">+   RAII_VAR(struct ast_sorcery_wizard *, wizard1, &test_wizard, ast_sorcery_wizard_unregister);</span><br><span style="color: hsl(120, 100%, 40%);">+      RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup);</span><br><span style="color: hsl(120, 100%, 40%);">+       struct ast_sorcery_wizard *wizard;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  switch (cmd) {</span><br><span style="color: hsl(120, 100%, 40%);">+        case TEST_INIT:</span><br><span style="color: hsl(120, 100%, 40%);">+               info->name = "wizard_read_only";</span><br><span style="color: hsl(120, 100%, 40%);">+         info->category = "/main/sorcery/";</span><br><span style="color: hsl(120, 100%, 40%);">+               info->summary = "sorcery wizard read-only test";</span><br><span style="color: hsl(120, 100%, 40%);">+         info->description =</span><br><span style="color: hsl(120, 100%, 40%);">+                        "sorcery wizard read-only test";</span><br><span style="color: hsl(120, 100%, 40%);">+            return AST_TEST_NOT_RUN;</span><br><span style="color: hsl(120, 100%, 40%);">+      case TEST_EXECUTE:</span><br><span style="color: hsl(120, 100%, 40%);">+            break;</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   wizard1->load = sorcery_test_load;</span><br><span style="color: hsl(120, 100%, 40%);">+ wizard1->reload = sorcery_test_load;</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+     if (!(sorcery = ast_sorcery_open())) {</span><br><span style="color: hsl(120, 100%, 40%);">+                ast_test_status_update(test, "Failed to open a sorcery instance\n");</span><br><span style="color: hsl(120, 100%, 40%);">+                return AST_TEST_FAIL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_sorcery_wizard_register(wizard_read_only);</span><br><span style="color: hsl(120, 100%, 40%);">+        ast_sorcery_wizard_register(wizard1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       if ((ast_sorcery_apply_default(sorcery, "test_object_type", "test-read-only", NULL) != AST_SORCERY_APPLY_SUCCESS) ||</span><br><span style="color: hsl(120, 100%, 40%);">+              ast_sorcery_internal_object_register(sorcery, "test_object_type", test_sorcery_object_alloc, NULL, NULL)) {</span><br><span style="color: hsl(120, 100%, 40%);">+         ast_test_status_update(test, "Failed to apply object defaults\n");</span><br><span style="color: hsl(120, 100%, 40%);">+          return AST_TEST_FAIL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   ast_test_validate(test,</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_sorcery_get_wizard_mapping_count(sorcery, "test_object_type") == 1);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  ast_test_validate(test,</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_sorcery_object_type_apply_wizard(sorcery, "test_object_type",</span><br><span style="color: hsl(120, 100%, 40%);">+                   "test", "test2data", AST_SORCERY_WIZARD_APPLY_READONLY, &wizard, NULL) == 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+       ast_test_validate(test, strcmp(wizard->name, wizard1->name) == 0);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    ast_test_validate(test,</span><br><span style="color: hsl(120, 100%, 40%);">+               ast_sorcery_get_wizard_mapping_count(sorcery, "test_object_type") == 2);</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+  if (!(obj = ast_sorcery_alloc(sorcery, "test_object_type", "blah"))) {</span><br><span style="color: hsl(120, 100%, 40%);">+            ast_test_status_update(test, "Failed to allocate a known object type\n");</span><br><span style="color: hsl(120, 100%, 40%);">+           return AST_TEST_FAIL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   if (ast_sorcery_create(sorcery, obj) == 0) {</span><br><span style="color: hsl(120, 100%, 40%);">+          ast_test_status_update(test, "Should not have created object using read-only wizard\n");</span><br><span style="color: hsl(120, 100%, 40%);">+            return AST_TEST_FAIL;</span><br><span style="color: hsl(120, 100%, 40%);">+ }</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+   return AST_TEST_PASS;</span><br><span style="color: hsl(120, 100%, 40%);">+}</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> static int unload_module(void)</span><br><span> {</span><br><span>    AST_TEST_UNREGISTER(wizard_registration);</span><br><span>@@ -3606,6 +3680,7 @@</span><br><span>    AST_TEST_UNREGISTER(instance_observation);</span><br><span>   AST_TEST_UNREGISTER(wizard_observation);</span><br><span>     AST_TEST_UNREGISTER(wizard_apply_and_insert);</span><br><span style="color: hsl(120, 100%, 40%);">+ AST_TEST_UNREGISTER(wizard_read_only);</span><br><span> </span><br><span>   return 0;</span><br><span> }</span><br><span>@@ -3662,6 +3737,7 @@</span><br><span>       AST_TEST_REGISTER(global_observation);</span><br><span>       AST_TEST_REGISTER(instance_observation);</span><br><span>     AST_TEST_REGISTER(wizard_observation);</span><br><span style="color: hsl(120, 100%, 40%);">+        AST_TEST_REGISTER(wizard_read_only);</span><br><span> </span><br><span>     return AST_MODULE_LOAD_SUCCESS;</span><br><span> }</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/c/asterisk/+/11156">change 11156</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/c/asterisk/+/11156"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I40f35840252e4313d99e11dbd80e270a3aa10605 </div>
<div style="display:none"> Gerrit-Change-Number: 11156 </div>
<div style="display:none"> Gerrit-PatchSet: 4 </div>
<div style="display:none"> Gerrit-Owner: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Friendly Automation </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Kevin Harwell <kharwell@digium.com> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>