]> git.zerfleddert.de Git - micropolis/blob - src/tk/tkrawtcp.c
f556e5f7df81b82fff62c573c8bd2e409ec75350
[micropolis] / src / tk / tkrawtcp.c
1 /*
2 * tkRawTCP.c --
3 *
4 * This file contains a simple Tcl "connect" command
5 * that returns an standard Tcl File descriptor (as would
6 * be returned by Tcl_OpenCmd).
7 * Extended to create servers, accept connections, shutdown parts of full
8 * duplex connections and handle UNIX domain sockets.
9 *
10 * Author: Pekka Nikander <pnr@innopoli.ajk.tele.fi>
11 * Modified: Tim MacKenzie <tym@dibbler.cs.monash.edu.au)
12 *
13 * Copyright 1992 Telecom Finland
14 *
15 * Permission to use, copy, modify, and distribute this
16 * software and its documentation for any purpose and without
17 * fee is hereby granted, provided that this copyright
18 * notice appears in all copies. Telecom Finland
19 * makes no representations about the suitability of this
20 * software for any purpose. It is provided "as is" without
21 * express or implied warranty.
22 *
23 * Created: Sun Mar 22 18:20:29 1992
24 * based on: Last modified: Sun Mar 22 21:34:31 1992 pnr
25 * Last modified: Mon Jun 29 15:25:14 EST 1992 tym
26 *
27 */
28
29 #ifndef lint
30 static char rcsid[] = "...";
31 #endif /* not lint */
32
33 #include "tclint.h"
34 #include "tclunix.h"
35
36 #include <assert.h>
37 #include <string.h>
38 #include <sys/types.h>
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <netdb.h>
42 #include <arpa/inet.h>
43 #include <sys/un.h>
44
45 #include <tk.h>
46
47 static int inet_connect _ANSI_ARGS_((char *host, char *port,int server));
48 static int unix_connect _ANSI_ARGS_((char *path, int server));
49 static void HandleSocket _ANSI_ARGS_ ((ClientData clientData, int mask));
50
51 typedef struct {
52 Tcl_Interp *interp;
53 OpenFile *filePtr;
54 char *tclCmd;
55 char *fileId;
56 } FileCmd;
57
58 /*
59 *------------------------------------------------------------------
60 *
61 * Tcp_MakeOpenFile --
62 *
63 * Set up on OpenFile structure in the interpreter for a newly
64 * opened file
65 *
66 * Results:
67 * none
68 *
69 * Side effects:
70 * Adds an OpenFile to the list.
71 *------------------------------------------------------------------
72 */
73
74 /* ARGSUSED */
75 void
76 Tcp_MakeOpenFile(interp,fd,r,w)
77 Tcl_Interp *interp;
78 int fd;
79 int r,w;
80 {/* Create an OpenFile structure using f and install it in the interpreter with
81 * Readable and Writable set to r and w
82 */
83 Interp *iPtr = (Interp *) interp;
84 register OpenFile *filePtr;
85
86 filePtr = (OpenFile *) ckalloc(sizeof(OpenFile));
87
88 filePtr->f = NULL;
89 filePtr->f2 = NULL;
90
91 /* Open the file with the correct type (doesn't handle !r && !w) */
92 #ifdef MSDOS
93 filePtr->f = fdopen(fd,(r&&w)?"rb+":(r?"rb":"wb"));
94 #else
95 filePtr->f = fdopen(fd,(r&&w)?"r+":(r?"r":"w"));
96 #endif
97 /* Don't do buffered communication if full-duplex... it breaks! */
98 if (r&w) setbuf(filePtr->f,0);
99
100 filePtr->readable = r;
101 filePtr->writable = w;
102 filePtr->numPids = 0;
103 filePtr->pidPtr = NULL;
104 filePtr->errorId = -1;
105
106 /*
107 * Enter this new OpenFile structure in the table for the
108 * interpreter. May have to expand the table to do this.
109 */
110
111 TclMakeFileTable(iPtr, fd);
112 if (iPtr->filePtrArray[fd] != NULL) {
113 panic("Tcl_OpenCmd found file already open");
114 }
115 iPtr->filePtrArray[fd] = filePtr;
116 }
117
118 /*
119 *------------------------------------------------------------------
120 *
121 * Tcp_ConnectCmd --
122 *
123 * Open a socket connection to a given host and service.
124 *
125 * Results:
126 * A standard Tcl result.
127 *
128 * Side effects:
129 * An open socket connection.
130 * Sets the global variable connect_info(file%d) to the obtained
131 * port when setting up server.
132 *------------------------------------------------------------------
133 */
134
135 /* ARGSUSED */
136 int
137 Tcp_ConnectCmd(notUsed, interp, argc, argv)
138 ClientData notUsed;
139 Tcl_Interp *interp;
140 int argc;
141 char **argv;
142 {
143 Interp *iPtr = (Interp *) interp;
144 char *host,*port;
145 int fd;
146 int server=0;
147 int unicks = 0;
148
149 if (argc != 2 && argc != 3 &&
150 (argc != 4 || (argc == 4 && strcmp(argv[1],"-server")))) {
151 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
152 "[{-server}] address_spec\"", (char *) NULL);
153 return TCL_ERROR;
154 }
155
156 if (!strcmp(argv[1],"-server"))
157 server = 1;
158
159 /*
160 * Create the connection
161 */
162 if (argc - server == 2) {/* Unix domain socket */
163 unicks = 1;
164 fd = unix_connect(argv[1+server],server);
165 } else
166 fd = inet_connect(argv[1+server], argv[2+server],server);
167
168 if (fd < 0) {
169 /* Tell them why it fell apart */
170 if (unicks)
171 if (server)
172 Tcl_AppendResult(interp,
173 "Couldn't setup listening socket with path \"",
174 argv[1+server],"\" : ",Tcl_UnixError(interp),
175 (char *) NULL);
176 else
177 Tcl_AppendResult(interp,
178 "Couldn't connect to \"",argv[1],"\" : ",
179 Tcl_UnixError(interp),(char *) NULL);
180 else
181 if (server)
182 Tcl_AppendResult(interp,
183 "couldn't setup listening socket on port:",
184 atoi(argv[3])==0?"any":argv[3]," using address \"",
185 strlen(argv[2])?argv[2]:"anywhere.","\": ",
186 Tcl_UnixError(interp), (char *)NULL);
187 else
188 Tcl_AppendResult(interp, "couldn't open connection to \"",
189 argv[1], "\" port \"", argv[2], "\": ",
190 Tcl_UnixError(interp), (char *) NULL);
191 return TCL_ERROR;
192 }
193
194 sprintf(interp->result, "file%d", fd);
195 if (server && !unicks) {
196 /* Find out what port we got */
197 char buf[50];
198 struct sockaddr_in sockaddr;
199 int res,len=sizeof(sockaddr);
200 res =getsockname(fd,(struct sockaddr *) &sockaddr, &len);
201 if (res < 0) {
202 sprintf(buf,"%d",errno);
203 } else
204 sprintf(buf,"%d",(int)ntohs(sockaddr.sin_port));
205 Tcl_SetVar2(interp,"connect_info",interp->result,buf,TCL_GLOBAL_ONLY);
206 }
207
208 Tcp_MakeOpenFile(iPtr,fd,1,1-server);
209
210 return TCL_OK;
211 }
212
213 /*
214 *------------------------------------------------------------------
215 *
216 * Tcp_ShutdownCmd --
217 *
218 * Shutdown a socket for reading writing or both using shutdown(2)
219 *
220 * Results:
221 * standard tcl result.
222 *
223 * Side effects:
224 * Modifies the OpenFile structure appropriately
225 *------------------------------------------------------------------
226 */
227
228 /* ARGSUSED */
229 int
230 Tcp_ShutdownCmd(notUsed, interp, argc, argv)
231 ClientData notUsed;
232 Tcl_Interp *interp;
233 int argc;
234 char **argv;
235 {
236 Interp *iPtr = (Interp *) interp;
237 OpenFile *filePtr;
238 register FILE *f;
239 int fd;
240
241 if (argc != 3) {
242 wrong_args:
243 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
244 " fileid <option>\"", (char *) NULL);
245 return TCL_ERROR;
246 }
247
248 if (TclGetOpenFile(interp, argv[1], &filePtr) != TCL_OK) {
249 return TCL_ERROR;
250 }
251
252 f = filePtr->f;
253 fd = fileno(filePtr->f);
254 if (!strcmp(argv[2],"0") || !strcmp(argv[2],"receives") ||
255 !strcmp(argv[2],"read")) {
256 if (!filePtr->readable) {
257 Tcl_AppendResult(interp, "File is not readable",(char *) NULL);
258 return TCL_ERROR;
259 }
260 if (shutdown(fd,0)) {
261 Tcl_AppendResult(interp, "shutdown: ", Tcl_UnixError(interp),
262 (char *) NULL);
263 return TCL_ERROR;
264 }
265 filePtr->readable=0;
266 } else if (!strcmp(argv[2],"1") || !strcmp(argv[2],"sends") ||
267 !strcmp(argv[2],"write")) {
268 if (!filePtr->writable) {
269 Tcl_AppendResult(interp, "File is not writable",(char *) NULL);
270 return TCL_ERROR;
271 }
272 if (shutdown(fd,1)) {
273 Tcl_AppendResult(interp, "shutdown: ", Tcl_UnixError(interp),
274 (char *) NULL);
275 return TCL_ERROR;
276 }
277 filePtr->writable=0;
278 } else if (!strcmp(argv[2],"2") || !strcmp(argv[2],"all") ||
279 !strcmp(argv[2],"both")) {
280 if (shutdown(fd,2)) {
281 Tcl_AppendResult(interp, "shutdown: ", Tcl_UnixError(interp),
282 (char *) NULL);
283 return TCL_ERROR;
284 }
285 filePtr->writable=0;
286 filePtr->readable=0;
287 } else
288 goto wrong_args;
289 return TCL_OK;
290 }
291
292
293
294 /*
295 *------------------------------------------------------------------
296 *
297 * Tcp_AcceptCmd --
298 *
299 * Accept a connection on a listening socket
300 *
301 * Results:
302 * a standard tcl result
303 *
304 * Side effects:
305 * Opens a new file.
306 * Sets the global variable connect_info(file%d) to a list
307 * containing the remote address (host ip, port) of the
308 * connector.
309 *------------------------------------------------------------------
310 */
311
312 /* ARGSUSED */
313 int
314 Tcp_AcceptCmd(notUsed, interp, argc, argv)
315 ClientData notUsed;
316 Tcl_Interp *interp;
317 int argc;
318 char **argv;
319 {
320 Interp *iPtr = (Interp *) interp;
321 struct sockaddr_in sockaddr;
322 int len = sizeof sockaddr;
323 OpenFile *filePtr;
324 register FILE *f;
325 int fd;
326
327 if (argc != 2) {
328 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
329 " listening_socket\"", (char *) NULL);
330 return TCL_ERROR;
331 }
332
333 if (TclGetOpenFile(interp, argv[1], &filePtr) != TCL_OK) {
334 return TCL_ERROR;
335 }
336 if (!filePtr->readable) {
337 Tcl_AppendResult(interp, "\"", argv[1],
338 "\" wasn't opened for reading", (char *) NULL);
339 return TCL_ERROR;
340 }
341
342 f = filePtr->f;
343 fd = fileno(filePtr->f);
344
345 fd = accept(fd,(struct sockaddr *)&sockaddr,&len);
346 if (fd < 0) {
347 Tcl_AppendResult(interp, "system error in accept()", (char *)NULL);
348 return TCL_ERROR;
349 }
350
351 {/* Set the global connect_info */
352 char buf[100];
353 char nm[10];
354 if (sockaddr.sin_family == AF_INET)
355 sprintf(buf,"%s %d",inet_ntoa(sockaddr.sin_addr),
356 ntohs(sockaddr.sin_port));
357 else
358 buf[0]=0; /* Empty string for UNIX domain sockets */
359 sprintf(nm,"file%d",fd);
360 Tcl_SetVar2(interp,"connect_info",nm,buf,TCL_GLOBAL_ONLY);
361 }
362
363 /*
364 * Create the FILE*
365 */
366 Tcp_MakeOpenFile(iPtr,fd,1,1);
367
368 sprintf(interp->result, "file%d", fd);
369 return TCL_OK;
370 }
371
372 /*
373 *----------------------------------------------------------------
374 *
375 * unix_connect --
376 *
377 * Create a (unix_domain) fd connection using given rendeavous
378 *
379 * Results:
380 * An open fd or -1.
381 *
382 * Side effects:
383 * None.
384 *----------------------------------------------------------------
385 */
386
387 static int
388 unix_connect(path,server)
389 char *path; /* Path name to create or use */
390 int server; /* 1->make server, 0->connect to server */
391 {
392 struct sockaddr_un sockaddr;
393 int sock, status;
394 extern int errno;
395
396 sock = socket(PF_UNIX, SOCK_STREAM, 0);
397 if (sock < 0) {
398 return -1;
399 }
400
401 sockaddr.sun_family = AF_UNIX;
402 strncpy(sockaddr.sun_path,path,sizeof(sockaddr.sun_path)-1);
403 sockaddr.sun_path[sizeof(sockaddr.sun_path)-1] = 0; /* Just in case */
404
405 if (server)
406 status = bind(sock,(struct sockaddr *) &sockaddr, sizeof(sockaddr));
407 else
408 status = connect(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr));
409
410 if (status < 0) {
411 close (sock);
412 return -1;
413 }
414
415 if (server) {
416 listen(sock,5);
417 return sock;
418 }
419
420 return sock;
421 }
422
423 /*
424 *----------------------------------------------------------------
425 *
426 * inet_connect --
427 *
428 * Create a (inet domain) fd connection to given host and port.
429 *
430 * Results:
431 * An open fd or -1.
432 *
433 * Side effects:
434 * None.
435 *----------------------------------------------------------------
436 */
437
438 static int
439 inet_connect(host, service,server)
440 char *host; /* Host to connect, name or IP address */
441 char *service; /* Port to use, service name or port number */
442 int server;
443 {
444 struct hostent *hostent, _hostent;
445 struct servent *servent, _servent;
446 struct protoent *protoent;
447 struct sockaddr_in sockaddr;
448 int sock, status;
449 int hostaddr, hostaddrPtr[2];
450 int servport;
451 extern int errno;
452
453 hostent = gethostbyname(host);
454 if (hostent == NULL) {
455 hostaddr = inet_addr(host);
456 if (hostaddr == -1) {
457 if (server && !strlen(host))
458 hostaddr = INADDR_ANY;
459 else {
460 errno = EINVAL;
461 return -1;
462 }
463 }
464 _hostent.h_addr_list = (char **)hostaddrPtr;
465 _hostent.h_addr_list[0] = (char *)&hostaddr;
466 _hostent.h_addr_list[1] = NULL;
467 _hostent.h_length = sizeof(hostaddr);
468 _hostent.h_addrtype = AF_INET;
469 hostent = &_hostent;
470 }
471 servent = getservbyname(service, "tcp");
472 if (servent == NULL) {
473 servport = htons(atoi(service));
474 if (servport == -1) {
475 errno = EINVAL;
476 return -1;
477 }
478 _servent.s_port = servport;
479 _servent.s_proto = "tcp";
480 servent = &_servent;
481 }
482 protoent = getprotobyname(servent->s_proto);
483 if (protoent == NULL) {
484 errno = EINVAL;
485 return -1;
486 }
487
488 sock = socket(PF_INET, SOCK_STREAM, protoent->p_proto);
489 if (sock < 0) {
490 return -1;
491 }
492
493 sockaddr.sin_family = AF_INET;
494 memcpy((char *)&(sockaddr.sin_addr.s_addr),
495 (char *) hostent->h_addr_list[0],
496 (size_t) hostent->h_length);
497 sockaddr.sin_port = servent->s_port;
498
499 if (server)
500 status = bind(sock,(struct sockaddr *) &sockaddr, sizeof(sockaddr));
501 else
502 status = connect(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr));
503
504 if (status < 0) {
505 close (sock);
506 return -1;
507 }
508
509 if (server) {
510 listen(sock,5);
511 return sock;
512 }
513
514 return sock;
515 }
516
517 /*
518 *----------------------------------------------------------------
519 *
520 * Tcp_FileHandlerCmd --
521 *
522 * Register a file handler with an open file. If there is
523 * already and existing handler, it will be no longer called.
524 * If no mask and command are given, any existing handler
525 * will be deleted.
526 *
527 * Results:
528 * A standard Tcl result. (Always OK).
529 *
530 * Side effects:
531 * A new file handler is associated with a give TCL open file.
532 * Whenever the file is readable, writeable and/or there is
533 * an expection condition on the file, a user supplied TCL
534 * command is called.
535 *
536 *----------------------------------------------------------------
537 */
538
539 /* ARGSUSED */
540 int
541 Tcp_FileHandlerCmd(notUsed, interp, argc, argv)
542 ClientData notUsed;
543 Tcl_Interp *interp;
544 int argc;
545 char **argv;
546 {
547 FileCmd *cmdPtr;
548 OpenFile *filePtr;
549 int mask;
550
551 if (argc != 2 && argc != 4) {
552 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
553 " fileId ?mode command?\"", (char *) NULL);
554 return TCL_ERROR;
555 }
556
557 if (TclGetOpenFile(interp, argv[1], &filePtr) != TCL_OK) {
558 return TCL_ERROR;
559 }
560
561 if (argc == 2) {
562 /*
563 * NOTE! Currently the cmdPtr structure will be left
564 * *unfreed* if the file handler is deleted
565 * via this code. Tough. Would need a hash table
566 * or something...
567 */
568 Tk_DeleteFileHandler(fileno(filePtr->f));
569 return TCL_OK;
570 }
571
572 /*
573 * Find out on what situations the user is interested in.
574 * This is not the most elegant or efficient way to do this,
575 * but who cares? (I do, but not much enough :-)
576 */
577 mask = 0;
578 if (strchr(argv[2], 'r')) {
579 mask |= TK_READABLE;
580 }
581 if (strchr(argv[2], 'w')) {
582 mask |= TK_WRITABLE;
583 }
584 if (strchr(argv[2], 'e')) {
585 mask |= TK_EXCEPTION;
586 }
587 if (mask == 0 || (strlen(argv[2]) != strspn(argv[2], "rwe"))) {
588 Tcl_AppendResult(interp, "bad mask argument \"", argv[2],
589 "\": should be any combination of \"r\", \"w\" and \"e\"",
590 (char *) NULL);
591 fclose(filePtr->f);
592 return TCL_ERROR;
593 }
594
595 cmdPtr = (FileCmd *)ckalloc(sizeof(FileCmd));
596 cmdPtr->interp = interp;
597 cmdPtr->filePtr = filePtr;
598 cmdPtr->tclCmd = ckalloc(strlen(argv[3]) + 1);
599 strcpy(cmdPtr->tclCmd, argv[3]);
600 cmdPtr->fileId = ckalloc(strlen(argv[1]) + 1);
601 strcpy(cmdPtr->fileId, argv[1]);
602
603 /*
604 * NOTE! There may be an earlier file handler. Should do something.
605 */
606 Tk_CreateFileHandler(fileno(filePtr->f), mask, HandleSocket,
607 (ClientData) cmdPtr);
608
609 return TCL_OK;
610 }
611 /*
612 *----------------------------------------------------------------
613 *
614 * HandleSocket --
615 *
616 * This procedure is called from Tk_DoOneEvent whenever there is
617 * a desired condition on a given open socket. An Tcl command
618 * given by the user is executed to handle the connection. If
619 * and EOF or ERROR condition is noticed, all memory resources
620 * associated with the socket are released and the socket is closed.
621 *
622 * Results:
623 * None.
624 *
625 * Side effects:
626 * The user supplied command can do anything.
627 *
628 *----------------------------------------------------------------
629 */
630
631 static void
632 HandleSocket(clientData, mask)
633 ClientData clientData;
634 int mask;
635 {
636 int result;
637 FileCmd *cmdPtr = (FileCmd *) clientData;
638 OpenFile *filePtr = cmdPtr->filePtr;
639 Tcl_Interp *interp = cmdPtr->interp;
640 OpenFile *dummy;
641 int delete;
642 int fd = fileno(filePtr->f);
643
644 Tk_Preserve((ClientData)cmdPtr);
645
646 delete = 0;
647 if (TclGetOpenFile(interp, cmdPtr->fileId, &dummy) != TCL_OK) {
648 /* File is closed! */
649 Tcl_ResetResult(interp);
650 delete = 1;
651 } else {
652 assert(dummy == cmdPtr->filePtr);
653
654 if (mask & TK_READABLE) {
655 result = Tcl_VarEval(interp, cmdPtr->tclCmd, " r ", cmdPtr->fileId,
656 (char *) NULL);
657 if (result != TCL_OK) {
658 TkBindError(interp);
659 }
660 }
661 if (mask & TK_WRITABLE) {
662 result = Tcl_VarEval(interp, cmdPtr->tclCmd, " w ", cmdPtr->fileId,
663 (char *) NULL);
664 if (result != TCL_OK) {
665 TkBindError(interp);
666 }
667 }
668 if (mask & TK_EXCEPTION) {
669 result = Tcl_VarEval(interp, cmdPtr->tclCmd, " e ", cmdPtr->fileId,
670 (char *) NULL);
671 if (result != TCL_OK) {
672 TkBindError(interp);
673 }
674 }
675
676 if (feof(filePtr->f) || ferror(filePtr->f)) {
677 result = Tcl_VarEval(interp, "close ", cmdPtr->fileId,
678 (char *) NULL);
679 if (result != TCL_OK) {
680 TkBindError(interp);
681 }
682 delete = 1;
683 }
684 }
685
686 Tk_Release((ClientData)cmdPtr);
687
688 if (delete) {
689 Tk_DeleteFileHandler(fd);
690 Tk_EventuallyFree((ClientData)cmdPtr, (Tk_FreeProc *)free);
691 }
692 }
Impressum, Datenschutz