From d2b596f1082874b76a8c3ceaf0851b26787d3be6 Mon Sep 17 00:00:00 2001
From: Martin Pycko <martinp@digium.com>
Date: Fri, 19 Dec 2003 18:06:29 +0000
Subject: [PATCH] Add voicemail prepending feature plus forwarding to many
 extensions if you specify exten1*exten2*.....#

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@1872 65c4cc65-6c06-0410-ace0-fbb531ad65f3
---
 apps/app_voicemail.c         | 443 ++++++++++++++++++++++++++++-------
 file.c                       |  68 +++++-
 include/asterisk/file.h      |  23 ++
 sounds.txt                   |   2 +
 sounds/vm-forwardoptions.gsm | Bin 0 -> 9702 bytes
 5 files changed, 452 insertions(+), 84 deletions(-)
 create mode 100755 sounds/vm-forwardoptions.gsm

diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
index a7b43a88d7..ced94c6393 100755
--- a/apps/app_voicemail.c
+++ b/apps/app_voicemail.c
@@ -109,6 +109,23 @@ struct vm_zone {
 	struct vm_zone *next;
 };
 
+struct vm_state {
+	char curbox[80];
+	char username[80];
+	char curdir[256];
+	char vmbox[256];
+	char fn[256];
+	char fn2[256];
+	int deleted[MAXMSG];
+	int heard[MAXMSG];
+	int curmsg;
+	int lastmsg;
+	int newmessages;
+	int oldmessages;
+	int starting;
+	int repeats;
+};
+
 static char *tdesc = "Comedian Mail (Voicemail System)";
 
 static char *adapp = "CoMa";
@@ -874,6 +891,214 @@ static int play_and_wait(struct ast_channel *chan, char *fn)
 	return d;
 }
 
