Wed Apr 6 11:29:42 2011

Asterisk developer's documentation


chan_local.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2005, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \author Mark Spencer <markster@digium.com>
00022  *
00023  * \brief Local Proxy Channel
00024  * 
00025  * \ingroup channel_drivers
00026  */
00027 
00028 #include "asterisk.h"
00029 
00030 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 299626 $")
00031 
00032 #include <fcntl.h>
00033 #include <sys/signal.h>
00034 
00035 #include "asterisk/lock.h"
00036 #include "asterisk/channel.h"
00037 #include "asterisk/config.h"
00038 #include "asterisk/module.h"
00039 #include "asterisk/pbx.h"
00040 #include "asterisk/sched.h"
00041 #include "asterisk/io.h"
00042 #include "asterisk/acl.h"
00043 #include "asterisk/callerid.h"
00044 #include "asterisk/file.h"
00045 #include "asterisk/cli.h"
00046 #include "asterisk/app.h"
00047 #include "asterisk/musiconhold.h"
00048 #include "asterisk/manager.h"
00049 #include "asterisk/stringfields.h"
00050 #include "asterisk/devicestate.h"
00051 #include "asterisk/astobj2.h"
00052 
00053 /*** DOCUMENTATION
00054    <manager name="LocalOptimizeAway" language="en_US">
00055       <synopsis>
00056          Optimize away a local channel when possible.
00057       </synopsis>
00058       <syntax>
00059          <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
00060          <parameter name="Channel" required="true">
00061             <para>The channel name to optimize away.</para>
00062          </parameter>
00063       </syntax>
00064       <description>
00065          <para>A local channel created with "/n" will not automatically optimize away.
00066          Calling this command on the local channel will clear that flag and allow
00067          it to optimize away if it's bridged or when it becomes bridged.</para>
00068       </description>
00069    </manager>
00070  ***/
00071 
00072 static const char tdesc[] = "Local Proxy Channel Driver";
00073 
00074 #define IS_OUTBOUND(a,b) (a == b->chan ? 1 : 0)
00075 
00076 /* right now we are treating the locals astobj2 container as a
00077  * list.  If there is ever a reason to make this more efficient
00078  * increasing the bucket size would help. */
00079 static const int BUCKET_SIZE = 1;
00080 
00081 static struct ao2_container *locals;
00082 
00083 static struct ast_jb_conf g_jb_conf = {
00084    .flags = 0,
00085    .max_size = -1,
00086    .resync_threshold = -1,
00087    .impl = "",
00088    .target_extra = -1,
00089 };
00090 
00091 static struct ast_channel *local_request(const char *type, format_t format, const struct ast_channel *requestor, void *data, int *cause);
00092 static int local_digit_begin(struct ast_channel *ast, char digit);
00093 static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
00094 static int local_call(struct ast_channel *ast, char *dest, int timeout);
00095 static int local_hangup(struct ast_channel *ast);
00096 static int local_answer(struct ast_channel *ast);
00097 static struct ast_frame *local_read(struct ast_channel *ast);
00098 static int local_write(struct ast_channel *ast, struct ast_frame *f);
00099 static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
00100 static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
00101 static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
00102 static int local_sendtext(struct ast_channel *ast, const char *text);
00103 static int local_devicestate(void *data);
00104 static struct ast_channel *local_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge);
00105 static int local_queryoption(struct ast_channel *ast, int option, void *data, int *datalen);
00106 static int local_setoption(struct ast_channel *chan, int option, void *data, int datalen);
00107 
00108 /* PBX interface structure for channel registration */
00109 static const struct ast_channel_tech local_tech = {
00110    .type = "Local",
00111    .description = tdesc,
00112    .capabilities = -1,
00113    .requester = local_request,
00114    .send_digit_begin = local_digit_begin,
00115    .send_digit_end = local_digit_end,
00116    .call = local_call,
00117    .hangup = local_hangup,
00118    .answer = local_answer,
00119    .read = local_read,
00120    .write = local_write,
00121    .write_video = local_write,
00122    .exception = local_read,
00123    .indicate = local_indicate,
00124    .fixup = local_fixup,
00125    .send_html = local_sendhtml,
00126    .send_text = local_sendtext,
00127    .devicestate = local_devicestate,
00128    .bridged_channel = local_bridgedchannel,
00129    .queryoption = local_queryoption,
00130    .setoption = local_setoption,
00131 };
00132 
00133 /*! \brief the local pvt structure for all channels
00134 
00135    The local channel pvt has two ast_chan objects - the "owner" and the "next channel", the outbound channel
00136 
00137    ast_chan owner -> local_pvt -> ast_chan chan -> yet-another-pvt-depending-on-channel-type
00138 
00139 */
00140 struct local_pvt {
00141    unsigned int flags;                     /*!< Private flags */
00142    char context[AST_MAX_CONTEXT];      /*!< Context to call */
00143    char exten[AST_MAX_EXTENSION];      /*!< Extension to call */
00144    int reqformat;          /*!< Requested format */
00145    struct ast_jb_conf jb_conf;      /*!< jitterbuffer configuration for this local channel */
00146    struct ast_channel *owner;    /*!< Master Channel - Bridging happens here */
00147    struct ast_channel *chan;     /*!< Outbound channel - PBX is run here */
00148    struct ast_module_user *u_owner; /*!< reference to keep the module loaded while in use */
00149    struct ast_module_user *u_chan;     /*!< reference to keep the module loaded while in use */
00150 };
00151 
00152 #define LOCAL_ALREADY_MASQED  (1 << 0) /*!< Already masqueraded */
00153 #define LOCAL_LAUNCHED_PBX    (1 << 1) /*!< PBX was launched */
00154 #define LOCAL_NO_OPTIMIZATION (1 << 2) /*!< Do not optimize using masquerading */
00155 #define LOCAL_BRIDGE          (1 << 3) /*!< Report back the "true" channel as being bridged to */
00156 #define LOCAL_MOH_PASSTHRU    (1 << 4) /*!< Pass through music on hold start/stop frames */
00157 
00158 static int local_setoption(struct ast_channel *chan, int option, void * data, int datalen)
00159 {
00160    int res;
00161    struct local_pvt *p;
00162    struct ast_channel *otherchan;
00163    ast_chan_write_info_t *write_info;
00164 
00165    if (option != AST_OPTION_CHANNEL_WRITE) {
00166       return -1;
00167    }
00168 
00169    write_info = data;
00170 
00171    if (write_info->version != AST_CHAN_WRITE_INFO_T_VERSION) {
00172       ast_log(LOG_ERROR, "The chan_write_info_t type has changed, and this channel hasn't been updated!\n");
00173       return -1;
00174    }
00175 
00176 
00177 startover:
00178    ast_channel_lock(chan);
00179 
00180    p = chan->tech_pvt;
00181    if (!p) {
00182       ast_channel_unlock(chan);
00183       ast_log(LOG_WARNING, "Could not update other side of %s, local_pvt went away.\n", chan->name);
00184       return -1;
00185    }
00186 
00187    while (ao2_trylock(p)) {
00188       ast_channel_unlock(chan);
00189       sched_yield();
00190       ast_channel_lock(chan);
00191       p = chan->tech_pvt;
00192       if (!p) {
00193          ast_channel_unlock(chan);
00194          ast_log(LOG_WARNING, "Could not update other side of %s, local_pvt went away.\n", chan->name);
00195          return -1;
00196       }
00197    }
00198 
00199    otherchan = (write_info->chan == p->owner) ? p->chan : p->owner;
00200 
00201    if (!otherchan || otherchan == write_info->chan) {
00202       ao2_unlock(p);
00203       ast_channel_unlock(chan);
00204       ast_log(LOG_WARNING, "Could not update other side of %s, other side went away.\n", chan->name);
00205       return 0;
00206    }
00207 
00208    if (ast_channel_trylock(otherchan)) {
00209       ao2_unlock(p);
00210       ast_channel_unlock(chan);
00211       goto startover;
00212    }
00213 
00214    res = write_info->write_fn(otherchan, write_info->function, write_info->data, write_info->value);
00215 
00216    ast_channel_unlock(otherchan);
00217    ao2_unlock(p);
00218    ast_channel_unlock(chan);
00219 
00220    return res;
00221 }
00222 
00223 /*! \brief Adds devicestate to local channels */
00224 static int local_devicestate(void *data)
00225 {
00226    char *exten = ast_strdupa(data);
00227    char *context = NULL, *opts = NULL;
00228    int res;
00229    struct local_pvt *lp;
00230    struct ao2_iterator it;
00231 
00232    if (!(context = strchr(exten, '@'))) {
00233       ast_log(LOG_WARNING, "Someone used Local/%s somewhere without a @context. This is bad.\n", exten);
00234       return AST_DEVICE_INVALID; 
00235    }
00236 
00237    *context++ = '\0';
00238 
00239    /* Strip options if they exist */
00240    if ((opts = strchr(context, '/')))
00241       *opts = '\0';
00242 
00243    ast_debug(3, "Checking if extension %s@%s exists (devicestate)\n", exten, context);
00244 
00245    res = ast_exists_extension(NULL, context, exten, 1, NULL);
00246    if (!res)      
00247       return AST_DEVICE_INVALID;
00248    
00249    res = AST_DEVICE_NOT_INUSE;
00250 
00251    it = ao2_iterator_init(locals, 0);
00252    while ((lp = ao2_iterator_next(&it))) {
00253       if (!strcmp(exten, lp->exten) && !strcmp(context, lp->context) && lp->owner) {
00254          res = AST_DEVICE_INUSE;
00255          ao2_ref(lp, -1);
00256          break;
00257       }
00258       ao2_ref(lp, -1);
00259    }
00260    ao2_iterator_destroy(&it);
00261 
00262    return res;
00263 }
00264 
00265 /*! \brief Return the bridged channel of a Local channel */
00266 static struct ast_channel *local_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge)
00267 {
00268    struct local_pvt *p = bridge->tech_pvt;
00269    struct ast_channel *bridged = bridge;
00270 
00271    if (!p) {
00272       ast_debug(1, "Asked for bridged channel on '%s'/'%s', returning <none>\n",
00273          chan->name, bridge->name);
00274       return NULL;
00275    }
00276 
00277    ao2_lock(p);
00278 
00279    if (ast_test_flag(p, LOCAL_BRIDGE)) {
00280       /* Find the opposite channel */
00281       bridged = (bridge == p->owner ? p->chan : p->owner);
00282       
00283       /* Now see if the opposite channel is bridged to anything */
00284       if (!bridged) {
00285          bridged = bridge;
00286       } else if (bridged->_bridge) {
00287          bridged = bridged->_bridge;
00288       }
00289    }
00290 
00291    ao2_unlock(p);
00292 
00293    return bridged;
00294 }
00295 
00296 static int local_queryoption(struct ast_channel *ast, int option, void *data, int *datalen)
00297 {
00298    struct local_pvt *p = ast->tech_pvt;
00299    struct ast_channel *chan, *bridged;
00300    int res;
00301 
00302    if (!p) {
00303       return -1;
00304    }
00305 
00306    if (option != AST_OPTION_T38_STATE) {
00307       /* AST_OPTION_T38_STATE is the only supported option at this time */
00308       return -1;
00309    }
00310 
00311    ao2_lock(p);
00312    chan = IS_OUTBOUND(ast, p) ? p->owner : p->chan;
00313 
00314 try_again:
00315    if (!chan) {
00316       ao2_unlock(p);
00317       return -1;
00318    }
00319 
00320    if (ast_channel_trylock(chan)) {
00321       ao2_unlock(p);
00322       sched_yield();
00323       ao2_lock(p);
00324       chan = IS_OUTBOUND(ast, p) ? p->owner : p->chan;
00325       goto try_again;
00326    }
00327 
00328    bridged = ast_bridged_channel(chan);
00329    if (!bridged) {
00330       /* can't query channel unless we are bridged */
00331       ao2_unlock(p);
00332       ast_channel_unlock(chan);
00333       return -1;
00334    }
00335 
00336    if (ast_channel_trylock(bridged)) {
00337       ast_channel_unlock(chan);
00338       ao2_unlock(p);
00339       sched_yield();
00340       ao2_lock(p);
00341       chan = IS_OUTBOUND(ast, p) ? p->owner : p->chan;
00342       goto try_again;
00343    }
00344 
00345    res = ast_channel_queryoption(bridged, option, data, datalen, 0);
00346    ao2_unlock(p);
00347    ast_channel_unlock(chan);
00348    ast_channel_unlock(bridged);
00349    return res;
00350 }
00351 
00352 /*! \brief queue a frame on a to either the p->owner or p->chan
00353  *
00354  * \note the local_pvt MUST have it's ref count bumped before entering this function and
00355  * decremented after this function is called.  This is a side effect of the deadlock
00356  * avoidance that is necessary to lock 2 channels and a tech_pvt.  Without a ref counted
00357  * local_pvt, it is impossible to guarantee it will not be destroyed by another thread
00358  * during deadlock avoidance.
00359  */
00360 static int local_queue_frame(struct local_pvt *p, int isoutbound, struct ast_frame *f, 
00361    struct ast_channel *us, int us_locked)
00362 {
00363    struct ast_channel *other = NULL;
00364 
00365    /* Recalculate outbound channel */
00366    other = isoutbound ? p->owner : p->chan;
00367 
00368    if (!other) {
00369       return 0;
00370    }
00371 
00372    /* do not queue frame if generator is on both local channels */
00373    if (us && us->generator && other->generator) {
00374       return 0;
00375    }
00376 
00377    /* Ensure that we have both channels locked */
00378    while (other && ast_channel_trylock(other)) {
00379       int res;
00380       if ((res = ao2_unlock(p))) {
00381          ast_log(LOG_ERROR, "chan_local bug! '&p->lock' was not locked when entering local_queue_frame! (%s)\n", strerror(res));
00382          return -1;
00383       }
00384       if (us && us_locked) {
00385          do {
00386             CHANNEL_DEADLOCK_AVOIDANCE(us);
00387          } while (ao2_trylock(p));
00388       } else {
00389          usleep(1);
00390          ao2_lock(p);
00391       }
00392       other = isoutbound ? p->owner : p->chan;
00393    }
00394 
00395    if (other) {
00396       if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_RINGING) {
00397          ast_setstate(other, AST_STATE_RINGING);
00398       }
00399       ast_queue_frame(other, f);
00400       ast_channel_unlock(other);
00401    }
00402 
00403    return 0;
00404 }
00405 
00406 static int local_answer(struct ast_channel *ast)
00407 {
00408    struct local_pvt *p = ast->tech_pvt;
00409    int isoutbound;
00410    int res = -1;
00411 
00412    if (!p)
00413       return -1;
00414 
00415    ao2_lock(p);
00416    ao2_ref(p, 1);
00417    isoutbound = IS_OUTBOUND(ast, p);
00418    if (isoutbound) {
00419       /* Pass along answer since somebody answered us */
00420       struct ast_frame answer = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
00421       res = local_queue_frame(p, isoutbound, &answer, ast, 1);
00422    } else {
00423       ast_log(LOG_WARNING, "Huh?  Local is being asked to answer?\n");
00424    }
00425    ao2_unlock(p);
00426    ao2_ref(p, -1);
00427    return res;
00428 }
00429 
00430 /*!
00431  * \internal
00432  * \note This function assumes that we're only called from the "outbound" local channel side
00433  */
00434 static void check_bridge(struct local_pvt *p)
00435 {
00436    struct ast_channel_monitor *tmp;
00437    if (ast_test_flag(p, LOCAL_ALREADY_MASQED) || ast_test_flag(p, LOCAL_NO_OPTIMIZATION) || !p->chan || !p->owner || (p->chan->_bridge != ast_bridged_channel(p->chan)))
00438       return;
00439 
00440    /* only do the masquerade if we are being called on the outbound channel,
00441       if it has been bridged to another channel and if there are no pending
00442       frames on the owner channel (because they would be transferred to the
00443       outbound channel during the masquerade)
00444    */
00445    if (p->chan->_bridge /* Not ast_bridged_channel!  Only go one step! */ && AST_LIST_EMPTY(&p->owner->readq)) {
00446       /* Masquerade bridged channel into owner */
00447       /* Lock everything we need, one by one, and give up if
00448          we can't get everything.  Remember, we'll get another
00449          chance in just a little bit */
00450       if (!ast_channel_trylock(p->chan->_bridge)) {
00451          if (!ast_check_hangup(p->chan->_bridge)) {
00452             if (!ast_channel_trylock(p->owner)) {
00453                if (!ast_check_hangup(p->owner)) {
00454                   if (p->owner->monitor && !p->chan->_bridge->monitor) {
00455                      /* If a local channel is being monitored, we don't want a masquerade
00456                       * to cause the monitor to go away. Since the masquerade swaps the monitors,
00457                       * pre-swapping the monitors before the masquerade will ensure that the monitor
00458                       * ends up where it is expected.
00459                       */
00460                      tmp = p->owner->monitor;
00461                      p->owner->monitor = p->chan->_bridge->monitor;
00462                      p->chan->_bridge->monitor = tmp;
00463                   }
00464                   if (p->chan->audiohooks) {
00465                      struct ast_audiohook_list *audiohooks_swapper;
00466                      audiohooks_swapper = p->chan->audiohooks;
00467                      p->chan->audiohooks = p->owner->audiohooks;
00468                      p->owner->audiohooks = audiohooks_swapper;
00469                   }
00470 
00471                   /* If any Caller ID was set, preserve it after masquerade like above. We must check
00472                    * to see if Caller ID was set because otherwise we'll mistakingly copy info not
00473                    * set from the dialplan and will overwrite the real channel Caller ID. The reason
00474                    * for this whole preswapping action is because the Caller ID is set on the channel
00475                    * thread (which is the to be masqueraded away local channel) before both local
00476                    * channels are optimized away.
00477                    */
00478                   if (p->owner->caller.id.name.valid || p->owner->caller.id.number.valid
00479                      || p->owner->caller.id.subaddress.valid || p->owner->caller.ani.name.valid
00480                      || p->owner->caller.ani.number.valid || p->owner->caller.ani.subaddress.valid) {
00481                      struct ast_party_caller tmp;
00482                      tmp = p->owner->caller;
00483                      p->owner->caller = p->chan->_bridge->caller;
00484                      p->chan->_bridge->caller = tmp;
00485                   }
00486                   if (p->owner->redirecting.from.name.valid || p->owner->redirecting.from.number.valid
00487                      || p->owner->redirecting.from.subaddress.valid || p->owner->redirecting.to.name.valid
00488                      || p->owner->redirecting.to.number.valid || p->owner->redirecting.to.subaddress.valid) {
00489                      struct ast_party_redirecting tmp;
00490                      tmp = p->owner->redirecting;
00491                      p->owner->redirecting = p->chan->_bridge->redirecting;
00492                      p->chan->_bridge->redirecting = tmp;
00493                   }
00494                   if (p->owner->dialed.number.str || p->owner->dialed.subaddress.valid) {
00495                      struct ast_party_dialed tmp;
00496                      tmp = p->owner->dialed;
00497                      p->owner->dialed = p->chan->_bridge->dialed;
00498                      p->chan->_bridge->dialed = tmp;
00499                   }
00500 
00501 
00502                   ast_app_group_update(p->chan, p->owner);
00503                   ast_channel_masquerade(p->owner, p->chan->_bridge);
00504                   ast_set_flag(p, LOCAL_ALREADY_MASQED);
00505                }
00506                ast_channel_unlock(p->owner);
00507             }
00508             ast_channel_unlock(p->chan->_bridge);
00509          }
00510       }
00511    }
00512 }
00513 
00514 static struct ast_frame  *local_read(struct ast_channel *ast)
00515 {
00516    return &ast_null_frame;
00517 }
00518 
00519 static int local_write(struct ast_channel *ast, struct ast_frame *f)
00520 {
00521    struct local_pvt *p = ast->tech_pvt;
00522    int res = -1;
00523    int isoutbound;
00524 
00525    if (!p)
00526       return -1;
00527 
00528    /* Just queue for delivery to the other side */
00529    ao2_lock(p);
00530    ao2_ref(p, 1); /* ref for local_queue_frame */
00531    isoutbound = IS_OUTBOUND(ast, p);
00532    if (isoutbound && f && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO))
00533       check_bridge(p);
00534    if (!ast_test_flag(p, LOCAL_ALREADY_MASQED))
00535       res = local_queue_frame(p, isoutbound, f, ast, 1);
00536    else {
00537       ast_debug(1, "Not posting to queue since already masked on '%s'\n", ast->name);
00538       res = 0;
00539    }
00540    ao2_unlock(p);
00541    ao2_ref(p, -1);
00542 
00543    return res;
00544 }
00545 
00546 static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
00547 {
00548    struct local_pvt *p = newchan->tech_pvt;
00549 
00550    if (!p)
00551       return -1;
00552 
00553    ao2_lock(p);
00554 
00555    if ((p->owner != oldchan) && (p->chan != oldchan)) {
00556       ast_log(LOG_WARNING, "Old channel wasn't %p but was %p/%p\n", oldchan, p->owner, p->chan);
00557       ao2_unlock(p);
00558       return -1;
00559    }
00560    if (p->owner == oldchan)
00561       p->owner = newchan;
00562    else
00563       p->chan = newchan;
00564 
00565    /* Do not let a masquerade cause a Local channel to be bridged to itself! */
00566    if (!ast_check_hangup(newchan) && (p->owner->_bridge == p->chan || p->chan->_bridge == p->owner)) {
00567       ast_log(LOG_WARNING, "You can not bridge a Local channel to itself!\n");
00568       ao2_unlock(p);
00569       ast_queue_hangup(newchan);
00570       return -1;
00571    }
00572 
00573    ao2_unlock(p);
00574    return 0;
00575 }
00576 
00577 static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
00578 {
00579    struct local_pvt *p = ast->tech_pvt;
00580    int res = 0;
00581    struct ast_frame f = { AST_FRAME_CONTROL, };
00582    int isoutbound;
00583 
00584    if (!p)
00585       return -1;
00586 
00587    ao2_ref(p, 1); /* ref for local_queue_frame */
00588 
00589    /* If this is an MOH hold or unhold, do it on the Local channel versus real channel */
00590    if (!ast_test_flag(p, LOCAL_MOH_PASSTHRU) && condition == AST_CONTROL_HOLD) {
00591       ast_moh_start(ast, data, NULL);
00592    } else if (!ast_test_flag(p, LOCAL_MOH_PASSTHRU) && condition == AST_CONTROL_UNHOLD) {
00593       ast_moh_stop(ast);
00594    } else if (condition == AST_CONTROL_CONNECTED_LINE || condition == AST_CONTROL_REDIRECTING) {
00595       struct ast_channel *this_channel;
00596       struct ast_channel *the_other_channel;
00597       /* A connected line update frame may only contain a partial amount of data, such
00598        * as just a source, or just a ton, and not the full amount of information. However,
00599        * the collected information is all stored in the outgoing channel's connectedline
00600        * structure, so when receiving a connected line update on an outgoing local channel,
00601        * we need to transmit the collected connected line information instead of whatever
00602        * happens to be in this control frame. The same applies for redirecting information, which
00603        * is why it is handled here as well.*/
00604       ao2_lock(p);
00605       isoutbound = IS_OUTBOUND(ast, p);
00606       if (isoutbound) {
00607          this_channel = p->chan;
00608          the_other_channel = p->owner;
00609       } else {
00610          this_channel = p->owner;
00611          the_other_channel = p->chan;
00612       }
00613       if (the_other_channel) {
00614          unsigned char frame_data[1024];
00615          if (condition == AST_CONTROL_CONNECTED_LINE) {
00616             if (isoutbound) {
00617                ast_connected_line_copy_to_caller(&the_other_channel->caller, &this_channel->connected);
00618             }
00619             f.datalen = ast_connected_line_build_data(frame_data, sizeof(frame_data), &this_channel->connected, NULL);
00620          } else {
00621             f.datalen = ast_redirecting_build_data(frame_data, sizeof(frame_data), &this_channel->redirecting, NULL);
00622          }
00623          f.subclass.integer = condition;
00624          f.data.ptr = frame_data;
00625          res = local_queue_frame(p, isoutbound, &f, ast, 1);
00626       }
00627       ao2_unlock(p);
00628    } else {
00629       /* Queue up a frame representing the indication as a control frame */
00630       ao2_lock(p);
00631       isoutbound = IS_OUTBOUND(ast, p);
00632       f.subclass.integer = condition;
00633       f.data.ptr = (void*)data;
00634       f.datalen = datalen;
00635       res = local_queue_frame(p, isoutbound, &f, ast, 1);
00636       ao2_unlock(p);
00637    }
00638 
00639    ao2_ref(p, -1);
00640    return res;
00641 }
00642 
00643 static int local_digit_begin(struct ast_channel *ast, char digit)
00644 {
00645    struct local_pvt *p = ast->tech_pvt;
00646    int res = -1;
00647    struct ast_frame f = { AST_FRAME_DTMF_BEGIN, };
00648    int isoutbound;
00649 
00650    if (!p)
00651       return -1;
00652 
00653    ao2_ref(p, 1); /* ref for local_queue_frame */
00654    ao2_lock(p);
00655    isoutbound = IS_OUTBOUND(ast, p);
00656    f.subclass.integer = digit;
00657    res = local_queue_frame(p, isoutbound, &f, ast, 0);
00658    ao2_unlock(p);
00659    ao2_ref(p, -1);
00660 
00661    return res;
00662 }
00663 
00664 static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
00665 {
00666    struct local_pvt *p = ast->tech_pvt;
00667    int res = -1;
00668    struct ast_frame f = { AST_FRAME_DTMF_END, };
00669    int isoutbound;
00670 
00671    if (!p)
00672       return -1;
00673 
00674    ao2_ref(p, 1); /* ref for local_queue_frame */
00675    ao2_lock(p);
00676    isoutbound = IS_OUTBOUND(ast, p);
00677    f.subclass.integer = digit;
00678    f.len = duration;
00679    res = local_queue_frame(p, isoutbound, &f, ast, 0);
00680    ao2_unlock(p);
00681    ao2_ref(p, -1);
00682 
00683    return res;
00684 }
00685 
00686 static int local_sendtext(struct ast_channel *ast, const char *text)
00687 {
00688    struct local_pvt *p = ast->tech_pvt;
00689    int res = -1;
00690    struct ast_frame f = { AST_FRAME_TEXT, };
00691    int isoutbound;
00692 
00693    if (!p)
00694       return -1;
00695 
00696    ao2_lock(p);
00697    ao2_ref(p, 1); /* ref for local_queue_frame */
00698    isoutbound = IS_OUTBOUND(ast, p);
00699    f.data.ptr = (char *) text;
00700    f.datalen = strlen(text) + 1;
00701    res = local_queue_frame(p, isoutbound, &f, ast, 0);
00702    ao2_unlock(p);
00703    ao2_ref(p, -1);
00704    return res;
00705 }
00706 
00707 static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
00708 {
00709    struct local_pvt *p = ast->tech_pvt;
00710    int res = -1;
00711    struct ast_frame f = { AST_FRAME_HTML, };
00712    int isoutbound;
00713 
00714    if (!p)
00715       return -1;
00716 
00717    ao2_lock(p);
00718    ao2_ref(p, 1); /* ref for local_queue_frame */
00719    isoutbound = IS_OUTBOUND(ast, p);
00720    f.subclass.integer = subclass;
00721    f.data.ptr = (char *)data;
00722    f.datalen = datalen;
00723    res = local_queue_frame(p, isoutbound, &f, ast, 0);
00724    ao2_unlock(p);
00725    ao2_ref(p, -1);
00726 
00727    return res;
00728 }
00729 
00730 /*! \brief Initiate new call, part of PBX interface 
00731  *    dest is the dial string */
00732 static int local_call(struct ast_channel *ast, char *dest, int timeout)
00733 {
00734    struct local_pvt *p = ast->tech_pvt;
00735    int res;
00736    struct ast_var_t *varptr = NULL, *new;
00737    size_t len, namelen;
00738    char *reduced_dest = ast_strdupa(dest);
00739    char *slash;
00740 
00741    if (!p || p->owner != ast) {
00742       return -1;
00743    }
00744 
00745    /* since we are letting go of channel locks that were locked coming into
00746     * this function, then we need to give the tech pvt a ref */
00747    ao2_ref(p, 1);
00748 
00749    while (ao2_trylock(p)) {
00750       ast_channel_unlock(ast);
00751       sched_yield();
00752       ast_channel_lock(ast);
00753    }
00754    while ((p->chan && p->owner) && ast_channel_trylock(p->chan)) {
00755       ao2_unlock(p);
00756       if (p->owner) {
00757          ast_channel_unlock(p->owner);
00758       }
00759       sched_yield();
00760       if (p->owner) {
00761          ast_channel_lock(p->owner);
00762       }
00763       ao2_lock(p);
00764    }
00765 
00766    if (!p->owner || !p->chan) {
00767       /* someone went away during the locking madness.
00768        * time to bail. */
00769       if (p->chan) {
00770          ast_channel_unlock(p->chan);
00771       }
00772       ao2_unlock(p);
00773       ao2_ref(p,-1);
00774       return -1;
00775    }
00776 
00777    /*
00778     * Note that cid_num and cid_name aren't passed in the ast_channel_alloc
00779     * call, so it's done here instead.
00780     *
00781     * All these failure points just return -1. The individual strings will
00782     * be cleared when we destroy the channel.
00783     */
00784    ast_party_redirecting_copy(&p->chan->redirecting, &p->owner->redirecting);
00785 
00786    ast_party_dialed_copy(&p->chan->dialed, &p->owner->dialed);
00787 
00788    ast_connected_line_copy_to_caller(&p->chan->caller, &p->owner->connected);
00789    ast_connected_line_copy_from_caller(&p->chan->connected, &p->owner->caller);
00790 
00791    ast_string_field_set(p->chan, language, p->owner->language);
00792    ast_string_field_set(p->chan, accountcode, p->owner->accountcode);
00793    ast_string_field_set(p->chan, musicclass, p->owner->musicclass);
00794    ast_cdr_update(p->chan);
00795 
00796    ast_channel_cc_params_init(p->chan, ast_channel_get_cc_config_params(p->owner));
00797 
00798    /* Make sure we inherit the ANSWERED_ELSEWHERE flag if it's set on the queue/dial call request in the dialplan */
00799    if (ast_test_flag(ast, AST_FLAG_ANSWERED_ELSEWHERE)) {
00800       ast_set_flag(p->chan, AST_FLAG_ANSWERED_ELSEWHERE);
00801    }
00802 
00803    /* copy the channel variables from the incoming channel to the outgoing channel */
00804    /* Note that due to certain assumptions, they MUST be in the same order */
00805    AST_LIST_TRAVERSE(&p->owner->varshead, varptr, entries) {
00806       namelen = strlen(varptr->name);
00807       len = sizeof(struct ast_var_t) + namelen + strlen(varptr->value) + 2;
00808       if ((new = ast_calloc(1, len))) {
00809          memcpy(new, varptr, len);
00810          new->value = &(new->name[0]) + namelen + 1;
00811          AST_LIST_INSERT_TAIL(&p->chan->varshead, new, entries);
00812       }
00813    }
00814    ast_channel_datastore_inherit(p->owner, p->chan);
00815    /* If the local channel has /n or /b on the end of it,
00816     * we need to lop that off for our argument to setting
00817     * up the CC_INTERFACES variable
00818     */
00819    if ((slash = strrchr(reduced_dest, '/'))) {
00820       *slash = '\0';
00821    }
00822    ast_set_cc_interfaces_chanvar(p->chan, reduced_dest);
00823 
00824    if (!ast_exists_extension(p->chan, p->chan->context, p->chan->exten, 1,
00825       S_COR(p->owner->caller.id.number.valid, p->owner->caller.id.number.str, NULL))) {
00826       ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n", p->chan->exten, p->chan->context);
00827       ao2_unlock(p);
00828       ast_channel_unlock(p->chan);
00829       ao2_ref(p, -1);
00830       return -1;
00831    }
00832 
00833    /* Start switch on sub channel */
00834    if (!(res = ast_pbx_start(p->chan)))
00835       ast_set_flag(p, LOCAL_LAUNCHED_PBX);
00836 
00837    ao2_unlock(p);
00838    ast_channel_unlock(p->chan);
00839    ao2_ref(p, -1);
00840    return res;
00841 }
00842 
00843 /*! \brief Hangup a call through the local proxy channel */
00844 static int local_hangup(struct ast_channel *ast)
00845 {
00846    struct local_pvt *p = ast->tech_pvt;
00847    int isoutbound;
00848    struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_HANGUP }, .data.uint32 = ast->hangupcause };
00849    struct ast_channel *ochan = NULL;
00850 
00851    if (!p)
00852       return -1;
00853 
00854    /* we MUST give the tech_pvt a ref here since we are unlocking the
00855     * channel during deadlock avoidance. */
00856    ao2_ref(p, 1);
00857 
00858    ao2_lock(p);
00859 
00860    isoutbound = IS_OUTBOUND(ast, p);
00861 
00862    if (p->chan && ast_test_flag(ast, AST_FLAG_ANSWERED_ELSEWHERE)) {
00863       ast_set_flag(p->chan, AST_FLAG_ANSWERED_ELSEWHERE);
00864       ast_debug(2, "This local call has the ANSWERED_ELSEWHERE flag set.\n");
00865    }
00866 
00867    if (isoutbound) {
00868       const char *status = pbx_builtin_getvar_helper(p->chan, "DIALSTATUS");
00869       if ((status) && (p->owner)) {
00870          /* Deadlock avoidance */
00871          while (p->owner && ast_channel_trylock(p->owner)) {
00872             ao2_unlock(p);
00873             if (p->chan) {
00874                ast_channel_unlock(p->chan);
00875             }
00876             sched_yield();
00877             if (p->chan) {
00878                ast_channel_lock(p->chan);
00879             }
00880             ao2_lock(p);
00881          }
00882          if (p->owner) {
00883             pbx_builtin_setvar_helper(p->owner, "CHANLOCALSTATUS", status);
00884             ast_channel_unlock(p->owner);
00885          }
00886       }
00887       if (!p->chan) {
00888          /* chan was == to ast and was !NULL before deadlock avoidance started, if chan
00889           * is NULL now, then we should bail because that channel
00890           * hungup already. This is possible because we let go of the
00891           * lock given to the ast channel passed to this function during
00892           * deadlock avoidance. */
00893          ao2_unlock(p);
00894          ao2_ref(p, -1);
00895          return 0;
00896       }
00897       p->chan = NULL;
00898       ast_clear_flag(p, LOCAL_LAUNCHED_PBX);
00899       ast_module_user_remove(p->u_chan);
00900    } else {
00901       ast_module_user_remove(p->u_owner);
00902       while (p->chan && ast_channel_trylock(p->chan)) {
00903             ao2_unlock(p);
00904             if (p->owner) {
00905                ast_channel_unlock(p->owner);
00906             }
00907             sched_yield();
00908             if (p->owner) {
00909                ast_channel_lock(p->owner);
00910             }
00911             ao2_lock(p);
00912       }
00913       if (p->chan) {
00914          ast_queue_hangup(p->chan);
00915          ast_channel_unlock(p->chan);
00916       }
00917 
00918       if (!p->owner) {
00919          /* owner was == to ast and was !NULL before deadlock avoidance started, if
00920           * owner is NULL now, then we should bail because that channel
00921           * hungup already. This is possible because we let go of the
00922           * lock given to the ast channel passed to this function during
00923           * deadlock avoidance. */
00924          ao2_unlock(p);
00925          ao2_ref(p, -1);
00926          return 0;
00927       }
00928       p->owner = NULL;
00929    }
00930 
00931    ast->tech_pvt = NULL;
00932 
00933    if (!p->owner && !p->chan) {
00934       ao2_unlock(p);
00935 
00936       /* Remove from list */
00937       ao2_unlink(locals, p);
00938       ao2_ref(p, -1);
00939       return 0;
00940    }
00941    if (p->chan && !ast_test_flag(p, LOCAL_LAUNCHED_PBX)) {
00942       /* Need to actually hangup since there is no PBX */
00943       ochan = p->chan;
00944    } else {
00945       local_queue_frame(p, isoutbound, &f, NULL, 1);
00946    }
00947 
00948    ao2_unlock(p);
00949    if (ochan) {
00950       ast_hangup(ochan);
00951    }
00952 
00953    ao2_ref(p, -1);
00954    return 0;
00955 }
00956 
00957 /*! \brief Create a call structure */
00958 static struct local_pvt *local_alloc(const char *data, int format)
00959 {
00960    struct local_pvt *tmp = NULL;
00961    char *c = NULL, *opts = NULL;
00962 
00963    if (!(tmp = ao2_alloc(sizeof(*tmp), NULL))) {
00964       return NULL;
00965    }
00966 
00967    /* Initialize private structure information */
00968    ast_copy_string(tmp->exten, data, sizeof(tmp->exten));
00969 
00970    memcpy(&tmp->jb_conf, &g_jb_conf, sizeof(tmp->jb_conf));
00971 
00972    /* Look for options */
00973    if ((opts = strchr(tmp->exten, '/'))) {
00974       *opts++ = '\0';
00975       if (strchr(opts, 'n'))
00976          ast_set_flag(tmp, LOCAL_NO_OPTIMIZATION);
00977       if (strchr(opts, 'j')) {
00978          if (ast_test_flag(tmp, LOCAL_NO_OPTIMIZATION))
00979             ast_set_flag(&tmp->jb_conf, AST_JB_ENABLED);
00980          else {
00981             ast_log(LOG_ERROR, "You must use the 'n' option for chan_local "
00982                "to use the 'j' option to enable the jitterbuffer\n");
00983          }
00984       }
00985       if (strchr(opts, 'b')) {
00986          ast_set_flag(tmp, LOCAL_BRIDGE);
00987       }
00988       if (strchr(opts, 'm')) {
00989          ast_set_flag(tmp, LOCAL_MOH_PASSTHRU);
00990       }
00991    }
00992 
00993    /* Look for a context */
00994    if ((c = strchr(tmp->exten, '@')))
00995       *c++ = '\0';
00996 
00997    ast_copy_string(tmp->context, c ? c : "default", sizeof(tmp->context));
00998 
00999    tmp->reqformat = format;
01000 
01001 #if 0
01002    /* We can't do this check here, because we don't know the CallerID yet, and
01003     * the CallerID could potentially affect what step is actually taken (or
01004     * even if that step exists). */
01005    if (!ast_exists_extension(NULL, tmp->context, tmp->exten, 1, NULL)) {
01006       ast_log(LOG_NOTICE, "No such extension/context %s@%s creating local channel\n", tmp->exten, tmp->context);
01007       tmp = local_pvt_destroy(tmp);
01008    } else {
01009 #endif
01010       /* Add to list */
01011       ao2_link(locals, tmp);
01012 #if 0
01013    }
01014 #endif
01015    return tmp; /* this is returned with a ref */
01016 }
01017 
01018 /*! \brief Start new local channel */
01019 static struct ast_channel *local_new(struct local_pvt *p, int state, const char *linkedid)
01020 {
01021    struct ast_channel *tmp = NULL, *tmp2 = NULL;
01022    int randnum = ast_random() & 0xffff, fmt = 0;
01023    const char *t;
01024    int ama;
01025 
01026    /* Allocate two new Asterisk channels */
01027    /* safe accountcode */
01028    if (p->owner && p->owner->accountcode)
01029       t = p->owner->accountcode;
01030    else
01031       t = "";
01032 
01033    if (p->owner)
01034       ama = p->owner->amaflags;
01035    else
01036       ama = 0;
01037    if (!(tmp = ast_channel_alloc(1, state, 0, 0, t, p->exten, p->context, linkedid, ama, "Local/%s@%s-%04x;1", p->exten, p->context, randnum)) 
01038       || !(tmp2 = ast_channel_alloc(1, AST_STATE_RING, 0, 0, t, p->exten, p->context, linkedid, ama, "Local/%s@%s-%04x;2", p->exten, p->context, randnum))) {
01039       if (tmp) {
01040          tmp = ast_channel_release(tmp);
01041       }
01042       ast_log(LOG_WARNING, "Unable to allocate channel structure(s)\n");
01043       return NULL;
01044    }
01045 
01046    tmp2->tech = tmp->tech = &local_tech;
01047 
01048    tmp->nativeformats = p->reqformat;
01049    tmp2->nativeformats = p->reqformat;
01050 
01051    /* Determine our read/write format and set it on each channel */
01052    fmt = ast_best_codec(p->reqformat);
01053    tmp->writeformat = fmt;
01054    tmp2->writeformat = fmt;
01055    tmp->rawwriteformat = fmt;
01056    tmp2->rawwriteformat = fmt;
01057    tmp->readformat = fmt;
01058    tmp2->readformat = fmt;
01059    tmp->rawreadformat = fmt;
01060    tmp2->rawreadformat = fmt;
01061 
01062    tmp->tech_pvt = p;
01063    tmp2->tech_pvt = p;
01064 
01065    p->owner = tmp;
01066    p->chan = tmp2;
01067    p->u_owner = ast_module_user_add(p->owner);
01068    p->u_chan = ast_module_user_add(p->chan);
01069 
01070    ast_copy_string(tmp->context, p->context, sizeof(tmp->context));
01071    ast_copy_string(tmp2->context, p->context, sizeof(tmp2->context));
01072    ast_copy_string(tmp2->exten, p->exten, sizeof(tmp->exten));
01073    tmp->priority = 1;
01074    tmp2->priority = 1;
01075 
01076    ast_jb_configure(tmp, &p->jb_conf);
01077 
01078    return tmp;
01079 }
01080 
01081 /*! \brief Part of PBX interface */
01082 static struct ast_channel *local_request(const char *type, format_t format, const struct ast_channel *requestor, void *data, int *cause)
01083 {
01084    struct local_pvt *p = NULL;
01085    struct ast_channel *chan = NULL;
01086 
01087    /* Allocate a new private structure and then Asterisk channel */
01088    if ((p = local_alloc(data, format))) {
01089       if (!(chan = local_new(p, AST_STATE_DOWN, requestor ? requestor->linkedid : NULL))) {
01090          ao2_unlink(locals, p);
01091       }
01092       if (chan && ast_channel_cc_params_init(chan, requestor ? ast_channel_get_cc_config_params((struct ast_channel *)requestor) : NULL)) {
01093          chan = ast_channel_release(chan);
01094          ao2_unlink(locals, p);
01095       }
01096       ao2_ref(p, -1); /* kill the ref from the alloc */
01097    }
01098 
01099    return chan;
01100 }
01101 
01102 /*! \brief CLI command "local show channels" */
01103 static char *locals_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01104 {
01105    struct local_pvt *p = NULL;
01106    struct ao2_iterator it;
01107 
01108    switch (cmd) {
01109    case CLI_INIT:
01110       e->command = "local show channels";
01111       e->usage =
01112          "Usage: local show channels\n"
01113          "       Provides summary information on active local proxy channels.\n";
01114       return NULL;
01115    case CLI_GENERATE:
01116       return NULL;
01117    }
01118 
01119    if (a->argc != 3)
01120       return CLI_SHOWUSAGE;
01121 
01122    if (ao2_container_count(locals) == 0) {
01123       ast_cli(a->fd, "No local channels in use\n");
01124       return RESULT_SUCCESS;
01125    }
01126 
01127    it = ao2_iterator_init(locals, 0);
01128    while ((p = ao2_iterator_next(&it))) {
01129       ao2_lock(p);
01130       ast_cli(a->fd, "%s -- %s@%s\n", p->owner ? p->owner->name : "<unowned>", p->exten, p->context);
01131       ao2_unlock(p);
01132       ao2_ref(p, -1);
01133    }
01134    ao2_iterator_destroy(&it);
01135 
01136    return CLI_SUCCESS;
01137 }
01138 
01139 static struct ast_cli_entry cli_local[] = {
01140    AST_CLI_DEFINE(locals_show, "List status of local channels"),
01141 };
01142 
01143 static int manager_optimize_away(struct mansession *s, const struct message *m)
01144 {
01145    const char *channel;
01146    struct local_pvt *p, *tmp = NULL;
01147    struct ast_channel *c;
01148    int found = 0;
01149    struct ao2_iterator it;
01150 
01151    channel = astman_get_header(m, "Channel");
01152 
01153    if (ast_strlen_zero(channel)) {
01154       astman_send_error(s, m, "'Channel' not specified.");
01155       return 0;
01156    }
01157 
01158    c = ast_channel_get_by_name(channel);
01159    if (!c) {
01160       astman_send_error(s, m, "Channel does not exist.");
01161       return 0;
01162    }
01163 
01164    p = c->tech_pvt;
01165    ast_channel_unref(c);
01166    c = NULL;
01167 
01168    it = ao2_iterator_init(locals, 0);
01169    while ((tmp = ao2_iterator_next(&it))) {
01170       if (tmp == p) {
01171          ao2_lock(tmp);
01172          found = 1;
01173          ast_clear_flag(tmp, LOCAL_NO_OPTIMIZATION);
01174          ao2_unlock(tmp);
01175          ao2_ref(tmp, -1);
01176          break;
01177       }
01178       ao2_ref(tmp, -1);
01179    }
01180    ao2_iterator_destroy(&it);
01181 
01182    if (found) {
01183       astman_send_ack(s, m, "Queued channel to be optimized away");
01184    } else {
01185       astman_send_error(s, m, "Unable to find channel");
01186    }
01187 
01188    return 0;
01189 }
01190 
01191 
01192 static int locals_cmp_cb(void *obj, void *arg, int flags)
01193 {
01194    return (obj == arg) ? CMP_MATCH : 0;
01195 }
01196 
01197 /*! \brief Load module into PBX, register channel */
01198 static int load_module(void)
01199 {
01200    if (!(locals = ao2_container_alloc(BUCKET_SIZE, NULL, locals_cmp_cb))) {
01201       return AST_MODULE_LOAD_FAILURE;
01202    }
01203 
01204    /* Make sure we can register our channel type */
01205    if (ast_channel_register(&local_tech)) {
01206       ast_log(LOG_ERROR, "Unable to register channel class 'Local'\n");
01207       ao2_ref(locals, -1);
01208       return AST_MODULE_LOAD_FAILURE;
01209    }
01210    ast_cli_register_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
01211    ast_manager_register_xml("LocalOptimizeAway", EVENT_FLAG_SYSTEM|EVENT_FLAG_CALL, manager_optimize_away);
01212 
01213    return AST_MODULE_LOAD_SUCCESS;
01214 }
01215 
01216 /*! \brief Unload the local proxy channel from Asterisk */
01217 static int unload_module(void)
01218 {
01219    struct local_pvt *p = NULL;
01220    struct ao2_iterator it;
01221 
01222    /* First, take us out of the channel loop */
01223    ast_cli_unregister_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
01224    ast_manager_unregister("LocalOptimizeAway");
01225    ast_channel_unregister(&local_tech);
01226 
01227    it = ao2_iterator_init(locals, 0);
01228    while ((p = ao2_iterator_next(&it))) {
01229       if (p->owner) {
01230          ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
01231       }
01232       ao2_ref(p, -1);
01233    }
01234    ao2_iterator_destroy(&it);
01235    ao2_ref(locals, -1);
01236 
01237    return 0;
01238 }
01239 
01240 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Local Proxy Channel (Note: used internally by other modules)",
01241       .load = load_module,
01242       .unload = unload_module,
01243       .load_pri = AST_MODPRI_CHANNEL_DRIVER,
01244    );

Generated on Wed Apr 6 11:29:42 2011 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7