Tue Nov 4 13:20:37 2008

Asterisk developer's documentation


features.h File Reference

Call Parking and Pickup API Includes code and algorithms from the Zapata library. More...

Go to the source code of this file.

Data Structures

struct  ast_call_feature
 main call feature structure More...

Defines

#define FEATURE_APP_ARGS_LEN   256
#define FEATURE_APP_LEN   64
#define FEATURE_EXTEN_LEN   32
#define FEATURE_MAX_LEN   11
#define FEATURE_MOH_LEN   80
#define FEATURE_SNAME_LEN   32

Functions

int ast_bridge_call (struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
 Bridge a call, optionally allowing redirection.
int ast_masq_park_call (struct ast_channel *rchan, struct ast_channel *host, int timeout, int *extout)
 Park a call via a masqueraded channel.
int ast_park_call (struct ast_channel *chan, struct ast_channel *host, int timeout, int *extout)
 Park a call and read back parked location.
char * ast_parking_ext (void)
 Determine system parking extension Returns the call parking extension for drivers that provide special call parking help.
int ast_pickup_call (struct ast_channel *chan)
 Pickup a call.
char * ast_pickup_ext (void)
 Determine system call pickup extension.
void ast_register_feature (struct ast_call_feature *feature)
 register new feature into feature_set
void ast_unregister_feature (struct ast_call_feature *feature)
 unregister feature from feature_set


Detailed Description

Call Parking and Pickup API Includes code and algorithms from the Zapata library.

Definition in file features.h.


Define Documentation

#define FEATURE_APP_ARGS_LEN   256

Definition at line 29 of file features.h.

#define FEATURE_APP_LEN   64

Definition at line 28 of file features.h.

#define FEATURE_EXTEN_LEN   32

Definition at line 31 of file features.h.

#define FEATURE_MAX_LEN   11

Definition at line 27 of file features.h.

Referenced by ast_bridge_call().

#define FEATURE_MOH_LEN   80

Definition at line 32 of file features.h.

#define FEATURE_SNAME_LEN   32

Definition at line 30 of file features.h.


Function Documentation

int ast_bridge_call ( struct ast_channel chan,
struct ast_channel peer,
struct ast_bridge_config config 
)

Bridge a call, optionally allowing redirection.

append the event to featurecode. we rely on the string being zero-filled, and not overflowing it.

Todo:
XXX how do we guarantee the latter ?

Definition at line 1403 of file res_features.c.

References ast_channel::appl, ast_answer(), ast_cdr_alloc(), ast_cdr_appenduserfield(), ast_cdr_discard(), AST_CDR_FLAG_LOCKED, ast_cdr_init(), ast_cdr_merge(), ast_cdr_setdestchan(), ast_cdr_setuserfield(), ast_cdr_start(), ast_channel_bridge(), ast_channel_lock, ast_channel_setoption(), ast_channel_unlock, ast_check_hangup(), ast_clear_flag, AST_CONTROL_ATXFERCMD, AST_CONTROL_BUSY, AST_CONTROL_CONGESTION, AST_CONTROL_FLASH, AST_CONTROL_HANGUP, AST_CONTROL_HOLD, AST_CONTROL_OPTION, AST_CONTROL_RINGING, AST_CONTROL_UNHOLD, ast_dtmf_stream(), ast_feature_interpret(), AST_FEATURE_PLAY_WARNING, AST_FLAG_ZOMBIE, AST_FRAME_CONTROL, AST_FRAME_DTMF, AST_FRAME_DTMF_BEGIN, ast_frfree, ast_indicate(), ast_indicate_data(), ast_log(), AST_OPTION_FLAG_REQUEST, ast_strdupa, ast_strlen_zero(), ast_test_flag, ast_channel::cdr, ast_cdr::channel, cmd_atxfer(), config, ast_channel::data, ast_option_header::data, ast_cdr::dstchannel, f, FEATURE_MAX_LEN, FEATURE_RETURN_PASSDIGITS, FEATURE_RETURN_SUCCESS, FEATURE_SENSE_CHAN, FEATURE_SENSE_PEER, ast_option_header::flag, free, LOG_DEBUG, LOG_WARNING, monitor_app, ast_option_header::option, option_debug, pbx_builtin_getvar_helper(), pbx_builtin_setvar_helper(), pbx_exec(), pbx_findapp(), set_config_flags(), and ast_cdr::userfield.

Referenced by app_exec(), ast_bridge_call_thread(), do_atxfer(), park_exec(), and try_calling().

01404 {
01405    /* Copy voice back and forth between the two channels.  Give the peer
01406       the ability to transfer calls with '#<extension' syntax. */
01407    struct ast_frame *f;
01408    struct ast_channel *who;
01409    char chan_featurecode[FEATURE_MAX_LEN + 1]="";
01410    char peer_featurecode[FEATURE_MAX_LEN + 1]="";
01411    int res;
01412    int diff;
01413    int hasfeatures=0;
01414    int hadfeatures=0;
01415    struct ast_option_header *aoh;
01416    struct ast_bridge_config backup_config;
01417    struct ast_cdr *bridge_cdr;
01418 
01419    memset(&backup_config, 0, sizeof(backup_config));
01420 
01421    config->start_time = ast_tvnow();
01422 
01423    if (chan && peer) {
01424       pbx_builtin_setvar_helper(chan, "BRIDGEPEER", peer->name);
01425       pbx_builtin_setvar_helper(peer, "BRIDGEPEER", chan->name);
01426    } else if (chan)
01427       pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL);
01428 
01429    if (monitor_ok) {
01430       const char *monitor_exec;
01431       struct ast_channel *src = NULL;
01432       if (!monitor_app) { 
01433          if (!(monitor_app = pbx_findapp("Monitor")))
01434             monitor_ok=0;
01435       }
01436       if ((monitor_exec = pbx_builtin_getvar_helper(chan, "AUTO_MONITOR"))) 
01437          src = chan;
01438       else if ((monitor_exec = pbx_builtin_getvar_helper(peer, "AUTO_MONITOR")))
01439          src = peer;
01440       if (monitor_app && src) {
01441          char *tmp = ast_strdupa(monitor_exec);
01442          pbx_exec(src, monitor_app, tmp);
01443       }
01444    }
01445    
01446    set_config_flags(chan, peer, config);
01447    config->firstpass = 1;
01448 
01449    /* Answer if need be */
01450    if (ast_answer(chan))
01451       return -1;
01452    peer->appl = "Bridged Call";
01453    peer->data = chan->name;
01454 
01455    /* copy the userfield from the B-leg to A-leg if applicable */
01456    if (chan->cdr && peer->cdr && !ast_strlen_zero(peer->cdr->userfield)) {
01457       char tmp[256];
01458       if (!ast_strlen_zero(chan->cdr->userfield)) {
01459          snprintf(tmp, sizeof(tmp), "%s;%s", chan->cdr->userfield, peer->cdr->userfield);
01460          ast_cdr_appenduserfield(chan, tmp);
01461       } else
01462          ast_cdr_setuserfield(chan, peer->cdr->userfield);
01463       /* free the peer's cdr without ast_cdr_free complaining */
01464       free(peer->cdr);
01465       peer->cdr = NULL;
01466    }
01467 
01468    for (;;) {
01469       struct ast_channel *other; /* used later */
01470 
01471       res = ast_channel_bridge(chan, peer, config, &f, &who);
01472 
01473       if (config->feature_timer) {
01474          /* Update time limit for next pass */
01475          diff = ast_tvdiff_ms(ast_tvnow(), config->start_time);
01476          config->feature_timer -= diff;
01477          if (hasfeatures) {
01478             /* Running on backup config, meaning a feature might be being
01479                activated, but that's no excuse to keep things going 
01480                indefinitely! */
01481             if (backup_config.feature_timer && ((backup_config.feature_timer -= diff) <= 0)) {
01482                if (option_debug)
01483                   ast_log(LOG_DEBUG, "Timed out, realtime this time!\n");
01484                config->feature_timer = 0;
01485                who = chan;
01486                if (f)
01487                   ast_frfree(f);
01488                f = NULL;
01489                res = 0;
01490             } else if (config->feature_timer <= 0) {
01491                /* Not *really* out of time, just out of time for
01492                   digits to come in for features. */
01493                if (option_debug)
01494                   ast_log(LOG_DEBUG, "Timed out for feature!\n");
01495                if (!ast_strlen_zero(peer_featurecode)) {
01496                   ast_dtmf_stream(chan, peer, peer_featurecode, 0);
01497                   memset(peer_featurecode, 0, sizeof(peer_featurecode));
01498                }
01499                if (!ast_strlen_zero(chan_featurecode)) {
01500                   ast_dtmf_stream(peer, chan, chan_featurecode, 0);
01501                   memset(chan_featurecode, 0, sizeof(chan_featurecode));
01502                }
01503                if (f)
01504                   ast_frfree(f);
01505                hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
01506                if (!hasfeatures) {
01507                   /* Restore original (possibly time modified) bridge config */
01508                   memcpy(config, &backup_config, sizeof(struct ast_bridge_config));
01509                   memset(&backup_config, 0, sizeof(backup_config));
01510                }
01511                hadfeatures = hasfeatures;
01512                /* Continue as we were */
01513                continue;
01514             } else if (!f) {
01515                /* The bridge returned without a frame and there is a feature in progress.
01516                 * However, we don't think the feature has quite yet timed out, so just
01517                 * go back into the bridge. */
01518                continue;
01519             }
01520          } else {
01521             if (config->feature_timer <=0) {
01522                /* We ran out of time */
01523                config->feature_timer = 0;
01524                who = chan;
01525                if (f)
01526                   ast_frfree(f);
01527                f = NULL;
01528                res = 0;
01529             }
01530          }
01531       }
01532       if (res < 0) {
01533          if (!ast_test_flag(chan, AST_FLAG_ZOMBIE) && !ast_test_flag(peer, AST_FLAG_ZOMBIE) && !ast_check_hangup(chan) && !ast_check_hangup(peer))
01534             ast_log(LOG_WARNING, "Bridge failed on channels %s and %s\n", chan->name, peer->name);
01535          return -1;
01536       }
01537       
01538       if (!f || (f->frametype == AST_FRAME_CONTROL &&
01539             (f->subclass == AST_CONTROL_HANGUP || f->subclass == AST_CONTROL_BUSY || 
01540                f->subclass == AST_CONTROL_CONGESTION ) ) ) {
01541          res = -1;
01542          break;
01543       }
01544       /* many things should be sent to the 'other' channel */
01545       other = (who == chan) ? peer : chan;
01546       if (f->frametype == AST_FRAME_CONTROL) {
01547          switch (f->subclass) {
01548          case AST_CONTROL_RINGING:
01549          case AST_CONTROL_FLASH:
01550          case -1:
01551             ast_indicate(other, f->subclass);
01552             break;
01553          case AST_CONTROL_HOLD:
01554          case AST_CONTROL_UNHOLD:
01555             ast_indicate_data(other, f->subclass, f->data, f->datalen);
01556             break;
01557          case AST_CONTROL_OPTION:
01558             aoh = f->data;
01559             /* Forward option Requests */
01560             if (aoh && aoh->flag == AST_OPTION_FLAG_REQUEST) {
01561                ast_channel_setoption(other, ntohs(aoh->option), aoh->data, 
01562                   f->datalen - sizeof(struct ast_option_header), 0);
01563             }
01564             break;
01565          case AST_CONTROL_ATXFERCMD:
01566             cmd_atxfer(chan, peer, config, who, f->data);
01567             break;
01568          }
01569       } else if (f->frametype == AST_FRAME_DTMF_BEGIN) {
01570          /* eat it */
01571       } else if (f->frametype == AST_FRAME_DTMF) {
01572          char *featurecode;
01573          int sense;
01574 
01575          hadfeatures = hasfeatures;
01576          /* This cannot overrun because the longest feature is one shorter than our buffer */
01577          if (who == chan) {
01578             sense = FEATURE_SENSE_CHAN;
01579             featurecode = chan_featurecode;
01580          } else  {
01581             sense = FEATURE_SENSE_PEER;
01582             featurecode = peer_featurecode;
01583          }
01584          /*! append the event to featurecode. we rely on the string being zero-filled, and
01585           * not overflowing it. 
01586           * \todo XXX how do we guarantee the latter ?
01587           */
01588          featurecode[strlen(featurecode)] = f->subclass;
01589          /* Get rid of the frame before we start doing "stuff" with the channels */
01590          ast_frfree(f);
01591          f = NULL;
01592          config->feature_timer = backup_config.feature_timer;
01593          res = ast_feature_interpret(chan, peer, config, featurecode, sense);
01594          switch(res) {
01595          case FEATURE_RETURN_PASSDIGITS:
01596             ast_dtmf_stream(other, who, featurecode, 0);
01597             /* Fall through */
01598          case FEATURE_RETURN_SUCCESS:
01599             memset(featurecode, 0, sizeof(chan_featurecode));
01600             break;
01601          }
01602          if (res >= FEATURE_RETURN_PASSDIGITS) {
01603             res = 0;
01604          } else 
01605             break;
01606          hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
01607          if (hadfeatures && !hasfeatures) {
01608             /* Restore backup */
01609             memcpy(config, &backup_config, sizeof(struct ast_bridge_config));
01610             memset(&backup_config, 0, sizeof(struct ast_bridge_config));
01611          } else if (hasfeatures) {
01612             if (!hadfeatures) {
01613                /* Backup configuration */
01614                memcpy(&backup_config, config, sizeof(struct ast_bridge_config));
01615                /* Setup temporary config options */
01616                config->play_warning = 0;
01617                ast_clear_flag(&(config->features_caller), AST_FEATURE_PLAY_WARNING);
01618                ast_clear_flag(&(config->features_callee), AST_FEATURE_PLAY_WARNING);
01619                config->warning_freq = 0;
01620                config->warning_sound = NULL;
01621                config->end_sound = NULL;
01622                config->start_sound = NULL;
01623                config->firstpass = 0;
01624             }
01625             config->start_time = ast_tvnow();
01626             config->feature_timer = featuredigittimeout;
01627             if (option_debug)
01628                ast_log(LOG_DEBUG, "Set time limit to %ld\n", config->feature_timer);
01629          }
01630       }
01631       if (f)
01632          ast_frfree(f);
01633 
01634    }
01635 
01636    /* arrange the cdrs */
01637    bridge_cdr = ast_cdr_alloc();
01638    if (bridge_cdr) {
01639       if (chan->cdr && peer->cdr) { /* both of them? merge */
01640          ast_channel_lock(chan);  /* lock the channel before modifing cdrs */
01641          ast_cdr_init(bridge_cdr,chan); /* seems more logicaller to use the  destination as a base, but, really, it's random */
01642          ast_cdr_start(bridge_cdr); /* now is the time to start */
01643 
01644          /* absorb the channel cdr */
01645          ast_cdr_merge(bridge_cdr, chan->cdr);
01646          if (!ast_test_flag(chan->cdr, AST_CDR_FLAG_LOCKED)) 
01647             ast_cdr_discard(chan->cdr); /* if locked cdrs are in chan, they are taken over in the merge */
01648 
01649          chan->cdr = NULL; /* remove pointer to freed memory before releasing the lock */
01650 
01651          ast_channel_unlock(chan);
01652          
01653          /* absorb the peer cdr */
01654          ast_channel_lock(peer);
01655          ast_cdr_merge(bridge_cdr, peer->cdr);
01656          if (!ast_test_flag(peer->cdr, AST_CDR_FLAG_LOCKED))
01657             ast_cdr_discard(peer->cdr); /* if locked cdrs are in peer, they are taken over in the merge */
01658          
01659          peer->cdr = NULL;
01660          ast_channel_unlock(peer);
01661 
01662          ast_channel_lock(chan);
01663          chan->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */
01664          ast_channel_unlock(chan);
01665 
01666       } else if (chan->cdr) {
01667 
01668          ast_channel_lock(chan); /* Lock before modifying CDR */
01669          /* take the cdr from the channel - literally */
01670          ast_cdr_init(bridge_cdr,chan);
01671          /* absorb this data */
01672          ast_cdr_merge(bridge_cdr, chan->cdr);
01673          if (!ast_test_flag(chan->cdr, AST_CDR_FLAG_LOCKED))
01674             ast_cdr_discard(chan->cdr); /* if locked cdrs are in chan, they are taken over in the merge */
01675          chan->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */
01676          ast_channel_unlock(chan);
01677       } else if (peer->cdr) {
01678          ast_channel_lock(peer); /* Lock before modifying CDR */
01679          /* take the cdr from the peer - literally */
01680          ast_cdr_init(bridge_cdr,peer);
01681          /* absorb this data */
01682          ast_cdr_merge(bridge_cdr, peer->cdr);
01683          if (!ast_test_flag(peer->cdr, AST_CDR_FLAG_LOCKED))
01684             ast_cdr_discard(peer->cdr); /* if locked cdrs are in chan, they are taken over in the merge */
01685          peer->cdr = NULL;
01686          peer->cdr = bridge_cdr; /* make this available to the rest of the world via the chan while the call is in progress */
01687          ast_channel_unlock(peer);
01688       } else {
01689          ast_channel_lock(chan); /* Lock before modifying CDR */
01690          /* make up a new cdr */
01691          ast_cdr_init(bridge_cdr,chan); /* eh, just pick one of them */
01692          chan->cdr = bridge_cdr; /*  */
01693          ast_channel_unlock(chan);
01694       }
01695       if (ast_strlen_zero(bridge_cdr->dstchannel)) {
01696          if (strcmp(bridge_cdr->channel, peer->name) != 0)
01697             ast_cdr_setdestchan(bridge_cdr, peer->name);
01698          else
01699             ast_cdr_setdestchan(bridge_cdr, chan->name);
01700       }
01701    }
01702    return res;
01703 }

