/* * SPL - The SPL Programming Language * Copyright (C) 2006 Clifford Wolf * Copyright (C) 2006-2007 Raphael Langerhorst * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * mod_socket.c: Simple TCP network library */ /** * SPL Socket Module * * This Module provides basic functions for handling TCP sockets. */ #include #include #include #include #include #include #include #include #include #include #include #include "spl.h" #include "compat.h" // TODO: sanity check number and type of arguments // TODO: dump and restore extern void SPL_ABI(spl_mod_socket_init)(struct spl_vm *vm, struct spl_module *mod, int restore); extern void SPL_ABI(spl_mod_socket_done)(struct spl_vm *vm, struct spl_module *mod); /** * Create an initial socket and connect to given host. * Returns the handle of the connected socket or 0 on failure. */ // builtin socket_create(host,port) static struct spl_node *handler_socket_client(struct spl_task *task, void UNUSED(*data)) { char* host = spl_clib_get_string(task); int port = spl_clib_get_int(task); // int ip_version = spl_clib_get_int(task); //either "4" or "6" for ipv4 and ipv6 int connection = 0; //the socket that will be used. struct sockaddr_in saddr; struct hostent* hp = 0; hp = gethostbyname(host); if (hp == NULL) { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Host %s not found\n",host); return 0; } bzero(&saddr,sizeof(saddr)); bcopy(hp->h_addr,(char*)&saddr.sin_addr,hp->h_length); saddr.sin_family = hp->h_addrtype; saddr.sin_port = htons(port); connection = socket(hp->h_addrtype,SOCK_STREAM,0); if (connection < 0) { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task,"Could not create socket, error %d\n",errno); return 0; } int result = connect(connection,(const struct sockaddr*)(&saddr),sizeof(saddr)); if (result != 0) { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Could not connect to %s, error: %d\n",host,errno); close(connection); return 0; } if (fcntl(connection,F_SETOWN,getpid())==-1) { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Could not enable SIGIO signals on pid %d, error: %d\n",getpid(),errno); } if (fcntl(connection,F_SETFL,O_ASYNC)==-1) { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Could not enable SIGIO signals on socket %d, error: %d\n",connection,errno); } //spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_DEBUG, task, "Socket with handler %d created, connected to %s, port %d\n",connection,host,port); //socket created, now do the spl node: return SPL_NEW_INT(connection); } /** * Establish a server socket; originally from bzs@bu-cs.bu.edu * The port number is the only parameter needed for this function. * * Note that server sockets should not be used for read/write. * Rather use socket_accept(server) to accept incoming connections * that can be used for communication. */ // builtin socket_server(port) static struct spl_node *handler_socket_server(struct spl_task *task, void UNUSED(*data)) { int port = spl_clib_get_int(task); char myname[MAXHOSTNAMELEN+1]; int s; struct sockaddr_in sa; struct hostent *hp; bzero(&sa,sizeof(struct sockaddr_in)); /* clear our address */ gethostname(myname,MAXHOSTNAMELEN); /* who are we? */ hp= gethostbyname(myname); /* get our address info */ if (hp == NULL) /* we don't exist !? */ { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task,"Could not get own hostname, error %d\n",errno); return 0; } sa.sin_family= hp->h_addrtype; /* this is our host address */ sa.sin_port= htons(port); /* this is our port number */ if ((s= socket(AF_INET,SOCK_STREAM,0)) < 0) /* create socket */ { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task,"Could not create socket, error %d\n",errno); return 0; } if (bind(s,(struct sockaddr*)&sa,sizeof(sa)) < 0) /* bind address to socket */ { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task,"Could not bind socket, error %d\n",errno); close(s); return 0; } listen(s, 5); /* max # of queued connects */ return SPL_NEW_INT(s); } /** * Accept incoming connections on a server socket. * Use socket_server(port) to create an initial server socket * that can accept incoming connection. * * To determine if pending connections are available, use socket_poll(server). */ // builtin socket_accept(server) static struct spl_node *handler_socket_accept(struct spl_task *task, void UNUSED(*data)) { int s = spl_clib_get_int(task); int t; /* socket of connection */ if ((t = accept(s, NULL, NULL)) < 0) /* accept connection if there is one */ { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Error %d accepting connections on socket %d\n",errno,s); return 0; } if (fcntl(t,F_SETOWN,getpid())==-1) { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Could not enable SIGIO signals on pid %d, error: %d\n",getpid(),errno); } if (fcntl(t,F_SETFL,O_ASYNC)==-1) { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Could not enable SIGIO signals on accepted socket %d, error: %d\n",t,errno); } return SPL_NEW_INT(t); /* return the new socket */ } /** * Write data to given socket. * Note: can raise a SocketEx exception * Returns number of bytes written */ // builtin socket_write(socket,data) static struct spl_node *handler_socket_write(struct spl_task *task, void UNUSED(*data)) { int connection = spl_clib_get_int(task); char* bytes = spl_clib_get_string(task); size_t len = strlen(bytes); int result = 0; int tries = 0; int tries_max = 10; // size_t split_len = 32000; // hardware dependent??? int written_total = 0; for (size_t start = 0; start < len; start += result) { result = write(connection,bytes+start,len-start); if (result == -1 && errno == EINTR && tries < tries_max) { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_DEBUG, task, "EINTR while writing to socket %d, tries: %d\n",connection,tries); result = 0; // avoid decrement of start tries++; } //while(result < 0 && errno == EINTR && tries < 10); else if (result < 0) { //spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Error %d writing to socket %d: %s\n",errno,connection,strerror(errno)); spl_clib_exception(task, "SocketEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0,0,0, "Error %d writing data to socket %d: %s",errno,connection,strerror(errno))), NULL); return 0; } else { //spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_DEBUG, task, "%d bytes written to socket %d, stringtotal length: %d\n",result,connection,(int)len); written_total += result; tries = 0; } } return SPL_NEW_INT(written_total); } /** * Poll socket for available data. * Returns 1 if bytes are available for non-blocking read, 0 otherwise. * On error, -1 is returned; */ // builtin socket_poll(socket,timeout) static struct spl_node *handler_socket_poll(struct spl_task *task, void UNUSED(*data)) { int connection = spl_clib_get_int(task); int timeout = spl_clib_get_int(task); struct pollfd pfd; pfd.fd = connection; pfd.events = POLLIN; pfd.revents = 0; int bytes_ready = poll(&pfd,1,timeout); if ((pfd.revents & POLLIN) > 0 && bytes_ready == 1) { //spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_DEBUG, task, "bytes available from socket %d\n",connection); return SPL_NEW_INT(1); } else if (bytes_ready == -1) { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Error %d polling socket %d\n",errno,connection); return SPL_NEW_INT(-1); } else { //spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_DEBUG, task, "No bytes available from socket %d\n",connection); return SPL_NEW_INT(0); } } /** * Read data from socket. * Note: can raise a SocketEx exception * Returns the string that was read * * To achieve good read performance, it is suggested to use socket_poll() to * check if bytes are available for non-blocking read and to use * socket_read with a reasonably big buffer like 1000 bytes. The buffer * size is specified with the length parameter. * * Available bytes are read into the buffer until the buffer is full or * no further data is available at that moment to avoid blocking. * * Note: socket_read() will block until some data is available if there * is no data available initially. * * Note: Not tested with binary data. SPL is string oriented. */ // builtin socket_read(socket,length) static struct spl_node *handler_socket_read(struct spl_task *task, void UNUSED(*data)) { int connection = spl_clib_get_int(task); int length = spl_clib_get_int(task); char buffer[length+1]; int result = 0; if ( (result = read(connection, buffer, length)) != -1 && result != 0 ) { buffer[result] = '\0'; return SPL_NEW_STRING_DUP(buffer); } else { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Error %d reading from socket %d\n",errno,connection); spl_clib_exception(task, "SocketEx", "description", SPL_NEW_SPL_STRING(spl_string_printf(0,0,0, "Error %d occured while reading from connection %d",errno,connection)), NULL); return 0; } } /** * Closes socket. */ //builtin socket_close() static struct spl_node *handler_socket_close(struct spl_task *task, void UNUSED(*data)) { int connection = spl_clib_get_int(task); int result = close(connection); if (result < 0) { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Error %d closing socket %d\n",errno,connection); } return 0; } /** * SocketEx exception. * This can be thrown by read and write to signal an error. */ //object SocketEx // the SIGIO handler doesn't do anything in particular, // it's enough to wake up the process from task_sleep() void socket_sigio_handler() { //spl_report(SPL_REPORT_RUNTIME, task,"sigio\n"); //printf("sigio\n"); } static struct spl_node *handler_socket_sigio(struct spl_task *task, void UNUSED(*data)) { struct sigaction sigstruct; sigstruct.sa_sigaction = 0; sigstruct.sa_handler = socket_sigio_handler; sigstruct.sa_flags = 0; if (sigaction(SIGIO,&sigstruct,0)==-1) { spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task,"Could setup SIGIO handler in socket module\n"); } return 0; } void SPL_ABI(spl_mod_socket_init)(struct spl_vm *vm, struct spl_module UNUSED(*mod), int UNUSED(restore)) { spl_clib_reg(vm, "socket_client", handler_socket_client, 0); spl_clib_reg(vm, "socket_server", handler_socket_server, 0); spl_clib_reg(vm, "socket_accept", handler_socket_accept, 0); spl_clib_reg(vm, "socket_write", handler_socket_write, 0); spl_clib_reg(vm, "socket_poll", handler_socket_poll, 0); spl_clib_reg(vm, "socket_read", handler_socket_read, 0); spl_clib_reg(vm, "socket_close", handler_socket_close, 0); spl_clib_reg(vm, "socket_wake_on_sigio", handler_socket_sigio, 0); //make the object SocketEx known. spl_eval(vm, 0, strdup(mod->name), "object SocketEx { }"); } void SPL_ABI(spl_mod_socket_done)(struct spl_vm UNUSED(*vm), struct spl_module UNUSED(*mod)) { return; }