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

Reply via email to