Add support for configuring named groups of custom call features in
[asterisk/asterisk.git] / res / res_features.c
index a0df924..28b41de 100644 (file)
@@ -78,6 +78,24 @@ enum {
        AST_FEATURE_FLAG_BYBOTH  =   (3 << 3),
 };
 
+struct feature_group_exten {
+       AST_LIST_ENTRY(feature_group_exten) entry;
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(exten);
+       );
+       struct ast_call_feature *feature;
+};
+
+struct feature_group {
+       AST_LIST_ENTRY(feature_group) entry;
+       AST_DECLARE_STRING_FIELDS(
+               AST_STRING_FIELD(gname);
+       );
+       AST_LIST_HEAD_NOLOCK(, feature_group_exten) features;
+};
+
+static AST_RWLIST_HEAD_STATIC(feature_groups, feature_group);
+
 static char *parkedcall = "ParkedCall";
 
 static int parkaddhints = 0;                               /*!< Add parking hints automatically */
@@ -356,10 +374,13 @@ int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeou
        parkingexten = pbx_builtin_getvar_helper(chan, "PARKINGEXTEN");
        if (!ast_strlen_zero(parkingexten)) {
                if (ast_exists_extension(NULL, parking_con, parkingexten, 1, NULL)) {
+                       ast_mutex_unlock(&parking_lock);
+                       free(pu);
                        ast_log(LOG_WARNING, "Requested parking extension already exists: %s@%s\n", parkingexten, parking_con);
                        return 0;       /* Continue execution if possible */
                }
                ast_copy_string(pu->parkingexten, parkingexten, sizeof(pu->parkingexten));
+               x = atoi(parkingexten);
        } else {
                /* Select parking space within range */
                parking_range = parking_stop - parking_start+1;
@@ -1004,8 +1025,10 @@ static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, st
 /* add atxfer and automon as undefined so you can only use em if you configure them */
 #define FEATURES_COUNT ARRAY_LEN(builtin_features)
 
-struct ast_call_feature builtin_features[] = 
- {
+AST_RWLOCK_DEFINE_STATIC(features_lock);
+
+static struct ast_call_feature builtin_features[] = 
+{
        { AST_FEATURE_REDIRECT, "Blind Transfer", "blindxfer", "#", "#", builtin_blindtransfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
        { AST_FEATURE_REDIRECT, "Attended Transfer", "atxfer", "", "", builtin_atxfer, AST_FEATURE_FLAG_NEEDSDTMF, "" },
        { AST_FEATURE_AUTOMON, "One Touch Monitor", "automon", "", "", builtin_automonitor, AST_FEATURE_FLAG_NEEDSDTMF, "" },
@@ -1032,6 +1055,69 @@ void ast_register_feature(struct ast_call_feature *feature)
                ast_verbose(VERBOSE_PREFIX_2 "Registered Feature '%s'\n",feature->sname);
 }
 
+/*! \brief This function must be called while feature_groups is locked... */
+static struct feature_group* register_group(const char *fgname)
+{
+       struct feature_group *fg;
+
+       if (!fgname) {
+               ast_log(LOG_NOTICE, "You didn't pass a new group name!\n");
+               return NULL;
+       }
+
+       if (!(fg = ast_calloc(1, sizeof(*fg))))
+               return NULL;
+
+       if (ast_string_field_init(fg, 128)) {
+               free(fg);
+               return NULL;
+       }
+
+       ast_string_field_set(fg, gname, fgname);
+
+       AST_LIST_INSERT_HEAD(&feature_groups, fg, entry);
+
+       if (option_verbose >= 2) 
+               ast_verbose(VERBOSE_PREFIX_2 "Registered group '%s'\n", fg->gname);
+
+       return fg;
+}
+
+/*! \brief This function must be called while feature_groups is locked... */
+
+static void register_group_feature(struct feature_group *fg, const char *exten, struct ast_call_feature *feature) 
+{
+       struct feature_group_exten *fge;
+
+       if (!(fge = ast_calloc(1, sizeof(*fge))))
+               return;
+
+       if (ast_string_field_init(fge, 128)) {
+               free(fge);
+               return;
+       }
+
+       if (!fg) {
+               ast_log(LOG_NOTICE, "You didn't pass a group!\n");
+               return;
+       }
+
+       if (!feature) {
+               ast_log(LOG_NOTICE, "You didn't pass a feature!\n");
+               return;
+       }
+
+       ast_string_field_set(fge, exten, (ast_strlen_zero(exten) ? feature->exten : exten));
+
+       fge->feature = feature;
+
+       AST_LIST_INSERT_HEAD(&fg->features, fge, entry);                
+
+       if (option_verbose >= 2)
+               ast_verbose(VERBOSE_PREFIX_2 "Registered feature '%s' for group '%s' at exten '%s'\n", 
+                                       feature->sname, fg->gname, exten);
+}
+
 /*! \brief unregister feature from feature_list */
 void ast_unregister_feature(struct ast_call_feature *feature)
 {
@@ -1056,20 +1142,81 @@ static void ast_unregister_features(void)
 }
 
 /*! \brief find a call feature by name */
-struct ast_call_feature *ast_find_call_feature(char *name)
+static struct ast_call_feature *find_dynamic_feature(const char *name)
 {
        struct ast_call_feature *tmp;
 
-       AST_LIST_LOCK(&feature_list);
        AST_LIST_TRAVERSE(&feature_list, tmp, feature_entry) {
                if (!strcasecmp(tmp->sname, name))
                        break;
        }
-       AST_LIST_UNLOCK(&feature_list);
 
        return tmp;
 }
 
+/*! \brief Remove all groups in the list */
+static void ast_unregister_groups(void)
+{
+       struct feature_group *fg;
+       struct feature_group_exten *fge;
+
+       AST_RWLIST_WRLOCK(&feature_groups);
+       while ((fg = AST_LIST_REMOVE_HEAD(&feature_groups, entry))) {
+               while ((fge = AST_LIST_REMOVE_HEAD(&fg->features, entry))) {
+                       ast_string_field_free_all(fge);
+                       free(fge);
+               }
+
+               ast_string_field_free_all(fg);
+               free(fg);
+       }
+       AST_RWLIST_UNLOCK(&feature_groups);
+}
+
+/*! \brief Find a group by name */
+static struct feature_group *find_group(const char *name) {
+       struct feature_group *fg = NULL;
+
+       AST_LIST_TRAVERSE(&feature_groups, fg, entry) {
+               if (!strcasecmp(fg->gname, name))
+                       break;
+       }
+
+       return fg;
+}
+
+static struct feature_group_exten *find_group_exten(struct feature_group *fg, const char *code) {
+       struct feature_group_exten *fge = NULL;
+
+       AST_LIST_TRAVERSE(&fg->features, fge, entry) {
+               if(!strcasecmp(fge->exten, code))
+                       break;
+       }
+
+       return fge;
+}
+
+void ast_rdlock_call_features(void)
+{
+       ast_rwlock_rdlock(&features_lock);
+}
+
+void ast_unlock_call_features(void)
+{
+       ast_rwlock_unlock(&features_lock);
+}
+
+/*! \brief find a call feature by name */
+struct ast_call_feature *ast_find_call_feature(const char *name)
+{
+       int x;
+       for (x = 0; x < FEATURES_COUNT; x++) {
+               if (!strcasecmp(name, builtin_features[x].sname))
+                       return &builtin_features[x];
+       }
+       return NULL;
+}
+
 /*! \brief exec an app by feature */
 static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
 {
@@ -1142,23 +1289,28 @@ static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer,
 static void unmap_features(void)
 {
        int x;
+
+       ast_rwlock_wrlock(&features_lock);
        for (x = 0; x < FEATURES_COUNT; x++)
                strcpy(builtin_features[x].exten, builtin_features[x].default_exten);
+       ast_rwlock_unlock(&features_lock);
 }
 
 static int remap_feature(const char *name, const char *value)
 {
-       int x;
-       int res = -1;
+       int x, res = -1;
+
+       ast_rwlock_wrlock(&features_lock);
        for (x = 0; x < FEATURES_COUNT; x++) {
-               if (!strcasecmp(name, builtin_features[x].sname)) {
-                       ast_copy_string(builtin_features[x].exten, value, sizeof(builtin_features[x].exten));
-                       if (option_verbose > 1)
-                               ast_verbose(VERBOSE_PREFIX_2 "Remapping feature %s (%s) to sequence '%s'\n", builtin_features[x].fname, builtin_features[x].sname, builtin_features[x].exten);
-                       res = 0;
-               } else if (!strcmp(value, builtin_features[x].exten)) 
-                       ast_log(LOG_WARNING, "Sequence '%s' already mapped to function %s (%s) while assigning to %s\n", value, builtin_features[x].fname, builtin_features[x].sname, name);
+               if (strcasecmp(builtin_features[x].sname, name))
+                       continue;
+
+               ast_copy_string(builtin_features[x].exten, value, sizeof(builtin_features[x].exten));
+               res = 0;
+               break;
        }
+       ast_rwlock_unlock(&features_lock);
+
        return res;
 }
 
@@ -1168,16 +1320,20 @@ static int ast_feature_interpret(struct ast_channel *chan, struct ast_channel *p
        struct ast_flags features;
        int res = FEATURE_RETURN_PASSDIGITS;
        struct ast_call_feature *feature;
+       struct feature_group *fg = NULL;
+       struct feature_group_exten *fge;
        const char *dynamic_features=pbx_builtin_getvar_helper(chan,"DYNAMIC_FEATURES");
+       char *tmp, *tok;
 
        if (sense == FEATURE_SENSE_CHAN)
-               ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);   
+               ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);
        else
-               ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);   
+               ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);
        if (option_debug > 2)
                ast_log(LOG_DEBUG, "Feature interpret: chan=%s, peer=%s, sense=%d, features=%d\n", chan->name, peer->name, sense, features.flags);
 
-       for (x=0; x < FEATURES_COUNT; x++) {
+       ast_rwlock_rdlock(&features_lock);
+       for (x = 0; x < FEATURES_COUNT; x++) {
                if ((ast_test_flag(&features, builtin_features[x].feature_mask)) &&
                    !ast_strlen_zero(builtin_features[x].exten)) {
                        /* Feature is up for consideration */
@@ -1190,27 +1346,43 @@ static int ast_feature_interpret(struct ast_channel *chan, struct ast_channel *p
                        }
                }
        }
+       ast_rwlock_unlock(&features_lock);
 
+       if (ast_strlen_zero(dynamic_features))
+               return res;
 
-       if (!ast_strlen_zero(dynamic_features)) {
-               char *tmp = ast_strdupa(dynamic_features);
-               char *tok;
+       tmp = ast_strdupa(dynamic_features);
 
-               while ((tok = strsep(&tmp, "#")) != NULL) {
-                       feature = ast_find_call_feature(tok);
-                       
-                       if (feature) {
-                               /* Feature is up for consideration */
-                               if (!strcmp(feature->exten, code)) {
-                                       if (option_verbose > 2)
-                                               ast_verbose(VERBOSE_PREFIX_3 " Feature Found: %s exten: %s\n",feature->sname, tok);
-                                       res = feature->operation(chan, peer, config, code, sense);
-                                       break;
-                               } else if (!strncmp(feature->exten, code, strlen(code))) {
-                                       res = FEATURE_RETURN_STOREDIGITS;
-                               }
-                       }
+       while ((tok = strsep(&tmp, "#"))) {
+               AST_RWLIST_RDLOCK(&feature_groups);
+
+               fg = find_group(tok);
+
+               if (fg && (fge = find_group_exten(fg, code))) {
+                       res = fge->feature->operation(chan, peer, config, code, sense);
+                       AST_RWLIST_UNLOCK(&feature_groups);
+                       continue;
+               }
+
+               AST_RWLIST_UNLOCK(&feature_groups);
+               AST_LIST_LOCK(&feature_list);
+
+               if(!(feature = find_dynamic_feature(tok))) {
+                       AST_LIST_UNLOCK(&feature_list);
+                       continue;
                }
+                       
+               /* Feature is up for consideration */
+               if (!strcmp(feature->exten, code)) {
+                       if (option_verbose > 2)
+                               ast_verbose(VERBOSE_PREFIX_3 " Feature Found: %s exten: %s\n",feature->sname, tok);
+                       res = feature->operation(chan, peer, config, code, sense);
+                       AST_LIST_UNLOCK(&feature_list);
+                       break;
+               } else if (!strncmp(feature->exten, code, strlen(code)))
+                       res = FEATURE_RETURN_STOREDIGITS;
+
+               AST_LIST_UNLOCK(&feature_list);
        }
        
        return res;
@@ -1220,16 +1392,20 @@ static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer,
 {
        int x;
        
-       ast_clear_flag(config, AST_FLAGS_ALL);  
+       ast_clear_flag(config, AST_FLAGS_ALL);
+
+       ast_rwlock_rdlock(&features_lock);
        for (x = 0; x < FEATURES_COUNT; x++) {
-               if (ast_test_flag(builtin_features + x, AST_FEATURE_FLAG_NEEDSDTMF)) {
-                       if (ast_test_flag(&(config->features_caller), builtin_features[x].feature_mask))
-                               ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
+               if (!ast_test_flag(builtin_features + x, AST_FEATURE_FLAG_NEEDSDTMF))
+                       continue;
 
-                       if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask))
-                               ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
-               }
+               if (ast_test_flag(&(config->features_caller), builtin_features[x].feature_mask))
+                       ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
+
+               if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask))
+                       ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
        }
+       ast_rwlock_unlock(&features_lock);
        
        if (chan && peer && !(ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_0) && ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_1))) {
                const char *dynamic_features = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
@@ -1241,12 +1417,14 @@ static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer,
 
                        /* while we have a feature */
                        while ((tok = strsep(&tmp, "#"))) {
-                               if ((feature = ast_find_call_feature(tok)) && ast_test_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF)) {
+                               AST_LIST_LOCK(&feature_list);
+                               if ((feature = find_dynamic_feature(tok)) && ast_test_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF)) {
                                        if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLER))
                                                ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
                                        if (ast_test_flag(feature, AST_FEATURE_FLAG_BYCALLEE))
                                                ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
                                }