int ast_masq_park_call ( struct ast_channel rchan,
struct ast_channel host,
int  timeout,
int *  extout 
)

Park a call via a masqueraded channel.

Parameters:
rchan the real channel to be parked
host the channel to have the parking read to Masquerade the channel rchan into a new, empty channel which is then parked with ast_park_call
timeout is a timeout in milliseconds
extout is a parameter to an int that will hold the parked location, or NULL if you want

Definition at line 455 of file res_features.c.

References ast_channel::amaflags, ast_channel_alloc(), ast_channel_masquerade(), ast_frfree, ast_log(), ast_read(), AST_STATE_DOWN, ast_strdupa, ast_channel::context, ast_channel::exten, f, LOG_WARNING, park_call_full(), ast_channel::priority, ast_channel::readformat, set_c_e_p(), and ast_channel::writeformat.

Referenced by manager_park(), mgcp_ss(), parkandannounce_exec(), and ss_thread().

00456 {
00457    struct ast_channel *chan;
00458    struct ast_frame *f;
00459    char *orig_chan_name = NULL;
00460 
00461    /* Make a new, fake channel that we'll use to masquerade in the real one */
00462    if (!(chan = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, rchan->accountcode, rchan->exten, rchan->context, rchan->amaflags, "Parked/%s",rchan->name))) {
00463       ast_log(LOG_WARNING, "Unable to create parked channel\n");
00464       return -1;
00465    }
00466 
00467    /* Make formats okay */
00468    chan->readformat = rchan->readformat;
00469    chan->writeformat = rchan->writeformat;
00470    ast_channel_masquerade(chan, rchan);
00471 
00472    /* Setup the extensions and such */
00473    set_c_e_p(chan, rchan->context, rchan->exten, rchan->priority);
00474 
00475    /* Make the masq execute */
00476    f = ast_read(chan);
00477    if (f)
00478       ast_frfree(f);
00479 
00480    orig_chan_name = ast_strdupa(chan->name);
00481 
00482    park_call_full(chan, peer, timeout, extout, orig_chan_name);
00483 
00484    return 0;
00485 }