+static int play_and_prepend(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt)
+{
+	char d, *fmts;
+	char comment[256];
+	int x, fmtcnt=1, res=-1,outmsg=0;
+	struct ast_frame *f;
+	struct ast_filestream *others[MAX_OTHER_FORMATS];
+	struct ast_filestream *realfiles[MAX_OTHER_FORMATS];
+	char *sfmt[MAX_OTHER_FORMATS];
+	char *stringp=NULL;
+	time_t start, end;
+	struct ast_dsp *sildet;   	/* silence detector dsp */
+	int totalsilence = 0;
+	int dspsilence = 0;
+	int gotsilence = 0;		/* did we timeout for silence? */
+	int rfmt=0;	
+	char prependfile[80];
+	
+	ast_log(LOG_DEBUG,"play_and_preped: %s, %s, '%s'\n", playfile ? playfile : "<None>", recordfile, fmt);
+	snprintf(comment,sizeof(comment),"Playing %s, Recording to: %s on %s\n", playfile ? playfile : "<None>", recordfile, chan->name);
+
+	if (playfile) {	
+		d = play_and_wait(chan, playfile);
+		if (d > -1)
+			d = ast_streamfile(chan, "beep",chan->language);
+		if (!d)
+			d = ast_waitstream(chan,"");
+		if (d < 0)
+			return -1;
+	}
+	strncpy(prependfile, recordfile, sizeof(prependfile) -1);	
+	strcat(prependfile, "-prepend");
+			
+	fmts = ast_strdupa(fmt);
+	
+	stringp=fmts;
+	strsep(&stringp, "|");
+	ast_log(LOG_DEBUG,"Recording Formats: sfmts=%s\n", fmts);	
+	sfmt[0] = ast_strdupa(fmts);
+	
+	while((fmt = strsep(&stringp, "|"))) {
+		if (fmtcnt > MAX_OTHER_FORMATS - 1) {
+			ast_log(LOG_WARNING, "Please increase MAX_OTHER_FORMATS in app_voicemail.c\n");
+			break;
+		}
+		sfmt[fmtcnt++] = ast_strdupa(fmt);
+	}
+
+	if (maxtime)
+		time(&start);
+	for (x=0;x<fmtcnt;x++) {
+		others[x] = ast_writefile(prependfile, sfmt[x], comment, O_TRUNC, 0, 0700);
+		ast_verbose( VERBOSE_PREFIX_3 "x=%i, open writing:  %s format: %s, %p\n", x, prependfile, sfmt[x], others[x]);
+		if (!others[x]) {
+			break;
+		}
+	}
+	
+	sildet = ast_dsp_new(); //Create the silence detector
+	if (!sildet) {
+		ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
+		return -1;
+	}
+	ast_dsp_set_threshold(sildet, silencethreshold);
+	
+	if (maxsilence > 0) {
+		rfmt = chan->readformat;
+		res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
+		if (res < 0) {
+			ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
+			return -1;
+		}
+	}
+						
+	if (x == fmtcnt) {
+	/* Loop forever, writing the packets we read to the writer(s), until
+	   we read a # or get a hangup */
+		f = NULL;
+		for(;;) {
+		 	res = ast_waitfor(chan, 2000);
+			if (!res) {
+				ast_log(LOG_DEBUG, "One waitfor failed, trying another\n");
+				/* Try one more time in case of masq */
+			 	res = ast_waitfor(chan, 2000);
+				if (!res) {
+					ast_log(LOG_WARNING, "No audio available on %s??\n", chan->name);
+					res = -1;
+				}
+			}
+			
+			if (res < 0) {
+				f = NULL;
+				break;
+			}
+			f = ast_read(chan);
+			if (!f)
+				break;
+			if (f->frametype == AST_FRAME_VOICE) {
+				/* write each format */
+				for (x=0;x<fmtcnt;x++) {
+					if (!others[x])
+						break;
+					res = ast_writestream(others[x], f);
+				}
+				
+				/* Silence Detection */
+				if (maxsilence > 0) {
+					dspsilence = 0;
+					ast_dsp_silence(sildet, f, &dspsilence);
+					if (dspsilence)
+						totalsilence = dspsilence;
+					else
+						totalsilence = 0;
+					
+					if (totalsilence > maxsilence) {
+					/* Ended happily with silence */
+					ast_frfree(f);
+					gotsilence = 1;
+					outmsg=2;
+					break;
+					}
+				}
+				/* Exit on any error */
+				if (res) {
+					ast_log(LOG_WARNING, "Error writing frame\n");
+					ast_frfree(f);
+					break;
+				}
+			} else if (f->frametype == AST_FRAME_VIDEO) {
+				/* Write only once */
+				ast_writestream(others[0], f);
+			} else if (f->frametype == AST_FRAME_DTMF) {
+				/* stop recording with any digit */
+				if (option_verbose > 2) 
+					ast_verbose( VERBOSE_PREFIX_3 "User ended message by pressing %c\n", f->subclass);
+				res = f->subclass;
+				outmsg = 2;
+				ast_frfree(f);
+				break;
+			}
+			if (maxtime) {
+				time(&end);
+				if (maxtime < (end - start)) {
+					if (option_verbose > 2)
+						ast_verbose( VERBOSE_PREFIX_3 "Took too long, cutting it short...\n");
+					res = 't';
+					ast_frfree(f);
+					break;
+				}
+			}
+			ast_frfree(f);
+		}
+		if (!f) {
+			if (option_verbose > 2) 
+				ast_verbose( VERBOSE_PREFIX_3 "User hung up\n");
+			res = -1;
+			outmsg=1;
+			/* delete all the prepend files */
+			for (x=0;x<fmtcnt;x++) {
+				if (!others[x])
+					break;
+				ast_closestream(others[x]);
+				ast_filedelete(prependfile, sfmt[x]);
+			}
+		}
+	} else {
+		ast_log(LOG_WARNING, "Error creating writestream '%s', format '%s'\n", prependfile, sfmt[x]); 
+	}
+	if (outmsg > 1) {
+		struct ast_frame *fr;
+		for (x=0;x<fmtcnt;x++) {
+			snprintf(comment, sizeof(comment), "Opening the real file %s.%s\n", recordfile, sfmt[x]);
+			realfiles[x] = ast_writefile(recordfile, sfmt[x], comment, O_RDONLY, 0, 0);
+			if (!others[x] || !realfiles[x])
+				break;
+			if (totalsilence)
+				ast_stream_rewind(others[x], totalsilence-200);
+			else
+				ast_stream_rewind(others[x], 200);
+			ast_truncstream(others[x]);
+			/* add the original file too */
+			while ((fr = ast_readframe(realfiles[x]))) {
+				ast_writestream(others[x],fr);
+			}
+			ast_closestream(others[x]);
+			ast_closestream(realfiles[x]);
+			ast_filerename(prependfile, recordfile, sfmt[x]);
+#if 0
+			ast_verbose("Recording Format: sfmts=%s, prependfile %s, recordfile %s\n", sfmt[x],prependfile,recordfile);
+#endif
+			ast_filedelete(prependfile, sfmt[x]);
+		}
+	}
+	if (rfmt) {
+		if (ast_set_read_format(chan, rfmt)) {
+			ast_log(LOG_WARNING, "Unable to restore format %s to channel '%s'\n", ast_getformatname(rfmt), chan->name);
+		}
+	}
+	if (outmsg) {
+		if (outmsg > 1) {
+			/* Let them know it worked */
+			ast_streamfile(chan, "vm-msgsaved", chan->language);
+			ast_waitstream(chan, "");
+		}
+	}	
+	return res;
+}
+
 static int play_and_record(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt)
 {
 	char d, *fmts;
@@ -1891,8 +2116,45 @@ static int get_folder2(struct ast_channel *chan, char *fn, int start)
 	return res;
 }
 