+                               AST_LIST_UNLOCK(&feature_list);
                        }
                }
        }
@@ -1282,7 +1460,8 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
 
                        ast_indicate(caller, AST_CONTROL_RINGING);
                        /* support dialing of the featuremap disconnect code while performing an attended tranfer */
-                       for (x=0; x < FEATURES_COUNT; x++) {
+                       ast_rwlock_rdlock(&features_lock);
+                       for (x = 0; x < FEATURES_COUNT; x++) {
                                if (strcasecmp(builtin_features[x].sname, "disconnect"))
                                        continue;
 
@@ -1292,6 +1471,7 @@ static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *call
                                memset(dialed_code, 0, len);
                                break;
                        }
+                       ast_rwlock_unlock(&features_lock);
                        x = 0;
                        started = ast_tvnow();
                        to = timeout;
@@ -2061,7 +2241,6 @@ static int park_exec(struct ast_channel *chan, void *data)
 static int handle_showfeatures(int fd, int argc, char *argv[])
 {
        int i;
-       int fcount;
        struct ast_call_feature *feature;
        char format[] = "%-25s %-7s %-7s\n";
 
@@ -2070,23 +2249,20 @@ static int handle_showfeatures(int fd, int argc, char *argv[])
 
        ast_cli(fd, format, "Pickup", "*8", ast_pickup_ext());          /* default hardcoded above, so we'll hardcode it here */
 
-       fcount = sizeof(builtin_features) / sizeof(builtin_features[0]);
-
-       for (i = 0; i < fcount; i++)
-       {
+       ast_rwlock_rdlock(&features_lock);
+       for (i = 0; i < FEATURES_COUNT; i++)
                ast_cli(fd, format, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten);
-       }
+       ast_rwlock_unlock(&features_lock);
+
        ast_cli(fd, "\n");
        ast_cli(fd, format, "Dynamic Feature", "Default", "Current");
        ast_cli(fd, format, "---------------", "-------", "-------");
-       if (AST_LIST_EMPTY(&feature_list)) {
+       if (AST_LIST_EMPTY(&feature_list))
                ast_cli(fd, "(none)\n");
-       }
        else {
                AST_LIST_LOCK(&feature_list);
-               AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) {
+               AST_LIST_TRAVERSE(&feature_list, feature, feature_entry)
                        ast_cli(fd, format, feature->sname, "no def", feature->exten);  
-               }
                AST_LIST_UNLOCK(&feature_list);
        }
        ast_cli(fd, "\nCall parking\n");
@@ -2172,7 +2348,7 @@ static int action_bridge(struct mansession *s, const struct message *m)
                return 1;
        }
 
