1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
|
/*
* SPL - The SPL Programming Language
* Copyright (C) 2006 Clifford Wolf <clifford@clifford.at>
* Copyright (C) 2006-2007 Raphael Langerhorst <raphael@raphael.g-system.at>
*
* 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 <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include <poll.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <spl.h>
#include <compat.h>
#include <stdio.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 *data UNUSED)
{
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 *data UNUSED)
{
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 *data UNUSED)
{
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 *data UNUSED)
{
int connection = spl_clib_get_int(task);
char* bytes = spl_clib_get_string(task);
size_t len = strlen(bytes);
size_t split_len = 33000; // hardware dependent???
int written_total = 0;
for (size_t start = 0; start < len; start += split_len)
{
size_t sublen = split_len;
if (start + sublen > len)
sublen = len - start;
int result = write(connection,bytes+start,sublen);
if (result < 0)
{
spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_WARNING, task, "Error %d writing to socket %d\n",errno,connection);
spl_clib_exception(task, "SocketEx",
"description",
SPL_NEW_SPL_STRING(spl_string_printf(0,0,0,
"Error writing data to socket %d",connection)),
NULL);
}
else
{
spl_report(SPL_REPORT_RUNTIME|SPL_REPORT_DEBUG, task, "%d bytes written to socket %d, stringlength: %d\n",result,connection,(int)sublen);
written_total += result;
}
}
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 *data UNUSED)
{
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 *data UNUSED)
{
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 *data UNUSED)
{
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 *data UNUSED)
{
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 *mod UNUSED, int restore UNUSED)
{
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 *vm UNUSED, struct spl_module *mod UNUSED)
{
return;
}
|