int ast_park_call ( struct ast_channel chan,
struct ast_channel peer,
int  timeout,
int *  extout 
)

Park a call and read back parked location.

Note:
We put the user in the parking list, then wake up the parking thread to be sure it looks after these channels too

Definition at line 450 of file res_features.c.

References park_call_full().

Referenced by builtin_blindtransfer(), builtin_parkcall(), iax_park_thread(), and sip_park_thread().

00451 {
00452    return park_call_full(chan, peer, timeout, extout, NULL);
00453 }

char* ast_parking_ext ( void   ) 

Determine system parking extension Returns the call parking extension for drivers that provide special call parking help.

Definition at line 159 of file res_features.c.

Referenced by builtin_blindtransfer(), dp_lookup(), handle_request_refer(), mgcp_ss(), socket_process(), and ss_thread().

00160 {
00161    return parking_ext;
00162 }

int ast_pickup_call ( struct ast_channel chan  ) 

Pickup a call.

Definition at line 2253 of file res_features.c.

References ast_channel::_state, ast_answer(), ast_channel_masquerade(), ast_channel_unlock, ast_channel_walk_locked(), AST_CONTROL_ANSWER, ast_log(), ast_queue_control(), AST_STATE_RING, AST_STATE_RINGING, ast_channel::callgroup, LOG_DEBUG, LOG_WARNING, option_debug, ast_channel::pbx, and ast_channel::pickupgroup.