-       if (!(tmpchana = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, 
+       if (!(tmpchanb = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, 
                NULL, NULL, 0, "Bridge/%s", chanb->name))) {
                astman_send_error(s, m, "Unable to create temporary channels!");
                ast_channel_free(tmpchana);
@@ -2417,11 +2593,22 @@ static int load_config(void)
 {
        int start = 0, end = 0;
        int res;
+       int i;
        struct ast_context *con = NULL;
        struct ast_config *cfg = NULL;
        struct ast_variable *var = NULL;
+       struct feature_group *fg = NULL;
        char old_parking_ext[AST_MAX_EXTENSION];
        char old_parking_con[AST_MAX_EXTENSION] = "";
+       char *ctg; 
+       static const char *categories[] = { 
+               /* Categories in features.conf that are not
+                * to be parsed as group categories
+                */
+               "general",
+               "featuremap",
+               "applicationmap"
+       };
 
        if (!ast_strlen_zero(parking_con)) {
                strcpy(old_parking_ext, parking_ext);
@@ -2580,10 +2767,13 @@ static int load_config(void)
                        continue;
                }
 
-               if ((feature = ast_find_call_feature(var->name))) {
+               AST_LIST_LOCK(&feature_list);
+               if ((feature = find_dynamic_feature(var->name))) {
+                       AST_LIST_UNLOCK(&feature_list);
                        ast_log(LOG_WARNING, "Dynamic Feature '%s' specified more than once!\n", var->name);
                        continue;
                }
+               AST_LIST_UNLOCK(&feature_list);
                                
                if (!(feature = ast_calloc(1, sizeof(*feature))))
                        continue;                                       
@@ -2631,7 +2821,41 @@ static int load_config(void)
                        
                if (option_verbose >= 1)
                        ast_verbose(VERBOSE_PREFIX_2 "Mapping Feature '%s' to app '%s(%s)' with code '%s'\n", var->name, app, app_args, exten);  
-       }        
+       }
+
+       ast_unregister_groups();
+       AST_RWLIST_WRLOCK(&feature_groups);
+
+       ctg = NULL;
+       struct ast_call_feature *feature;
+       while ((ctg = ast_category_browse(cfg, ctg))) {
+               for (i = 0; i < ARRAY_LEN(categories); i++) {
+                       if (!strcasecmp(categories[i], ctg))
+                               break;
+               }
+
+               if (i < ARRAY_LEN(categories)) 
+                       continue;
+
+               if (!(fg = register_group(ctg)))
+                       continue;
+
+               for (var = ast_variable_browse(cfg, ctg); var; var = var->next) {
+                       AST_LIST_LOCK(&feature_list);
+                       if(!(feature = find_dynamic_feature(var->name)) && 
+                          !(feature = ast_find_call_feature(var->name))) {
+                               AST_LIST_UNLOCK(&feature_list);
+                               ast_log(LOG_WARNING, "Feature '%s' was not found.\n", var->name);
+                               continue;
+                       }
+                       AST_LIST_UNLOCK(&feature_list);
+
+                       register_group_feature(fg, var->value, feature);
+               }
+       }
+
+       AST_RWLIST_UNLOCK(&feature_groups);
+
        ast_config_destroy(cfg);
 
        /* Remove the old parking extension */