Sun Jun 12 16:37:46 2011

Asterisk developer's documentation


file.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2006, 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  * \brief Generic File Format Support.
00022  *
00023  * \author Mark Spencer <markster@digium.com> 
00024  */
00025 
00026 #include "asterisk.h"
00027 
00028 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 114550 $")
00029 
00030 #include <sys/types.h>
00031 #include <errno.h>
00032 #include <unistd.h>
00033 #include <stdlib.h>
00034 #include <string.h>
00035 #include <stdio.h>
00036 #include <fcntl.h>
00037 #include <dirent.h>
00038 #include <sys/types.h>
00039 #include <sys/stat.h>
00040 
00041 #include "asterisk/frame.h"
00042 #include "asterisk/file.h"
00043 #include "asterisk/cli.h"
00044 #include "asterisk/logger.h"
00045 #include "asterisk/channel.h"
00046 #include "asterisk/sched.h"
00047 #include "asterisk/options.h"
00048 #include "asterisk/translate.h"
00049 #include "asterisk/utils.h"
00050 #include "asterisk/lock.h"
00051 #include "asterisk/app.h"
00052 #include "asterisk/pbx.h"
00053 #include "asterisk/linkedlists.h"
00054 #include "asterisk/module.h"
00055 
00056 /*
00057  * The following variable controls the layout of localized sound files.
00058  * If 0, use the historical layout with prefix just before the filename
00059  * (i.e. digits/en/1.gsm , digits/it/1.gsm or default to digits/1.gsm),
00060  * if 1 put the prefix at the beginning of the filename
00061  * (i.e. en/digits/1.gsm, it/digits/1.gsm or default to digits/1.gsm).
00062  * The latter permits a language to be entirely in one directory.
00063  */
00064 int ast_language_is_prefix;
00065 
00066 static AST_LIST_HEAD_STATIC(formats, ast_format);
00067 
00068 int __ast_format_register(const struct ast_format *f, struct ast_module *mod)
00069 {
00070    struct ast_format *tmp;
00071 
00072    if (AST_LIST_LOCK(&formats)) {
00073       ast_log(LOG_WARNING, "Unable to lock format list\n");
00074       return -1;
00075    }
00076    AST_LIST_TRAVERSE(&formats, tmp, list) {
00077       if (!strcasecmp(f->name, tmp->name)) {
00078          AST_LIST_UNLOCK(&formats);
00079          ast_log(LOG_WARNING, "Tried to register '%s' format, already registered\n", f->name);
00080          return -1;
00081       }
00082    }
00083    if (!(tmp = ast_calloc(1, sizeof(*tmp)))) {
00084       AST_LIST_UNLOCK(&formats);
00085       return -1;
00086    }
00087    *tmp = *f;
00088    tmp->module = mod;
00089    if (tmp->buf_size) {
00090       /*
00091        * Align buf_size properly, rounding up to the machine-specific
00092        * alignment for pointers.
00093        */
00094       struct _test_align { void *a, *b; } p;
00095       int align = (char *)&p.b - (char *)&p.a;
00096       tmp->buf_size = ((f->buf_size + align - 1)/align)*align;
00097    }
00098    
00099    memset(&tmp->list, 0, sizeof(tmp->list));
00100 
00101    AST_LIST_INSERT_HEAD(&formats, tmp, list);
00102    AST_LIST_UNLOCK(&formats);
00103    if (option_verbose > 1)
00104       ast_verbose( VERBOSE_PREFIX_2 "Registered file format %s, extension(s) %s\n", f->name, f->exts);
00105 
00106    return 0;
00107 }
00108 
00109 int ast_format_unregister(const char *name)
00110 {
00111    struct ast_format *tmp;
00112    int res = -1;
00113 
00114    if (AST_LIST_LOCK(&formats)) {
00115       ast_log(LOG_WARNING, "Unable to lock format list\n");
00116       return -1;
00117    }
00118    AST_LIST_TRAVERSE_SAFE_BEGIN(&formats, tmp, list) {
00119       if (!strcasecmp(name, tmp->name)) {
00120          AST_LIST_REMOVE_CURRENT(&formats, list);
00121          free(tmp);
00122          res = 0;
00123       }
00124    }
00125    AST_LIST_TRAVERSE_SAFE_END
00126    AST_LIST_UNLOCK(&formats);
00127 
00128    if (!res) {
00129       if (option_verbose > 1)
00130          ast_verbose( VERBOSE_PREFIX_2 "Unregistered format %s\n", name);
00131    } else
00132       ast_log(LOG_WARNING, "Tried to unregister format %s, already unregistered\n", name);
00133 
00134    return res;
00135 }
00136 
00137 int ast_stopstream(struct ast_channel *tmp)
00138 {
00139    ast_channel_lock(tmp);
00140 
00141    /* Stop a running stream if there is one */
00142    if (tmp->stream) {
00143       ast_closestream(tmp->stream);
00144       tmp->stream = NULL;
00145       if (tmp->oldwriteformat && ast_set_write_format(tmp, tmp->oldwriteformat))
00146          ast_log(LOG_WARNING, "Unable to restore format back to %d\n", tmp->oldwriteformat);
00147    }
00148    /* Stop the video stream too */
00149    if (tmp->vstream != NULL) {
00150       ast_closestream(tmp->vstream);
00151       tmp->vstream = NULL;
00152    }
00153 
00154    ast_channel_unlock(tmp);
00155 
00156    return 0;
00157 }
00158 
00159 int ast_writestream(struct ast_filestream *fs, struct ast_frame *f)
00160 {
00161    int res = -1;
00162    int alt = 0;
00163    if (f->frametype == AST_FRAME_VIDEO) {
00164       if (fs->fmt->format < AST_FORMAT_MAX_AUDIO) {
00165          /* This is the audio portion.  Call the video one... */
00166          if (!fs->vfs && fs->filename) {
00167             const char *type = ast_getformatname(f->subclass & ~0x1);
00168             fs->vfs = ast_writefile(fs->filename, type, NULL, fs->flags, 0, fs->mode);
00169             ast_log(LOG_DEBUG, "Opened video output file\n");
00170          }
00171          if (fs->vfs)
00172             return ast_writestream(fs->vfs, f);
00173          /* else ignore */
00174          return 0;            
00175       } else {
00176          /* Might / might not have mark set */
00177          alt = 1;
00178       }
00179    } else if (f->frametype != AST_FRAME_VOICE) {
00180       ast_log(LOG_WARNING, "Tried to write non-voice frame\n");
00181       return -1;
00182    }
00183    if (((fs->fmt->format | alt) & f->subclass) == f->subclass) {
00184       res =  fs->fmt->write(fs, f);
00185       if (res < 0) 
00186          ast_log(LOG_WARNING, "Natural write failed\n");
00187       else if (res > 0)
00188          ast_log(LOG_WARNING, "Huh??\n");
00189    } else {
00190       /* XXX If they try to send us a type of frame that isn't the normal frame, and isn't
00191              the one we've setup a translator for, we do the "wrong thing" XXX */
00192       if (fs->trans && f->subclass != fs->lastwriteformat) {
00193          ast_translator_free_path(fs->trans);
00194          fs->trans = NULL;
00195       }
00196       if (!fs->trans) 
00197          fs->trans = ast_translator_build_path(fs->fmt->format, f->subclass);
00198       if (!fs->trans)
00199          ast_log(LOG_WARNING, "Unable to translate to format %s, source format %s\n",
00200             fs->fmt->name, ast_getformatname(f->subclass));
00201       else {
00202          struct ast_frame *trf;
00203          fs->lastwriteformat = f->subclass;
00204          /* Get the translated frame but don't consume the original in case they're using it on another stream */
00205          trf = ast_translate(fs->trans, f, 0);
00206          if (trf) {
00207             res = fs->fmt->write(fs, trf);
00208             ast_frfree(trf);
00209             if (res) 
00210                ast_log(LOG_WARNING, "Translated frame write failed\n");
00211          } else
00212             res = 0;
00213       }
00214    }
00215    return res;
00216 }
00217 
00218 static int copy(const char *infile, const char *outfile)
00219 {
00220    int ifd, ofd, len;
00221    char buf[4096];   /* XXX make it lerger. */
00222 
00223    if ((ifd = open(infile, O_RDONLY)) < 0) {
00224       ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
00225       return -1;
00226    }
00227    if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, 0600)) < 0) {
00228       ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
00229       close(ifd);
00230       return -1;
00231    }
00232    while ( (len = read(ifd, buf, sizeof(buf)) ) ) {
00233       int res;
00234       if (len < 0) {
00235          ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
00236          break;
00237       }
00238       /* XXX handle partial writes */
00239       res = write(ofd, buf, len);
00240       if (res != len) {
00241          ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
00242          len = -1; /* error marker */
00243          break;
00244       }
00245    }
00246    close(ifd);
00247    close(ofd);
00248    if (len < 0) {
00249       unlink(outfile);
00250       return -1; /* error */
00251    }
00252    return 0;   /* success */
00253 }
00254 
00255 /*!
00256  * \brief construct a filename. Absolute pathnames are preserved,
00257  * relative names are prefixed by the sounds/ directory.
00258  * The wav49 suffix is replaced by 'WAV'.
00259  * Returns a malloc'ed string to be freed by the caller.
00260  */
00261 static char *build_filename(const char *filename, const char *ext)
00262 {
00263    char *fn = NULL;
00264 
00265    if (!strcmp(ext, "wav49"))
00266       ext = "WAV";
00267 
00268    if (filename[0] == '/')
00269       asprintf(&fn, "%s.%s", filename, ext);
00270    else
00271       asprintf(&fn, "%s/sounds/%s.%s",
00272          ast_config_AST_DATA_DIR, filename, ext);
00273    return fn;
00274 }
00275 
00276 /* compare type against the list 'exts' */
00277 /* XXX need a better algorithm */
00278 static int exts_compare(const char *exts, const char *type)
00279 {
00280    char tmp[256];
00281    char *stringp = tmp, *ext;
00282 
00283    ast_copy_string(tmp, exts, sizeof(tmp));
00284    while ((ext = strsep(&stringp, "|"))) {
00285       if (!strcmp(ext, type))
00286          return 1;
00287    }
00288 
00289    return 0;
00290 }
00291 
00292 static struct ast_filestream *get_filestream(struct ast_format *fmt, FILE *bfile)
00293 {
00294    struct ast_filestream *s;
00295 
00296    int l = sizeof(*s) + fmt->buf_size + fmt->desc_size;  /* total allocation size */
00297    if ( (s = ast_calloc(1, l)) == NULL)
00298       return NULL;
00299    s->fmt = fmt;
00300    s->f = bfile;
00301 
00302    if (fmt->desc_size)
00303       s->_private = ((char *)(s+1)) + fmt->buf_size;
00304    if (fmt->buf_size)
00305       s->buf = (char *)(s+1);
00306    s->fr.src = fmt->name;
00307    return s;
00308 }
00309 
00310 /*
00311  * Default implementations of open and rewrite.
00312  * Only use them if you don't have expensive stuff to do.
00313  */
00314 enum wrap_fn { WRAP_OPEN, WRAP_REWRITE };
00315 
00316 static int fn_wrapper(struct ast_filestream *s, const char *comment, enum wrap_fn mode)
00317 {
00318    struct ast_format *f = s->fmt;
00319    int ret = -1;
00320 
00321    if (mode == WRAP_OPEN && f->open && f->open(s))
00322                 ast_log(LOG_WARNING, "Unable to open format %s\n", f->name);
00323    else if (mode == WRAP_REWRITE && f->rewrite && f->rewrite(s, comment))
00324                 ast_log(LOG_WARNING, "Unable to rewrite format %s\n", f->name);
00325    else {
00326       /* preliminary checks succeed. update usecount */
00327       ast_module_ref(f->module);
00328       ret = 0;
00329    }
00330         return ret;
00331 }
00332 
00333 static int rewrite_wrapper(struct ast_filestream *s, const char *comment)
00334 {
00335    return fn_wrapper(s, comment, WRAP_REWRITE);
00336 }
00337                 
00338 static int open_wrapper(struct ast_filestream *s)
00339 {
00340    return fn_wrapper(s, NULL, WRAP_OPEN);
00341 }
00342 
00343 enum file_action {
00344    ACTION_EXISTS = 1, /* return matching format if file exists, 0 otherwise */
00345    ACTION_DELETE, /* delete file, return 0 on success, -1 on error */
00346    ACTION_RENAME, /* rename file. return 0 on success, -1 on error */
00347    ACTION_OPEN,
00348    ACTION_COPY /* copy file. return 0 on success, -1 on error */
00349 };
00350 
00351 /*!
00352  * \brief perform various actions on a file. Second argument
00353  * arg2 depends on the command:
00354  * unused for EXISTS and DELETE
00355  * destination file name (const char *) for COPY and RENAME
00356  *    struct ast_channel * for OPEN
00357  * if fmt is NULL, OPEN will return the first matching entry,
00358  * whereas other functions will run on all matching entries.
00359  */
00360 static int ast_filehelper(const char *filename, const void *arg2, const char *fmt, const enum file_action action)
00361 {
00362    struct ast_format *f;
00363    int res = (action == ACTION_EXISTS) ? 0 : -1;
00364 
00365    if (AST_LIST_LOCK(&formats)) {
00366       ast_log(LOG_WARNING, "Unable to lock format list\n");
00367       return res;
00368    }
00369    /* Check for a specific format */
00370    AST_LIST_TRAVERSE(&formats, f, list) {
00371       char *stringp, *ext = NULL;
00372 
00373       if (fmt && !exts_compare(f->exts, fmt))
00374          continue;
00375 
00376       /* Look for a file matching the supported extensions.
00377        * The file must exist, and for OPEN, must match
00378        * one of the formats supported by the channel.
00379        */
00380       stringp = ast_strdupa(f->exts);  /* this is in the stack so does not need to be freed */
00381       while ( (ext = strsep(&stringp, "|")) ) {
00382          struct stat st;
00383          char *fn = build_filename(filename, ext);
00384 
00385          if (fn == NULL)
00386             continue;
00387 
00388          if ( stat(fn, &st) ) { /* file not existent */
00389             free(fn);
00390             continue;
00391          }
00392          /* for 'OPEN' we need to be sure that the format matches
00393           * what the channel can process
00394           */
00395          if (action == ACTION_OPEN) {
00396             struct ast_channel *chan = (struct ast_channel *)arg2;
00397             FILE *bfile;
00398             struct ast_filestream *s;
00399 
00400             if ( !(chan->writeformat & f->format) &&
00401                  !(f->format >= AST_FORMAT_MAX_AUDIO && fmt)) {
00402                free(fn);
00403                continue;   /* not a supported format */
00404             }
00405             if ( (bfile = fopen(fn, "r")) == NULL) {
00406                free(fn);
00407                continue;   /* cannot open file */
00408             }
00409             s = get_filestream(f, bfile);
00410             if (!s) {
00411                fclose(bfile);
00412                free(fn);   /* cannot allocate descriptor */
00413                continue;
00414             }
00415             if (open_wrapper(s)) {
00416                fclose(bfile);
00417                free(fn);
00418                free(s);
00419                continue;   /* cannot run open on file */
00420             }
00421             /* ok this is good for OPEN */
00422             res = 1; /* found */
00423             s->lasttimeout = -1;
00424             s->fmt = f;
00425             s->trans = NULL;
00426             s->filename = NULL;
00427             if (s->fmt->format < AST_FORMAT_MAX_AUDIO) {
00428                if (chan->stream)
00429                   ast_closestream(chan->stream);
00430                chan->stream = s;
00431             } else {
00432                if (chan->vstream)
00433                   ast_closestream(chan->vstream);
00434                chan->vstream = s;
00435             }
00436             free(fn);
00437             break;
00438          }
00439          switch (action) {
00440          case ACTION_OPEN:
00441             break;   /* will never get here */
00442 
00443          case ACTION_EXISTS:  /* return the matching format */
00444             res |= f->format;
00445             break;
00446 
00447          case ACTION_DELETE:
00448             if ( (res = unlink(fn)) )
00449                ast_log(LOG_WARNING, "unlink(%s) failed: %s\n", fn, strerror(errno));
00450             break;
00451 
00452          case ACTION_RENAME:
00453          case ACTION_COPY: {
00454             char *nfn = build_filename((const char *)arg2, ext);
00455             if (!nfn)
00456                ast_log(LOG_WARNING, "Out of memory\n");
00457             else {
00458                res = action == ACTION_COPY ? copy(fn, nfn) : rename(fn, nfn);
00459                if (res)
00460                   ast_log(LOG_WARNING, "%s(%s,%s) failed: %s\n",
00461                      action == ACTION_COPY ? "copy" : "rename",
00462                       fn, nfn, strerror(errno));
00463                free(nfn);
00464             }
00465              }
00466             break;
00467 
00468          default:
00469             ast_log(LOG_WARNING, "Unknown helper %d\n", action);
00470          }
00471          free(fn);
00472       }
00473    }
00474    AST_LIST_UNLOCK(&formats);
00475    return res;
00476 }
00477 
00478 static int is_absolute_path(const char *filename)
00479 {
00480    return filename[0] == '/';
00481 }
00482 
00483 static int fileexists_test(const char *filename, const char *fmt, const char *lang,
00484             char *buf, int buflen)
00485 {
00486    if (buf == NULL) {
00487       return -1;
00488    }
00489 
00490    if (ast_language_is_prefix && !is_absolute_path(filename)) { /* new layout */
00491       if (lang) {
00492          snprintf(buf, buflen, "%s/%s", lang, filename);
00493       } else {
00494          snprintf(buf, buflen, "%s", filename);
00495       }
00496    } else { /* old layout */
00497       strcpy(buf, filename);  /* first copy the full string */
00498       if (lang) {
00499          /* insert the language and suffix if needed */
00500          const char *c = strrchr(filename, '/');
00501          int offset = c ? c - filename + 1 : 0; /* points right after the last '/' */
00502          snprintf(buf + offset, buflen - offset, "%s/%s", lang, filename + offset);
00503       }
00504    }
00505 
00506    return ast_filehelper(buf, NULL, fmt, ACTION_EXISTS);
00507 }
00508 
00509 /*!
00510  * \brief helper routine to locate a file with a given format
00511  * and language preference.
00512  * Try preflang, preflang with stripped '_' suffix, or NULL.
00513  * In the standard asterisk, language goes just before the last component.
00514  * In an alternative configuration, the language should be a prefix
00515  * to the actual filename.
00516  *
00517  * The last parameter(s) point to a buffer of sufficient size,
00518  * which on success is filled with the matching filename.
00519  */
00520 static int fileexists_core(const char *filename, const char *fmt, const char *preflang,
00521             char *buf, int buflen)
00522 {
00523    int res = -1;
00524    char *lang = NULL;
00525 
00526    if (buf == NULL) {
00527       return -1;
00528    }
00529 
00530    /* We try languages in the following order:
00531     *    preflang (may include dialect)
00532     *    lang (preflang without dialect - if any)
00533     *    <none>
00534     *    default (unless the same as preflang or lang without dialect)
00535     */
00536 
00537    /* Try preferred language */
00538    if (!ast_strlen_zero(preflang)) {
00539       /* try the preflang exactly as it was requested */
00540       if ((res = fileexists_test(filename, fmt, preflang, buf, buflen)) > 0) {
00541          return res;
00542       } else {
00543          /* try without a dialect */
00544          char *postfix = NULL;
00545          postfix = lang = ast_strdupa(preflang);
00546 
00547          strsep(&postfix, "_");
00548          if (postfix) {
00549             if ((res = fileexists_test(filename, fmt, lang, buf, buflen)) > 0) {
00550                return res;
00551             }
00552          }
00553       }
00554    }
00555 
00556    /* Try without any language */
00557    if ((res = fileexists_test(filename, fmt, NULL, buf, buflen)) > 0) {
00558       return res;
00559    }
00560 
00561    /* Finally try the default language unless it was already tried before */
00562    if ((ast_strlen_zero(preflang) || strcmp(preflang, DEFAULT_LANGUAGE)) && (ast_strlen_zero(lang) || strcmp(lang, DEFAULT_LANGUAGE))) {
00563       if ((res = fileexists_test(filename, fmt, DEFAULT_LANGUAGE, buf, buflen)) > 0) {
00564          return res;
00565       }
00566    }
00567 
00568    return 0;
00569 }
00570 
00571 struct ast_filestream *ast_openstream(struct ast_channel *chan, const char *filename, const char *preflang)
00572 {
00573    return ast_openstream_full(chan, filename, preflang, 0);
00574 }
00575 
00576 struct ast_filestream *ast_openstream_full(struct ast_channel *chan, const char *filename, const char *preflang, int asis)
00577 {
00578    /* 
00579     * Use fileexists_core() to find a file in a compatible
00580     * language and format, set up a suitable translator,
00581     * and open the stream.
00582     */
00583    int fmts, res, buflen;
00584    char *buf;
00585 
00586    if (!asis) {
00587       /* do this first, otherwise we detect the wrong writeformat */
00588       ast_stopstream(chan);
00589       if (chan->generator)
00590          ast_deactivate_generator(chan);
00591    }
00592    if (preflang == NULL)
00593       preflang = "";
00594    buflen = strlen(preflang) + strlen(filename) + 4;
00595    buf = alloca(buflen);
00596    if (buf == NULL)
00597       return NULL;
00598    fmts = fileexists_core(filename, NULL, preflang, buf, buflen);
00599    if (fmts > 0)
00600       fmts &= AST_FORMAT_AUDIO_MASK;
00601    if (fmts < 1) {
00602       ast_log(LOG_WARNING, "File %s does not exist in any format\n", filename);
00603       return NULL;
00604    }
00605    chan->oldwriteformat = chan->writeformat;
00606    /* Set the channel to a format we can work with */
00607    res = ast_set_write_format(chan, fmts);
00608    res = ast_filehelper(buf, chan, NULL, ACTION_OPEN);
00609    if (res >= 0)
00610       return chan->stream;
00611    return NULL;
00612 }
00613 
00614 struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *filename, const char *preflang)
00615 {
00616    /* As above, but for video. But here we don't have translators
00617     * so we must enforce a format.
00618     */
00619    unsigned int format;
00620    char *buf;
00621    int buflen;
00622 
00623    if (preflang == NULL)
00624       preflang = "";
00625    buflen = strlen(preflang) + strlen(filename) + 4;
00626    buf = alloca(buflen);
00627    if (buf == NULL)
00628       return NULL;
00629 
00630    for (format = AST_FORMAT_MAX_AUDIO << 1; format <= AST_FORMAT_MAX_VIDEO; format = format << 1) {
00631       int fd;
00632       const char *fmt;
00633 
00634       if (!(chan->nativeformats & format))
00635          continue;
00636       fmt = ast_getformatname(format);
00637       if ( fileexists_core(filename, fmt, preflang, buf, buflen) < 1)   /* no valid format */
00638          continue;
00639       fd = ast_filehelper(buf, chan, fmt, ACTION_OPEN);
00640       if (fd >= 0)
00641          return chan->vstream;
00642       ast_log(LOG_WARNING, "File %s has video but couldn't be opened\n", filename);
00643    }
00644    return NULL;
00645 }
00646 
00647 struct ast_frame *ast_readframe(struct ast_filestream *s)
00648 {
00649    struct ast_frame *f = NULL;
00650    int whennext = 0; 
00651    if (s && s->fmt)
00652       f = s->fmt->read(s, &whennext);
00653    return f;
00654 }
00655 
00656 enum fsread_res {
00657    FSREAD_FAILURE,
00658    FSREAD_SUCCESS_SCHED,
00659    FSREAD_SUCCESS_NOSCHED,
00660 };
00661 
00662 static int ast_fsread_audio(const void *data);
00663 
00664 static enum fsread_res ast_readaudio_callback(struct ast_filestream *s)
00665 {
00666    int whennext = 0;
00667 
00668    while (!whennext) {
00669       struct ast_frame *fr;
00670       
00671       if (s->orig_chan_name && strcasecmp(s->owner->name, s->orig_chan_name))
00672          goto return_failure;
00673       
00674       fr = s->fmt->read(s, &whennext);
00675       if (!fr /* stream complete */ || ast_write(s->owner, fr) /* error writing */) {
00676          if (fr)
00677             ast_log(LOG_WARNING, "Failed to write frame\n");
00678          goto return_failure;
00679       }
00680    }
00681    if (whennext != s->lasttimeout) {
00682 #ifdef HAVE_ZAPTEL
00683       if (s->owner->timingfd > -1) {
00684          int zap_timer_samples = whennext;
00685          int rate;
00686          /* whennext is in samples, but zaptel timers operate in 8 kHz samples. */
00687          if ((rate = ast_format_rate(s->fmt->format)) != 8000) {
00688             float factor;
00689             factor = ((float) rate) / ((float) 8000.0); 
00690             zap_timer_samples = (int) ( ((float) zap_timer_samples) / factor );
00691          }
00692          ast_settimeout(s->owner, zap_timer_samples, ast_fsread_audio, s);
00693       } else
00694 #endif      
00695          s->owner->streamid = ast_sched_add(s->owner->sched, 
00696             whennext / (ast_format_rate(s->fmt->format) / 1000), 
00697             ast_fsread_audio, s);
00698       s->lasttimeout = whennext;
00699       return FSREAD_SUCCESS_NOSCHED;
00700    }
00701    return FSREAD_SUCCESS_SCHED;
00702 
00703 return_failure:
00704    s->owner->streamid = -1;
00705 #ifdef HAVE_ZAPTEL
00706    ast_settimeout(s->owner, 0, NULL, NULL);
00707 #endif         
00708    return FSREAD_FAILURE;
00709 }
00710 
00711 static int ast_fsread_audio(const void *data)
00712 {
00713    struct ast_filestream *fs = (struct ast_filestream *)data;
00714    enum fsread_res res;
00715 
00716    res = ast_readaudio_callback(fs);
00717 
00718    if (res == FSREAD_SUCCESS_SCHED)
00719       return 1;
00720    
00721    return 0;
00722 }
00723 
00724 static int ast_fsread_video(const void *data);
00725 
00726 static enum fsread_res ast_readvideo_callback(struct ast_filestream *s)
00727 {
00728    int whennext = 0;
00729 
00730    while (!whennext) {
00731       struct ast_frame *fr = s->fmt->read(s, &whennext);
00732       if (!fr || ast_write(s->owner, fr)) { /* no stream or error, as above */
00733          if (fr)
00734             ast_log(LOG_WARNING, "Failed to write frame\n");
00735          s->owner->vstreamid = -1;
00736          return FSREAD_FAILURE;
00737       }
00738    }
00739 
00740    if (whennext != s->lasttimeout) {
00741       s->owner->vstreamid = ast_sched_add(s->owner->sched, 
00742          whennext / (ast_format_rate(s->fmt->format) / 1000), 
00743          ast_fsread_video, s);
00744       s->lasttimeout = whennext;
00745       return FSREAD_SUCCESS_NOSCHED;
00746    }
00747 
00748    return FSREAD_SUCCESS_SCHED;
00749 }
00750 
00751 static int ast_fsread_video(const void *data)
00752 {
00753    struct ast_filestream *fs = (struct ast_filestream *)data;
00754    enum fsread_res res;
00755 
00756    res = ast_readvideo_callback(fs);
00757 
00758    if (res == FSREAD_SUCCESS_SCHED)
00759       return 1;
00760    
00761    return 0;
00762 }
00763 
00764 int ast_applystream(struct ast_channel *chan, struct ast_filestream *s)
00765 {
00766    s->owner = chan;
00767    return 0;
00768 }
00769 
00770 int ast_playstream(struct ast_filestream *s)
00771 {
00772    enum fsread_res res;
00773 
00774    if (s->fmt->format < AST_FORMAT_MAX_AUDIO)
00775       res = ast_readaudio_callback(s);
00776    else
00777       res = ast_readvideo_callback(s);
00778 
00779    return (res == FSREAD_FAILURE) ? -1 : 0;
00780 }
00781 
00782 int ast_seekstream(struct ast_filestream *fs, off_t sample_offset, int whence)
00783 {
00784    return fs->fmt->seek(fs, sample_offset, whence);
00785 }
00786 
00787 int ast_truncstream(struct ast_filestream *fs)
00788 {
00789    return fs->fmt->trunc(fs);
00790 }
00791 
00792 off_t ast_tellstream(struct ast_filestream *fs)
00793 {
00794    return fs->fmt->tell(fs);
00795 }
00796 
00797 int ast_stream_fastforward(struct ast_filestream *fs, off_t ms)
00798 {
00799    return ast_seekstream(fs, ms * DEFAULT_SAMPLES_PER_MS, SEEK_CUR);
00800 }
00801 
00802 int ast_stream_rewind(struct ast_filestream *fs, off_t ms)
00803 {
00804    return ast_seekstream(fs, -ms * DEFAULT_SAMPLES_PER_MS, SEEK_CUR);
00805 }
00806 
00807 int ast_closestream(struct ast_filestream *f)
00808 {
00809    char *cmd = NULL;
00810    size_t size = 0;
00811    /* Stop a running stream if there is one */
00812    if (f->owner) {
00813       if (f->fmt->format < AST_FORMAT_MAX_AUDIO) {
00814          f->owner->stream = NULL;
00815          AST_SCHED_DEL(f->owner->sched, f->owner->streamid);
00816 #ifdef HAVE_ZAPTEL
00817          ast_settimeout(f->owner, 0, NULL, NULL);
00818 #endif         
00819       } else {
00820          f->owner->vstream = NULL;
00821          AST_SCHED_DEL(f->owner->sched, f->owner->vstreamid);
00822       }
00823    }
00824    /* destroy the translator on exit */
00825    if (f->trans)
00826       ast_translator_free_path(f->trans);
00827 
00828    if (f->realfilename && f->filename) {
00829          size = strlen(f->filename) + strlen(f->realfilename) + 15;
00830          cmd = alloca(size);
00831          memset(cmd,0,size);
00832          snprintf(cmd,size,"/bin/mv -f %s %s",f->filename,f->realfilename);
00833          ast_safe_system(cmd);
00834    }
00835 
00836    if (f->filename)
00837       free(f->filename);
00838    if (f->realfilename)
00839       free(f->realfilename);
00840    if (f->fmt->close)
00841       f->fmt->close(f);
00842    fclose(f->f);
00843    if (f->vfs)
00844       ast_closestream(f->vfs);
00845    if (f->orig_chan_name)
00846       free((void *) f->orig_chan_name);
00847    ast_module_unref(f->fmt->module);
00848    free(f);
00849    return 0;
00850 }
00851 
00852 
00853 /*
00854  * Look the various language-specific places where a file could exist.
00855  */
00856 int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
00857 {
00858    char *buf;
00859    int buflen;
00860 
00861    if (preflang == NULL)
00862       preflang = "";
00863    buflen = strlen(preflang) + strlen(filename) + 4;  /* room for everything */
00864    buf = alloca(buflen);
00865    if (buf == NULL)
00866       return 0;
00867    return fileexists_core(filename, fmt, preflang, buf, buflen);
00868 }
00869 
00870 int ast_filedelete(const char *filename, const char *fmt)
00871 {
00872    return ast_filehelper(filename, NULL, fmt, ACTION_DELETE);
00873 }
00874 
00875 int ast_filerename(const char *filename, const char *filename2, const char *fmt)
00876 {
00877    return ast_filehelper(filename, filename2, fmt, ACTION_RENAME);
00878 }
00879 
00880 int ast_filecopy(const char *filename, const char *filename2, const char *fmt)
00881 {
00882    return ast_filehelper(filename, filename2, fmt, ACTION_COPY);
00883 }
00884 
00885 int ast_streamfile(struct ast_channel *chan, const char *filename, const char *preflang)
00886 {
00887    struct ast_filestream *fs;
00888    struct ast_filestream *vfs=NULL;
00889    char fmt[256];
00890 
00891    fs = ast_openstream(chan, filename, preflang);
00892    if (fs)
00893       vfs = ast_openvstream(chan, filename, preflang);
00894    if (vfs)
00895       ast_log(LOG_DEBUG, "Ooh, found a video stream, too, format %s\n", ast_getformatname(vfs->fmt->format));
00896    if (fs){
00897       int res;
00898       if (ast_test_flag(chan, AST_FLAG_MASQ_NOSTREAM))
00899          fs->orig_chan_name = ast_strdup(chan->name);
00900       if (ast_applystream(chan, fs))
00901          return -1;
00902       if (vfs && ast_applystream(chan, vfs))
00903          return -1;
00904       res = ast_playstream(fs);
00905       if (!res && vfs)
00906          res = ast_playstream(vfs);
00907       if (option_verbose > 2)
00908          ast_verbose(VERBOSE_PREFIX_3 "<%s> Playing '%s' (language '%s')\n", chan->name, filename, preflang ? preflang : "default");
00909 
00910       return res;
00911    }
00912    ast_log(LOG_WARNING, "Unable to open %s (format %s): %s\n", filename, ast_getformatname_multiple(fmt, sizeof(fmt), chan->nativeformats), strerror(errno));
00913    return -1;
00914 }
00915 
00916 struct ast_filestream *ast_readfile(const char *filename, const char *type, const char *comment, int flags, int check, mode_t mode)
00917 {
00918    FILE *bfile;
00919    struct ast_format *f;
00920    struct ast_filestream *fs = NULL;
00921    char *fn;
00922 
00923    if (AST_LIST_LOCK(&formats)) {
00924       ast_log(LOG_WARNING, "Unable to lock format list\n");
00925       return NULL;
00926    }
00927 
00928    AST_LIST_TRAVERSE(&formats, f, list) {
00929       fs = NULL;
00930       if (!exts_compare(f->exts, type))
00931          continue;
00932 
00933       fn = build_filename(filename, type);
00934       errno = 0;
00935       bfile = fopen(fn, "r");
00936       if (!bfile || (fs = get_filestream(f, bfile)) == NULL ||
00937           open_wrapper(fs) ) {
00938          ast_log(LOG_WARNING, "Unable to open %s\n", fn);
00939          if (fs)
00940             ast_free(fs);
00941          if (bfile)
00942             fclose(bfile);
00943          free(fn);
00944          continue;
00945       }
00946       /* found it */
00947       fs->trans = NULL;
00948       fs->fmt = f;
00949       fs->flags = flags;
00950       fs->mode = mode;
00951       fs->filename = strdup(filename);
00952       fs->vfs = NULL;
00953       break;
00954    }
00955 
00956    AST_LIST_UNLOCK(&formats);
00957    if (!fs) 
00958       ast_log(LOG_WARNING, "No such format '%s'\n", type);
00959 
00960    return fs;
00961 }
00962 
00963 struct ast_filestream *ast_writefile(const char *filename, const char *type, const char *comment, int flags, int check, mode_t mode)
00964 {
00965    int fd, myflags = 0;
00966    /* compiler claims this variable can be used before initialization... */
00967    FILE *bfile = NULL;
00968    struct ast_format *f;
00969    struct ast_filestream *fs = NULL;
00970    char *buf = NULL;
00971    size_t size = 0;
00972    int format_found = 0;
00973 
00974    if (AST_LIST_LOCK(&formats)) {
00975       ast_log(LOG_WARNING, "Unable to lock format list\n");
00976       return NULL;
00977    }
00978 
00979    /* set the O_TRUNC flag if and only if there is no O_APPEND specified */
00980    /* We really can't use O_APPEND as it will break WAV header updates */
00981    if (flags & O_APPEND) { 
00982       flags &= ~O_APPEND;
00983    } else {
00984       myflags = O_TRUNC;
00985    }
00986    
00987    myflags |= O_WRONLY | O_CREAT;
00988 
00989    /* XXX need to fix this - we should just do the fopen,
00990     * not open followed by fdopen()
00991     */
00992    AST_LIST_TRAVERSE(&formats, f, list) {
00993       char *fn, *orig_fn = NULL;
00994       if (fs)
00995          break;
00996 
00997       if (!exts_compare(f->exts, type))
00998          continue;
00999       else
01000          format_found = 1;
01001 
01002       fn = build_filename(filename, type);
01003       fd = open(fn, flags | myflags, mode);
01004       if (fd > -1) {
01005          /* fdopen() the resulting file stream */
01006          bfile = fdopen(fd, ((flags | myflags) & O_RDWR) ? "w+" : "w");
01007          if (!bfile) {
01008             ast_log(LOG_WARNING, "Whoa, fdopen failed: %s!\n", strerror(errno));
01009             close(fd);
01010             fd = -1;
01011          }
01012       }
01013       
01014       if (ast_opt_cache_record_files && (fd > -1)) {
01015          char *c;
01016 
01017          fclose(bfile); /* this also closes fd */
01018          /*
01019            We touch orig_fn just as a place-holder so other things (like vmail) see the file is there.
01020            What we are really doing is writing to record_cache_dir until we are done then we will mv the file into place.
01021          */
01022          orig_fn = ast_strdupa(fn);
01023          for (c = fn; *c; c++)
01024             if (*c == '/')
01025                *c = '_';
01026 
01027          size = strlen(fn) + strlen(record_cache_dir) + 2;
01028          buf = alloca(size);
01029          strcpy(buf, record_cache_dir);
01030          strcat(buf, "/");
01031          strcat(buf, fn);
01032          free(fn);
01033          fn = buf;
01034          fd = open(fn, flags | myflags, mode);
01035          if (fd > -1) {
01036             /* fdopen() the resulting file stream */
01037             bfile = fdopen(fd, ((flags | myflags) & O_RDWR) ? "w+" : "w");
01038             if (!bfile) {
01039                ast_log(LOG_WARNING, "Whoa, fdopen failed: %s!\n", strerror(errno));
01040                close(fd);
01041                fd = -1;
01042             }
01043          }
01044       }
01045       if (fd > -1) {
01046          errno = 0;
01047          fs = get_filestream(f, bfile);
01048          if (!fs || rewrite_wrapper(fs, comment)) {
01049             ast_log(LOG_WARNING, "Unable to rewrite %s\n", fn);
01050             close(fd);
01051             if (orig_fn) {
01052                unlink(fn);
01053                unlink(orig_fn);
01054             }
01055             if (fs)
01056                ast_free(fs);
01057             fs = NULL;
01058             continue;
01059          }
01060          fs->trans = NULL;
01061          fs->fmt = f;
01062          fs->flags = flags;
01063          fs->mode = mode;
01064          if (orig_fn) {
01065             fs->realfilename = strdup(orig_fn);
01066             fs->filename = strdup(fn);
01067          } else {
01068             fs->realfilename = NULL;
01069             fs->filename = strdup(filename);
01070          }
01071          fs->vfs = NULL;
01072          /* If truncated, we'll be at the beginning; if not truncated, then append */
01073          f->seek(fs, 0, SEEK_END);
01074       } else if (errno != EEXIST) {
01075          ast_log(LOG_WARNING, "Unable to open file %s: %s\n", fn, strerror(errno));
01076          if (orig_fn)
01077             unlink(orig_fn);
01078       }
01079       /* if buf != NULL then fn is already free and pointing to it */
01080       if (!buf)
01081          free(fn);
01082    }
01083 
01084    AST_LIST_UNLOCK(&formats);
01085 
01086    if (!format_found)
01087       ast_log(LOG_WARNING, "No such format '%s'\n", type);
01088 
01089    return fs;
01090 }
01091 
01092 /*!
01093  * \brief the core of all waitstream() functions
01094  */
01095 static int waitstream_core(struct ast_channel *c, const char *breakon,
01096    const char *forward, const char *rewind, int skip_ms,
01097    int audiofd, int cmdfd,  const char *context)
01098 {
01099    const char *orig_chan_name = NULL;
01100    int err = 0;
01101 
01102    if (!breakon)
01103       breakon = "";
01104    if (!forward)
01105       forward = "";
01106    if (!rewind)
01107       rewind = "";
01108 
01109    /* Switch the channel to end DTMF frame only. waitstream_core doesn't care about the start of DTMF. */
01110    ast_set_flag(c, AST_FLAG_END_DTMF_ONLY);
01111 
01112    if (ast_test_flag(c, AST_FLAG_MASQ_NOSTREAM))
01113       orig_chan_name = ast_strdupa(c->name);
01114 
01115    while (c->stream) {
01116       int res;
01117       int ms;
01118 
01119       if (orig_chan_name && strcasecmp(orig_chan_name, c->name)) {
01120          ast_stopstream(c);
01121          err = 1;
01122          break;
01123       }
01124 
01125       ms = ast_sched_wait(c->sched);
01126 
01127       if (ms < 0 && !c->timingfunc) {
01128          ast_stopstream(c);
01129          break;
01130       }
01131       if (ms < 0)
01132          ms = 1000;
01133       if (cmdfd < 0) {
01134          res = ast_waitfor(c, ms);
01135          if (res < 0) {
01136             ast_log(LOG_WARNING, "Select failed (%s)\n", strerror(errno));
01137             ast_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
01138             return res;
01139          }
01140       } else {
01141          int outfd;
01142          struct ast_channel *rchan = ast_waitfor_nandfds(&c, 1, &cmdfd, (cmdfd > -1) ? 1 : 0, NULL, &outfd, &ms);
01143          if (!rchan && (outfd < 0) && (ms)) {
01144             /* Continue */
01145             if (errno == EINTR)
01146                continue;
01147             ast_log(LOG_WARNING, "Wait failed (%s)\n", strerror(errno));
01148             ast_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
01149             return -1;
01150          } else if (outfd > -1) { /* this requires cmdfd set */
01151             /* The FD we were watching has something waiting */
01152             ast_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
01153             return 1;
01154          }
01155          /* if rchan is set, it is 'c' */
01156          res = rchan ? 1 : 0; /* map into 'res' values */
01157       }
01158       if (res > 0) {
01159          struct ast_frame *fr = ast_read(c);
01160          if (!fr) {
01161             ast_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
01162             return -1;
01163          }
01164          switch(fr->frametype) {
01165          case AST_FRAME_DTMF_END:
01166             if (context) {
01167                const char exten[2] = { fr->subclass, '\0' };
01168                if (ast_exists_extension(c, context, exten, 1, c->cid.cid_num)) {
01169                   res = fr->subclass;
01170                   ast_frfree(fr);
01171                   ast_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
01172                   return res;
01173                }
01174             } else {
01175                res = fr->subclass;
01176                if (strchr(forward,res)) {
01177                   ast_stream_fastforward(c->stream, skip_ms);
01178                } else if (strchr(rewind,res)) {
01179                   ast_stream_rewind(c->stream, skip_ms);
01180                } else if (strchr(breakon, res)) {
01181                   ast_frfree(fr);
01182                   ast_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
01183                   return res;
01184                }              
01185             }
01186             break;
01187          case AST_FRAME_CONTROL:
01188             switch(fr->subclass) {
01189             case AST_CONTROL_HANGUP:
01190             case AST_CONTROL_BUSY:
01191             case AST_CONTROL_CONGESTION:
01192                ast_frfree(fr);
01193                ast_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
01194                return -1;
01195             case AST_CONTROL_RINGING:
01196             case AST_CONTROL_ANSWER:
01197             case AST_CONTROL_VIDUPDATE:
01198             case AST_CONTROL_SRCUPDATE:
01199             case AST_CONTROL_HOLD:
01200             case AST_CONTROL_UNHOLD:
01201                /* Unimportant */
01202                break;
01203             default:
01204                ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", fr->subclass);
01205             }
01206             break;
01207          case AST_FRAME_VOICE:
01208             /* Write audio if appropriate */
01209             if (audiofd > -1)
01210                write(audiofd, fr->data, fr->datalen);
01211          default:
01212             /* Ignore all others */
01213             break;
01214          }
01215          ast_frfree(fr);
01216       }
01217       ast_sched_runq(c->sched);
01218    }
01219 
01220    ast_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
01221 
01222    return (err || c->_softhangup) ? -1 : 0;
01223 }
01224 
01225 int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *rewind, int ms)
01226 {
01227    return waitstream_core(c, breakon, forward, rewind, ms,
01228       -1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */);
01229 }
01230 
01231 int ast_waitstream(struct ast_channel *c, const char *breakon)
01232 {
01233    return waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL);
01234 }
01235 
01236 int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, int cmdfd)
01237 {
01238    return waitstream_core(c, breakon, NULL, NULL, 0,
01239       audiofd, cmdfd, NULL /* no context */);
01240 }
01241 
01242 int ast_waitstream_exten(struct ast_channel *c, const char *context)
01243 {
01244    /* Waitstream, with return in the case of a valid 1 digit extension */
01245    /* in the current or specified context being pressed */
01246 
01247    if (!context)
01248       context = c->context;
01249    return waitstream_core(c, NULL, NULL, NULL, 0,
01250       -1, -1, context);
01251 }
01252 
01253 /*
01254  * if the file name is non-empty, try to play it.
01255  * Return 0 if success, -1 if error, digit if interrupted by a digit.
01256  * If digits == "" then we can simply check for non-zero.
01257  */
01258 int ast_stream_and_wait(struct ast_channel *chan, const char *file,
01259    const char *language, const char *digits)
01260 {
01261         int res = 0;
01262         if (!ast_strlen_zero(file)) {
01263                 res =  ast_streamfile(chan, file, language);
01264                 if (!res)
01265                         res = ast_waitstream(chan, digits);
01266         }
01267         return res;
01268 } 
01269 
01270 static int show_file_formats(int fd, int argc, char *argv[])
01271 {
01272 #define FORMAT "%-10s %-10s %-20s\n"
01273 #define FORMAT2 "%-10s %-10s %-20s\n"
01274    struct ast_format *f;
01275    int count_fmt = 0;
01276 
01277    if (argc != 4)
01278       return RESULT_SHOWUSAGE;
01279    ast_cli(fd, FORMAT, "Format", "Name", "Extensions");
01280            
01281    if (AST_LIST_LOCK(&formats)) {
01282       ast_log(LOG_WARNING, "Unable to lock format list\n");
01283       return -1;
01284    }
01285 
01286    AST_LIST_TRAVERSE(&formats, f, list) {
01287       ast_cli(fd, FORMAT2, ast_getformatname(f->format), f->name, f->exts);
01288       count_fmt++;
01289    }
01290    AST_LIST_UNLOCK(&formats);
01291    ast_cli(fd, "%d file formats registered.\n", count_fmt);
01292    return RESULT_SUCCESS;
01293 #undef FORMAT
01294 #undef FORMAT2
01295 }
01296 
01297 static int show_file_formats_deprecated(int fd, int argc, char *argv[])
01298 {
01299 #define FORMAT "%-10s %-10s %-20s\n"
01300 #define FORMAT2 "%-10s %-10s %-20s\n"
01301    struct ast_format *f;
01302    int count_fmt = 0;
01303    
01304    if (argc != 3)
01305       return RESULT_SHOWUSAGE;
01306    ast_cli(fd, FORMAT, "Format", "Name", "Extensions");
01307    
01308    if (AST_LIST_LOCK(&formats)) {
01309       ast_log(LOG_WARNING, "Unable to lock format list\n");
01310       return -1;
01311    }
01312    
01313    AST_LIST_TRAVERSE(&formats, f, list) {
01314       ast_cli(fd, FORMAT2, ast_getformatname(f->format), f->name, f->exts);
01315       count_fmt++;
01316    }
01317    AST_LIST_UNLOCK(&formats);
01318    ast_cli(fd, "%d file formats registered.\n", count_fmt);
01319    return RESULT_SUCCESS;
01320 #undef FORMAT
01321 #undef FORMAT2
01322 }
01323 
01324 char show_file_formats_usage[] = 
01325 "Usage: core show file formats\n"
01326 "       Displays currently registered file formats (if any)\n";
01327 
01328 struct ast_cli_entry cli_show_file_formats_deprecated = {
01329    { "show", "file", "formats" },
01330    show_file_formats_deprecated, NULL,
01331    NULL };
01332 
01333 struct ast_cli_entry cli_file[] = {
01334    { { "core", "show", "file", "formats" },
01335    show_file_formats, "Displays file formats",
01336    show_file_formats_usage, NULL, &cli_show_file_formats_deprecated },
01337 };
01338 
01339 int ast_file_init(void)
01340 {
01341    ast_cli_register_multiple(cli_file, sizeof(cli_file) / sizeof(struct ast_cli_entry));
01342    return 0;
01343 }

Generated on Sun Jun 12 16:37:46 2011 for Asterisk - the Open Source PBX by  doxygen 1.5.6