Referenced by cb_events(), handle_request_invite(), mgcp_ss(), and ss_thread().

02254 {
02255    struct ast_channel *cur = NULL;
02256    int res = -1;
02257 
02258    while ( (cur = ast_channel_walk_locked(cur)) != NULL) {
02259       if (!cur->pbx && 
02260          (cur != chan) &&
02261          (chan->pickupgroup & cur->callgroup) &&
02262          ((cur->_state == AST_STATE_RINGING) ||
02263           (cur->_state == AST_STATE_RING))) {
02264             break;
02265       }
02266       ast_channel_unlock(cur);
02267    }
02268    if (cur) {
02269       if (option_debug)
02270          ast_log(LOG_DEBUG, "Call pickup on chan '%s' by '%s'\n",cur->name, chan->name);
02271       res = ast_answer(chan);
02272       if (res)
02273          ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name);
02274       res = ast_queue_control(chan, AST_CONTROL_ANSWER);
02275       if (res)
02276          ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name);
02277       res = ast_channel_masquerade(cur, chan);
02278       if (res)
02279          ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, cur->name);     /* Done */
02280       ast_channel_unlock(cur);
02281    } else   {
02282       if (option_debug)
02283          ast_log(LOG_DEBUG, "No call pickup possible...\n");
02284    }
02285    return res;
02286 }

