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