1/*
2 * zselect.c - builtin support for select system call
3 *
4 * This file is part of zsh, the Z shell.
5 *
6 * Copyright (c) 1998-2001 Peter Stephenson
7 * All rights reserved.
8 *
9 * Permission is hereby granted, without written agreement and without
10 * license or royalty fees, to use, copy, modify, and distribute this
11 * software and to distribute modified versions of this software for any
12 * purpose, provided that the above copyright notice and the following
13 * two paragraphs appear in all copies of this software.
14 *
15 * In no event shall Peter Stephenson or the Zsh Development
16 * Group be liable to any party for direct, indirect, special, incidental,
17 * or consequential damages arising out of the use of this software and
18 * its documentation, even if Peter Stephenson, and the Zsh
19 * Development Group have been advised of the possibility of such damage.
20 *
21 * Peter Stephenson and the Zsh Development Group specifically
22 * disclaim any warranties, including, but not limited to, the implied
23 * warranties of merchantability and fitness for a particular purpose.  The
24 * software provided hereunder is on an "as is" basis, and Peter Stephenson
25 * and the Zsh Development Group have no obligation to provide maintenance,
26 * support, updates, enhancements, or modifications.
27 *
28 */
29
30#include "zselect.mdh"
31#include "zselect.pro"
32
33/* Helper functions */
34
35/*
36 * Handle an fd by adding it to the current fd_set.
37 * Return 1 for error (after printing a message), 0 for OK.
38 */
39static int
40handle_digits(char *nam, char *argptr, fd_set *fdset, int *fdmax)
41{
42    int fd;
43    char *endptr;
44
45    if (!idigit(*argptr)) {
46	zwarnnam(nam, "expecting file descriptor: %s", argptr);
47	return 1;
48    }
49    fd = (int)zstrtol(argptr, &endptr, 10);
50    if (*endptr) {
51	zwarnnam(nam, "garbage after file descriptor: %s", endptr);
52	return 1;
53    }
54
55    FD_SET(fd, fdset);
56    if (fd+1 > *fdmax)
57	*fdmax = fd+1;
58    return 0;
59}
60
61/* The builtin itself */
62
63/**/
64static int
65bin_zselect(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
66{
67#ifdef HAVE_SELECT
68    int i, fd, fdsetind = 0, fdmax = 0, fdcount;
69    fd_set fdset[3];
70    const char fdchar[3] = "rwe";
71    struct timeval tv, *tvptr = NULL;
72    char *outarray = "reply", **outdata, **outptr;
73    char *outhash = NULL;
74    LinkList fdlist;
75
76    for (i = 0; i < 3; i++)
77	FD_ZERO(fdset+i);
78
79    for (; *args; args++) {
80	char *argptr = *args, *endptr;
81	zlong tempnum;
82	if (*argptr == '-') {
83	    for (argptr++; *argptr; argptr++) {
84		switch (*argptr) {
85		    /*
86		     * Array name for reply, if not $reply.
87		     * This gets set to e.g. `-r 0 -w 1' if 0 is ready
88		     * for reading and 1 is ready for writing.
89		     */
90		case 'a':
91		case 'A':
92		    i = *argptr;
93		    if (argptr[1])
94			argptr++;
95		    else if (args[1]) {
96			argptr = *++args;
97		    } else {
98			zwarnnam(nam, "argument expected after -%c", *argptr);
99			return 1;
100		    }
101		    if (idigit(*argptr) || !isident(argptr)) {
102			zwarnnam(nam, "invalid array name: %s", argptr);
103			return 1;
104		    }
105		    if (i == 'a')
106			outarray = argptr;
107		    else
108			outhash = argptr;
109		    /* set argptr to next to last char because of increment */
110		    while (argptr[1])
111			argptr++;
112		    break;
113
114		    /* Following numbers indicate fd's for reading */
115		case 'r':
116		    fdsetind = 0;
117		    break;
118
119		    /* Following numbers indicate fd's for writing */
120		case 'w':
121		    fdsetind = 1;
122		    break;
123
124		    /* Following numbers indicate fd's for errors */
125		case 'e':
126		    fdsetind = 2;
127		    break;
128
129		    /*
130		     * Get a timeout value in hundredths of a second
131		     * (same units as KEYTIMEOUT).  0 means just poll.
132		     * If not given, blocks indefinitely.
133		     */
134		case 't':
135		    if (argptr[1])
136			argptr++;
137		    else if (args[1]) {
138			argptr = *++args;
139		    } else {
140			zwarnnam(nam, "argument expected after -%c", *argptr);
141			return 1;
142		    }
143		    if (!idigit(*argptr)) {
144			zwarnnam(nam, "number expected after -t");
145			return 1;
146		    }
147		    tempnum = zstrtol(argptr, &endptr, 10);
148		    if (*endptr) {
149			zwarnnam(nam, "garbage after -t argument: %s",
150				 endptr);
151			return 1;
152		    }
153		    /* timevalue now active */
154		    tvptr = &tv;
155		    tv.tv_sec = (long)(tempnum / 100);
156		    tv.tv_usec = (long)(tempnum % 100) * 10000L;
157
158		    /* remember argptr is incremented at end of loop */
159		    argptr = endptr - 1;
160		    break;
161
162		    /* Digits following option without arguments are fd's. */
163		default:
164		    if (handle_digits(nam, argptr, fdset+fdsetind,
165				      &fdmax))
166			return 1;
167		}
168	    }
169	} else if (handle_digits(nam, argptr, fdset+fdsetind, &fdmax))
170	    return 1;
171    }
172
173    errno = 0;
174    do {
175	i = select(fdmax, (SELECT_ARG_2_T)fdset, (SELECT_ARG_2_T)(fdset+1),
176		   (SELECT_ARG_2_T)(fdset+2), tvptr);
177    } while (i < 0 && errno == EINTR && !errflag);
178
179    if (i <= 0) {
180	if (i < 0)
181	    zwarnnam(nam, "error on select: %e", errno);
182	/* else no fd's set.  Presumably a timeout. */
183	return 1;
184    }
185
186    /*
187     * Make a linked list of all file descriptors which are ready.
188     * These go into an array preceded by -r, -w or -e for read, write,
189     * error as appropriate.  Typically there will only be one set
190     * so this looks rather like overkill.
191     */
192    fdlist = znewlinklist();
193    for (i = 0; i < 3; i++) {
194	int doneit = 0;
195	for (fd = 0; fd < fdmax; fd++) {
196	    if (FD_ISSET(fd, fdset+i)) {
197		char buf[BDIGBUFSIZE];
198		if (outhash) {
199		    /*
200		     * Key/value pairs; keys are fd's (as strings),
201		     * value is a (possibly improper) subset of "rwe".
202		     */
203		    LinkNode nptr;
204		    int found = 0;
205
206		    convbase(buf, fd, 10);
207		    for (nptr = firstnode(fdlist); nptr;
208			 nptr = nextnode(nextnode(nptr))) {
209			if (!strcmp((char *)getdata(nptr), buf)) {
210			    /* Already there, add new character. */
211			    void **dataptr = getaddrdata(nextnode(nptr));
212			    char *data = (char *)*dataptr, *ptr;
213			    found = 1;
214			    if (!strchr(data, fdchar[i])) {
215				strcpy(buf, data);
216				for (ptr = buf; *ptr; ptr++)
217				    ;
218				*ptr++ = fdchar[i];
219				*ptr = '\0';
220				zsfree(data);
221				*dataptr = ztrdup(buf);
222			    }
223			    break;
224			}
225		    }
226		    if (!found) {
227			/* Add new key/value pair. */
228			zaddlinknode(fdlist, ztrdup(buf));
229			buf[0] = fdchar[i];
230			buf[1] = '\0';
231			zaddlinknode(fdlist, ztrdup(buf));
232		    }
233		} else {
234		    /* List of fd's preceded by -r, -w, -e. */
235		    if (!doneit) {
236			buf[0] = '-';
237			buf[1] = fdchar[i];
238			buf[2] = 0;
239			zaddlinknode(fdlist, ztrdup(buf));
240			doneit = 1;
241		    }
242		    convbase(buf, fd, 10);
243		    zaddlinknode(fdlist, ztrdup(buf));
244		}
245	    }
246	}
247    }
248
249    /* convert list to array */
250    fdcount = countlinknodes(fdlist);
251    outptr = outdata = (char **)zalloc((fdcount+1)*sizeof(char *));
252    while (nonempty(fdlist))
253	*outptr++ = getlinknode(fdlist);
254    *outptr = '\0';
255    /* and store in array parameter */
256    if (outhash)
257	sethparam(outhash, outdata);
258    else
259	setaparam(outarray, outdata);
260    freelinklist(fdlist, NULL);
261
262    return 0;
263#else
264    /* TODO: use poll */
265    zerrnam(nam, "your system does not implement the select system call.");
266    return 2;
267#endif
268}
269
270
271static struct builtin bintab[] = {
272    BUILTIN("zselect", 0, bin_zselect, 0, -1, 0, NULL, NULL),
273};
274
275static struct features module_features = {
276    bintab, sizeof(bintab)/sizeof(*bintab),
277    NULL, 0,
278    NULL, 0,
279    NULL, 0,
280    0
281};
282
283
284/* The load/unload routines required by the zsh library interface */
285
286/**/
287int
288setup_(UNUSED(Module m))
289{
290    return 0;
291}
292
293/**/
294int
295features_(Module m, char ***features)
296{
297    *features = featuresarray(m, &module_features);
298    return 0;
299}
300
301/**/
302int
303enables_(Module m, int **enables)
304{
305    return handlefeatures(m, &module_features, enables);
306}
307
308/**/
309int
310boot_(Module m)
311{
312    return 0;
313}
314
315
316/**/
317int
318cleanup_(Module m)
319{
320    return setfeatureenables(m, &module_features, NULL);
321}
322
323/**/
324int
325finish_(UNUSED(Module m))
326{
327    return 0;
328}
329