-static int
-forward_message(struct ast_channel *chan, char *context, char *dir, int curmsg, struct ast_vm_user *sender, char *fmt)
+static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu, char *curdir, int curmsg, char *vmfts, char *context)
+{
+	int cmd = 0;
+	int retries = 0;
+
+	while((cmd >= 0) && (cmd != 't') && (cmd != '#')) {
+		if (cmd)
+			retries = 0;
+		switch (cmd) {
+		case '1': 
+			/* prepend a message to the current message and return */
+		{
+			char file[200];
+			snprintf(file, sizeof(file), "%s/msg%04d", curdir, curmsg);
+			cmd = play_and_prepend(chan, NULL, file, 0, vmfmts);
+			break;
+		}
+		case '2': 
+			cmd = 't';
+			break;
+		case '#':
+			cmd = '#';
+			break;
+		default: 
+			cmd = play_and_wait(chan,"vm-forwardoptions");
+			if (!cmd)
+				cmd = ast_waitfordigit(chan,6000);
+			if (!cmd)
+				retries++;
+			if (retries > 3)
+				cmd = 't';
+		 }
+	}
+	if (cmd == 't')
+		cmd = 0;
+	return cmd;
+}
+
+static int forward_message(struct ast_channel *chan, char *context, char *dir, int curmsg, struct ast_vm_user *sender, char *fmt)
 {
 	char username[70];
 	char sys[256];
@@ -1903,106 +2165,127 @@ forward_message(struct ast_channel *chan, char *context, char *dir, int curmsg,
 	char miffile[256];
 	char fn[256];
 	char callerid[512];
-	int res = 0;
-	struct ast_vm_user *receiver, srec;
+	int res = 0, cmd = 0;
+	struct ast_vm_user *receiver, *extensions = NULL, *vmtmp = NULL;
 	char tmp[256];
 	char *stringp, *s;
-	
-	while(!res) {
+	int saved_messages = 0, found = 0;
+	int valid_extensions = 0;
+	while (!res && !valid_extensions) {
 		res = ast_streamfile(chan, "vm-extension", chan->language);
 		if (res)
 			break;
 		if ((res = ast_readstring(chan, username, sizeof(username) - 1, 2000, 10000, "#") < 0))
 			break;
-		if ((receiver = find_user(&srec, context, username))) {
-			/* if (play_and_wait(chan, "vm-savedto"))
+		/* start all over if no username */
+		if (!strlen(username))
+			continue;
+		stringp = username;
+		s = strsep(&stringp, "*");
+		/* start optimistic */
+		valid_extensions = 1;
+		while (s) {
+			/* find_user is going to malloc since we have a NULL as first argument */
+			if ((receiver = find_user(NULL, context, s))) {
+				if (!extensions)
+					vmtmp = extensions = receiver;
+				else {
+					vmtmp->next = receiver;
+					vmtmp = receiver;
+				}
+				found++;
+			} else {
+				valid_extensions = 0;
 				break;
-			*/
+			}
+			s = strsep(&stringp, "*");
+		}
+		/* break from the loop of reading the extensions */
+		if (valid_extensions)
+			break;
+		/* invalid extension, try again */
+		res = play_and_wait(chan, "pbx-invalid");
+	}
+	/* check if we're clear to proceed */
+	if (!extensions || !valid_extensions)
+		return res;
+	vmtmp = extensions;
+	cmd = vm_forwardoptions(chan, sender, dir, curmsg, vmfmts, context);
 
-			snprintf(todir, sizeof(todir), "%s/voicemail/%s/%s/INBOX",  (char *)ast_config_AST_SPOOL_DIR, receiver->context, username);
-			snprintf(sys, sizeof(sys), "mkdir -p %s\n", todir);
+	while(!res && vmtmp) {
+		/* if (play_and_wait(chan, "vm-savedto"))
+			break;
+		*/
+		snprintf(todir, sizeof(todir), "%s/voicemail/%s/%s/INBOX",  (char *)ast_config_AST_SPOOL_DIR, vmtmp->context, vmtmp->mailbox);
+		snprintf(sys, sizeof(sys), "mkdir -p %s\n", todir);
+		ast_log(LOG_DEBUG, sys);
+		system(sys);
+
+		todircount = count_messages(todir);
+		strncpy(tmp, fmt, sizeof(tmp));
+		stringp = tmp;
+		while((s = strsep(&stringp, "|"))) {
+			/* XXX This is a hack -- we should use build_filename or similar XXX */
+			if (!strcasecmp(s, "wav49"))
+				s = "WAV";
+			snprintf(sys, sizeof(sys), "cp %s/msg%04d.%s %s/msg%04d.%s\n", dir, curmsg, s, todir, todircount, s);
 			ast_log(LOG_DEBUG, sys);
 			system(sys);
+		}
+		snprintf(sys, sizeof(sys), "cp %s/msg%04d.txt %s/msg%04d.txt\n", dir, curmsg, todir, todircount);
+		ast_log(LOG_DEBUG, sys);
+		system(sys);
+		snprintf(fn, sizeof(fn), "%s/msg%04d", todir,todircount);
 
-			todircount = count_messages(todir);
-			strncpy(tmp, fmt, sizeof(tmp));
-			stringp = tmp;
-			while((s = strsep(&stringp, "|"))) {
-				/* XXX This is a hack -- we should use build_filename or similar XXX */
-				if (!strcasecmp(s, "wav49"))
-					s = "WAV";
-				snprintf(sys, sizeof(sys), "cp %s/msg%04d.%s %s/msg%04d.%s\n", dir, curmsg, s, todir, todircount, s);
-				ast_log(LOG_DEBUG, sys);
-				system(sys);
-			}
-			snprintf(sys, sizeof(sys), "cp %s/msg%04d.txt %s/msg%04d.txt\n", dir, curmsg, todir, todircount);
-			ast_log(LOG_DEBUG, sys);
-			system(sys);
-			snprintf(fn, sizeof(fn), "%s/msg%04d", todir,todircount);
-
-			/* load the information on the source message so we can send an e-mail like a new message */
-			snprintf(miffile, sizeof(miffile), "%s/msg%04d.txt", dir, curmsg);
-			if ((mif=ast_load(miffile))) {
-
-              /* set callerid and duration variables */
-              snprintf(callerid, sizeof(callerid), "FWD from: %s from %s", sender->fullname, ast_variable_retrieve(mif, NULL, "callerid"));
-	      s = ast_variable_retrieve(mif, NULL, "duration");
-              if (s)
-		      duration = atol(s);
-	      else
-		      duration = 0;
-	      if (strlen(receiver->email)) {
+		/* load the information on the source message so we can send an e-mail like a new message */
+		snprintf(miffile, sizeof(miffile), "%s/msg%04d.txt", dir, curmsg);
+		if ((mif=ast_load(miffile))) {
+
+			/* set callerid and duration variables */
+			snprintf(callerid, sizeof(callerid), "FWD from: %s from %s", sender->fullname, ast_variable_retrieve(mif, NULL, "callerid"));
+			s = ast_variable_retrieve(mif, NULL, "duration");
+			if (s)
+				duration = atol(s);
+			else
+				duration = 0;
+			if (strlen(vmtmp->email)) {
 				int attach_user_voicemail = attach_voicemail;
 				char *myserveremail = serveremail;
-				if (receiver->attach > -1)
-					attach_user_voicemail = receiver->attach;
-				if (strlen(receiver->serveremail))
-					myserveremail = receiver->serveremail;
-		      sendmail(myserveremail, receiver, todircount, username, callerid, fn, tmp, duration, attach_user_voicemail);
-	      }
-	     
-			if (strlen(receiver->pager)) {
+				if (vmtmp->attach > -1)
+					attach_user_voicemail = vmtmp->attach;
+				if (strlen(vmtmp->serveremail))
+					myserveremail = vmtmp->serveremail;
+				sendmail(myserveremail, vmtmp, todircount, vmtmp->mailbox, callerid, fn, tmp, duration, attach_user_voicemail);
+			}
+		     
+			if (strlen(vmtmp->pager)) {
 				char *myserveremail = serveremail;
-				if (strlen(receiver->serveremail))
-					myserveremail = receiver->serveremail;
-				sendpage(myserveremail, receiver->pager, todircount, username, callerid, duration, receiver);
+				if (strlen(vmtmp->serveremail))
+					myserveremail = vmtmp->serveremail;
+				sendpage(myserveremail, vmtmp->pager, todircount, vmtmp->mailbox, callerid, duration, vmtmp);
 			}
 			  
-			  ast_destroy(mif); /* or here */
-			}
-			/* Leave voicemail for someone */
-			manager_event(EVENT_FLAG_CALL, "MessageWaiting", "Mailbox: %s\r\nWaiting: %d\r\n", username, ast_app_has_voicemail(username));
+			ast_destroy(mif); /* or here */
+		}
+		/* Leave voicemail for someone */
+		manager_event(EVENT_FLAG_CALL, "MessageWaiting", "Mailbox: %s\r\nWaiting: %d\r\n", vmtmp->mailbox, ast_app_has_voicemail(vmtmp->mailbox));
 
-			/* give confirmatopm that the message was saved */
+		free_user(vmtmp);
+		saved_messages++;
+		vmtmp = vmtmp->next;
+	}
+	if (saved_messages > 0) {
+		/* give confirmatopm that the message was saved */
+		if (saved_messages == 1)
 			res = play_and_wait(chan, "vm-message");
-			if (!res)
-				res = play_and_wait(chan, "vm-saved");
-			free_user(receiver);
-			break;
-		} else {
-			res = play_and_wait(chan, "pbx-invalid");
-		}
+		else
+			res = play_and_wait(chan, "vm-messages");
+		if (!res)
+			res = play_and_wait(chan, "vm-saved");
 	}
-	return res;
+	return res ? res : cmd;
 }
 
-struct vm_state {
-	char curbox[80];
-	char username[80];
-	char curdir[256];
-	char vmbox[256];
-	char fn[256];
-	char fn2[256];
-	int deleted[MAXMSG];
-	int heard[MAXMSG];
-	int curmsg;
-	int lastmsg;
-	int newmessages;
-	int oldmessages;
-	int starting;
-	int repeats;
-};
-
 
 static int wait_file2(struct ast_channel *chan, struct vm_state *vms, char *file)
 {
diff --git a/file.c b/file.c
index 4213a23e2d..9869eb325f 100755
--- a/file.c
+++ b/file.c
@@ -499,6 +499,15 @@ struct ast_filestream *ast_openvstream(struct ast_channel *chan, char *filename,
 	return NULL;
 }
 
+struct ast_frame *ast_readframe(struct ast_filestream *s)
+{
+	struct ast_frame *f = NULL;
+	int whennext = 0;	
+	if (s && s->fmt)
+		f = s->fmt->read(s, &whennext);
+	return f;
+}
+
 static int ast_readaudio_callback(void *data)
 {
 	struct ast_filestream *s = data;
@@ -726,10 +735,58 @@ int ast_streamfile(struct ast_channel *chan, char *filename, char *preflang)
 	return -1;
 }
 
+struct ast_filestream *ast_readfile(char *filename, char *type, char *comment, int flags, int check, mode_t mode)
+{
+	int fd,myflags = 0;
+	struct ast_format *f;
+	struct ast_filestream *fs=NULL;
+	char *fn;
+	char *ext;
+	if (ast_mutex_lock(&formatlock)) {
+		ast_log(LOG_WARNING, "Unable to lock format list\n");
+		return NULL;
+	}
+	f = formats;
+	while(f) {
+		if (!strcasecmp(f->name, type)) {
+			char *stringp=NULL;
+			/* XXX Implement check XXX */
+			ext = strdup(f->exts);
+			stringp=ext;
+			ext = strsep(&stringp, "|");
+			fn = build_filename(filename, ext);
+			fd = open(fn, flags | myflags);
+			if (fd >= 0) {
+				errno = 0;
+				if ((fs = f->open(fd))) {
+					fs->trans = NULL;
+					fs->fmt = f;
+					fs->flags = flags;
+					fs->mode = mode;
+					fs->filename = strdup(filename);
+					fs->vfs = NULL;
+				} else {
+					ast_log(LOG_WARNING, "Unable to open %s\n", fn);
+					close(fd);
+					unlink(fn);
+				}
+			} else if (errno != EEXIST)
+				ast_log(LOG_WARNING, "Unable to open file %s: %s\n", fn, strerror(errno));
+			free(fn);
+			free(ext);
+			break;
+		}
+		f = f->next;
+	}
+	ast_mutex_unlock(&formatlock);
+	if (!f) 
+		ast_log(LOG_WARNING, "No such format '%s'\n", type);
+	return fs;
+}
 
 struct ast_filestream *ast_writefile(char *filename, char *type, char *comment, int flags, int check, mode_t mode)
 {
-	int fd,myflags;
+	int fd,myflags = 0;
 	struct ast_format *f;
 	struct ast_filestream *fs=NULL;
 	char *fn;
@@ -738,9 +795,12 @@ struct ast_filestream *ast_writefile(char *filename, char *type, char *comment,
 		ast_log(LOG_WARNING, "Unable to lock format list\n");
 		return NULL;
 	}
-	myflags = 0;
 	/* set the O_TRUNC flag if and only if there is no O_APPEND specified */
-	if (!(flags & O_APPEND)) myflags = O_TRUNC;
+	if (!(flags & O_APPEND)) 
+		myflags = O_TRUNC;
+	
+	myflags |= O_WRONLY | O_CREAT;
+
 	f = formats;
 	while(f) {
 		if (!strcasecmp(f->name, type)) {
@@ -750,7 +810,7 @@ struct ast_filestream *ast_writefile(char *filename, char *type, char *comment,
 			stringp=ext;
 			ext = strsep(&stringp, "|");
 			fn = build_filename(filename, ext);
-			fd = open(fn, flags | myflags | O_WRONLY | O_CREAT, mode);
+			fd = open(fn, flags | myflags, mode);
 			if (fd >= 0) {
 				errno = 0;
 				if ((fs = f->rewrite(fd, comment))) {
diff --git a/include/asterisk/file.h b/include/asterisk/file.h
index f7808e435c..316e471121 100755
--- a/include/asterisk/file.h
+++ b/include/asterisk/file.h
@@ -140,6 +140,22 @@ char ast_waitstream_fr(struct ast_channel *c, char *breakon, char *forward, char
    1 if monfd is ready for reading */
 char ast_waitstream_full(struct ast_channel *c, char *breakon, int audiofd, int monfd);
 
+//! Starts reading from a file
+/*!
+ * \param filename the name of the file to write to
+ * \param type format of file you wish to write out to
+ * \param comment comment to go with
+ * \param oflags output file flags
+ * \param check (unimplemented, hence negligible)
+ * \param mode Open mode
+ * Open an incoming file stream.  oflags are flags for the open() command, and 
+ * if check is non-zero, then it will not write a file if there are any files that 
+ * start with that name and have an extension
+ * Please note, this is a blocking function.  Program execution will not return until ast_waitstream completes it's execution.
+ * Returns a struct ast_filestream on success, NULL on failure
+ */
+struct ast_filestream *ast_readfile(char *filename, char *type, char *comment, int oflags, int check, mode_t mode);
+
 //! Starts writing a file
 /*!
  * \param filename the name of the file to write to
@@ -261,6 +277,13 @@ int ast_stream_rewind(struct ast_filestream *fs, long ms);
  */
 long ast_tellstream(struct ast_filestream *fs);
 
+//! Read a frame from a filestream
+/*!
+ * \param ast_filestream fs to act on
+ * Returns a frame or NULL if read failed
+ */ 
+struct ast_frame *ast_readframe(struct ast_filestream *s);
+
 #define AST_RESERVED_POINTERS 20
 
 #if defined(__cplusplus) || defined(c_plusplus)
diff --git a/sounds.txt b/sounds.txt
index 86655bc478..71b5c16556 100755
--- a/sounds.txt
+++ b/sounds.txt
@@ -392,3 +392,5 @@
 
 %minutes.gsm%minutes
 
+%vm-forwardoptions.gsm%press 1 to prepend a message or 2 to forward the
+ message without prepending
diff --git a/sounds/vm-forwardoptions.gsm b/sounds/vm-forwardoptions.gsm
new file mode 100755
index 0000000000000000000000000000000000000000..b9ccb7f98002c9b899db20eac904530744db273a
GIT binary patch
literal 9702
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
P3fs^v_WD1d{2%;(Plke3

literal 0
HcmV?d00001

-- 
GitLab