char* ast_pickup_ext ( void   ) 

Determine system call pickup extension.

Definition at line 164 of file res_features.c.

Referenced by cb_events(), get_destination(), handle_request_invite(), handle_showfeatures(), mgcp_ss(), and ss_thread().

00165 {
00166    return pickup_ext;
00167 }

void ast_register_feature ( struct ast_call_feature feature  ) 

register new feature into feature_set

Parameters:
feature an ast_call_feature object which contains a keysequence and a callback function which is called when this keysequence is pressed during a call.

Definition at line 963 of file res_features.c.

References AST_LIST_INSERT_HEAD, AST_LIST_LOCK, AST_LIST_UNLOCK, ast_log(), ast_verbose(), LOG_NOTICE, option_verbose, ast_call_feature::sname, and VERBOSE_PREFIX_2.

00964 {
00965    if (!feature) {
00966       ast_log(LOG_NOTICE,"You didn't pass a feature!\n");
00967          return;
00968    }
00969   
00970    AST_LIST_LOCK(&feature_list);
00971    AST_LIST_INSERT_HEAD(&feature_list,feature,feature_entry);
00972    AST_LIST_UNLOCK(&feature_list);
00973 
00974    if (option_verbose >= 2) 
00975       ast_verbose(VERBOSE_PREFIX_2 "Registered Feature '%s'\n",feature->sname);
00976 }

void ast_unregister_feature ( struct ast_call_feature feature  ) 

unregister feature from feature_set

Parameters:
feature the ast_call_feature object which was registered before

Definition at line 979 of file res_features.c.

References AST_LIST_LOCK, AST_LIST_REMOVE, AST_LIST_UNLOCK, and free.

00980 {
00981    if (!feature)
00982       return;
00983 
00984    AST_LIST_LOCK(&feature_list);
00985    AST_LIST_REMOVE(&feature_list,feature,feature_entry);
00986    AST_LIST_UNLOCK(&feature_list);
00987    free(feature);
00988 }


Generated on Tue Nov 4 13:20:37 2008 for Asterisk - the Open Source PBX by  doxygen 1.4.7