Index: file.c
===================================================================
--- file.c	(revision 8860)
+++ file.c	(working copy)
@@ -100,6 +100,16 @@
 	struct ast_channel *owner;
 };
 
+/*
+ * The following variable controls the layout of localized sound files.
+ * If 0, use the historical layout with prefix just before the filename
+ * (i.e. digits/en/1.gsm , digits/it/1.gsm or default to digits/1.gsm),
+ * if 1 put the prefix at the beginning of the filename
+ * (i.e. en/digits/1.gsm, it/digits/1.gsm or default to digits/1.gsm).
+ * The latter permits a language to be entirely in one directory.
+ */
+int ast_language_is_prefix;
+
 static AST_LIST_HEAD_STATIC(formats, ast_format);
 
 int ast_format_register(const char *name, const char *exts, int format,
@@ -250,10 +260,7 @@
 
 static int copy(const char *infile, const char *outfile)
 {
-	int ifd;
-	int ofd;
-	int res;
-	int len;
+	int ifd, ofd, len;
 	char buf[4096];
 
 	if ((ifd = open(infile, O_RDONLY)) < 0) {
@@ -265,189 +272,244 @@
 		close(ifd);
 		return -1;
 	}
-	do {
-		len = read(ifd, buf, sizeof(buf));
+	while ( (len = read(ifd, buf, sizeof(buf)) ) ) {
+		int res;
 		if (len < 0) {
 			ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
-			close(ifd);
-			close(ofd);
-			unlink(outfile);
+			break;
 		}
-		if (len) {
-			res = write(ofd, buf, len);
-			if (res != len) {
-				ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
-				close(ifd);
-				close(ofd);
-				unlink(outfile);
-			}
+		res = write(ofd, buf, len);
+		if (res != len) {
+			ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
+			len = -1; /* error marker */
+			break;
 		}
-	} while(len);
+	}
 	close(ifd);
 	close(ofd);
-	return 0;
+	if (len < 0) {
+		unlink(outfile);
+		return -1; /* error */
+	}
+	return 0;	/* success */
 }
 
+/*!
+ * \brief construct a filename. Absolute pathnames are preserved,
+ * relative names are prefixed by the sounds/ directory.
+ * The wav49 suffix is replaced by 'WAV'.
+ */
 static char *build_filename(const char *filename, const char *ext)
 {
-	char *fn, type[16];
-	int fnsize = 0;
+	char *fn = NULL;
 
-	if (!strcmp(ext, "wav49")) {
-		ast_copy_string(type, "WAV", sizeof(type));
-	} else {
-		ast_copy_string(type, ext, sizeof(type));
-	}
+	if (!strcmp(ext, "wav49"))
+		ext = "WAV";
 
-	if (filename[0] == '/') {
-		fnsize = strlen(filename) + strlen(type) + 2;
-		fn = malloc(fnsize);
-		if (fn)
-			snprintf(fn, fnsize, "%s.%s", filename, type);
-	} else {
-		char tmp[AST_CONFIG_MAX_PATH] = "";
-
-		snprintf(tmp, sizeof(tmp), "%s/%s", ast_config_AST_VAR_DIR, "sounds");
-		fnsize = strlen(tmp) + strlen(filename) + strlen(type) + 3;
-		fn = malloc(fnsize);
-		if (fn)
-			snprintf(fn, fnsize, "%s/%s.%s", tmp, filename, type);
-	}
-
+	if (filename[0] == '/')
+		asprintf(&fn, "%s.%s", filename, ext);
+	else
+		asprintf(&fn, "%s/sounds/%s.%s",
+			ast_config_AST_VAR_DIR, filename, ext);
 	return fn;
 }
 
+/* compare type against the list 'exts' */
+/* XXX need a better algorithm */
 static int exts_compare(const char *exts, const char *type)
 {
-	char *stringp = NULL, *ext;
 	char tmp[256];
+	char *stringp = tmp, *ext;
 
 	ast_copy_string(tmp, exts, sizeof(tmp));
-	stringp = tmp;
 	while ((ext = strsep(&stringp, "|"))) {
-		if (!strcmp(ext, type)) {
+		if (!strcmp(ext, type))
 			return 1;
-		}
 	}
 
 	return 0;
 }
 
-#define ACTION_EXISTS 1
-#define ACTION_DELETE 2
-#define ACTION_RENAME 3
+#define ACTION_EXISTS 1	/* return matching format if file exists, 0 otherwise */
+#define ACTION_DELETE 2	/* delete file, return 0 on success, -1 on error */
+#define ACTION_RENAME 3	/* rename file. return 0 on success, -1 on error */
 #define ACTION_OPEN   4
-#define ACTION_COPY   5
+#define ACTION_COPY   5	/* copy file. return 0 on success, -1 on error */
 
-static int ast_filehelper(const char *filename, const char *filename2, const char *fmt, int action)
+/*!
+ * \brief perform various actions on a file. Second argument
+ * arg2 depends on the command:
+ *	unused for EXISTS and DELETE
+ *	destination file name (const char *) for COPY and RENAME
+ * 	struct ast_channel * for OPEN
+ */
+static int ast_filehelper(const char *filename, const void *arg2, const char *fmt, int action)
 {
-	struct stat st;
 	struct ast_format *f;
-	struct ast_filestream *s;
-	int res=0, ret = 0;
-	char *ext=NULL, *exts, *fn, *nfn;
-	FILE *bfile;
-	struct ast_channel *chan = (struct ast_channel *)filename2;
-	
-	/* Start with negative response */
-	if (action == ACTION_EXISTS)
-		res = 0;
-	else
-		res = -1;
-	if (action == ACTION_OPEN)
-		ret = -1;
-	/* Check for a specific format */
+	char *ext = NULL, *fn = NULL;
+	int res = (action == ACTION_EXISTS) ? 0 : -1;
+
 	if (AST_LIST_LOCK(&formats)) {
 		ast_log(LOG_WARNING, "Unable to lock format list\n");
 		return res;
 	}
+	/* Check for a specific format */
 	AST_LIST_TRAVERSE(&formats, f, list) {
-		if (!fmt || exts_compare(f->exts, fmt)) {
-			char *stringp=NULL;
-			exts = ast_strdupa(f->exts);
-			/* Try each kind of extension */
-			stringp=exts;
-			ext = strsep(&stringp, "|");
-			do {
-				fn = build_filename(filename, ext);
-				if (fn) {
-					res = stat(fn, &st);
-					if (!res) {
-						switch(action) {
-						case ACTION_EXISTS:
-							ret |= f->format;
-							break;
-						case ACTION_DELETE:
-							res = unlink(fn);
-							if (res)
-								ast_log(LOG_WARNING, "unlink(%s) failed: %s\n", fn, strerror(errno));
-							break;
-						case ACTION_RENAME:
-							nfn = build_filename(filename2, ext);
-							if (nfn) {
-								res = rename(fn, nfn);
-								if (res)
-									ast_log(LOG_WARNING, "rename(%s,%s) failed: %s\n", fn, nfn, strerror(errno));
-								free(nfn);
-							} else
-								ast_log(LOG_WARNING, "Out of memory\n");
-							break;
-						case ACTION_COPY:
-							nfn = build_filename(filename2, ext);
-							if (nfn) {
-								res = copy(fn, nfn);
-								if (res)
-									ast_log(LOG_WARNING, "copy(%s,%s) failed: %s\n", fn, nfn, strerror(errno));
-								free(nfn);
-							} else
-								ast_log(LOG_WARNING, "Out of memory\n");
-							break;
-						case ACTION_OPEN:
-							if ((ret < 0) && ((chan->writeformat & f->format) ||
-										((f->format >= AST_FORMAT_MAX_AUDIO) && fmt))) {
-								bfile = fopen(fn, "r");
-								if (bfile) {
-									ret = 1;
-									s = f->open(bfile);
-									if (s) {
-										s->lasttimeout = -1;
-										s->fmt = f;
-										s->trans = NULL;
-										s->filename = NULL;
-										if (s->fmt->format < AST_FORMAT_MAX_AUDIO)
-											chan->stream = s;
-										else
-											chan->vstream = s;
-									} else {
-										fclose(bfile);
-										ast_log(LOG_WARNING, "Unable to open file on %s\n", fn);
-										ret = -1;
-									}
-								} else{
-									ast_log(LOG_WARNING, "Couldn't open file %s\n", fn);
-									ret = -1;
-								}
-							}
-							break;
-						default:
-							ast_log(LOG_WARNING, "Unknown helper %d\n", action);
-						}
-						/* Conveniently this logic is the same for all */
-						if (res)
-							break;
-					}
+		char *stringp;
+
+		if (fmt && !exts_compare(f->exts, fmt))
+			continue;
+
+		/* Look for a file matching the supported extensions.
+		 * The file must exist, and for OPEN, must match
+		 * one of the formats supported by the channel.
+		 */
+		stringp = ast_strdupa(f->exts);
+		while ( (ext = strsep(&stringp, "|")) ) {
+			struct stat st;
+			fn = build_filename(filename, ext);
+			if (fn == NULL)
+				continue;
+
+			if ( stat(fn, &st) ) { /* file not existent */
+				free(fn);
+				continue;
+			}
+			/* for 'OPEN' we need to be sure that the format matches
+			 * what the channel can process
+			 */
+			if (action == ACTION_OPEN) {
+				struct ast_channel *chan = (struct ast_channel *)arg2;
+				FILE *bfile;
+				struct ast_filestream *s;
+
+				if ( !(chan->writeformat & f->format) &&
+				     !(f->format >= AST_FORMAT_MAX_AUDIO && fmt)) {
 					free(fn);
+					continue;	/* not a supported format */
 				}
-				ext = strsep(&stringp, "|");
-			} while(ext);
-			
+				if ( (bfile = fopen(fn, "r")) == NULL) {
+					free(fn);
+					continue;	/* cannot open file */
+				}
+				s = f->open(bfile);
+				if (!s) {
+					fclose(bfile);
+					free(fn);
+					continue;	/* cannot run open on file */
+				}
+				/* ok this is good for OPEN */
+				res = 1;	/* found */
+				s->lasttimeout = -1;
+				s->fmt = f;
+				s->trans = NULL;
+				s->filename = NULL;
+				if (s->fmt->format < AST_FORMAT_MAX_AUDIO)
+					chan->stream = s;
+				else
+					chan->vstream = s;
+			}
+			break;	/* found the file */
 		}
+		if (ext)
+			break;
 	}
+	if (ext) {	/* break out on a valid 'ext', so fn is also valid */
+		char *nfn;
+
+		switch (action) {
+		case ACTION_EXISTS:	/* return the matching format */
+			res |= f->format;
+			break;
+
+		case ACTION_DELETE:
+			if ( (res = unlink(fn)) )
+				ast_log(LOG_WARNING, "unlink(%s) failed: %s\n", fn, strerror(errno));
+			break;
+		case ACTION_RENAME:
+		case ACTION_COPY:
+			nfn = build_filename((const char *)arg2, ext);
+			if (!nfn)
+				ast_log(LOG_WARNING, "Out of memory\n");
+			else {
+				res = action == ACTION_COPY ? copy(fn, nfn) : rename(fn, nfn);
+				if (res)
+					ast_log(LOG_WARNING, "%s(%s,%s) failed: %s\n",
+						action == ACTION_COPY ? "copy" : "rename",
+						 fn, nfn, strerror(errno));
+				free(nfn);
+			}
+			break;
+		case ACTION_OPEN:	/* all done already! */
+			break;
+		default:
+			ast_log(LOG_WARNING, "Unknown helper %d\n", action);
+		}
+		free(fn);
+	}
 	AST_LIST_UNLOCK(&formats);
-	if ((action == ACTION_EXISTS) || (action == ACTION_OPEN))
-		res = ret ? ret : -1;
 	return res;
 }
+
+/*!
+ * \brief helper routine to locate a file with a given format
+ * and language preference.
+ * Try preflang, preflang with stripped '_' suffix, or NULL.
+ * In the standard asterisk, language goes just before the last component.
+ * In an alternative configuration, the language should be a prefix
+ * to the actual filename.
+ *
+ * The last parameter(s) point to a buffer of sufficient size,
+ * which on success is filled with the matching filename.
+ */
+static int fileexists_core(const char *filename, const char *fmt, const char *preflang, char *buf, int buflen)
+{
+	int res = -1;
+	int langlen;	/* length of language string */
+	const char *c = strrchr(filename, '/');
+	int offset = c ? c - filename + 1 : 0;	/* points right after the last '/' */
+
+	if (preflang == NULL)
+		preflang = "";
+	langlen = strlen(preflang);
+	
+	if (buflen < langlen + strlen(filename) + 2) {
+		ast_log(LOG_WARNING, "buffer too small\n");
+		buf[0] = '\0'; /* set to empty */
+		buf = alloca(langlen + strlen(filename) + 2);	/* room for everything */
+	}
+	if (buf == NULL)
+		return 0;
+	buf[0] = '\0';
+	for (;;) {
+		if (ast_language_is_prefix) { /* new layout */
+			if (langlen) {
+				strcpy(buf, preflang);
+				buf[langlen] = '/';
+				strcpy(buf + langlen + 1, filename);
+			} else
+				strcpy(buf, filename);	/* first copy the full string */
+		} else { /* old layout */
+			strcpy(buf, filename);	/* first copy the full string */
+			if (langlen) {
+				/* insert the language and suffix if needed */
+				strcpy(buf + offset, preflang);
+				sprintf(buf + offset + langlen, "/%s", filename + offset);
+			}
+		}
+		res = ast_filehelper(buf, NULL, fmt, ACTION_EXISTS);
+		if (res > 0)		/* found format */
+			break;
+		if (langlen == 0)	/* no more formats */
+			break;
+		if (preflang[langlen] == '_') /* we are on the local suffix */
+			langlen = 0;	/* try again with no language */
+		else
+			langlen = (c = strchr(preflang, '_')) ? c - preflang : 0;
+	}
+	return res;
+}
+
 struct ast_filestream *ast_openstream(struct ast_channel *chan, const char *filename, const char *preflang)
 {
 	return ast_openstream_full(chan, filename, preflang, 0);
@@ -455,23 +517,13 @@
 
 struct ast_filestream *ast_openstream_full(struct ast_channel *chan, const char *filename, const char *preflang, int asis)
 {
-	/* This is a fairly complex routine.  Essentially we should do 
-	   the following:
-	   
-	   1) Find which file handlers produce our type of format.
-	   2) Look for a filename which it can handle.
-	   3) If we find one, then great.  
-	   4) If not, see what files are there
-	   5) See what we can actually support
-	   6) Choose the one with the least costly translator path and
-	       set it up.
-		   
-	*/
-	int fmts = -1;
-	char filename2[256]="";
-	char filename3[256];
-	char *endpart;
-	int res;
+	/* 
+	 * Use fileexists_core() to find a file in a compatible
+	 * language and format, set up a suitable translator,
+	 * and open the stream.
+	 */
+	int fmts, res, buflen;
+	char *buf;
 
 	if (!asis) {
 		/* do this first, otherwise we detect the wrong writeformat */
@@ -479,34 +531,23 @@
 		if (chan->generator)
 			ast_deactivate_generator(chan);
 	}
-	if (!ast_strlen_zero(preflang)) {
-		ast_copy_string(filename3, filename, sizeof(filename3));
-		endpart = strrchr(filename3, '/');
-		if (endpart) {
-			*endpart = '\0';
-			endpart++;
-			snprintf(filename2, sizeof(filename2), "%s/%s/%s", filename3, preflang, endpart);
-		} else
-			snprintf(filename2, sizeof(filename2), "%s/%s", preflang, filename);
-		fmts = ast_fileexists(filename2, NULL, NULL);
-		if (fmts > 0) 
-			fmts &= AST_FORMAT_AUDIO_MASK;
-	}
+	if (preflang == NULL)
+		preflang = "";
+	buflen = strlen(preflang) + strlen(filename) + 2;
+	buf = alloca(buflen);
+	if (buf == NULL)
+		return NULL;
+	fmts = fileexists_core(filename, NULL, preflang, buf, buflen);
+	if (fmts > 0)
+		fmts &= AST_FORMAT_AUDIO_MASK;
 	if (fmts < 1) {
-		ast_copy_string(filename2, filename, sizeof(filename2));
-		fmts = ast_fileexists(filename2, NULL, NULL);
-		if (fmts > 0)
-			fmts &= AST_FORMAT_AUDIO_MASK;
-	}
-	if (fmts < 1) {
 		ast_log(LOG_WARNING, "File %s does not exist in any format\n", filename);
 		return NULL;
 	}
 	chan->oldwriteformat = chan->writeformat;
 	/* Set the channel to a format we can work with */
 	res = ast_set_write_format(chan, fmts);
-	
- 	res = ast_filehelper(filename2, (char *)chan, NULL, ACTION_OPEN);
+ 	res = ast_filehelper(buf, chan, NULL, ACTION_OPEN);
 	if (res >= 0)
 		return chan->stream;
 	return NULL;
@@ -514,45 +555,30 @@
 
 struct ast_filestream *ast_openvstream(struct ast_channel *chan, const char *filename, const char *preflang)
 {
-	/* This is a fairly complex routine.  Essentially we should do 
-	   the following:
-	   
-	   1) Find which file handlers produce our type of format.
-	   2) Look for a filename which it can handle.
-	   3) If we find one, then great.  
-	   4) If not, see what files are there
-	   5) See what we can actually support
-	   6) Choose the one with the least costly translator path and
-	       set it up.
-		   
-	*/
-	int fd = -1;
-	int fmts = -1;
+	/* As above, but for video. But here we don't have translators
+	 * so we must enforce a format.
+	 */
 	unsigned int format;
-	char filename2[256];
-	char lang2[MAX_LANGUAGE];
-	const char *fmt;
+	char *buf;
+	int buflen;
+
+	if (preflang == NULL)
+		preflang = "";
+	buflen = strlen(preflang) + strlen(filename) + 2;
+	buf = alloca(buflen);
+	if (buf == NULL)
+		return NULL;
+
 	for (format = AST_FORMAT_MAX_AUDIO << 1; format <= AST_FORMAT_MAX_VIDEO; format = format << 1) {
+		int fd;
+		const char *fmt;
+
 		if (!(chan->nativeformats & format))
 			continue;
 		fmt = ast_getformatname(format);
-		if (!ast_strlen_zero(preflang)) {
-			snprintf(filename2, sizeof(filename2), "%s/%s", preflang, filename);
-			fmts = ast_fileexists(filename2, fmt, NULL);
-			if (fmts < 1) {
-				ast_copy_string(lang2, preflang, sizeof(lang2));
-				snprintf(filename2, sizeof(filename2), "%s/%s", lang2, filename);
-				fmts = ast_fileexists(filename2, fmt, NULL);
-			}
-		}
-		if (fmts < 1) {
-			ast_copy_string(filename2, filename, sizeof(filename2));
-			fmts = ast_fileexists(filename2, fmt, NULL);
-		}
-		if (fmts < 1) {
+		if ( fileexists_core(filename, fmt, preflang, buf, buflen) < 1)	/* no valid format */
 			continue;
-		}
-	 	fd = ast_filehelper(filename2, (char *)chan, fmt, ACTION_OPEN);
+	 	fd = ast_filehelper(buf, chan, fmt, ACTION_OPEN);
 		if (fd >= 0)
 			return chan->vstream;
 		ast_log(LOG_WARNING, "File %s has video but couldn't be opened\n", filename);
@@ -572,22 +598,13 @@
 static int ast_readaudio_callback(void *data)
 {
 	struct ast_filestream *s = data;
-	struct ast_frame *fr;
 	int whennext = 0;
 
 	while(!whennext) {
-		fr = s->fmt->read(s, &whennext);
-		if (fr) {
-			if (ast_write(s->owner, fr)) {
+		struct ast_frame *fr = s->fmt->read(s, &whennext);
+		if (!fr /* stream complete */ || ast_write(s->owner, fr) /* error writing */) {
+			if (fr)
 				ast_log(LOG_WARNING, "Failed to write frame\n");
-				s->owner->streamid = -1;
-#ifdef ZAPTEL_OPTIMIZATIONS
-				ast_settimeout(s->owner, 0, NULL, NULL);
-#endif			
-				return 0;
-			}
-		} else {
-			/* Stream has finished */
 			s->owner->streamid = -1;
 #ifdef ZAPTEL_OPTIMIZATIONS
 			ast_settimeout(s->owner, 0, NULL, NULL);
@@ -611,19 +628,13 @@
 static int ast_readvideo_callback(void *data)
 {
 	struct ast_filestream *s = data;
-	struct ast_frame *fr;
 	int whennext = 0;
 
-	while(!whennext) {
-		fr = s->fmt->read(s, &whennext);
-		if (fr) {
-			if (ast_write(s->owner, fr)) {
+	while (!whennext) {
+		struct ast_frame *fr = s->fmt->read(s, &whennext);
+		if (!fr || ast_write(s->owner, fr)) { /* no stream or error, as above */
+			if (fr)
 				ast_log(LOG_WARNING, "Failed to write frame\n");
-				s->owner->vstreamid = -1;
-				return 0;
-			}
-		} else {
-			/* Stream has finished */
 			s->owner->vstreamid = -1;
 			return 0;
 		}
@@ -733,52 +744,21 @@
 }
 
 
+/*
+ * Look the various language-specific places where a file could exist.
+ */
 int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
 {
-	char filename2[256];
-	char tmp[256];
-	char *postfix;
-	char *prefix;
-	char *c;
-	char lang2[MAX_LANGUAGE];
-	int res = -1;
-	if (!ast_strlen_zero(preflang)) {
-		/* Insert the language between the last two parts of the path */
-		ast_copy_string(tmp, filename, sizeof(tmp));
-		c = strrchr(tmp, '/');
-		if (c) {
-			*c = '\0';
-			postfix = c+1;
-			prefix = tmp;
-			snprintf(filename2, sizeof(filename2), "%s/%s/%s", prefix, preflang, postfix);
-		} else {
-			postfix = tmp;
-			prefix="";
-			snprintf(filename2, sizeof(filename2), "%s/%s", preflang, postfix);
-		}
-		res = ast_filehelper(filename2, NULL, fmt, ACTION_EXISTS);
-		if (res < 1) {
-			char *stringp=NULL;
-			ast_copy_string(lang2, preflang, sizeof(lang2));
-			stringp=lang2;
-			strsep(&stringp, "_");
-			/* If language is a specific locality of a language (like es_MX), strip the locality and try again */
-			if (strcmp(lang2, preflang)) {
-				if (ast_strlen_zero(prefix)) {
-					snprintf(filename2, sizeof(filename2), "%s/%s", lang2, postfix);
-				} else {
-					snprintf(filename2, sizeof(filename2), "%s/%s/%s", prefix, lang2, postfix);
-				}
-				res = ast_filehelper(filename2, NULL, fmt, ACTION_EXISTS);
-			}
-		}
-	}
+	char *buf;
+	int buflen;
 
-	/* Fallback to no language (usually winds up being American English) */
-	if (res < 1) {
-		res = ast_filehelper(filename, NULL, fmt, ACTION_EXISTS);
-	}
-	return res;
+	if (preflang == NULL)
+		preflang = "";
+	buflen = strlen(preflang) + strlen(filename) + 2;	/* room for everything */
+	buf = alloca(buflen);
+	if (buf == NULL)
+		return 0;
+	return fileexists_core(filename, fmt, preflang, buf, buflen);
 }
 
 int ast_filedelete(const char *filename, const char *fmt)
@@ -993,176 +973,73 @@
 	return fs;
 }
 
-int ast_waitstream(struct ast_channel *c, const char *breakon)
+/*!
+ * \brief the core of all waitstream() functions
+ */
+static int waitstream_core(struct ast_channel *c, const char *breakon,
+	const char *forward, const char *rewind, int skip_ms,
+	int audiofd, int cmdfd,  const char *context)
 {
-	/* XXX Maybe I should just front-end ast_waitstream_full ? XXX */
-	int res;
-	struct ast_frame *fr;
-	if (!breakon) breakon = "";
-	while(c->stream) {
-		res = ast_sched_wait(c->sched);
-		if ((res < 0) && !c->timingfunc) {
-			ast_stopstream(c);
-			break;
-		}
-		if (res < 0)
-			res = 1000;
-		res = ast_waitfor(c, res);
-		if (res < 0) {
-			ast_log(LOG_WARNING, "Select failed (%s)\n", strerror(errno));
-			return res;
-		} else if (res > 0) {
-			fr = ast_read(c);
-			if (!fr) {
-#if 0
-				ast_log(LOG_DEBUG, "Got hung up\n");
-#endif
-				return -1;
-			}
-			
-			switch(fr->frametype) {
-			case AST_FRAME_DTMF:
-				res = fr->subclass;
-				if (strchr(breakon, res)) {
-					ast_frfree(fr);
-					return res;
-				}
-				break;
-			case AST_FRAME_CONTROL:
-				switch(fr->subclass) {
-				case AST_CONTROL_HANGUP:
-					ast_frfree(fr);
-					return -1;
-				case AST_CONTROL_RINGING:
-				case AST_CONTROL_ANSWER:
-				case AST_CONTROL_VIDUPDATE:
-					/* Unimportant */
-					break;
-				default:
-					ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", fr->subclass);
-				}
-			}
-			/* Ignore */
-			ast_frfree(fr);
-		}
-		ast_sched_runq(c->sched);
-	}
-	return (c->_softhangup ? -1 : 0);
-}
-
-int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *rewind, int ms)
-{
-	int res;
-	struct ast_frame *fr;
-
 	if (!breakon)
-			breakon = "";
+		breakon = "";
 	if (!forward)
-			forward = "";
+		forward = "";
 	if (!rewind)
-			rewind = "";
+		rewind = "";
 	
-	while(c->stream) {
-		res = ast_sched_wait(c->sched);
-		if ((res < 0) && !c->timingfunc) {
+	while (c->stream) {
+		int res;
+		int ms = ast_sched_wait(c->sched);
+		if (ms < 0 && !c->timingfunc) {
 			ast_stopstream(c);
 			break;
 		}
-		if (res < 0)
-			res = 1000;
-		res = ast_waitfor(c, res);
-		if (res < 0) {
-			ast_log(LOG_WARNING, "Select failed (%s)\n", strerror(errno));
-			return res;
-		} else
-		if (res > 0) {
-			fr = ast_read(c);
-			if (!fr) {
-#if 0
-				ast_log(LOG_DEBUG, "Got hung up\n");
-#endif
-				return -1;
-			}
-			
-			switch(fr->frametype) {
-			case AST_FRAME_DTMF:
-				res = fr->subclass;
-				if (strchr(forward,res)) {
-					ast_stream_fastforward(c->stream, ms);
-				} else if (strchr(rewind,res)) {
-					ast_stream_rewind(c->stream, ms);
-				} else if (strchr(breakon, res)) {
-					ast_frfree(fr);
-					return res;
-				}					
-				break;
-			case AST_FRAME_CONTROL:
-				switch(fr->subclass) {
-				case AST_CONTROL_HANGUP:
-					ast_frfree(fr);
-					return -1;
-				case AST_CONTROL_RINGING:
-				case AST_CONTROL_ANSWER:
-					/* Unimportant */
-					break;
-				default:
-					ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", fr->subclass);
-				}
-			}
-			/* Ignore */
-			ast_frfree(fr);
-		} else
-			ast_sched_runq(c->sched);
-	
-		
-	}
-	return (c->_softhangup ? -1 : 0);
-}
-
-int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, int cmdfd)
-{
-	int res;
-	int ms;
-	int outfd;
-	struct ast_frame *fr;
-	struct ast_channel *rchan;
-
-	if (!breakon)
-		breakon = "";
-	
-	while(c->stream) {
-		ms = ast_sched_wait(c->sched);
-		if ((ms < 0) && !c->timingfunc) {
-			ast_stopstream(c);
-			break;
-		}
 		if (ms < 0)
 			ms = 1000;
-		rchan = ast_waitfor_nandfds(&c, 1, &cmdfd, (cmdfd > -1) ? 1 : 0, NULL, &outfd, &ms);
-		if (!rchan && (outfd < 0) && (ms)) {
-			/* Continue */
-			if (errno == EINTR)
-				continue;
-			ast_log(LOG_WARNING, "Wait failed (%s)\n", strerror(errno));
-			return -1;
-		} else if (outfd > -1) {
-			/* The FD we were watching has something waiting */
-			return 1;
-		} else if (rchan) {
-			fr = ast_read(c);
-			if (!fr) {
-#if 0
-				ast_log(LOG_DEBUG, "Got hung up\n");
-#endif
+		if (!cmdfd) {
+			res = ast_waitfor(c, ms);
+			if (res < 0) {
+				ast_log(LOG_WARNING, "Select failed (%s)\n", strerror(errno));
+				return res;
+			}
+		} else {
+			int outfd;
+			struct ast_channel *rchan = ast_waitfor_nandfds(&c, 1, &cmdfd, (cmdfd > -1) ? 1 : 0, NULL, &outfd, &ms);
+			if (!rchan && (outfd < 0) && (ms)) {
+				/* Continue */
+				if (errno == EINTR)
+					continue;
+				ast_log(LOG_WARNING, "Wait failed (%s)\n", strerror(errno));
 				return -1;
+			} else if (outfd > -1) { /* this requires cmdfd set */
+				/* The FD we were watching has something waiting */
+				return 1;
 			}
-			
+			/* if rchan is set, it is 'c' */
+			res = rchan ? 1 : 0; /* map into 'res' values */
+		}
+		if (res > 0) {
+			struct ast_frame *fr = ast_read(c);
+			if (!fr)
+				return -1;
 			switch(fr->frametype) {
 			case AST_FRAME_DTMF:
-				res = fr->subclass;
-				if (strchr(breakon, res)) {
-					ast_frfree(fr);
-					return res;
+				if (context) {
+					const char exten[2] = { fr->subclass, '\0' };
+					if (ast_exists_extension(c, context, exten, 1, c->cid.cid_num)) {
+						ast_frfree(fr);
+						return res;
+					}
+				} else {
+					res = fr->subclass;
+					if (strchr(forward,res)) {
+						ast_stream_fastforward(c->stream, skip_ms);
+					} else if (strchr(rewind,res)) {
+						ast_stream_rewind(c->stream, skip_ms);
+					} else if (strchr(breakon, res)) {
+						ast_frfree(fr);
+						return res;
+					}					
 				}
 				break;
 			case AST_FRAME_CONTROL:
@@ -1182,7 +1059,7 @@
 				if (audiofd > -1)
 					write(audiofd, fr->data, fr->datalen);
 			}
-			/* Ignore */
+			/* Ignore all others */
 			ast_frfree(fr);
 		}
 		ast_sched_runq(c->sched);
@@ -1190,65 +1067,32 @@
 	return (c->_softhangup ? -1 : 0);
 }
 
+int ast_waitstream_fr(struct ast_channel *c, const char *breakon, const char *forward, const char *rewind, int ms)
+{
+	return waitstream_core(c, breakon, forward, rewind, ms,
+		-1 /* no audiofd */, -1 /* no cmdfd */, NULL /* no context */);
+}
+
+int ast_waitstream(struct ast_channel *c, const char *breakon)
+{
+	return waitstream_core(c, breakon, NULL, NULL, 0, -1, -1, NULL);
+}
+
+int ast_waitstream_full(struct ast_channel *c, const char *breakon, int audiofd, int cmdfd)
+{
+	return waitstream_core(c, breakon, NULL, NULL, 0,
+		audiofd, cmdfd, NULL /* no context */);
+}
+
 int ast_waitstream_exten(struct ast_channel *c, const char *context)
 {
 	/* Waitstream, with return in the case of a valid 1 digit extension */
 	/* in the current or specified context being pressed */
-	/* XXX Maybe I should just front-end ast_waitstream_full ? XXX */
-	int res;
-	struct ast_frame *fr;
-	char exten[AST_MAX_EXTENSION];
 
-	if (!context) context = c->context;
-	while(c->stream) {
-		res = ast_sched_wait(c->sched);
-		if ((res < 0) && !c->timingfunc) {
-			ast_stopstream(c);
-			break;
-		}
-		if (res < 0)
-			res = 1000;
-		res = ast_waitfor(c, res);
-		if (res < 0) {
-			ast_log(LOG_WARNING, "Select failed (%s)\n", strerror(errno));
-			return res;
-		} else if (res > 0) {
-			fr = ast_read(c);
-			if (!fr) {
-#if 0
-				ast_log(LOG_DEBUG, "Got hung up\n");
-#endif
-				return -1;
-			}
-			
-			switch(fr->frametype) {
-			case AST_FRAME_DTMF:
-				res = fr->subclass;
-				snprintf(exten, sizeof(exten), "%c", res);
-				if (ast_exists_extension(c, context, exten, 1, c->cid.cid_num)) {
-					ast_frfree(fr);
-					return res;
-				}
-				break;
-			case AST_FRAME_CONTROL:
-				switch(fr->subclass) {
-				case AST_CONTROL_HANGUP:
-					ast_frfree(fr);
-					return -1;
-				case AST_CONTROL_RINGING:
-				case AST_CONTROL_ANSWER:
-					/* Unimportant */
-					break;
-				default:
-					ast_log(LOG_WARNING, "Unexpected control subclass '%d'\n", fr->subclass);
-				}
-			}
-			/* Ignore */
-			ast_frfree(fr);
-		}
-		ast_sched_runq(c->sched);
-	}
-	return (c->_softhangup ? -1 : 0);
+	if (!context)
+		context = c->context;
+	return waitstream_core(c, NULL, NULL, NULL, 0,
+		-1, -1, context);
 }
 
 static int show_file_formats(int fd, int argc, char *argv[])
@@ -1276,7 +1120,6 @@
 	return RESULT_SUCCESS;
 #undef FORMAT
 #undef FORMAT2
-	
 }
 
 struct ast_cli_entry show_file =
