docs: minor typo with lassign
[jimtcl.git] / jim-aio.c
blobef3f139fe53b56033f740cab2eac0b03e63df09f
1 /* Jim - A small embeddable Tcl interpreter
3 * Copyright 2005 Salvatore Sanfilippo <antirez@invece.org>
4 * Copyright 2005 Clemens Hintze <c.hintze@gmx.net>
5 * Copyright 2005 patthoyts - Pat Thoyts <patthoyts@users.sf.net>
6 * Copyright 2008 oharboe - Øyvind Harboe - oyvind.harboe@zylin.com
7 * Copyright 2008 Andrew Lunn <andrew@lunn.ch>
8 * Copyright 2008 Duane Ellis <openocd@duaneellis.com>
9 * Copyright 2008 Uwe Klein <uklein@klein-messgeraete.de>
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above
18 * copyright notice, this list of conditions and the following
19 * disclaimer in the documentation and/or other materials
20 * provided with the distribution.
22 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
23 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
24 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
27 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
33 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 * The views and conclusions contained in the software and documentation
36 * are those of the authors and should not be interpreted as representing
37 * official policies, either expressed or implied, of the Jim Tcl Project.
38 **/
40 #include "jimautoconf.h"
42 #include <stdio.h>
43 #include <string.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #ifdef HAVE_UNISTD_H
47 #include <unistd.h>
48 #include <sys/stat.h>
49 #endif
51 #include "jim.h"
53 #if defined(HAVE_SYS_SOCKET_H) && defined(HAVE_SELECT) && defined(HAVE_NETINET_IN_H) && defined(HAVE_NETDB_H) && defined(HAVE_ARPA_INET_H)
54 #include <sys/socket.h>
55 #include <netinet/in.h>
56 #include <arpa/inet.h>
57 #include <netdb.h>
58 #ifdef HAVE_SYS_UN_H
59 #include <sys/un.h>
60 #endif
61 #else
62 #define JIM_ANSIC
63 #endif
65 #include "jim-eventloop.h"
66 #include "jim-subcmd.h"
68 #define AIO_CMD_LEN 32 /* e.g. aio.handleXXXXXX */
69 #define AIO_BUF_LEN 256 /* Can keep this small and rely on stdio buffering */
71 #ifndef HAVE_FTELLO
72 #define ftello ftell
73 #endif
74 #ifndef HAVE_FSEEKO
75 #define fseeko fseek
76 #endif
78 #define AIO_KEEPOPEN 1
80 #if defined(JIM_IPV6)
81 #define IPV6 1
82 #else
83 #define IPV6 0
84 #ifndef PF_INET6
85 #define PF_INET6 0
86 #endif
87 #endif
89 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
90 union sockaddr_any {
91 struct sockaddr sa;
92 struct sockaddr_in sin;
93 #if IPV6
94 struct sockaddr_in6 sin6;
95 #endif
98 #ifndef HAVE_INET_NTOP
99 const char *inet_ntop(int af, const void *src, char *dst, int size)
101 if (af != PF_INET) {
102 return NULL;
104 snprintf(dst, size, "%s", inet_ntoa(((struct sockaddr_in *)src)->sin_addr));
105 return dst;
107 #endif
108 #endif /* JIM_BOOTSTRAP */
110 typedef struct AioFile
112 FILE *fp;
113 Jim_Obj *filename;
114 int type;
115 int openFlags; /* AIO_KEEPOPEN? keep FILE* */
116 int fd;
117 Jim_Obj *rEvent;
118 Jim_Obj *wEvent;
119 Jim_Obj *eEvent;
120 int addr_family;
121 } AioFile;
123 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
124 static int JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
125 const char *hdlfmt, int family, const char *mode);
127 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
128 static int JimParseIPv6Address(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
130 #if IPV6
132 * An IPv6 addr/port looks like:
133 * [::1]
134 * [::1]:2000
135 * [fe80::223:6cff:fe95:bdc0%en1]:2000
136 * [::]:2000
137 * 2000
139 * Note that the "any" address is ::, which is the same as when no address is specified.
141 char *sthost = NULL;
142 const char *stport;
143 int ret = JIM_OK;
144 struct addrinfo req;
145 struct addrinfo *ai;
147 stport = strrchr(hostport, ':');
148 if (!stport) {
149 /* No : so, the whole thing is the port */
150 stport = hostport;
151 hostport = "::";
152 sthost = Jim_StrDup(hostport);
154 else {
155 stport++;
158 if (*hostport == '[') {
159 /* This is a numeric ipv6 address */
160 char *pt = strchr(++hostport, ']');
161 if (pt) {
162 sthost = Jim_StrDupLen(hostport, pt - hostport);
166 if (!sthost) {
167 sthost = Jim_StrDupLen(hostport, stport - hostport - 1);
170 memset(&req, '\0', sizeof(req));
171 req.ai_family = PF_INET6;
173 if (getaddrinfo(sthost, NULL, &req, &ai)) {
174 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
175 ret = JIM_ERR;
177 else {
178 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
179 *salen = ai->ai_addrlen;
181 sa->sin.sin_port = htons(atoi(stport));
183 freeaddrinfo(ai);
185 Jim_Free(sthost);
187 return ret;
188 #else
189 Jim_SetResultString(interp, "ipv6 not supported", -1);
190 return JIM_ERR;
191 #endif
194 static int JimParseIpAddress(Jim_Interp *interp, const char *hostport, union sockaddr_any *sa, int *salen)
196 /* An IPv4 addr/port looks like:
197 * 192.168.1.5
198 * 192.168.1.5:2000
199 * 2000
201 * If the address is missing, INADDR_ANY is used.
202 * If the port is missing, 0 is used (only useful for server sockets).
204 char *sthost = NULL;
205 const char *stport;
206 int ret = JIM_OK;
208 stport = strrchr(hostport, ':');
209 if (!stport) {
210 /* No : so, the whole thing is the port */
211 stport = hostport;
212 sthost = Jim_StrDup("0.0.0.0");
214 else {
215 sthost = Jim_StrDupLen(hostport, stport - hostport);
216 stport++;
220 #ifdef HAVE_GETADDRINFO
221 struct addrinfo req;
222 struct addrinfo *ai;
223 memset(&req, '\0', sizeof(req));
224 req.ai_family = PF_INET;
226 if (getaddrinfo(sthost, NULL, &req, &ai)) {
227 ret = JIM_ERR;
229 else {
230 memcpy(&sa->sin, ai->ai_addr, ai->ai_addrlen);
231 *salen = ai->ai_addrlen;
232 freeaddrinfo(ai);
234 #else
235 struct hostent *he;
237 ret = JIM_ERR;
239 if ((he = gethostbyname(sthost)) != NULL) {
240 if (he->h_length == sizeof(sa->sin.sin_addr)) {
241 *salen = sizeof(sa->sin);
242 sa->sin.sin_family= he->h_addrtype;
243 memcpy(&sa->sin.sin_addr, he->h_addr, he->h_length); /* set address */
244 ret = JIM_OK;
247 #endif
249 sa->sin.sin_port = htons(atoi(stport));
251 Jim_Free(sthost);
253 if (ret != JIM_OK) {
254 Jim_SetResultFormatted(interp, "Not a valid address: %s", hostport);
257 return ret;
260 #ifdef HAVE_SYS_UN_H
261 static int JimParseDomainAddress(Jim_Interp *interp, const char *path, struct sockaddr_un *sa)
263 sa->sun_family = PF_UNIX;
264 snprintf(sa->sun_path, sizeof(sa->sun_path), "%s", path);
266 return JIM_OK;
268 #endif
271 * Format that address in 'sa' as a string and store in variable 'varObjPtr'
273 static int JimFormatIpAddress(Jim_Interp *interp, Jim_Obj *varObjPtr, const union sockaddr_any *sa)
275 /* INET6_ADDRSTRLEN is 46. Add some for [] and port */
276 char addrbuf[60];
278 #if IPV6
279 if (sa->sa.sa_family == PF_INET6) {
280 addrbuf[0] = '[';
281 /* Allow 9 for []:65535\0 */
282 inet_ntop(sa->sa.sa_family, &sa->sin6.sin6_addr, addrbuf + 1, sizeof(addrbuf) - 9);
283 snprintf(addrbuf + strlen(addrbuf), 8, "]:%d", ntohs(sa->sin.sin_port));
285 else
286 #endif
287 if (sa->sa.sa_family == PF_INET) {
288 /* Allow 7 for :65535\0 */
289 inet_ntop(sa->sa.sa_family, &sa->sin.sin_addr, addrbuf, sizeof(addrbuf) - 7);
290 snprintf(addrbuf + strlen(addrbuf), 7, ":%d", ntohs(sa->sin.sin_port));
292 else {
293 /* recvfrom still works on unix domain sockets, etc */
294 addrbuf[0] = 0;
297 return Jim_SetVariable(interp, varObjPtr, Jim_NewStringObj(interp, addrbuf, -1));
300 #endif /* JIM_BOOTSTRAP */
302 static void JimAioSetError(Jim_Interp *interp, Jim_Obj *name)
304 if (name) {
305 Jim_SetResultFormatted(interp, "%#s: %s", name, strerror(errno));
307 else {
308 Jim_SetResultString(interp, strerror(errno), -1);
312 static void JimAioDelProc(Jim_Interp *interp, void *privData)
314 AioFile *af = privData;
316 JIM_NOTUSED(interp);
318 Jim_DecrRefCount(interp, af->filename);
320 #ifdef jim_ext_eventloop
321 /* remove all existing EventHandlers */
322 Jim_DeleteFileHandler(interp, af->fp, JIM_EVENT_READABLE | JIM_EVENT_WRITABLE | JIM_EVENT_EXCEPTION);
323 #endif
325 if (!(af->openFlags & AIO_KEEPOPEN)) {
326 fclose(af->fp);
329 Jim_Free(af);
332 static int JimCheckStreamError(Jim_Interp *interp, AioFile *af)
334 if (!ferror(af->fp)) {
335 return JIM_OK;
337 clearerr(af->fp);
338 /* EAGAIN and similar are not error conditions. Just treat them like eof */
339 if (feof(af->fp) || errno == EAGAIN || errno == EINTR) {
340 return JIM_OK;
342 #ifdef ECONNRESET
343 if (errno == ECONNRESET) {
344 return JIM_OK;
346 #endif
347 #ifdef ECONNABORTED
348 if (errno != ECONNABORTED) {
349 return JIM_OK;
351 #endif
352 JimAioSetError(interp, af->filename);
353 return JIM_ERR;
356 static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
358 AioFile *af = Jim_CmdPrivData(interp);
359 char buf[AIO_BUF_LEN];
360 Jim_Obj *objPtr;
361 int nonewline = 0;
362 jim_wide neededLen = -1; /* -1 is "read as much as possible" */
364 if (argc && Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
365 nonewline = 1;
366 argv++;
367 argc--;
369 if (argc == 1) {
370 if (Jim_GetWide(interp, argv[0], &neededLen) != JIM_OK)
371 return JIM_ERR;
372 if (neededLen < 0) {
373 Jim_SetResultString(interp, "invalid parameter: negative len", -1);
374 return JIM_ERR;
377 else if (argc) {
378 return -1;
380 objPtr = Jim_NewStringObj(interp, NULL, 0);
381 while (neededLen != 0) {
382 int retval;
383 int readlen;
385 if (neededLen == -1) {
386 readlen = AIO_BUF_LEN;
388 else {
389 readlen = (neededLen > AIO_BUF_LEN ? AIO_BUF_LEN : neededLen);
391 retval = fread(buf, 1, readlen, af->fp);
392 if (retval > 0) {
393 Jim_AppendString(interp, objPtr, buf, retval);
394 if (neededLen != -1) {
395 neededLen -= retval;
398 if (retval != readlen)
399 break;
401 /* Check for error conditions */
402 if (JimCheckStreamError(interp, af)) {
403 Jim_FreeNewObj(interp, objPtr);
404 return JIM_ERR;
406 if (nonewline) {
407 int len;
408 const char *s = Jim_GetString(objPtr, &len);
410 if (len > 0 && s[len - 1] == '\n') {
411 objPtr->length--;
412 objPtr->bytes[objPtr->length] = '\0';
415 Jim_SetResult(interp, objPtr);
416 return JIM_OK;
419 static int aio_cmd_copy(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
421 AioFile *af = Jim_CmdPrivData(interp);
422 jim_wide count = 0;
423 jim_wide maxlen = JIM_WIDE_MAX;
424 FILE *outfh = Jim_AioFilehandle(interp, argv[0]);
426 if (outfh == NULL) {
427 return JIM_ERR;
430 if (argc == 2) {
431 if (Jim_GetWide(interp, argv[1], &maxlen) != JIM_OK) {
432 return JIM_ERR;
436 while (count < maxlen) {
437 int ch = fgetc(af->fp);
439 if (ch == EOF || fputc(ch, outfh) == EOF) {
440 break;
442 count++;
445 if (ferror(af->fp)) {
446 Jim_SetResultFormatted(interp, "error while reading: %s", strerror(errno));
447 clearerr(af->fp);
448 return JIM_ERR;
451 if (ferror(outfh)) {
452 Jim_SetResultFormatted(interp, "error while writing: %s", strerror(errno));
453 clearerr(outfh);
454 return JIM_ERR;
457 Jim_SetResultInt(interp, count);
459 return JIM_OK;
462 static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
464 AioFile *af = Jim_CmdPrivData(interp);
465 char buf[AIO_BUF_LEN];
466 Jim_Obj *objPtr;
467 int len;
469 errno = 0;
471 objPtr = Jim_NewStringObj(interp, NULL, 0);
472 while (1) {
473 buf[AIO_BUF_LEN - 1] = '_';
474 if (fgets(buf, AIO_BUF_LEN, af->fp) == NULL)
475 break;
477 if (buf[AIO_BUF_LEN - 1] == '\0' && buf[AIO_BUF_LEN - 2] != '\n') {
478 Jim_AppendString(interp, objPtr, buf, AIO_BUF_LEN - 1);
480 else {
481 len = strlen(buf);
483 if (len && (buf[len - 1] == '\n')) {
484 /* strip "\n" */
485 len--;
488 Jim_AppendString(interp, objPtr, buf, len);
489 break;
492 if (JimCheckStreamError(interp, af)) {
493 /* I/O error */
494 Jim_FreeNewObj(interp, objPtr);
495 return JIM_ERR;
498 if (argc) {
499 if (Jim_SetVariable(interp, argv[0], objPtr) != JIM_OK) {
500 Jim_FreeNewObj(interp, objPtr);
501 return JIM_ERR;
504 len = Jim_Length(objPtr);
506 if (len == 0 && feof(af->fp)) {
507 /* On EOF returns -1 if varName was specified */
508 len = -1;
510 Jim_SetResultInt(interp, len);
512 else {
513 Jim_SetResult(interp, objPtr);
515 return JIM_OK;
518 static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
520 AioFile *af = Jim_CmdPrivData(interp);
521 int wlen;
522 const char *wdata;
523 Jim_Obj *strObj;
525 if (argc == 2) {
526 if (!Jim_CompareStringImmediate(interp, argv[0], "-nonewline")) {
527 return -1;
529 strObj = argv[1];
531 else {
532 strObj = argv[0];
535 wdata = Jim_GetString(strObj, &wlen);
536 if (fwrite(wdata, 1, wlen, af->fp) == (unsigned)wlen) {
537 if (argc == 2 || putc('\n', af->fp) != EOF) {
538 return JIM_OK;
541 JimAioSetError(interp, af->filename);
542 return JIM_ERR;
545 static int aio_cmd_isatty(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
547 #ifdef HAVE_ISATTY
548 AioFile *af = Jim_CmdPrivData(interp);
549 Jim_SetResultInt(interp, isatty(fileno(af->fp)));
550 #else
551 Jim_SetResultInt(interp, 0);
552 #endif
554 return JIM_OK;
557 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
558 static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
560 AioFile *af = Jim_CmdPrivData(interp);
561 char *buf;
562 union sockaddr_any sa;
563 long len;
564 socklen_t salen = sizeof(sa);
565 int rlen;
567 if (Jim_GetLong(interp, argv[0], &len) != JIM_OK) {
568 return JIM_ERR;
571 buf = Jim_Alloc(len + 1);
573 rlen = recvfrom(fileno(af->fp), buf, len, 0, &sa.sa, &salen);
574 if (rlen < 0) {
575 Jim_Free(buf);
576 JimAioSetError(interp, NULL);
577 return JIM_ERR;
579 buf[rlen] = 0;
580 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
582 if (argc > 1) {
583 return JimFormatIpAddress(interp, argv[1], &sa);
586 return JIM_OK;
590 static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
592 AioFile *af = Jim_CmdPrivData(interp);
593 int wlen;
594 int len;
595 const char *wdata;
596 union sockaddr_any sa;
597 const char *addr = Jim_String(argv[1]);
598 int salen;
600 if (IPV6 && af->addr_family == PF_INET6) {
601 if (JimParseIPv6Address(interp, addr, &sa, &salen) != JIM_OK) {
602 return JIM_ERR;
605 else if (JimParseIpAddress(interp, addr, &sa, &salen) != JIM_OK) {
606 return JIM_ERR;
608 wdata = Jim_GetString(argv[0], &wlen);
610 /* Note that we don't validate the socket type. Rely on sendto() failing if appropriate */
611 len = sendto(fileno(af->fp), wdata, wlen, 0, &sa.sa, salen);
612 if (len < 0) {
613 JimAioSetError(interp, NULL);
614 return JIM_ERR;
616 Jim_SetResultInt(interp, len);
617 return JIM_OK;
620 static int aio_cmd_accept(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
622 AioFile *af = Jim_CmdPrivData(interp);
623 int sock;
624 union sockaddr_any sa;
625 socklen_t addrlen = sizeof(sa);
627 sock = accept(af->fd, &sa.sa, &addrlen);
628 if (sock < 0) {
629 JimAioSetError(interp, NULL);
630 return JIM_ERR;
633 if (argc > 0) {
634 if (JimFormatIpAddress(interp, argv[0], &sa) != JIM_OK) {
635 return JIM_ERR;
639 /* Create the file command */
640 return JimMakeChannel(interp, NULL, sock, Jim_NewStringObj(interp, "accept", -1),
641 "aio.sockstream%ld", af->addr_family, "r+");
644 static int aio_cmd_listen(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
646 AioFile *af = Jim_CmdPrivData(interp);
647 long backlog;
649 if (Jim_GetLong(interp, argv[0], &backlog) != JIM_OK) {
650 return JIM_ERR;
653 if (listen(af->fd, backlog)) {
654 JimAioSetError(interp, NULL);
655 return JIM_ERR;
658 return JIM_OK;
660 #endif /* JIM_BOOTSTRAP */
662 static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
664 AioFile *af = Jim_CmdPrivData(interp);
666 if (fflush(af->fp) == EOF) {
667 JimAioSetError(interp, af->filename);
668 return JIM_ERR;
670 return JIM_OK;
673 static int aio_cmd_eof(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
675 AioFile *af = Jim_CmdPrivData(interp);
677 Jim_SetResultInt(interp, feof(af->fp));
678 return JIM_OK;
681 static int aio_cmd_close(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
683 if (argc == 3) {
684 #if !defined(JIM_ANSIC) && defined(HAVE_SHUTDOWN)
685 static const char * const options[] = { "r", "w", NULL };
686 enum { OPT_R, OPT_W, };
687 int option;
688 AioFile *af = Jim_CmdPrivData(interp);
690 if (Jim_GetEnum(interp, argv[2], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
691 return JIM_ERR;
693 if (shutdown(af->fd, option == OPT_R ? SHUT_RD : SHUT_WR) == 0) {
694 return JIM_OK;
696 JimAioSetError(interp, NULL);
697 #else
698 Jim_SetResultString(interp, "async close not supported", -1);
699 #endif
700 return JIM_ERR;
703 return Jim_DeleteCommand(interp, Jim_String(argv[0]));
706 static int aio_cmd_seek(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
708 AioFile *af = Jim_CmdPrivData(interp);
709 int orig = SEEK_SET;
710 jim_wide offset;
712 if (argc == 2) {
713 if (Jim_CompareStringImmediate(interp, argv[1], "start"))
714 orig = SEEK_SET;
715 else if (Jim_CompareStringImmediate(interp, argv[1], "current"))
716 orig = SEEK_CUR;
717 else if (Jim_CompareStringImmediate(interp, argv[1], "end"))
718 orig = SEEK_END;
719 else {
720 return -1;
723 if (Jim_GetWide(interp, argv[0], &offset) != JIM_OK) {
724 return JIM_ERR;
726 if (fseeko(af->fp, offset, orig) == -1) {
727 JimAioSetError(interp, af->filename);
728 return JIM_ERR;
730 return JIM_OK;
733 static int aio_cmd_tell(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
735 AioFile *af = Jim_CmdPrivData(interp);
737 Jim_SetResultInt(interp, ftello(af->fp));
738 return JIM_OK;
741 static int aio_cmd_filename(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
743 AioFile *af = Jim_CmdPrivData(interp);
745 Jim_SetResult(interp, af->filename);
746 return JIM_OK;
749 #ifdef O_NDELAY
750 static int aio_cmd_ndelay(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
752 AioFile *af = Jim_CmdPrivData(interp);
754 int fmode = fcntl(af->fd, F_GETFL);
756 if (argc) {
757 long nb;
759 if (Jim_GetLong(interp, argv[0], &nb) != JIM_OK) {
760 return JIM_ERR;
762 if (nb) {
763 fmode |= O_NDELAY;
765 else {
766 fmode &= ~O_NDELAY;
768 (void)fcntl(af->fd, F_SETFL, fmode);
770 Jim_SetResultInt(interp, (fmode & O_NONBLOCK) ? 1 : 0);
771 return JIM_OK;
773 #endif
775 #ifdef HAVE_FSYNC
776 static int aio_cmd_sync(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
778 AioFile *af = Jim_CmdPrivData(interp);
780 fflush(af->fp);
781 fsync(af->fd);
782 return JIM_OK;
784 #endif
786 static int aio_cmd_buffering(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
788 AioFile *af = Jim_CmdPrivData(interp);
790 static const char * const options[] = {
791 "none",
792 "line",
793 "full",
794 NULL
796 enum
798 OPT_NONE,
799 OPT_LINE,
800 OPT_FULL,
802 int option;
804 if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
805 return JIM_ERR;
807 switch (option) {
808 case OPT_NONE:
809 setvbuf(af->fp, NULL, _IONBF, 0);
810 break;
811 case OPT_LINE:
812 setvbuf(af->fp, NULL, _IOLBF, BUFSIZ);
813 break;
814 case OPT_FULL:
815 setvbuf(af->fp, NULL, _IOFBF, BUFSIZ);
816 break;
818 return JIM_OK;
821 #ifdef jim_ext_eventloop
822 static void JimAioFileEventFinalizer(Jim_Interp *interp, void *clientData)
824 Jim_Obj **objPtrPtr = clientData;
826 Jim_DecrRefCount(interp, *objPtrPtr);
827 *objPtrPtr = NULL;
830 static int JimAioFileEventHandler(Jim_Interp *interp, void *clientData, int mask)
832 Jim_Obj **objPtrPtr = clientData;
834 return Jim_EvalObjBackground(interp, *objPtrPtr);
837 static int aio_eventinfo(Jim_Interp *interp, AioFile * af, unsigned mask, Jim_Obj **scriptHandlerObj,
838 int argc, Jim_Obj * const *argv)
840 if (argc == 0) {
841 /* Return current script */
842 if (*scriptHandlerObj) {
843 Jim_SetResult(interp, *scriptHandlerObj);
845 return JIM_OK;
848 if (*scriptHandlerObj) {
849 /* Delete old handler */
850 Jim_DeleteFileHandler(interp, af->fp, mask);
853 /* Now possibly add the new script(s) */
854 if (Jim_Length(argv[0]) == 0) {
855 /* Empty script, so done */
856 return JIM_OK;
859 /* A new script to add */
860 Jim_IncrRefCount(argv[0]);
861 *scriptHandlerObj = argv[0];
863 Jim_CreateFileHandler(interp, af->fp, mask,
864 JimAioFileEventHandler, scriptHandlerObj, JimAioFileEventFinalizer);
866 return JIM_OK;
869 static int aio_cmd_readable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
871 AioFile *af = Jim_CmdPrivData(interp);
873 return aio_eventinfo(interp, af, JIM_EVENT_READABLE, &af->rEvent, argc, argv);
876 static int aio_cmd_writable(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
878 AioFile *af = Jim_CmdPrivData(interp);
880 return aio_eventinfo(interp, af, JIM_EVENT_WRITABLE, &af->wEvent, argc, argv);
883 static int aio_cmd_onexception(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
885 AioFile *af = Jim_CmdPrivData(interp);
887 return aio_eventinfo(interp, af, JIM_EVENT_EXCEPTION, &af->eEvent, argc, argv);
889 #endif
891 static const jim_subcmd_type aio_command_table[] = {
892 { "read",
893 "?-nonewline? ?len?",
894 aio_cmd_read,
897 /* Description: Read and return bytes from the stream. To eof if no len. */
899 { "copyto",
900 "handle ?size?",
901 aio_cmd_copy,
904 /* Description: Copy up to 'size' bytes to the given filehandle, or to eof if no size. */
906 { "gets",
907 "?var?",
908 aio_cmd_gets,
911 /* Description: Read one line and return it or store it in the var */
913 { "puts",
914 "?-nonewline? str",
915 aio_cmd_puts,
918 /* Description: Write the string, with newline unless -nonewline */
920 { "isatty",
921 NULL,
922 aio_cmd_isatty,
925 /* Description: Is the file descriptor a tty? */
927 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
928 { "recvfrom",
929 "len ?addrvar?",
930 aio_cmd_recvfrom,
933 /* Description: Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set */
935 { "sendto",
936 "str address",
937 aio_cmd_sendto,
940 /* Description: Send 'str' to the given address (dgram only) */
942 { "accept",
943 "?addrvar?",
944 aio_cmd_accept,
947 /* Description: Server socket only: Accept a connection and return stream */
949 { "listen",
950 "backlog",
951 aio_cmd_listen,
954 /* Description: Set the listen backlog for server socket */
956 #endif /* JIM_BOOTSTRAP */
957 { "flush",
958 NULL,
959 aio_cmd_flush,
962 /* Description: Flush the stream */
964 { "eof",
965 NULL,
966 aio_cmd_eof,
969 /* Description: Returns 1 if stream is at eof */
971 { "close",
972 "?r(ead)|w(rite)?",
973 aio_cmd_close,
976 JIM_MODFLAG_FULLARGV,
977 /* Description: Closes the stream. */
979 { "seek",
980 "offset ?start|current|end",
981 aio_cmd_seek,
984 /* Description: Seeks in the stream (default 'current') */
986 { "tell",
987 NULL,
988 aio_cmd_tell,
991 /* Description: Returns the current seek position */
993 { "filename",
994 NULL,
995 aio_cmd_filename,
998 /* Description: Returns the original filename */
1000 #ifdef O_NDELAY
1001 { "ndelay",
1002 "?0|1?",
1003 aio_cmd_ndelay,
1006 /* Description: Set O_NDELAY (if arg). Returns current/new setting. */
1008 #endif
1009 #ifdef HAVE_FSYNC
1010 { "sync",
1011 NULL,
1012 aio_cmd_sync,
1015 /* Description: Flush and fsync() the stream */
1017 #endif
1018 { "buffering",
1019 "none|line|full",
1020 aio_cmd_buffering,
1023 /* Description: Sets buffering */
1025 #ifdef jim_ext_eventloop
1026 { "readable",
1027 "?readable-script?",
1028 aio_cmd_readable,
1031 /* Description: Returns script, or invoke readable-script when readable, {} to remove */
1033 { "writable",
1034 "?writable-script?",
1035 aio_cmd_writable,
1038 /* Description: Returns script, or invoke writable-script when writable, {} to remove */
1040 { "onexception",
1041 "?exception-script?",
1042 aio_cmd_onexception,
1045 /* Description: Returns script, or invoke exception-script when oob data, {} to remove */
1047 #endif
1048 { NULL }
1051 static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1053 return Jim_CallSubCmd(interp, Jim_ParseSubCmd(interp, aio_command_table, argc, argv), argc, argv);
1056 static int JimAioOpenCommand(Jim_Interp *interp, int argc,
1057 Jim_Obj *const *argv)
1059 const char *mode;
1061 if (argc != 2 && argc != 3) {
1062 Jim_WrongNumArgs(interp, 1, argv, "filename ?mode?");
1063 return JIM_ERR;
1066 mode = (argc == 3) ? Jim_String(argv[2]) : "r";
1068 #ifdef jim_ext_tclcompat
1070 const char *filename = Jim_String(argv[1]);
1072 /* If the filename starts with '|', use popen instead */
1073 if (*filename == '|') {
1074 Jim_Obj *evalObj[3];
1076 evalObj[0] = Jim_NewStringObj(interp, "::popen", -1);
1077 evalObj[1] = Jim_NewStringObj(interp, filename + 1, -1);
1078 evalObj[2] = Jim_NewStringObj(interp, mode, -1);
1080 return Jim_EvalObjVector(interp, 3, evalObj);
1083 #endif
1084 return JimMakeChannel(interp, NULL, -1, argv[1], "aio.handle%ld", 0, mode);
1088 * Creates a channel for fh/fd/filename.
1090 * If fh is not NULL, uses that as the channel (and sets AIO_KEEPOPEN).
1091 * Otherwise, if fd is >= 0, uses that as the channel.
1092 * Otherwise opens 'filename' with mode 'mode'.
1094 * hdlfmt is a sprintf format for the filehandle. Anything with %ld at the end will do.
1095 * mode is used for open or fdopen.
1097 * Creates the command and sets the name as the current result.
1099 static int JimMakeChannel(Jim_Interp *interp, FILE *fh, int fd, Jim_Obj *filename,
1100 const char *hdlfmt, int family, const char *mode)
1102 AioFile *af;
1103 char buf[AIO_CMD_LEN];
1104 int openFlags = 0;
1106 if (fh) {
1107 filename = Jim_NewStringObj(interp, hdlfmt, -1);
1108 openFlags = AIO_KEEPOPEN;
1111 Jim_IncrRefCount(filename);
1113 if (fh == NULL) {
1114 #if !defined(JIM_ANSIC)
1115 if (fd >= 0) {
1116 fh = fdopen(fd, mode);
1118 else
1119 #endif
1120 fh = fopen(Jim_String(filename), mode);
1122 if (fh == NULL) {
1123 JimAioSetError(interp, filename);
1124 #if !defined(JIM_ANSIC)
1125 if (fd >= 0) {
1126 close(fd);
1128 #endif
1129 Jim_DecrRefCount(interp, filename);
1130 return JIM_ERR;
1134 /* Create the file command */
1135 af = Jim_Alloc(sizeof(*af));
1136 memset(af, 0, sizeof(*af));
1137 af->fp = fh;
1138 af->fd = fileno(fh);
1139 af->filename = filename;
1140 #ifdef FD_CLOEXEC
1141 if ((openFlags & AIO_KEEPOPEN) == 0) {
1142 (void)fcntl(af->fd, F_SETFD, FD_CLOEXEC);
1144 #endif
1145 af->openFlags = openFlags;
1146 af->addr_family = family;
1147 snprintf(buf, sizeof(buf), hdlfmt, Jim_GetId(interp));
1148 Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
1150 /* Note that the command must use the global namespace, even if
1151 * the current namespace is something different
1153 Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1)));
1155 return JIM_OK;
1158 #if defined(HAVE_PIPE) || (defined(HAVE_SOCKETPAIR) && defined(HAVE_SYS_UN_H))
1160 * Create a pair of channels. e.g. from pipe() or socketpair()
1162 static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename,
1163 const char *hdlfmt, int family, const char *mode[2])
1165 if (JimMakeChannel(interp, NULL, p[0], filename, hdlfmt, family, mode[0]) == JIM_OK) {
1166 Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0);
1167 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
1169 if (JimMakeChannel(interp, NULL, p[1], filename, hdlfmt, family, mode[1]) == JIM_OK) {
1170 Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
1171 Jim_SetResult(interp, objPtr);
1172 return JIM_OK;
1176 /* Can only be here if fdopen() failed */
1177 close(p[0]);
1178 close(p[1]);
1179 JimAioSetError(interp, NULL);
1180 return JIM_ERR;
1182 #endif
1184 #if !defined(JIM_ANSIC) && !defined(JIM_BOOTSTRAP)
1186 static int JimAioSockCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1188 const char *hdlfmt = "aio.unknown%ld";
1189 const char *socktypes[] = {
1190 "unix",
1191 "unix.server",
1192 "dgram",
1193 "dgram.server",
1194 "stream",
1195 "stream.server",
1196 "pipe",
1197 "pair",
1198 NULL
1200 enum
1202 SOCK_UNIX,
1203 SOCK_UNIX_SERVER,
1204 SOCK_DGRAM_CLIENT,
1205 SOCK_DGRAM_SERVER,
1206 SOCK_STREAM_CLIENT,
1207 SOCK_STREAM_SERVER,
1208 SOCK_STREAM_PIPE,
1209 SOCK_STREAM_SOCKETPAIR,
1211 int socktype;
1212 int sock;
1213 const char *hostportarg = NULL;
1214 int res;
1215 int on = 1;
1216 const char *mode = "r+";
1217 int family = PF_INET;
1218 Jim_Obj *argv0 = argv[0];
1219 int ipv6 = 0;
1221 if (argc > 1 && Jim_CompareStringImmediate(interp, argv[1], "-ipv6")) {
1222 if (!IPV6) {
1223 Jim_SetResultString(interp, "ipv6 not supported", -1);
1224 return JIM_ERR;
1226 ipv6 = 1;
1227 family = PF_INET6;
1229 argc -= ipv6;
1230 argv += ipv6;
1232 if (argc < 2) {
1233 wrongargs:
1234 Jim_WrongNumArgs(interp, 1, &argv0, "?-ipv6? type ?address?");
1235 return JIM_ERR;
1238 if (Jim_GetEnum(interp, argv[1], socktypes, &socktype, "socket type", JIM_ERRMSG) != JIM_OK)
1239 return JIM_ERR;
1241 Jim_SetEmptyResult(interp);
1243 hdlfmt = "aio.sock%ld";
1245 if (argc > 2) {
1246 hostportarg = Jim_String(argv[2]);
1249 switch (socktype) {
1250 case SOCK_DGRAM_CLIENT:
1251 if (argc == 2) {
1252 /* No address, so an unconnected dgram socket */
1253 sock = socket(family, SOCK_DGRAM, 0);
1254 if (sock < 0) {
1255 JimAioSetError(interp, NULL);
1256 return JIM_ERR;
1258 break;
1260 /* fall through */
1261 case SOCK_STREAM_CLIENT:
1263 union sockaddr_any sa;
1264 int salen;
1266 if (argc != 3) {
1267 goto wrongargs;
1270 if (ipv6) {
1271 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1272 return JIM_ERR;
1275 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1276 return JIM_ERR;
1278 sock = socket(family, (socktype == SOCK_DGRAM_CLIENT) ? SOCK_DGRAM : SOCK_STREAM, 0);
1279 if (sock < 0) {
1280 JimAioSetError(interp, NULL);
1281 return JIM_ERR;
1283 res = connect(sock, &sa.sa, salen);
1284 if (res) {
1285 JimAioSetError(interp, argv[2]);
1286 close(sock);
1287 return JIM_ERR;
1290 break;
1292 case SOCK_STREAM_SERVER:
1293 case SOCK_DGRAM_SERVER:
1295 union sockaddr_any sa;
1296 int salen;
1298 if (argc != 3) {
1299 goto wrongargs;
1302 if (ipv6) {
1303 if (JimParseIPv6Address(interp, hostportarg, &sa, &salen) != JIM_OK) {
1304 return JIM_ERR;
1307 else if (JimParseIpAddress(interp, hostportarg, &sa, &salen) != JIM_OK) {
1308 return JIM_ERR;
1310 sock = socket(family, (socktype == SOCK_DGRAM_SERVER) ? SOCK_DGRAM : SOCK_STREAM, 0);
1311 if (sock < 0) {
1312 JimAioSetError(interp, NULL);
1313 return JIM_ERR;
1316 /* Enable address reuse */
1317 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));
1319 res = bind(sock, &sa.sa, salen);
1320 if (res) {
1321 JimAioSetError(interp, argv[2]);
1322 close(sock);
1323 return JIM_ERR;
1325 if (socktype == SOCK_STREAM_SERVER) {
1326 res = listen(sock, 5);
1327 if (res) {
1328 JimAioSetError(interp, NULL);
1329 close(sock);
1330 return JIM_ERR;
1333 hdlfmt = "aio.socksrv%ld";
1335 break;
1337 #ifdef HAVE_SYS_UN_H
1338 case SOCK_UNIX:
1340 struct sockaddr_un sa;
1341 socklen_t len;
1343 if (argc != 3 || ipv6) {
1344 goto wrongargs;
1347 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1348 JimAioSetError(interp, argv[2]);
1349 return JIM_ERR;
1351 family = PF_UNIX;
1352 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1353 if (sock < 0) {
1354 JimAioSetError(interp, NULL);
1355 return JIM_ERR;
1357 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1358 res = connect(sock, (struct sockaddr *)&sa, len);
1359 if (res) {
1360 JimAioSetError(interp, argv[2]);
1361 close(sock);
1362 return JIM_ERR;
1364 hdlfmt = "aio.sockunix%ld";
1365 break;
1368 case SOCK_UNIX_SERVER:
1370 struct sockaddr_un sa;
1371 socklen_t len;
1373 if (argc != 3 || ipv6) {
1374 goto wrongargs;
1377 if (JimParseDomainAddress(interp, hostportarg, &sa) != JIM_OK) {
1378 JimAioSetError(interp, argv[2]);
1379 return JIM_ERR;
1381 family = PF_UNIX;
1382 sock = socket(PF_UNIX, SOCK_STREAM, 0);
1383 if (sock < 0) {
1384 JimAioSetError(interp, NULL);
1385 return JIM_ERR;
1387 len = strlen(sa.sun_path) + 1 + sizeof(sa.sun_family);
1388 res = bind(sock, (struct sockaddr *)&sa, len);
1389 if (res) {
1390 JimAioSetError(interp, argv[2]);
1391 close(sock);
1392 return JIM_ERR;
1394 res = listen(sock, 5);
1395 if (res) {
1396 JimAioSetError(interp, NULL);
1397 close(sock);
1398 return JIM_ERR;
1400 hdlfmt = "aio.sockunixsrv%ld";
1401 break;
1403 #endif
1405 #if defined(HAVE_SOCKETPAIR) && defined(HAVE_SYS_UN_H)
1406 case SOCK_STREAM_SOCKETPAIR:
1408 int p[2];
1409 static const char *mode[2] = { "r+", "r+" };
1411 if (argc != 2 || ipv6) {
1412 goto wrongargs;
1415 if (socketpair(PF_UNIX, SOCK_STREAM, 0, p) < 0) {
1416 JimAioSetError(interp, NULL);
1417 return JIM_ERR;
1419 return JimMakeChannelPair(interp, p, argv[1], "aio.sockpair%ld", PF_UNIX, mode);
1421 break;
1422 #endif
1424 #if defined(HAVE_PIPE)
1425 case SOCK_STREAM_PIPE:
1427 int p[2];
1428 static const char *mode[2] = { "r", "w" };
1430 if (argc != 2 || ipv6) {
1431 goto wrongargs;
1434 if (pipe(p) < 0) {
1435 JimAioSetError(interp, NULL);
1436 return JIM_ERR;
1439 return JimMakeChannelPair(interp, p, argv[1], "aio.pipe%ld", 0, mode);
1441 break;
1442 #endif
1444 default:
1445 Jim_SetResultString(interp, "Unsupported socket type", -1);
1446 return JIM_ERR;
1449 return JimMakeChannel(interp, NULL, sock, argv[1], hdlfmt, family, mode);
1451 #endif /* JIM_BOOTSTRAP */
1454 * Returns the file descriptor of a writable, newly created temp file
1455 * or -1 on error.
1457 * On success, leaves the filename in the interpreter result, otherwise
1458 * leaves an error message.
1460 int Jim_MakeTempFile(Jim_Interp *interp, const char *template)
1462 #ifdef HAVE_MKSTEMP
1463 int fd;
1464 mode_t mask;
1465 Jim_Obj *filenameObj;
1467 if (template == NULL) {
1468 const char *tmpdir = getenv("TMPDIR");
1469 if (tmpdir == NULL || *tmpdir == '\0' || access(tmpdir, W_OK) != 0) {
1470 tmpdir = "/tmp/";
1472 filenameObj = Jim_NewStringObj(interp, tmpdir, -1);
1473 if (tmpdir[0] && tmpdir[strlen(tmpdir) - 1] != '/') {
1474 Jim_AppendString(interp, filenameObj, "/", 1);
1476 Jim_AppendString(interp, filenameObj, "tcl.tmp.XXXXXX", -1);
1478 else {
1479 filenameObj = Jim_NewStringObj(interp, template, -1);
1482 mask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
1484 /* Update the template name directly with the filename */
1485 fd = mkstemp(filenameObj->bytes);
1486 umask(mask);
1487 if (fd < 0) {
1488 JimAioSetError(interp, filenameObj);
1489 Jim_FreeNewObj(interp, filenameObj);
1490 return -1;
1493 Jim_SetResult(interp, filenameObj);
1494 return fd;
1495 #else
1496 Jim_SetResultString(interp, "platform has no tempfile support", -1);
1497 return -1;
1498 #endif
1501 FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command)
1503 Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG);
1505 /* XXX: There ought to be a supported API for this */
1506 if (cmdPtr && !cmdPtr->isproc && cmdPtr->u.native.cmdProc == JimAioSubCmdProc) {
1507 return ((AioFile *) cmdPtr->u.native.privData)->fp;
1509 Jim_SetResultFormatted(interp, "Not a filehandle: \"%#s\"", command);
1510 return NULL;
1513 int Jim_aioInit(Jim_Interp *interp)
1515 if (Jim_PackageProvide(interp, "aio", "1.0", JIM_ERRMSG))
1516 return JIM_ERR;
1518 Jim_CreateCommand(interp, "open", JimAioOpenCommand, NULL, NULL);
1519 #ifndef JIM_ANSIC
1520 Jim_CreateCommand(interp, "socket", JimAioSockCommand, NULL, NULL);
1521 #endif
1523 /* Create filehandles for stdin, stdout and stderr */
1524 JimMakeChannel(interp, stdin, -1, NULL, "stdin", 0, "r");
1525 JimMakeChannel(interp, stdout, -1, NULL, "stdout", 0, "w");
1526 JimMakeChannel(interp, stderr, -1, NULL, "stderr", 0, "w");
1528 return JIM_OK;