From 304aac9d8f68a374d9cd5d2b2204f8c26f10a948 Mon Sep 17 00:00:00 2001 From: kpfleming Date: Mon, 22 Aug 2005 19:29:29 +0000 Subject: support new format for musiconhold.conf (issue #4908) support non-SLINEAR moh streams (issue #4908) add external app to feed TCP stream into Asterisk for moh (issue #4908) git-svn-id: http://svn.digium.com/svn/asterisk/trunk@6353 f38db490-d61c-443f-a65b-d21fe96a405b --- UPGRADE.txt | 6 ++ configs/musiconhold.conf.sample | 48 ++++++--- frame.c | 33 +++++- include/asterisk/frame.h | 3 + res/res_musiconhold.c | 231 +++++++++++++++++++++++++++++++--------- utils/Makefile | 5 +- utils/streamplayer.c | 84 +++++++++++++++ 7 files changed, 341 insertions(+), 69 deletions(-) create mode 100755 utils/streamplayer.c diff --git a/UPGRADE.txt b/UPGRADE.txt index 509e318f1..6f7a3bcbb 100755 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -137,3 +137,9 @@ AGI: behavior has been fixed. If you do not want your script to terminate at the end of AGI being called (e.g. on a hangup) then set SIGHUP to be ignored within your application. + +Music On Hold: + +* The preferred format for musiconhold.conf has changed; please see the + sample configuration file for the new format. The existing format + is still supported but will generate warnings when the module is loaded. diff --git a/configs/musiconhold.conf.sample b/configs/musiconhold.conf.sample index 91d692d15..56379efe7 100755 --- a/configs/musiconhold.conf.sample +++ b/configs/musiconhold.conf.sample @@ -1,16 +1,30 @@ ; -; Music on hold class definitions +; Music on Hold -- Sample Configuration ; -[classes] -default => quietmp3:/var/lib/asterisk/mohmp3 -;loud => mp3:/var/lib/asterisk/mohmp3 -;random => quietmp3:/var/lib/asterisk/mohmp3,-z -;unbuffered => mp3nb:/var/lib/asterisk/mohmp3 -;quietunbuf => quietmp3nb:/var/lib/asterisk/mohmp3 -; Note that the custom mode cannot handle escaped parameters (specifically embedded spaces) -;manual => custom:/var/lib/asterisk/mohmp3,/usr/bin/mpg123 -q -r 8000 -f 8192 -b 2048 --mono -s -; For a custom app, reading from a directory may not be necessary -;manual => custom:nodir,/bin/my-dirless-app myarg + +[default] +mode=quietmp3 +directory=/var/lib/asterisk/mohmp3 + +; valid mode options: +; quietmp3 -- default +; mp3 -- loud +; mp3nb -- unbuffered +; quietmp3nb -- quiet unbuffered +; custom -- run a custom application +; files -- read files from a directory in any Asterisk supported format + +;[manual] +;mode=custom +; Note that with mode=custom, a directory is not required, such as when reading +; from a stream. +;directory=/var/lib/asterisk/mohmp3 +;application=/usr/bin/mpg123 -q -r 8000 -f 8192 -b 2048 --mono -s + +;[ulawstream] +;mode=custom +;application=/usr/bin/streamplayer 80.254.173.176 888 +;format=ulaw ; ; File-based (native) music on hold @@ -31,6 +45,12 @@ default => quietmp3:/var/lib/asterisk/mohmp3 ; this, res_musiconhold will skip the files it is not able to ; understand when it loads. ; -[moh_files] -;native => /var/lib/asterisk/moh-native -;native-random => /var/lib/asterisk/moh-native,r + +;[native] +;mode=files +;directory=/var/lib/asterisk/moh-native +; +;[native-random] +;mode=files +;directory=/var/lib/asterisk/moh-native +;random=yes ; Play the files in a random order diff --git a/frame.c b/frame.c index c6abc0024..5089a968a 100755 --- a/frame.c +++ b/frame.c @@ -1168,7 +1168,6 @@ static int speex_samples(unsigned char *data, int len) return cnt; } - int ast_codec_get_samples(struct ast_frame *f) { int samples=0; @@ -1210,3 +1209,35 @@ int ast_codec_get_samples(struct ast_frame *f) return samples; } +int ast_codec_get_len(int format, int samples) +{ + int len = 0; + + /* XXX Still need speex, g723, and lpc10 XXX */ + switch(format) { + case AST_FORMAT_ILBC: + len = (samples / 240) * 50; + break; + case AST_FORMAT_GSM: + len = (samples / 160) * 33; + break; + case AST_FORMAT_G729A: + len = samples / 8; + break; + case AST_FORMAT_SLINEAR: + len = samples * 2; + break; + case AST_FORMAT_ULAW: + case AST_FORMAT_ALAW: + len = samples; + break; + case AST_FORMAT_ADPCM: + case AST_FORMAT_G726: + len = samples / 2; + break; + default: + ast_log(LOG_WARNING, "Unable to calculate sample length for format %s\n", ast_getformatname(format)); + } + + return len; +} diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h index 7cff43ee6..41a4e7cb5 100755 --- a/include/asterisk/frame.h +++ b/include/asterisk/frame.h @@ -398,6 +398,9 @@ extern void ast_codec_pref_convert(struct ast_codec_pref *pref, char *buf, size_ /* Returns the number of samples contained in the frame */ extern int ast_codec_get_samples(struct ast_frame *f); +/* Returns the number of bytes for the number of samples of the given format */ +extern int ast_codec_get_len(int format, int samples); + /* Gets duration in ms of interpolation frame for a format */ static inline int ast_codec_interp_len(int format) { diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c index e2a291c94..c2db43d74 100755 --- a/res/res_musiconhold.c +++ b/res/res_musiconhold.c @@ -108,12 +108,14 @@ struct moh_files_state { #define MOH_RANDOMIZE (1 << 3) struct mohclass { - char class[80]; + char name[MAX_MUSICCLASS]; char dir[256]; - char miscargs[256]; + char args[256]; + char mode[80]; char filearray[MAX_MOHFILES][MAX_MOHFILE_LEN]; unsigned int flags; int total_files; + int format; int pid; /* PID of mpg123 */ time_t start; pthread_t thread; @@ -212,8 +214,8 @@ static int ast_moh_files_next(struct ast_channel *chan) return -1; } - if (option_verbose > 2) - ast_log(LOG_NOTICE, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]); + if (option_debug) + ast_log(LOG_DEBUG, "%s Opened file %d '%s'\n", chan->name, state->pos, state->class->filearray[state->pos]); if (state->samples) ast_seekstream(chan->stream, state->samples, SEEK_SET); @@ -285,7 +287,7 @@ static void *moh_files_alloc(struct ast_channel *chan, void *params) chan->music_state = NULL; } else { if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", class->class, chan->name); + ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", class->name, chan->name); } } @@ -343,7 +345,7 @@ static int spawn_mp3(struct mohclass *class) argv[argc++] = "8192"; /* Look for extra arguments and add them to the list */ - strncpy(xargs, class->miscargs, sizeof(xargs) - 1); + strncpy(xargs, class->args, sizeof(xargs) - 1); argptr = xargs; while (argptr && !ast_strlen_zero(argptr)) { argv[argc++] = argptr; @@ -355,7 +357,7 @@ static int spawn_mp3(struct mohclass *class) } } else { /* Format arguments for argv vector */ - strncpy(xargs, class->miscargs, sizeof(xargs) - 1); + strncpy(xargs, class->args, sizeof(xargs) - 1); argptr = xargs; while (argptr && !ast_strlen_zero(argptr)) { argv[argc++] = argptr; @@ -460,6 +462,7 @@ static void *monmp3thread(void *data) char buf[8192]; short sbuf[8192]; int res, res2; + int len; struct timeval tv, tv_tmp; tv.tv_sec = 0; @@ -495,7 +498,9 @@ static void *monmp3thread(void *data) if (!class->members) continue; /* Read mp3 audio */ - if ((res2 = read(class->srcfd, sbuf, res * 2)) != res * 2) { + len = ast_codec_get_len(class->format, res); + + if ((res2 = read(class->srcfd, sbuf, len)) != len) { if (!res2) { close(class->srcfd); class->srcfd = -1; @@ -504,7 +509,7 @@ static void *monmp3thread(void *data) class->pid = 0; } } else - ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, res * 2); + ast_log(LOG_DEBUG, "Read %d bytes of audio while expecting %d\n", res2, len); continue; } ast_mutex_lock(&moh_lock); @@ -581,7 +586,7 @@ static struct mohclass *get_mohbyname(char *name) struct mohclass *moh; moh = mohclasses; while (moh) { - if (!strcasecmp(name, moh->class)) + if (!strcasecmp(name, moh->name)) return moh; moh = moh->next; } @@ -647,19 +652,18 @@ static void moh_release(struct ast_channel *chan, void *data) static void *moh_alloc(struct ast_channel *chan, void *params) { struct mohdata *res; - struct mohclass *class; - class = params; + struct mohclass *class = params; res = mohalloc(class); if (res) { res->origwfmt = chan->writeformat; - if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) { - ast_log(LOG_WARNING, "Unable to set '%s' to signed linear format\n", chan->name); + if (ast_set_write_format(chan, class->format)) { + ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(class->format)); moh_release(NULL, res); res = NULL; } if (option_verbose > 2) - ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on %s\n", class->class, chan->name); + ast_verbose(VERBOSE_PREFIX_3 "Started music on hold, class '%s', on channel '%s'\n", class->name, chan->name); } return res; } @@ -674,7 +678,8 @@ static int moh_generate(struct ast_channel *chan, void *data, int len, int sampl if (!moh->parent->pid) return -1; - len = samples * 2; + len = ast_codec_get_len(moh->parent->format, samples); + if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) { ast_log(LOG_WARNING, "Only doing %d of %d requested bytes on %s\n", (int)sizeof(buf), len, chan->name); len = sizeof(buf) - AST_FRIENDLY_OFFSET; @@ -689,17 +694,20 @@ static int moh_generate(struct ast_channel *chan, void *data, int len, int sampl return 0; memset(&f, 0, sizeof(f)); + f.frametype = AST_FRAME_VOICE; - f.subclass = AST_FORMAT_SLINEAR; + f.subclass = moh->parent->format; f.mallocd = 0; f.datalen = res; - f.samples = res / 2; f.data = buf + AST_FRIENDLY_OFFSET / 2; f.offset = AST_FRIENDLY_OFFSET; + f.samples = ast_codec_get_samples(&f); + if (ast_write(chan, &f) < 0) { ast_log(LOG_WARNING, "Failed to write frame to '%s': %s\n", chan->name, strerror(errno)); return -1; } + return 0; } @@ -763,53 +771,42 @@ static int moh_scan_files(struct mohclass *class) { return class->total_files; } -static int moh_register(char *classname, char *mode, char *param, char *miscargs) +static int moh_register(struct mohclass *moh) { - struct mohclass *moh; #ifdef ZAPATA_MOH int x; #endif ast_mutex_lock(&moh_lock); - moh = get_mohbyname(classname); - ast_mutex_unlock(&moh_lock); - if (moh) { - ast_log(LOG_WARNING, "Music on Hold '%s' already exists\n", classname); + if (get_mohbyname(moh->name)) { + ast_log(LOG_WARNING, "Music on Hold class '%s' already exists\n", moh->name); + free(moh); + ast_mutex_unlock(&moh_lock); return -1; } - moh = malloc(sizeof(struct mohclass)); - if (!moh) - return -1; - memset(moh, 0, sizeof(struct mohclass)); + ast_mutex_unlock(&moh_lock); + time(&moh->start); moh->start -= respawn_time; - strncpy(moh->class, classname, sizeof(moh->class) - 1); - if (miscargs) { - strncpy(moh->miscargs, miscargs, sizeof(moh->miscargs) - 1); - if (strchr(miscargs,'r')) - ast_set_flag(moh, MOH_RANDOMIZE); - } - if (!strcasecmp(mode, "files")) { - if (param) - strncpy(moh->dir, param, sizeof(moh->dir) - 1); + + if (!strcasecmp(moh->mode, "files")) { if (!moh_scan_files(moh)) { ast_moh_free_class(&moh); return -1; } - } else if (!strcasecmp(mode, "mp3") || !strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb") || !strcasecmp(mode, "httpmp3") || !strcasecmp(mode, "custom")) { - - if (param) - strncpy(moh->dir, param, sizeof(moh->dir) - 1); + if (strchr(moh->args, 'r')) + ast_set_flag(moh, MOH_RANDOMIZE); + } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) { - if (!strcasecmp(mode, "custom")) + if (!strcasecmp(moh->mode, "custom")) ast_set_flag(moh, MOH_CUSTOM); - else if (!strcasecmp(mode, "mp3nb") || !strcasecmp(mode, "quietmp3nb")) + else if (!strcasecmp(moh->mode, "mp3nb") || !strcasecmp(moh->mode, "quietmp3nb")) ast_set_flag(moh, MOH_SINGLE); - else if (!strcasecmp(mode, "quietmp3") || !strcasecmp(mode, "quietmp3nb")) + else if (!strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb")) ast_set_flag(moh, MOH_QUIET); moh->srcfd = -1; #ifdef ZAPATA_MOH - /* It's an MP3 Moh -- Open /dev/zap/pseudo for timing... Is + /* Open /dev/zap/pseudo for timing... Is there a better, yet reliable way to do this? */ moh->pseudofd = open("/dev/zap/pseudo", O_RDONLY); if (moh->pseudofd < 0) { @@ -829,7 +826,7 @@ static int moh_register(char *classname, char *mode, char *param, char *miscargs return -1; } } else { - ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", mode); + ast_log(LOG_WARNING, "Don't know how to do a mode '%s' music on hold\n", moh->mode); ast_moh_free_class(&moh); return -1; } @@ -885,21 +882,100 @@ static void local_ast_moh_stop(struct ast_channel *chan) } } +static struct mohclass *moh_class_malloc(void) +{ + struct mohclass *class; + + class = malloc(sizeof(struct mohclass)); + + if (!class) + return NULL; + + memset(class, 0, sizeof(struct mohclass)); + + class->format = AST_FORMAT_SLINEAR; + + return class; +} + static int load_moh_classes(void) { struct ast_config *cfg; struct ast_variable *var; + struct mohclass *class; char *data; char *args; - int x = 0; + char *cat; + int numclasses = 0; + static int dep_warning = 0; cfg = ast_config_load("musiconhold.conf"); if (!cfg) return 0; + cat = ast_category_browse(cfg, NULL); + for (; cat; cat = ast_category_browse(cfg, cat)) { + if (strcasecmp(cat, "classes") && strcasecmp(cat, "moh_files")) { + class = moh_class_malloc(); + if (!class) { + ast_log(LOG_WARNING, "Out of memory!\n"); + break; + } + ast_copy_string(class->name, cat, sizeof(class->name)); + var = ast_variable_browse(cfg, cat); + while (var) { + if (!strcasecmp(var->name, "mode")) + ast_copy_string(class->mode, var->value, sizeof(class->name)); + else if (!strcasecmp(var->name, "directory")) + ast_copy_string(class->dir, var->value, sizeof(class->dir)); + else if (!strcasecmp(var->name, "application")) + ast_copy_string(class->args, var->value, sizeof(class->args)); + else if (!strcasecmp(var->name, "random")) + ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE); + else if (!strcasecmp(var->name, "format")) { + class->format = ast_getformatbyname(var->value); + if (!class->format) { + ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value); + class->format = AST_FORMAT_SLINEAR; + } + } + var = var->next; + } + + if (ast_strlen_zero(class->dir)) { + if (!strcasecmp(class->mode, "custom")) { + strcpy(class->dir, "nodir"); + } else { + ast_log(LOG_WARNING, "A directory must be specified for class '%s'!\n", class->name); + free(class); + continue; + } + } + if (ast_strlen_zero(class->mode)) { + ast_log(LOG_WARNING, "A mode must be specified for class '%s'!\n", class->name); + free(class); + continue; + } + if (ast_strlen_zero(class->args) && !strcasecmp(class->mode, "custom")) { + ast_log(LOG_WARNING, "An application must be specified for class '%s'!\n", class->name); + free(class); + continue; + } + + moh_register(class); + numclasses++; + } + } + + + /* Deprecated Old-School Configuration */ var = ast_variable_browse(cfg, "classes"); while (var) { + if (!dep_warning) { + ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated! Please refer to the sample configuration for information on the new syntax.\n"); + dep_warning = 1; + } data = strchr(var->value, ':'); if (data) { *data++ = '\0'; @@ -907,26 +983,55 @@ static int load_moh_classes(void) if (args) *args++ = '\0'; if (!(get_mohbyname(var->name))) { - moh_register(var->name, var->value, data, args); - x++; + class = moh_class_malloc(); + if (!class) { + ast_log(LOG_WARNING, "Out of memory!\n"); + return numclasses; + } + + ast_copy_string(class->name, var->name, sizeof(class->name)); + ast_copy_string(class->dir, data, sizeof(class->dir)); + ast_copy_string(class->mode, var->value, sizeof(class->mode)); + if (args) + ast_copy_string(class->args, args, sizeof(class->args)); + + moh_register(class); + numclasses++; } } var = var->next; } var = ast_variable_browse(cfg, "moh_files"); while (var) { + if (!dep_warning) { + ast_log(LOG_WARNING, "The old musiconhold.conf syntax has been deprecated! Please refer to the sample configuration for information on the new syntax.\n"); + dep_warning = 1; + } if (!(get_mohbyname(var->name))) { args = strchr(var->value, ','); if (args) *args++ = '\0'; - moh_register(var->name, "files", var->value, args); - x++; + class = moh_class_malloc(); + if (!class) { + ast_log(LOG_WARNING, "Out of memory!\n"); + return numclasses; + } + + ast_copy_string(class->name, var->name, sizeof(class->name)); + ast_copy_string(class->dir, var->value, sizeof(class->dir)); + strcpy(class->mode, "files"); + if (args) + ast_copy_string(class->args, args, sizeof(class->args)); + + moh_register(class); + numclasses++; } var = var->next; } ast_config_destroy(cfg); - return x; + + return numclasses; } static void ast_moh_destroy(void) @@ -998,7 +1103,7 @@ static int cli_files_show(int fd, int argc, char *argv[]) if (!class->total_files) continue; - ast_cli(fd, "Class: %s\n", class->class); + ast_cli(fd, "Class: %s\n", class->name); for (i = 0; i < class->total_files; i++) ast_cli(fd, "\tFile: %s\n", class->filearray[i]); } @@ -1007,8 +1112,27 @@ static int cli_files_show(int fd, int argc, char *argv[]) return 0; } +static int moh_classes_show(int fd, int argc, char *argv[]) +{ + struct mohclass *class; + + ast_mutex_lock(&moh_lock); + for (class = mohclasses; class; class = class->next) { + ast_cli(fd, "Class: %s\n", class->name); + ast_cli(fd, "\tDirectory: %s\n", ast_strlen_zero(class->dir) ? "" : class->dir); + if (ast_test_flag(class, MOH_CUSTOM)) + ast_cli(fd, "\tApplication: %s\n", ast_strlen_zero(class->args) ? "" : class->args); + ast_cli(fd, "\tFormat: %s\n", ast_getformatname(class->format)); + } + ast_mutex_unlock(&moh_lock); + + return 0; +} + static struct ast_cli_entry cli_moh = { { "moh", "reload"}, moh_cli, "Music On Hold", "Music On Hold", NULL}; +static struct ast_cli_entry cli_moh_classes_show = { { "moh", "classes", "show"}, moh_classes_show, "List MOH classes", "Lists all MOH classes", NULL}; + static struct ast_cli_entry cli_moh_files_show = { { "moh", "files", "show"}, cli_files_show, "List MOH file-based classes", "Lists all loaded file-based MOH classes and their files", NULL}; static void init_classes(void) @@ -1033,6 +1157,7 @@ int load_module(void) ast_register_atexit(ast_moh_destroy); ast_cli_register(&cli_moh); ast_cli_register(&cli_moh_files_show); + ast_cli_register(&cli_moh_classes_show); if (!res) res = ast_register_application(app1, moh1_exec, synopsis1, descrip1); if (!res) diff --git a/utils/Makefile b/utils/Makefile index 689aacbd1..137b0b748 100755 --- a/utils/Makefile +++ b/utils/Makefile @@ -21,7 +21,7 @@ ifeq ($(findstring BSD,${OSARCH}),BSD) CFLAGS+=-I$(CROSS_COMPILE_TARGET)/usr/local/include -L$(CROSS_COMPILE_TARGET)/usr/local/lib endif -TARGET=stereorize +TARGET=stereorize streamplayer TARGET+=$(shell if [ -f $(CROSS_COMPILE_TARGET)/usr/include/popt.h ]; then echo "smsq"; else if [ -f $(CROSS_COMPILE_TARGET)/usr/local/include/popt.h ]; then echo "smsq"; fi ; fi) TARGET+=$(shell if [ -f $(CROSS_COMPILE_TARGET)/usr/include/newt.h ]; then echo "astman"; else if [ -f $(CROSS_COMPILE_TARGET)/usr/local/include/newt.h ]; then echo "astman"; fi ; fi) @@ -53,6 +53,9 @@ check_expr : check_expr.c ../ast_expr.a smsq: smsq.o $(CC) $(CFLAGS) -o smsq ${SOL} smsq.o -lpopt +streamplayer: streamplayer.o + $(CC) $(CFLAGS) -o streamplayer ${SOL} streamplayer.o + ifneq ($(wildcard .depend),) include .depend endif diff --git a/utils/streamplayer.c b/utils/streamplayer.c new file mode 100755 index 000000000..5ae742f08 --- /dev/null +++ b/utils/streamplayer.c @@ -0,0 +1,84 @@ +/* +* streamplayer.c +* +* A utility for reading from a stream +* +* Copyright (C) 2005, Digium, Inc. +* +* Russell Bryant +* +* This program is free software, distributed under the terms of +* the GNU General Public License +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + + +int main(int argc, char *argv[]) +{ + struct sockaddr_in sin; + struct hostent *hp; + int s; + int res; + char buf[2048]; + fd_set wfds; + struct timeval tv; + + if (argc != 3) { + fprintf(stderr, "Usage: ./streamplayer \n"); + exit(1); + } + + hp = gethostbyname(argv[1]); + if (!hp) { + fprintf(stderr, "Unable to lookup IP for host '%s'\n", argv[1]); + exit(1); + } + + memset(&sin, 0, sizeof(sin)); + + sin.sin_family = AF_INET; + sin.sin_port = htons(atoi(argv[2])); + memcpy(&sin.sin_addr, hp->h_addr, sizeof(sin.sin_addr)); + + s = socket(AF_INET, SOCK_STREAM, 0); + + if (s < 0) { + fprintf(stderr, "Unable to allocate socket!\n"); + exit(1); + } + + res = connect(s, (struct sockaddr *)&sin, sizeof(sin)); + + if (res) { + fprintf(stderr, "Unable to connect to host!\n"); + close(s); + exit(1); + } + + while (1) { + res = read(s, buf, sizeof(buf)); + + if (res < 1) + break; + + memset(&tv, 0, sizeof(tv)); + FD_ZERO(&wfds); + FD_SET(1, &wfds); + + select(2, NULL, &wfds, NULL, &tv); + + if (FD_ISSET(1, &wfds)) + write(1, buf, res); + } + + close(s); + exit(res); +} -- cgit v1.2.3