Hi! ----
Attached (as "astksh_builtin_poll20120725_001.diff.txt") is the _prototype_ patch for a poll(1) builtin and a demo application (attached as "shircbot.sh.txt") which shows it's usage. Basic usage example: -- snip -- $ ksh -c 'builtin poll ; function l { compound -a pl=( fd=0 events="POLLIN" ) ; poll pl ; print -v pl ; } ; l ' <enter a character> ( ( events=POLLIN fd=0 revents=POLLIN ) ) -- snip -- See output of $ poll --man # for usage and https://mailman.research.att.com/pipermail/ast-developers/2012q3/001585.html for further comments... Changes since the last version: - Associative arrays now work. I've added a basic workaround for the issue that walking an associative compound variable array using the |nv*()|-API and then adding compound variable members to an existing compound variable array element adds a new [0] element... which screwed-up builtin internals. The "workaround" is to remember the array subscripts and use these to write into the compound variable array instead of doing this while walking the array using the |nv*()|-API. - "shircbot.sh" has been updated to use an associative array for poll(1) data - tty raw/cooked handling now uses the correct fd numbers. There is still the issues that this doesn't really work with multiple different ttys because the underlying |tty_raw()|/|tty_cooked()| API is not designed for that. - Some error handling has been fixed. The code is still so ugly that little kitten will get blind and implode. ToDo: - Fix usage of |sfpoll()| ... IMO it should only be called _once_ per poll(1) call (poll(2) still needs to be called to listen for POLLHUP&co.) - POSIX-like shell mode (e.g. add API which makes it possible that poll(1) can be implemented by dash(1) or bash(1) or other shells which do not have compound variables): 1. If the input variable is a plain string array (not compound variable array) it should read those strings and append/replace "revents='...'" to those strings (this is mainly intended for simplification and to allow authors to write a bash4/zsh/dash version of poll(1) without having to implement compound variable support first... ;-/ ) 2. If the input variable is a compound variable array or type variable array "events" and "revents" should be compound variables themselves - Fix timeout when poll(2) needs to be restarted after EINTR/EAGAIN - EXAMPLES section in manpage... but I hit an issue with ']' ... mhhh... AFAIK we had a similar request in the list this month: How do I quote ']' that |_ast_getopts()| interprets it as plain text ? ---- Bye, Roland -- __ . . __ (o.\ \/ /.o) roland.ma...@nrubsig.org \__\/\/__/ MPEG specialist, C&&JAVA&&Sun&&Unix programmer /O /==\ O\ TEL +49 641 3992797 (;O/ \/ \O;)
diff -r -u -N original/src/cmd/ksh93/bltins/poll.c build_poll/src/cmd/ksh93/bltins/poll.c --- src/cmd/ksh93/bltins/poll.c 1970-01-01 01:00:00.000000000 +0100 +++ src/cmd/ksh93/bltins/poll.c 2012-07-24 18:00:17.210061124 +0200 @@ -0,0 +1,591 @@ +/*********************************************************************** +* * +* This software is part of the ast package * +* Copyright (c) 2007-2012 AT&T Intellectual Property * +* and is licensed under the * +* Eclipse Public License, Version 1.0 * +* by AT&T Intellectual Property * +* * +* A copy of the License is available at * +* http://www.eclipse.org/org/documents/epl-v10.html * +* (with md5 checksum b35adb5213ca9657e911e9befb180842) * +* * +* Information and Software Systems Research * +* AT&T Research * +* Florham Park NJ * +* * +* Roland Mainz <roland.ma...@nrubsig.org> * +* * +***********************************************************************/ +#pragma prototyped + +#include "defs.h" +#include "variables.h" +#include "lexstates.h" +#include "io.h" +#include "name.h" +#include "builtins.h" +#include "history.h" +#include "terminal.h" +#include <stdio.h> +#include <stdbool.h> +#include <poll.h> + +#include <tm.h> +#undef nv_isnull +#ifndef SH_DICT +# define SH_DICT "libshell" +#endif + + +#define sh_contexttoshell(context) ((context)?((context)->shp):(NULL)) + +static const char sh_optpoll[] = +"[-?\n@(#)$Id: poll (AT&T Labs Research) 2012-07-20 $\n]" +"[-author?Roland Mainz <roland.ma...@nrubsig.org>]" +"[-license?http://www.eclipse.org/org/documents/epl-v10.html]" +"[+NAME? poll - input/output multiplexing]" +"[+DESCRIPTION?The poll command provides applications with a mechanism " + "for multiplexing input/output over a set of file descriptors. " + "For each member of the (optionally sparse) indexed or associative " + "array variable \bvar\b, poll examines the given file descriptor " + "in the subscript \b.fd\b for the event(s) specified in the " + "subscript \b.events\b. " + "The poll command identifies those file descriptors on which an " + "application can read or write data, or on which certain events have " + "occurred.]" +"[+?The \bvar\b argument specifies an array of file descriptors to be examined " + "and the events of interest for each file descriptor. " + "It is a array of compound variables (or user-defined type) with one " + "member for each open file descriptor of interest. The array's compound " + "or type variable members contain the following subscripts:]{" + "[+?\b.fd\b # file descriptor]" + "[+?\b.events\b # requested events]" + "[+?\b.revents\b # returned event]" + "}" +"[+?The \bfd\b variable specifies an open file descriptor and the " + "\bevents\b and \brevents\b members are strings constructed from " + "a concaternation of the following event flags, seperated by '|':]" + "{ " + "[+POLLIN?Data other than high priority data may be " + "read without blocking. For STREAMS, this " + "flag is set in revents even if the message " + "is of zero length.]" + "[+POLLRDNORM?Normal data (priority band equals 0) may be " + "read without blocking. For STREAMS, this " + "flag is set in revents even if the message " + "is of zero length.]" + "[+POLLRDBAND?Data from a non-zero priority band may be " + "read without blocking. For STREAMS, this " + "flag is set in revents even if the message " + "is of zero length.]" + "[+POLLPRI?High priority data may be received without " + "blocking. For STREAMS, this flag is set in " + "revents even if the message is of zero " + "length.]" + "[+POLLOUT?Normal data (priority band equals 0) may be " + "written without blocking.]" + "[+POLLWRNORM?The same as POLLOUT.]" + "[+POLLWRBAND?Priority data (priority band > 0) may be " + "written. This event only examines bands " + "that have been written to at least once.]" + "[+POLLERR?An error has occurred on the device or " + "stream. This flag is only valid in the " + "revents bitmask; it is not used in the " + "events member.]" + "[+POLLHUP?A hangup has occurred on the stream. This " + "event and POLLOUT are mutually exclusive; a " + "stream can never be writable if a hangup has " + "occurred. However, this event and POLLIN, " + "POLLRDBAND, or POLLPRI are not " + "mutually exclusive. This flag is only valid " + "in the revents bitmask; it is not used in " + "the events member.]" + "[+POLLNVAL?The specified fd value does not belong to an " + "open file. This flag is only valid in the " + "revents member; it is not used in the events " + "member.]" + "}" +"]" + +"[+?If the value fd is less than 0, events is ignored and " + "revents is set to 0 in that entry on return from poll.]" + +"[+?The results of the poll query are stored in the revents " + "member in the \bvar\b structure. POLL*-strings are set in the \brevents\b " + "variable to indicate which of the requested events are true. " + "If none are true, the \brevents\b will be an empty string when " + "the poll command returns. The event flags " + "POLLHUP, POLLERR, and POLLNVAL are always set in \brevents\b " + "if the conditions they indicate are true; this occurs even " + "though these flags were not present in events.]" + +"[+?If none of the defined events have occurred on any selected " + "file descriptor, poll waits at least timeout milliseconds " + "for an event to occur on any of the selected file descriptors. " + "On a computer where millisecond timing accuracy is not " + "available, timeout is rounded up to the nearest legal value " + "available on that system. If the value timeout is 0, poll " + "returns immediately. If the value of timeout is -1, poll " + "blocks until a requested event occurs or until the call is " + "interrupted.]" + +"[+?The poll function supports regular files, terminal and " + "pseudo-terminal devices, STREAMS-based files, FIFOs and " + "pipes. The behavior of poll on elements of fds that refer " + "to other types of file is unspecified.]" + +"[+?The poll function supports sockets.]" + +"[+?A file descriptor for a socket that is listening for connections " + "will indicate that it is ready for reading, once connections " + "are available. A file descriptor for a socket that " + "is connecting asynchronously will indicate that it is ready " + "for writing, once a connection has been established.]" + +"[+?Regular files always poll TRUE for reading and writing.]" + +"[e:eventarray]:[fdcount?Upon successful completion, an indexed array " + "of strings is returned which contains a list of array subscripts " + "in the poll array which received events.]" +"[S!:pollsfio?Look into sfio streams for buffered information and set " + "POLLIN/POLLOUT to reflect sfio stream state.]" +"[R:pollttyraw?Put tty connections into raw mode when polling. The fd is " + "returned to tty cooked mode before poll(1) exits.]" +"[t:timeout]:[seconds?Timeout in seconds. If the value timeout is 0, " + "poll returns immediately. If the value of timeout is -1, poll " + "blocks until a requested event occurs or until the call is " + "interrupted.]" +"\n" +"\nvar\n" +"\n" +"[+EXIT STATUS?]{" + "[+0?Success.]" + "[+>0?An error occurred.]" +"}" +#ifdef WHO_KNOWS_HOW_TO_QUOTE_THIS_FOR_AST_GETOPTS +"[+EXAMPLE?The following example will wait for 10 seconds for input on fd 0:]" + "[+?compound -A p=( \\[0\\]=( fd=0 events=\"POLLIN\" ) ) ; poll -t10 p ; print -v p]" +#endif +"[+SEE ALSO?\bopen\b(1),\btmpfile\b(1),\bdup\b(1),\bclose\b(1),\bpoll\b(2)]" +; + + +static +Namval_t *nv_open_fmt(Dt_t *dict, int flags, const char *namefmt, ...) +{ + char varnamebuff[PATH_MAX]; + va_list ap; + + va_start(ap, namefmt); + vsnprintf(varnamebuff, sizeof(varnamebuff), namefmt, ap); + va_end(ap); + + return nv_open(varnamebuff, dict, flags); +} + +#ifndef _lib_stpcpy +/* + * |stpcpy| - like |strcpy()| but returns the end of the buffer + * + * Copy string s2 to s1. s1 must be large enough. + * return s1-1 (position of string terminator ('\0') in destination buffer). + */ +static +char *mystpcpy(char *s1, const char *s2) +{ + while (*s1++ = *s2++) + ; + return (s1-1); +} +#define stpcpy(s1, s2) mystpcpy((s1), (s2)) +#endif + +static +int poll_strtoevents(const char *str) +{ + int events = 0; + + /* Using |strstr()| is not elegant but it is simple and saves code space */ + if (strstr(str, "POLLIN")) events |= POLLIN; +#ifdef POLLRDNORM + if (strstr(str, "POLLRDNORM")) events |= POLLRDNORM; +#endif +#ifdef POLLRDBAND + if (strstr(str, "POLLRDBAND")) events |= POLLRDBAND; +#endif + if (strstr(str, "POLLPRI")) events |= POLLPRI; + if (strstr(str, "POLLOUT")) events |= POLLOUT; +#ifdef POLLWRNORM + if (strstr(str, "POLLWRNORM")) events |= POLLWRNORM; +#endif +#ifdef POLLWRBAND + if (strstr(str, "POLLWRBAND")) events |= POLLWRBAND; +#endif +#ifdef POLLMSG + if (strstr(str, "POLLMSG")) events |= POLLMSG; +#endif +#ifdef POLLREMOVE + if (strstr(str, "POLLREMOVE")) events |= POLLREMOVE; +#endif +#ifdef POLLRDHUP + if (strstr(str, "POLLRDHUP")) events |= POLLRDHUP; +#endif + if (strstr(str, "POLLERR")) events |= POLLERR; + if (strstr(str, "POLLHUP")) events |= POLLHUP; + if (strstr(str, "POLLNVAL")) events |= POLLNVAL; + + return events; +} + + +static +void poll_eventstostr(char *buff, int events) +{ + char *s=buff; + + *s='\0'; + if (!events) + return; + + if (events & POLLIN) s=stpcpy(s, "POLLIN|"); +#ifdef POLLRDNORM + if (events & POLLRDNORM) s=stpcpy(s, "POLLRDNORM|"); +#endif +#ifdef POLLRDBAND + if (events & POLLRDBAND) s=stpcpy(s, "POLLRDBAND|"); +#endif + if (events & POLLPRI) s=stpcpy(s, "POLLPRI|"); + if (events & POLLOUT) s=stpcpy(s, "POLLOUT|"); +#ifdef POLLWRNORM + if (events & POLLWRNORM) s=stpcpy(s, "POLLWRNORM|"); +#endif +#ifdef POLLWRBAND + if (events & POLLWRBAND) s=stpcpy(s, "POLLWRBAND|"); +#endif +#ifdef POLLMSG + if (events & POLLMSG) s=stpcpy(s, "POLLMSG|"); +#endif +#ifdef POLLREMOVE + if (events & POLLREMOVE) s=stpcpy(s, "POLLREMOVE|"); +#endif +#ifdef POLLRDHUP + if (events & POLLRDHUP) s=stpcpy(s, "POLLRDHUP|"); +#endif + if (events & POLLERR) s=stpcpy(s, "POLLERR|"); + if (events & POLLHUP) s=stpcpy(s, "POLLHUP|"); + if (events & POLLNVAL) s=stpcpy(s, "POLLNVAL|"); + + /* Return if we didn't write anything to the buffer */ + if (s==buff) + return; + + /* Remove trailling '|' */ + s--; + if(*s=='|') + *s='\0'; +} + +static +const char *mysfstrdup(Sfio_t *str, const char *s) +{ + ssize_t pos=stktell(str); + sfputr(str, s, 0); + return stkptr(str, pos); +} + +#undef getconf +#define getconf(x) (strtol(astconf((x), NiL, NiL), NiL, 0)) + +static +ssize_t getsfiopollflags(Sfio_t*sfd) +{ + ssize_t flags; + + if (sfpoll(&sfd, 1, 0) == 1) + flags=sfvalue(sfd); + else + flags=0; + + return flags; +} + + +#ifdef DEBUG_CMD_POLL +#define D(x) x +#else +#define D(x) +#endif + +/* structure to keep track of information from |sfpoll()| */ +struct pollstat +{ + Sfio_t *sfd; + ssize_t flags; +}; + +extern int b_poll(int argc, char *argv[], Shbltin_t* context) +{ + Shell_t *shp = sh_contexttoshell(context); + Namval_t *np, *array_np, *array_np_sub; + const char *subname; + char *varname; + int n; + int fd; + nfds_t numpollfd = 0; + int i; + char *s; + double timeout = -1.; + char buff[PATH_MAX*2+1]; /* fixme: theoretically enough to hold two variable names */ + char *eventarrayname = NULL; + bool ttyraw = false; + bool pollsfio = true; + Sfio_t *strbuff=NULL; + int retval=0; + + while (n = optget(argv, sh_optpoll)) switch (n) + { + case 't': + errno = 0; + timeout = strtod(opt_info.arg, (char **)NULL); + if (errno != 0) + errormsg(SH_DICT, ERROR_system(1), "%s: invalid timeout", opt_info.arg); + + /* -t uses seconds */ + if (timeout >=0) + timeout *= 1000.; + break; + case 'e': + eventarrayname = opt_info.arg; + break; + case 'S': + pollsfio=opt_info.num?true:false; + break; + case 'R': + ttyraw=opt_info.num?true:false; + break; + case ':': + errormsg(SH_DICT, 2, "%s", opt_info.arg); + break; + case '?': + errormsg(SH_DICT, ERROR_usage(2), "%s", opt_info.arg); + break; + } + argc -= opt_info.index; + argv += opt_info.index; + if(argc!=1) + errormsg(SH_DICT, ERROR_usage(2), optusage((char*)0)); + + varname = argv[0]; + + strbuff = sfstropen(); + if (!strbuff) + errormsg(SH_DICT, ERROR_system(1), "out of memory for sfstropen()"); + + D(if (!pollsfio) fprintf(stderr, "#poll-debug: sfio polling DISABLED\n")); + D(if (!ttyraw) fprintf(stderr, "#poll-debug: ttyraw DISABLED\n")); + + array_np = nv_open(varname, shp->var_tree, NV_VARNAME|NV_NOFAIL|NV_NOADD); + if (!array_np) + { + sfclose(strbuff); + errormsg(SH_DICT, ERROR_system(1), "cannot find array variable %s", varname); + } + if (!nv_isattr(array_np, NV_ARRAY)) + { + nv_close(array_np); + sfclose(strbuff); + errormsg(SH_DICT, ERROR_system(1), "variable %s is not an array", varname); + } + + /* Count number of array elememts. We need to do it "manually" to + * handle sparse indexed and associative arrays */ + nv_putsub(array_np, NULL, ARRAY_SCAN); + array_np_sub = array_np; + do + { + if (!(subname=nv_getsub(array_np_sub))) + break; + numpollfd++; + } while(array_np_sub && nv_nextsub(array_np_sub)); + + /* We must allocate one more entry, C99 "variable length + * arrays"(="VLA") with zero elements do not work with all + * compilers */ + struct pollfd pollfd[numpollfd+1]; + struct pollstat pollstat[numpollfd+1]; + const char *poll_array_subnames[numpollfd+1]; + + nv_putsub(array_np, NULL, ARRAY_SCAN); + array_np_sub = array_np; + i = 0; + do + { + if (!(subname=nv_getsub(array_np_sub))) + break; + + poll_array_subnames[i]=mysfstrdup(strbuff, subname); + if (!poll_array_subnames[i]) + { + errormsg(SH_DICT, ERROR_warn(1), "out of memory"); + goto done_error; + } + + np = nv_open_fmt(shp->var_tree, NV_VARNAME|NV_NOFAIL|NV_NOADD, "%s[%s].fd", varname, subname); + if (!np) + { + errormsg(SH_DICT, ERROR_warn(1), "missing pollfd %s[%s].fd", varname, subname); + goto done_error; + } + fd = (int)nv_getnum(np); + nv_close(np); + if ((fd < -1) || (fd > OPEN_MAX)) + { + errormsg(SH_DICT, ERROR_warn(1), "invalid pollfd pollfd %s[%s].fd %d", varname, subname, fd); + goto done_error; + } + pollfd[i].fd = fd; + + np = nv_open_fmt(shp->var_tree, NV_VARNAME|NV_NOFAIL|NV_NOADD, "%s[%s].events", varname, subname); + if (!np) + { + errormsg(SH_DICT, ERROR_warn(1), "missing pollfd %s[%s].events", varname, subname); + goto done_error; + } + + s = nv_getval(np); + if (!s) + { + errormsg(SH_DICT, ERROR_warn(1), "missing pollfd events value"); + nv_close(np); + goto done_error; + } + pollfd[i].events = poll_strtoevents(s); + nv_close(np); + + pollfd[i].revents = 0; + + if (pollsfio) + { + pollstat[i].sfd=(fd>=0)?sh_fd2sfio(fd):NULL; + if (pollstat[i].sfd!=NULL) + { + /* + * This algorithm is not very smart. Calling + * |sfpoll()| (via |getsfiopollflags()| wrapper) + * means we |poll()| once per fd to check whether + * sfio has any data buffered. + * However there is currently no (semi-)public sfio + * API which can poll on sfio streams (including + * calling SFPOLL disciplines if neccesary), provides + * access to all other poll(2) flags/information + * and can turn the probing of sfio buffered data on/off + * on demand. + */ + pollstat[i].flags=getsfiopollflags(pollstat[i].sfd); + + if (((pollfd[i].events & POLLIN) && (pollstat[i].flags & SF_READ)) || + ((pollfd[i].events & POLLOUT) && (pollstat[i].flags & SF_WRITE))) + timeout=0; + } + else + { + pollstat[i].flags=0; + } + } + + i++; + } while(array_np_sub && nv_nextsub(array_np_sub)); + + nv_close(array_np); + array_np=NULL; + + if (ttyraw) + { + for (i=0 ; i < numpollfd ; i++) + { + fd=pollfd[i].fd; + if ((fd >=0) && (shp->fdstatus[fd]&IOTTY)) + tty_raw(fd, 1); + } + } + + /* fixme: When re-trying after |EINTR| or |EAGAIN| we should + * substract the time already spent in previous calls to + * |poll()| from |timeout| */ + while(((n = poll(pollfd, numpollfd, (int)timeout)) < 0) && + ((errno == EINTR) || (errno == EAGAIN)) && + (!context->sigset)) + errno=0; + + if (ttyraw) + { + for (i=0 ; i < numpollfd ; i++) + { + fd=pollfd[i].fd; + if ((fd >=0) && (shp->fdstatus[fd]&IOTTY)) + tty_cooked(fd); + } + } + + if (n < 0) + { + errormsg(SH_DICT, ERROR_warn(1), "failure"); + retval=1; + } + + if (eventarrayname) + { + np = nv_open_fmt(shp->var_tree, NV_VARNAME|NV_ARRAY|NV_NOFAIL, "%s", eventarrayname); + if (np) + nv_close(np); + else + { + errormsg(SH_DICT, ERROR_warn(1), "could not create poll count variable %s", eventarrayname); + goto done_error; + } + } + + for(i=0 ; i < numpollfd ; i++) + { + subname=poll_array_subnames[i]; + + np = nv_open_fmt(shp->var_tree, NV_VARNAME|NV_NOFAIL, "%s[%s].revents", varname, subname); + if (!np) + { + errormsg(SH_DICT, ERROR_warn(1), "could not create pollfd %s[%s].revents", varname, subname); + continue; + } + + if (pollsfio) + { + if ((pollfd[i].events & POLLIN) && (pollstat[i].flags & SF_READ)) + pollfd[i].revents |= POLLIN; + if ((pollfd[i].events & POLLOUT) && (pollstat[i].flags & SF_WRITE)) + pollfd[i].revents |= POLLOUT; + } + + poll_eventstostr(buff, pollfd[i].revents); + nv_putval(np, buff, 0); + + nv_close(np); + + if (eventarrayname && pollfd[i].revents) + { + sprintf(buff, "%s+=( '%s' )", eventarrayname, subname); + sh_trap(buff, 0); + } + } + + goto done; + +done_error: + retval=1; +done: + if (array_np) + nv_close(array_np); + if (strbuff) + sfclose(strbuff); + + return(retval); +} diff -r -u -N original/src/cmd/ksh93/data/builtins.c build_poll/src/cmd/ksh93/data/builtins.c --- src/cmd/ksh93/data/builtins.c 2012-06-19 10:02:12.000000000 +0200 +++ src/cmd/ksh93/data/builtins.c 2012-07-24 16:48:50.366790951 +0200 @@ -109,6 +109,7 @@ #endif /* JOBS */ "false", NV_BLTIN|BLT_ENV, bltin(false), "getopts", NV_BLTIN|BLT_ENV, bltin(getopts), + "poll", NV_BLTIN, bltin(poll), "print", NV_BLTIN|BLT_ENV, bltin(print), "printf", NV_BLTIN|BLT_ENV, bltin(printf), "pwd", NV_BLTIN, bltin(pwd), diff -r -u -N original/src/cmd/ksh93/include/builtins.h build_poll/src/cmd/ksh93/include/builtins.h --- src/cmd/ksh93/include/builtins.h 2012-01-10 20:11:54.000000000 +0100 +++ src/cmd/ksh93/include/builtins.h 2012-07-24 16:48:50.367268676 +0200 @@ -100,6 +100,7 @@ extern int b_whence(int, char*[],Shbltin_t*); extern int b_alarm(int, char*[],Shbltin_t*); +extern int b_poll(int, char*[],Shbltin_t*); extern int b_print(int, char*[],Shbltin_t*); extern int b_printf(int, char*[],Shbltin_t*); extern int b_pwd(int, char*[],Shbltin_t*); diff -r -u -N original/src/cmd/ksh93/Makefile build_poll/src/cmd/ksh93/Makefile --- src/cmd/ksh93/Makefile 2012-06-19 09:46:54.000000000 +0200 +++ src/cmd/ksh93/Makefile 2012-07-24 16:48:50.367629371 +0200 @@ -161,6 +161,7 @@ shell$(RELEASE) $(VERSION) id=shell :LIBRARY: shell.3 nval.3 alarm.c cd_pwd.c cflow.c deparse.c \ enum.c getopts.c hist.c misc.c print.c read.c sleep.c trap.c test.c \ + poll.c \ typeset.c ulimit.c umask.c whence.c main.c nvdisc.c nvtype.c \ arith.c args.c array.c completion.c defs.c edit.c expand.c regress.c \ fault.c fcin.c history.c init.c io.c jobs.c lex.c macro.c name.c \ diff -r -u -N original/src/cmd/ksh93/Mamfile build_poll/src/cmd/ksh93/Mamfile --- src/cmd/ksh93/Mamfile 2012-06-30 01:55:19.000000000 +0200 +++ src/cmd/ksh93/Mamfile 2012-07-24 16:48:50.368931742 +0200 @@ -548,6 +548,22 @@ prev bltins/test.c exec - ${CC} ${mam_cc_FLAGS} ${CCFLAGS} -I. -Iinclude -I${PACKAGE_ast_INCLUDE} -D_API_ast=20100309 -D_PACKAGE_ast -D_BLD_shell -DSHOPT_DYNAMIC -DSHOPT_MULTIBYTE -DSHOPT_PFSH -DSHOPT_STATS -DSHOPT_NAMESPACE -DSHOPT_COSHELL -DSHOPT_HISTEXPAND -DERROR_CONTEXT_T=Error_context_t -DSHOPT_FIXEDARRAY -DSHOPT_ESH -DKSHELL -c bltins/test.c done test.o generated + +make poll.o +make bltins/poll.c +prev ${PACKAGE_ast_INCLUDE}/tmx.h implicit +prev FEATURE/poll implicit +prev FEATURE/externs implicit +prev include/builtins.h implicit +prev include/io.h implicit +prev ${PACKAGE_ast_INCLUDE}/error.h implicit +prev include/defs.h implicit +done bltins/poll.c +meta poll.o %.c>%.o bltins/poll.c test +prev bltins/poll.c +exec - ${CC} ${mam_cc_FLAGS} ${CCFLAGS} -I. -Iinclude -I${PACKAGE_ast_INCLUDE} -D_API_ast=20100309 -D_PACKAGE_ast -D_BLD_shell -DSHOPT_DYNAMIC -DSHOPT_MULTIBYTE -DSHOPT_PFSH -DSHOPT_STATS -DSHOPT_NAMESPACE -DSHOPT_COSHELL -DSHOPT_HISTEXPAND -DERROR_CONTEXT_T=Error_context_t -DSHOPT_FIXEDARRAY -DSHOPT_ESH -DKSHELL -c bltins/poll.c +done poll.o generated + make typeset.o make bltins/typeset.c prev FEATURE/dynamic implicit @@ -1328,7 +1344,7 @@ prev edit/hexpand.c exec - ${CC} ${mam_cc_FLAGS} ${CCFLAGS} -I. -Iinclude -I${PACKAGE_ast_INCLUDE} -DSHOPT_HISTEXPAND -DSHOPT_EDPREDICT -DSHOPT_MULTIBYTE -DKSHELL -DSHOPT_ESH -DSHOPT_VSH -D_PACKAGE_ast -DSHOPT_PFSH -DSHOPT_STATS -DSHOPT_NAMESPACE -DSHOPT_COSHELL -D_BLD_shell -D_API_ast=20100309 -DERROR_CONTEXT_T=Error_context_t -DSHOPT_FIXEDARRAY -c edit/hexpand.c done hexpand.o generated -exec - ${AR} rc libshell.a alarm.o cd_pwd.o cflow.o deparse.o enum.o getopts.o hist.o misc.o print.o read.o sleep.o trap.o test.o typeset.o ulimit.o umask.o whence.o main.o nvdisc.o nvtype.o arith.o args.o array.o completion.o defs.o edit.o expand.o regress.o fault.o fcin.o +exec - ${AR} rc libshell.a alarm.o cd_pwd.o cflow.o deparse.o enum.o getopts.o hist.o misc.o poll.o print.o read.o sleep.o trap.o test.o typeset.o ulimit.o umask.o whence.o main.o nvdisc.o nvtype.o arith.o args.o array.o completion.o defs.o edit.o expand.o regress.o fault.o fcin.o exec - ${AR} rc libshell.a history.o init.o io.o jobs.o lex.o macro.o name.o nvtree.o parse.o path.o string.o streval.o subshell.o tdump.o timers.o trestore.o waitevent.o xec.o env.o limits.o msg.o strdata.o testops.o keywords.o options.o signals.o aliases.o builtins.o variables.o lexstates.o emacs.o vi.o hexpand.o exec - (ranlib libshell.a) >/dev/null 2>&1 || true done libshell.a generated
#!/usr/bin/ksh93 ######################################################################## # # # This software is part of the ast package # # Copyright (c) 2009-2012 Roland Mainz # # and is licensed under the # # Eclipse Public License, Version 1.0 # # by AT&T Intellectual Property # # # # A copy of the License is available at # # http://www.eclipse.org/org/documents/epl-v10.html # # (with md5 checksum b35adb5213ca9657e911e9befb180842) # # # # # # Roland Mainz <roland.ma...@nrubsig.org> # # # ######################################################################## # # Copyright (c) 2009, 2012, Roland Mainz. All rights reserved. # # # shircbot - a simple IRC client/bot demo # # Solaris needs /usr/xpg6/bin:/usr/xpg4/bin because the tools in /usr/bin are not POSIX-conformant export PATH='/usr/xpg6/bin:/usr/xpg4/bin:/bin:/usr/bin' # Make sure all math stuff runs in the "C" locale to avoid problems # with alternative # radix point representations (e.g. ',' instead of # '.' in de_DE.*-locales). This needs to be set _before_ any # floating-point constants are defined in this script). if [[ "${LC_ALL-}" != '' ]] ; then export \ LC_MONETARY="${LC_ALL}" \ LC_MESSAGES="${LC_ALL}" \ LC_COLLATE="${LC_ALL}" \ LC_CTYPE="${LC_ALL}" unset LC_ALL fi export LC_NUMERIC='C' # Definition for a IRC session class typeset -T ircsession_t=( compound server=( typeset name integer port ) typeset -a join_channels typeset nick='ksh93irc' typeset running='true' integer fd=-1 function createsession { integer i set -o xtrace _.server.name="$1" _.server.port="$2" _.nick="$3" nameref jc="$4" for (( i=0 ; i < ${#jc[@]} ; i++ )) ; do _.join_channels+=( "${jc[i]}" ) done redirect {_.fd}<> "/dev/tcp/${_.server.name}/${_.server.port}" (( $? == 0 )) || { print -n2 $"Could not open server connection." ; return 1 ; } printf 'fd=%d\n' _.fd return 0 } function login { { printf 'USER %s %s %s %s\n' "${_.nick}" "${_.nick}" "${_.nick}" "${_.nick}" printf 'NICK %s\n' "${_.nick}" } >&${_.fd} return 0 } function join_channel { printf 'JOIN %s\n' "$1" >&${_.fd} return 0 } function mainloop { typeset line float -S last_tick=0 # We use the linebuf_t class here since network traffic # isn't guranteed to fit a single $'\n'-terminated line # into one TCP package. linebuf_t buffers characters # until it has one complete line. This avoids the need for # async I/O normally used by IRC clients linebuf_t serverbuf linebuf_t clientbuf integer fd=${_.fd} integer infd redirect {infd}< '/dev/stdin' _.login if builtin poll 2>'/dev/null' ; then print -u2 $"## Using poll(1) builtin for event loop..." while ${_.running} ; do compound -A pt=( [irc]=( fd=${fd} events='POLLIN' ) [userinput]=( fd=${infd} events='POLLIN' ) ) # poll all fds but return every 3 seconds to do clock # processing for _.mainloop_tick below poll -R -t3 pt || print $"poll error $?" #{ printf $"#%T: " 'now' ; print -C pt ; } if [[ "${pt[irc].revents}" == *POLLIN* ]] ; then if serverbuf.readbuf line ${fd} ; then _.dispatch_serverevent "${line}" fi fi if [[ "${pt[userinput].revents}" == *POLLIN* ]] ; then if clientbuf.readbuf line ${infd} ; then printf $"client: %q\n" "${line}" printf '%s\n' "${line}" >&${fd} fi fi # call mainloop_tick function in intervals to handle # async events (e.g. automatic /join etc.) if (( (SECONDS-last_tick) > 5. )) ; then (( last_tick=SECONDS )) _.mainloop_tick fi done else print -u2 $"## Using manual read(1)-based polling for event loop..." while ${_.running} ; do while serverbuf.readbuf line ${fd} ; do _.dispatch_serverevent "${line}" done while clientbuf.readbuf line ${infd} ; do printf $"client: %q\n" "${line}" printf '%s\n' "${line}" >&${fd} done # call mainloop_tick function in intervals to handle # async events (e.g. automatic /join etc.) if (( (SECONDS-last_tick) > 5. )) ; then (( last_tick=SECONDS )) _.mainloop_tick fi done fi return 0 } function mainloop_tick { return 0 } function dispatch_serverevent { typeset line="$1" case "${line}" in ~(El)PING) compound ping_args=( typeset line="$line" ) _.serverevent_ping ping_args ;; ~(El):.*\ PRIVMSG) compound privmsg_args=( typeset line="$line" typeset msguser="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\1}" typeset msgchannel="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\3}" typeset msg="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\4}" ) _.serverevent_privmsg privmsg_args ;; ~(El):.*\ INVITE) compound invite_args=( typeset line="$line" typeset inviteuser="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\1}" typeset invitenick="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\3}" typeset invitechannel="${line/~(Elr)([^ ]+) ([^ ]+) ([^ ]+) (.*)/\4}" ) _.serverevent_invite invite_args ;; *) printf $"server: %q\n" "${line}" ;; esac return 0 } function serverevent_privmsg { nameref args=$1 typeset msguser="${args.msguser}" typeset msgchannel="${args.msgchannel}" typeset msg="${args.msg}" printf $"#privmsg: user=%q, channel=%q, msg=%q\n" "$msguser" "$msgchannel" "$msg" return 0 } function serverevent_invite { nameref args=$1 printf 'JOIN %s\n' "${args.invitechannel/:/}" >&${_.fd} return 0 } function send_privmsg { typeset channel="$1" typeset msg="$2" # Do we have to escape any characters in "msg" ? printf 'PRIVMSG %s :%s\n' "${channel}" "${msg}" >&${_.fd} return 0 } function serverevent_ping { nameref args=$1 printf 'PONG %s\n' "${args.line/~(Elr)([^ ]+) ([^ ]+).*/\2}" >&${_.fd} return 0 } ) # line buffer class # The buffer class tries to read characters from the given <fd> until # it has read a whole line. typeset -T linebuf_t=( typeset buf function reset { _.buf='' return 0 } function readbuf { nameref var=$1 integer fd=$2 typeset ch while IFS='' read -u${fd} -r -t 0.2 -N 1 ch ; do [[ "${ch}" == $'\r' ]] && continue if [[ "${ch}" == $'\n' ]] ; then var="${_.buf}" _.reset return 0 fi _.buf+="$ch" done return 1 } ) function usage { OPTIND=0 getopts -a "${progname}" "${shircbot_usage}" OPT '-?' exit 2 } function main { compound config=( typeset nickname="${LOGNAME}bot" typeset servername='irc.freenode.net' integer port=6667 typeset -a join_channels ) ircsession_t mybot=( # override ircsession_t::serverevent_privmsg with a new method for our bot function serverevent_privmsg { nameref args=$1 typeset msguser="${args.msguser}" typeset msgchannel="${args.msgchannel}" typeset msg="${args.msg}" printf $"#message: user=%q, channel=%q, msg=%q\n" "${msguser}" "${msgchannel}" "${msg}" # Check if we get a private message if [[ "${msgchannel}" == "${_.nick}" ]] ; then # ${msgchannel} point to our own nick if we got a private message, # we need to extract the sender's nickname from ${msguser} and put # it into msgchannel msgchannel="${msguser/~(El):(.*)!.*/\1}" else # check if this is a command for this bot [[ "$msg" != ~(Eli):${_.nick}:[[:space:]] ]] && return 0 fi # strip beginning (e.g. ":<nick>:" or ":") plus extra spaces msg="${msg/~(Eli)(:${_.nick})?:[[:space:]]*/}" printf 'botmsg=%q, msguser=%q, msgchannel=%q\n' "${msg}" "${msguser}" "${msgchannel}" case "${msg}" in ~(Eli)date) _.send_privmsg "${msgchannel}" "${ printf '%(%Y-%m-%d, %Th/%Z)T\n' 'now' }" ;; ~(Eli)echo) _.send_privmsg "${msgchannel}" "${msg#*echo}" ;; ~(Eli)exitbot) typeset exitkey="${ printf '%s\n' "$msguser" | sum -x sha1 ; }" # this is unwise&&insecure if [[ "${msg}" == *"${exitkey}"* ]] ; then _.running='false' else printf $"Wrong exit key, expected match for *%q*, got %q\n" "${exitkey}" "${msg}" fi ;; ~(Eli)help) _.send_privmsg "${msgchannel}" "${ printf $"Hello, this is shircbot, written in ksh93 (%s). " "${.sh.version}" printf $"Subcommands are 'say hello', 'math <math-expr>', 'stocks', 'uptime', 'uuid', 'date' and 'echo'." }" ;; ~(Eli)math) if [[ "${msg}" == ~(E)[\`\$] ]] ; then # "restricted" shell mode would prevent any damage but we try to be carefull... _.send_privmsg "${msgchannel}" $"Syntax error." else typeset mathexpr="${msg#*math}" printf $"Calculating '%s'\n" "${mathexpr}" _.send_privmsg "${msgchannel}" "${ # run in subshell with restricted shell mode turned on ( printf 'export PATH="/usr/${RANDOM}/$$/${RANDOM}/foo" set -o restricted printf "%%s = %%.40g\n" "%s" $(( %s ))\n' "${mathexpr}" "${mathexpr}" | source /dev/stdin 2>&1 ) }" fi ;; ~(Eli)say[[:space:]]+hello) _.send_privmsg "$msgchannel" $"Hello, this is a bot." ;; ~(Eli)stocks) typeset stockmsg tickersymbol for tickersymbol in 'ORCL' 'IBM' 'AAPL' 'HPQ' ; do stockmsg="$( # some versions of Solaris have 'wget' in /usr/sfw/bin, others in /usr/bin export PATH='/usr/sfw/bin:/usr/bin' wget --quiet --output-document=- \ "http://quote.yahoo.com/d/quotes.csv?f=sl1d1t1c1ohgv&e=.csv&s=${tickersymbol}" 2>&1 )" _.send_privmsg "${msgchannel}" "${tickersymbol}: ${stockmsg//,/ }" done ;; ~(Eli)tell) typeset tell_user="${msg/~(Eli)tell[[:blank:]]*([#_[:alnum:]]*):?[[:blank:]]+(.*)/\1}" typeset tell_msg="${.sh.match[2]-}" printf 'telluser=%q, tellmsg=%q\n' "${tell_user}" "${tell_msg}" [[ "${tell_msg}" != '' ]] && _.send_privmsg "${tell_user}" "${tell_msg}" ;; ~(Eli)uptime) _.send_privmsg "${msgchannel}" "${ /usr/bin/uptime }" ;; ~(Eli)uuid) _.send_privmsg "${msgchannel}" "${ printf '%(%Y%m%d,%S,%N)T,%d,%s\n' 'now' RANDOM "${msguser}" | sum -x sha256 }" ;; esac return 0 } # Automatically join the list of channels listed in |config.join_channels| # after the client is connected to the server for some time function mainloop_tick { integer -S autojoin_done=2 integer i if (( autojoin_done-- == 0 && ${#_.join_channels[@]} > 0 )) ; then printf $"# Autojoin channels %q...\n" "${_.join_channels[*]}" for (( i=0 ; i < ${#_.join_channels[@]} ; i++ )) ; do _.join_channel "${_.join_channels[i]}" done fi return 0 } ) while getopts -a "${progname}" "${shircbot_usage}" OPT ; do case "${OPT}" in 'n') config.nickname="${OPTARG}" ;; 's') config.servername="${OPTARG}" ;; 'j') config.join_channels+=( "${OPTARG}" ) ;; *) usage ;; esac done shift $(( OPTIND-1 )) # if no channel was provided we join a predefined set of channels if (( ${#config.join_channels[@]} == 0 )) ; then if [[ "${config.servername}" == 'irc.freenode.net' ]] ; then # Illumos main channel config.join_channels+=( '#illumos' ) # ksh channel config.join_channels+=( '#ksh' ) # OpenIndiana distribution channels config.join_channels+=( '#oi-dev' ) config.join_channels+=( '#openindiana' ) # OpenSolaris channels config.join_channels+=( '#opensolaris' ) config.join_channels+=( '#opensolaris-dev' ) config.join_channels+=( '#opensolaris-arc' ) config.join_channels+=( '#opensolaris-meeting' ) # OSPKG channel config.join_channels+=( '#ospkg' ) elif [[ "${config.servername}" == ~(E)irc.(sfbay|sweden) ]] ; then config.join_channels+=( '#onnv' ) fi fi print $"## Start." mybot.createsession "${config.servername}" ${config.port} "${config.nickname}" config.join_channels # This is a network-facing application - once we've set eveything up # we set PATH to a random value and switch to the shell's restricted # mode to make sure noone can escape the jail. #export PATH=/usr/$RANDOM/foo #set -o restricted mybot.mainloop print $"## End." return 0 } # program start # (be carefull with builtins here - they are unconditionally available # in the shell's "restricted" mode) builtin basename builtin sum set -o nounset set -o noglob typeset progname="${ basename "${0}" ; }" typeset -r shircbot_usage=$'+ [-?\n@(#)\$Id: shircbot (Roland Mainz) 2012-07-20 \$\n] [-author?Roland Mainz <roland.ma...@nrubsig.org>] [+NAME?shircbot - simple IRC bot demo] [+DESCRIPTION?\bshircbot\b is a small demo IRC bot which provides a simple IRC bot with several subcommands.] [n:nickname?IRC nickname for this bot.]:[nick] [s:ircserver?IRC servername.]:[servername] [j:joinchannel?IRC servername.]:[channelname] [+SEE ALSO?\bksh93\b(1)] ' main "$@" exit $? # EOF.
_______________________________________________ ast-developers mailing list ast-developers@research.att.com https://mailman.research.att.com/mailman/listinfo/ast-developers