From d455d1707fbdd03592f78a5c5476e90822221359 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Fri, 2 Jun 2000 17:55:14 +0000 Subject: [PATCH] Initial revision --- src/luasocket.c | 1357 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1357 insertions(+) create mode 100644 src/luasocket.c diff --git a/src/luasocket.c b/src/luasocket.c new file mode 100644 index 0000000..027afa3 --- /dev/null +++ b/src/luasocket.c @@ -0,0 +1,1357 @@ +/*=========================================================================*\ +* TCP/IP bind for the Lua language +* Diego Nehab +* 26/11/1999 +* +* Module: LSOCK.C +* +* This module is part of an effort to make the most important features +* of the TCP/IP protocol available for Lua scripts. +* The main intent of the project was the distribution with the CGILua +* toolkit, in which is is used to implement the SMTP client functions. +* The Lua interface to TCP/IP follows the BSD TCP/IP API closely, +* trying to simplify all tasks involved in setting up a client connection +* and simple server connections. +* The provided IO routines, send and receive, follow the Lua style, being +* very similar to the read and write functions found in that language. +* The module implements both a BSD bind and a Winsock2 bind, and has +* been tested on several Unix flavors, as well as Windows 98 and NT. +\*=========================================================================*/ + +/*=========================================================================*\ +* Common include files +\*=========================================================================*/ +#include +#include +#include +#include +#include + +#include + +#define LUA_REENTRANT + +#include +#include +#include + +#include "lsock.h" + +/*=========================================================================*\ +* WinSock2 include files +\*=========================================================================*/ +#ifdef WIN32 +#include +#include + +/*=========================================================================*\ +* BSD include files +\*=========================================================================*/ +#else +/* close function */ +#include +/* fnctnl function and associated constants */ +#include +/* struct timeval and CLK_TCK */ +#include +/* times function and struct tms */ +#include +/* internet protocol definitions */ +#include +#include +/* struct sockaddr */ +#include +/* socket function */ +#include +/* gethostbyname and gethostbyaddr functions */ +#include +/* for some reason, bcopy and it's friends are not defined automatically +** on the IRIX plataforms... */ +#ifdef __sgi +#include +#endif +#endif + +/*=========================================================================*\ +* Datatype compatibilization and some simple changes +\*=========================================================================*/ +#ifndef WIN32 +#define closesocket close /* WinSock2 has a closesock function instead + ** of using the regular close function */ +#define SOCKET int /* it defines a SOCKET type instead of + ** using an integer file descriptor */ +#define INVALID_SOCKET (-1) /* and uses the this macro to represent and + ** invalid socket */ +#ifndef INADDR_NONE /* some unix flavours don't define this */ +#define INADDR_NONE (-1) +#endif +#ifndef CLK_TCK /* SunOS, for instance, does not define */ +#define CLK_TCK 60 /* CLK_TCK */ +#endif +#endif + +/*=========================================================================*\ +* Module definitions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* The send and receive function can return one of the following return +* codes. The values are mapped into Lua values by the function +* push_error. +\*-------------------------------------------------------------------------*/ +#define NET_DONE -1 /* operation completed successfully */ +#define NET_TIMEOUT 0 /* operation timed out */ +#define NET_CLOSED 1 /* the connection has been closed */ + +/*-------------------------------------------------------------------------*\ +* As far as a Lua script is concerned, there are two kind of objects +* representing a socket. A client socket is an object created by the +* function connect, and implementing the methods send, receive, timeout +* and close. A server socket is an object created by the function bind, +* and implementing the methods listen, accept and close. Lua tag values +* for these objects are created in the lua_socklibopen function, and +* passed as closure values (first argumnents to every library function, +# because we can't have any global variables. +\*-------------------------------------------------------------------------*/ +#define CLIENT_TAG 1 +#define SERVER_TAG 2 +#define FIRST_PAR 3 + +/*-------------------------------------------------------------------------*\ +* Both socket types are stored in the same structure to simplify +* implementation. The tag value used is different, though. The timeout +* fields are not used for the server socket object. +* There are two timeout values. The block timeout specifies the maximum +* time the any IO operation performed by luasock can be blocked waiting +* for completion. The return timeout specifies the maximum time a Lua script +* can be blocked waiting for an luasock IO operation to complete. +\*-------------------------------------------------------------------------*/ +typedef struct t_sock { + SOCKET sock; /* operating system socket object */ + int b; /* block timeout in ms */ + int r; /* return timeout in ms */ + int blocking; /* is this socket in blocking mode? */ +} t_sock; +typedef t_sock *p_sock; + +/*-------------------------------------------------------------------------*\ +* Macros and internal declarations +\*-------------------------------------------------------------------------*/ +/* internal function prototypes */ +#include "sock.h" + +/* return time since marked start in ms */ +#define time_since(start) (get_time()-start) + +/* min and max macros */ +#ifndef min +#define min(x, y) ((x) < (y) ? x : y) +#endif +#ifndef max +#define max(x, y) ((x) > (y) ? x : y) +#endif + +/*=========================================================================*\ +* Test support functions +\*=========================================================================*/ +#ifdef _DEBUG +/*-------------------------------------------------------------------------*\ +* Returns the time the system has been up, in secconds. +\*-------------------------------------------------------------------------*/ +static void net_time(lua_State *L); +static void net_time(lua_State *L) +{ + lua_pushnumber(L, get_time()/1000.0); +} + +/*-------------------------------------------------------------------------*\ +* Causes a Lua script to sleep for the specified number of secconds +\*-------------------------------------------------------------------------*/ +static void net_sleep(lua_State *L); +static void net_sleep(lua_State *L) +{ + int sec = (int) luaL_check_number(L, 1); +#ifdef WIN32 + Sleep(1000*sec); +#else + sleep(sec); +#endif +} + +#endif + +/*=========================================================================*\ +* Lua exported functions +* These functions can be accessed from a Lua script. +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Creates a client socket and returns it to the Lua script. The timeout +* values are initialized as -1 so that the socket will block at any +* IO operation. +* Input +* host: host name or ip address to connect to +* port: port number on host +* Returns +* On success: client socket +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static void net_connect(lua_State *L) +{ + const char *hostname = luaL_check_string(L, FIRST_PAR); + unsigned short port = (unsigned short) luaL_check_number(L, FIRST_PAR+1); + struct sockaddr_in server; + p_sock sock = create_tcpsock(); + if (!sock) { + lua_pushnil(L); + lua_pushstring(L, sock_strerror()); + } + /* fills the sockaddr structure with the information needed to + ** connect our socket with the remote host */ + if (!fill_sockaddr(&server, hostname, port)) { + free(sock); + lua_pushnil(L); + lua_pushstring(L, host_strerror()); + return; + } + if (connect(sock->sock,(struct sockaddr *)&server,sizeof(server)) < 0) { + /* no connection? we close the socket to free the descriptor */ + closesocket(sock->sock); + lua_pushnil(L); + lua_pushstring(L, connect_strerror()); + return; + } + push_client(L, sock); + /* no need to push second return value, since it would be nil anyways */ +} + +/*-------------------------------------------------------------------------*\ +* Specifies the number of connections that can be queued on a server +* socket. +* Input +* sock: server socket created by the bind function +* Returns +* On success: nil +* On error: an error message +\*-------------------------------------------------------------------------*/ +static void net_listen(lua_State *L) +{ + p_sock sock = check_server(L, FIRST_PAR); + unsigned int backlog = (unsigned int) luaL_check_number(L, FIRST_PAR+1); + + if (listen(sock->sock, backlog) < 0) + lua_pushstring(L, "listen error"); + /* no need to push return value, since it would be nil anyways */ +} + +/*-------------------------------------------------------------------------*\ +* Returns a client socket attempting to connect to a server socket. +* The function blocks until a client shows up. +* Input +* sock: server socket created by the bind function +* Returns +* On success: client socket attempting connection +* On error: nil followed by an error message +\*-------------------------------------------------------------------------*/ +static void net_accept(lua_State *L) +{ + struct sockaddr_in client_addr; + p_sock server = check_server(L, FIRST_PAR); + int client_sock = -1; + unsigned int client_len = sizeof(client_addr); + p_sock client; + /* waits for a connection */ + client_sock = accept(server->sock, (struct sockaddr *) &client_addr, + &client_len); + /* we create and return a client socket object, passing the received + ** socket to Lua, as a client socket */ + client = create_sock(); + if (!client) { + lua_pushnil(L); + lua_pushstring(L, "out of memory"); + return; + } + client->sock = client_sock; + push_client(L, client); +} + +/*-------------------------------------------------------------------------*\ +* Associates an address to a server socket. +* Input +* host: host name or ip address to bind to +* port: port to bind to +* backlog: optional parameter specifying the number of connections +* to keep waiting before refuse a connection. the default value is 1. +* Returns +* On success: server socket bound to address +* On error: nil, followed by an error message +\*-------------------------------------------------------------------------*/ +static void net_bind(lua_State *L) +{ + const char *hostname = luaL_check_string(L, FIRST_PAR); + unsigned short port = (unsigned short) luaL_check_number(L, FIRST_PAR+1); + unsigned int backlog = (unsigned int) luaL_opt_number(L, FIRST_PAR+2, 1.0); + struct sockaddr_in server; + p_sock sock = create_tcpsock(); + if (!sock) { + lua_pushnil(L); + lua_pushstring(L, sock_strerror()); + } + /* fills the sockaddr structure with the information needed to + ** connect our socket with local address */ + if (!fill_sockaddr(&server, hostname, port)) { + free(sock); + lua_pushnil(L); + lua_pushstring(L, host_strerror()); + return; + } + if (bind(sock->sock,(struct sockaddr *)&server,sizeof(server)) < 0) { + lua_pushnil(L); + lua_pushstring(L, bind_strerror()); + return; + } + /* define the connection waiting queue length */ + if (listen(sock->sock, backlog) < 0) { + lua_pushnil(L); + lua_pushstring(L, "listen error"); + return; + } + /* pass the created socket to Lua, as a server socket */ + push_server(L, sock); + /* no need to push return value, since it would be nil anyways */ +} + +/*-------------------------------------------------------------------------*\ +* Sets timeout values for IO operations on a client socket +* Input +* sock: client socket created by the connect function +* time: time out value in seconds +* mode: optional timeout mode. "block" specifies the upper bound on +* the time any IO operation on sock can cause the program to block. +* "return" specifies the upper bound on the time elapsed before the +* function returns control to the script. "block" is the default. +* Returns +* no return value +\*-------------------------------------------------------------------------*/ +static void net_timeout(lua_State *L) +{ + p_sock sock = check_client(L, FIRST_PAR); + int ms = (int) (luaL_check_number(L, FIRST_PAR+1)*1000.0); + const char *mode = luaL_opt_string(L, FIRST_PAR+2, "b"); + switch (*mode) { + case 'b': + sock->b = ms; + break; + case 'r': + sock->r = ms; + break; + default: + luaL_arg_check(L, 0, FIRST_PAR+2, "invalid timeout mode"); + break; + } +} + +/*-------------------------------------------------------------------------*\ +* Send data through a socket +* Input: sock, a_1 [, a_2, a_3 ... a_n] +* sock: client socket created by the connect function +* a_i: strings to be sent. The strings will be sent on the order they +* appear as parameters +* Returns +* On success: nil, followed by the total number of bytes sent +* On error: NET_TIMEOUT if the connection timedout, or NET_CLOSED if +* the connection has been closed +\*-------------------------------------------------------------------------*/ +static void net_send(lua_State *L) +{ + p_sock sock = check_client(L, FIRST_PAR); + const char *data; + long size; + int start = get_time(); + long total = 0; + int arg = FIRST_PAR+1; + int err = NET_DONE; + int end; +#ifdef _DEBUG_BLOCK +printf("luasock: send start\n"); +#endif + while ((data = luaL_opt_lstr(L, arg++, NULL, &size)) && err == NET_DONE) + total += send_raw(sock, data, size, start, &err, &end); + push_error(L, err); + lua_pushnumber(L, (double) total); +#ifdef _DEBUG_BLOCK +printf("luasock: send end\n"); +#endif +#ifdef _DEBUG +/* pass the time elapsed during function execution to Lua, so that +** the test script can make sure we respected the timeouts */ +lua_pushnumber(L, (end-start)/1000.0); +#endif +} + +/*-------------------------------------------------------------------------*\ +* Receive data from a socket +* Input: sock [pat_1, pat_2 ... pat_n] +* sock: client socket created by the connect function +* pat_i: may be one of the following +* "*l": reads a text line, defined as a string of caracters terminates +* by a LF character, preceded or not by a CR character. This is +* the default pattern +* "*lu": reads a text line, terminanted by a CR character only. (Unix mode) +* number: reads 'number' characters from the socket +* Returns +* On success: one string for each pattern +* On error: all strings for which there was no error, followed by on +* nil value for each failed string, followed by an error code +\*-------------------------------------------------------------------------*/ +static void net_receive(lua_State *L) +{ + static const char *const modenames[] = {"*l", "*lu", NULL}; + p_sock sock = check_client(L, FIRST_PAR); + int err = NET_DONE, arg = FIRST_PAR+1, got = 0; + char *data = NULL; + int start = get_time(); + const char *mode = luaL_opt_string(L, arg++, "*l"); + int end; +#ifdef _DEBUG_BLOCK +printf("luasock: receive start\n"); +#endif + do { + /* if one pattern failed, we just skip all other patterns */ + if (err != NET_DONE) { + lua_pushnil(L); + continue; + } + /* get next pattern */ + switch (luaL_findstring(mode, modenames)) { + /* DOS line mode */ + case 0: + got = receive_dosline(L, sock, &data, start, &err, &end); + break; + /* Unix line mode */ + case 2: + got = receive_unixline(L, sock, &data, start, &err, &end); + break; + /* else it must be a number, raw mode */ + default: { + long size = (long) luaL_check_number(L, arg-1); + got = receive_raw(L, sock, size, &data, start, &err, &end); + break; + } + } + /* pass result */ + lua_pushlstring(L, data, got); + } while ((mode = luaL_opt_string(L, arg++, NULL))); + /* last return is an error code */ + push_error(L, err); +#ifdef _DEBUG_BLOCK +printf("luasock: receive end\n"); +#endif +#ifdef _DEBUG +/* pass the time elapsed during function execution to Lua, so that +** the test script can make sure we respected the timeouts */ +lua_pushnumber(L, (end-start)/1000.0); +#endif +} + +/*-------------------------------------------------------------------------*\ +* Closes a socket. +* Input +* sock: socket to be closed +\*-------------------------------------------------------------------------*/ +static void net_close(lua_State *L) +{ + p_sock sock = check_sock(L, FIRST_PAR); + closesocket(sock->sock); + /* set value to -1 so that we can later detect the use of a + ** closed socket */ + sock->sock = -1; +} + +/*-------------------------------------------------------------------------*\ +* Gettable fallback for the client socket. This function provides the +* alternative interface client:receive, client:send etc for the client +* socket methods. +\*-------------------------------------------------------------------------*/ +static void client_gettable(lua_State *L) +{ + static const char *const net_api[] = {"receive","send","timeout","close", + "connect", NULL}; + const char *idx = luaL_check_string(L, FIRST_PAR+1); + switch (luaL_findstring(idx, net_api)) { + case 0: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_receive, 2); + break; + case 1: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_send, 2); + break; + case 2: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_timeout, 2); + break; + case 3: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_close, 2); + break; + default: + lua_pushnil(L); + break; + } +} + +/*-------------------------------------------------------------------------*\ +* Gettable fallback for the server socket. This function provides the +* alternative interface server:listen, server:accept etc for the server +* socket methods. +\*-------------------------------------------------------------------------*/ +static void server_gettable(lua_State *L) +{ + static const char *const net_api[] = {"listen","accept","close", NULL}; + const char *idx = luaL_check_string(L, FIRST_PAR+1); + switch (luaL_findstring(idx, net_api)) { + case 0: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_listen, 2); + break; + case 1: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_accept, 2); + break; + case 2: + lua_pushnumber(L, get_tag(L, CLIENT_TAG)); + lua_pushnumber(L, get_tag(L, SERVER_TAG)); + lua_pushcclosure(L, net_close, 2); + break; + default: + lua_pushnil(L); + break; + } +} + +/*-------------------------------------------------------------------------*\ +* Garbage collection fallback for the socket objects. This function +* makes sure that all collected sockets are closed and that the memory +* used by the C structure t_sock is properly released. +\*-------------------------------------------------------------------------*/ +static void sock_gc(lua_State *L) +{ + p_sock sock = check_sock(L, FIRST_PAR); + if (sock->sock >= 0) + closesocket(sock->sock); + free(sock); +} + +/*=========================================================================*\ +* Internal functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Instals a handler to ignore sigpipe. That is, unless the signal had +* already been redefined. This function is not needed on the WinSock2, +* since it's sockets don't raise this signal. +\*-------------------------------------------------------------------------*/ +#ifndef WIN32 +static void handle_sigpipe(void); +static void handle_sigpipe(void) +{ + struct sigaction old, new; + bzero(&new, sizeof new); + new.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &new, &old); + /* test if the signal had been before, and restore it if so */ + if (old.sa_handler != SIG_DFL) { +#ifdef _DEBUG +/* this is a somewhat dangerous situation. we can only hope the +** installed signal handler understands that this signal can be +** raised by a socket operation */ +printf("SIGPIPE ALREADY REDEFINED!!!\n"); +#endif + sigaction(SIGPIPE, &old, NULL); + } +} +#endif + +/*-------------------------------------------------------------------------*\ +* Creates a t_sock structure with default values. +\*-------------------------------------------------------------------------*/ +static p_sock create_sock(void) +{ + p_sock sock = (p_sock) malloc(sizeof(t_sock)); + if (!sock) + return NULL; + sock->sock = -1; + sock->r = -1; + sock->b = -1; + sock->blocking = 1; + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Creates a TCP/IP socket. +* Returns +* A pointer to a t_sock structure or NULL in case of error +\*-------------------------------------------------------------------------*/ +static p_sock create_tcpsock(void) +{ + p_sock sock = create_sock(); + if (!sock) + return NULL; + sock->sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock->sock < 0) { + free(sock); + sock = NULL; + } +#ifdef _DEBUG +/* this allow us to re-bind onto an address even if there is still +** a TIME_WAIT condition. debugging is much more confortable, because +** we don't get "address already in use" errors all the time we +** re-run the program before the OS is ready. in real life, though +** there could be data pending on the socket and this could lead to +** some weird errors. */ +{ + int val = 1; + setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, (char *) &val, + sizeof(val)); +} +#endif + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Fills a sockaddr structure according to a given host name of ip +* address and a port number. +* Input +* address: pointer to sockaddr structure to be filled +* hostname: host name or ip address +* port: port number +* Returns +* 1 in case of success, 0 otherwise +\*-------------------------------------------------------------------------*/ +static int fill_sockaddr(struct sockaddr_in *address, const char *hostname, + unsigned short port) +{ + struct hostent *host = NULL; + unsigned long addr = inet_addr(hostname); + /* BSD says we could have used gethostbyname even if the hostname is + ** in ip address form, but WinSock2 says we can't. Therefore we + ** choose a method that works on both plataforms */ + if (addr == INADDR_NONE) + host = gethostbyname(hostname); + else + host = gethostbyaddr((char * ) &addr, sizeof(unsigned long), AF_INET); + if (!host) + return 0; + memset(address, 0, sizeof(struct sockaddr_in)); + address->sin_family = AF_INET; + address->sin_port = htons(port); + memcpy(&(address->sin_addr), host->h_addr, (unsigned) host->h_length); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Determine the time limit to be passed to the select function, given +* the time elapsed since the beginning of the operation. +* Input +* sock: socket structure being used in operation +* elapsed: time elapsed since operation started +* Returns +* time limit before function return in ms or -1 in case there is no +* time limit +\*-------------------------------------------------------------------------*/ +static int get_timeout(p_sock sock, int elapsed) +{ + /* no timeout */ + if (sock->b < 0 && sock->r < 0) + return -1; + /* there is no block timeout, we use the return timeout */ + if (sock->b < 0) + return max(0, sock->r - elapsed); + /* there is no return timeout, we use the block timeout */ + else if (sock->r < 0) + return sock->b; + /* both timeouts are specified */ + else + return min(sock->b, max(0, sock->r - elapsed)); +} + +/*-------------------------------------------------------------------------*\ +* Determines if we have a timeout condition or if we can proceed with +* an IO read operation. +* Input +* sock: socket structure being used in operation +* elapsed: time elapsed since operation started +* Returns +* 1 if we can proceed, 0 if a timeou has occured +\*-------------------------------------------------------------------------*/ +static int read_or_timeout(p_sock sock, int elapsed) +{ + fd_set set; /* file descriptor set */ + struct timeval to; /* timeout structure */ + int ms = get_timeout(sock, elapsed); + int err; + /* got timeout */ + if (ms == 0) + return 0; + FD_ZERO(&set); + FD_SET(sock->sock, &set); + /* we have a limit on the time we can wait */ + if (ms > 0) { + to.tv_sec = ms / 1000; + to.tv_usec = (ms % 1000) * 1000; + err = select(sock->sock+1, &set, NULL, NULL, &to); + set_nonblocking(sock); + /* we can wait forever */ + } else { + err = select(sock->sock+1, &set, NULL, NULL, NULL); + set_blocking(sock); + } + return (err > 0); +} + +/*-------------------------------------------------------------------------*\ +* Determines if we have a timeout condition or if we can proceed with +* an IO write operation. +* Input +* sock: socket structure being used in operation +* elapsed: time elapsed since operation started +* Returns +* 1 if we can proceed, 0 if a timeou has occured +\*-------------------------------------------------------------------------*/ +static int write_or_timeout(p_sock sock, int elapsed) +{ + fd_set set; /* file descriptor set */ + struct timeval to; /* timeout structure */ + int ms = get_timeout(sock, elapsed); + int err; + /* got timeout */ + if (ms == 0) + return 0; + FD_ZERO(&set); + FD_SET(sock->sock, &set); + /* we have a limit on the time we can wait */ + if (ms > 0) { + to.tv_sec = ms / 1000; + to.tv_usec = (ms % 1000) * 1000; + err = select(sock->sock+1, NULL, &set, NULL, &to); + set_nonblocking(sock); + /* we can wait forever */ + } else { + err = select(sock->sock+1, NULL, &set, NULL, NULL); + set_blocking(sock); + } + return (err > 0); +} + +/*-------------------------------------------------------------------------*\ +* Sends a raw block of data through a socket. The operations are all +* non-blocking and the function respects the timeout values in sock. +* Input +* sock: socket structure being used in operation +* data: buffer to be sent +* wanted: number of bytes in buffer +* start: time the operation started, in ms +* Output +* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +* Returns +* Number of bytes written +\*-------------------------------------------------------------------------*/ +static int send_raw(p_sock sock, const char *data, int wanted, + int start, int *err, int *end) +{ + int put = 0, total = 0; + *end = start; + while (wanted > 0) { + if(!write_or_timeout(sock, time_since(start))) { +#ifdef _DEBUG +*end = get_time(); +#endif + *err = NET_TIMEOUT; + return total; + } +#ifdef _DEBUG +/* the lua_pushlstring function can take a long time to pass a large block +** to Lua, therefore, we mark the time before passing the result. +** also, the call to write of read might take longer then the time we had +** left, so that the end of the operation is marked before the last call +** to the OS */ +*end = get_time(); +#endif + put = send(sock->sock, data, wanted, 0); + if (put <= 0) { +#ifdef WIN32 + /* on WinSock, a select over a socket on which there is a + ** non-blocking operation pending returns immediately, even + ** if the call would block. therefore, we have to do a busy + ** wait here. */ + if (WSAGetLastError() == WSAEWOULDBLOCK) + continue; +#endif + *err = NET_CLOSED; + return total; + } +#ifdef _DEBUG_BLOCK +printf("luasock: sent %d bytes, %dms elapsed\n", put, time_since(start)); +#endif + wanted -= put; + data += put; + total += put; + } + *err = NET_DONE; + return total; +} + +/*-------------------------------------------------------------------------*\ +* Reads a raw block of data from a socket. The operations are all +* non-blocking and the function respects the timeout values in sock. +* Input +* sock: socket structure being used in operation +* wanted: number of bytes to be read +* start: time the operation started, in ms +* Output +* data: pointer to an internal buffer containing the data read +* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +* Returns +* Number of bytes read +\*-------------------------------------------------------------------------*/ +static int receive_raw(lua_State *L, p_sock sock, int wanted, char **data, + int start, int *err, int *end) +{ + int got = 0; + char *buffer = NULL; + *end = start; + luaL_resetbuffer(L); + while (wanted > 0) { + buffer = luaL_openspace(L, wanted); + if(!read_or_timeout(sock, time_since(start))) { +#ifdef _DEBUG +*end = get_time(); +#endif + *data = luaL_buffer(L); + *err = NET_TIMEOUT; + return luaL_getsize(L); + } +#ifdef _DEBUG +*end = get_time(); +#endif + got = recv(sock->sock, buffer, wanted, 0); +#ifdef _DEBUG_BLOCK +printf("luasock: wanted %d, got %d, %dms elapsed\n", wanted, got, time_since(start)); +#endif + if (got <= 0) { + *data = luaL_buffer(L); + *err = NET_CLOSED; + return luaL_getsize(L); + } + wanted -= got; + luaL_addsize(L,got); + } + *data = luaL_buffer(L); + *err = NET_DONE; + return luaL_getsize(L); +} + +/*-------------------------------------------------------------------------*\ +* Reads a line terminated by a CR LF pair or just by a LF. The CR and LF +* are not returned by the function. All operations are non-blocking and the +* function respects the timeout values in sock. +* Input +* sock: socket structure being used in operation +* wanted: number of bytes in buffer +* start: time the operation started, in ms +* Output +* data: pointer to an internal buffer containing the data read +* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +* Returns +* Number of bytes read +\*-------------------------------------------------------------------------*/ +static long receive_dosline(lua_State *L, p_sock sock, char **data, + int start, int *err, int *end) +{ + char c = ' '; + long got = 0; + *end = start; + luaL_resetbuffer(L); + while (c != '\n') { + if (read_or_timeout(sock, time_since(start))) { +#ifdef _DEBUG +*end = get_time(); +#endif + got = recv(sock->sock, &c, 1, 0); + if (got <= 0) { + *err = NET_CLOSED; + *data = luaL_buffer(L); + return luaL_getsize(L); + } + luaL_addchar(L, c); + } else { + *err = NET_TIMEOUT; + *data = luaL_buffer(L); + return luaL_getsize(L); + } + } + *err = NET_DONE; + *data = luaL_buffer(L); + got = luaL_getsize(L); + if ((*data)[got-2] == '\r') return got-2; + else return got-1; +} + +/*-------------------------------------------------------------------------*\ +* Reads a line terminated by a LF character, which is not returned by +* the function. All operations are non-blocking and the function respects +* the timeout values in sock. +* Input +* sock: socket structure being used in operation +* wanted: number of bytes in buffer +* start: time the operation started, in ms +* Output +* data: pointer to an internal buffer containing the data read +* err: operation error code. NET_DONE, NET_TIMEOUT or NET_CLOSED +* Returns +* Number of bytes read +\*-------------------------------------------------------------------------*/ +static long receive_unixline(lua_State *L, p_sock sock, char **data, + int start, int *err, int *end) +{ + char c = ' '; + *end = start; + luaL_resetbuffer(L); + while (c != '\n') { + if (read_or_timeout(sock, time_since(start))) { +#ifdef _DEBUG +*end = get_time(); +#endif + if (recv(sock->sock, &c, 1, 0) <= 0) { + *err = NET_CLOSED; + *data = luaL_buffer(L); + return luaL_getsize(L); + } + luaL_addchar(L, c); + } else { + *err = NET_TIMEOUT; + *data = luaL_buffer(L); + return luaL_getsize(L); + } + } + *err = NET_DONE; + *data = luaL_buffer(L); + return luaL_getsize(L) - 1; +} + +/*-------------------------------------------------------------------------*\ +* Gets a tag from a closure parameter +* Input +* L: lua environment +* par: parameter number +\*-------------------------------------------------------------------------*/ +static int get_tag(lua_State *L, int par) +{ + return (int) lua_getnumber(L, lua_getparam(L, par)); +} + +/*-------------------------------------------------------------------------*\ +* Passes an error code to Lua. The NET_DONE error is translated to nil. +* Input +* err: error code to be passed to Lua +\*-------------------------------------------------------------------------*/ +static void push_error(lua_State *L, int err) +{ + switch (err) { + case NET_DONE: + lua_pushnil(L); + break; + case NET_TIMEOUT: + lua_pushstring(L, "timeout"); + break; + case NET_CLOSED: + lua_pushstring(L, "closed"); + break; + } +} + +/*-------------------------------------------------------------------------*\ +* Passes a client socket to Lua. +* Must be called from a closure receiving the socket tags as its +* parameters. +* Input +* L: lua environment +* sock: pointer to socket structure to be used +\*-------------------------------------------------------------------------*/ +static void push_client(lua_State *L, p_sock sock) +{ + lua_pushusertag(L, (void *) sock, get_tag(L, CLIENT_TAG)); +} + +/*-------------------------------------------------------------------------*\ +* Passes a server socket to Lua. +* Must be called from a closure receiving the socket tags as its +* parameters. +* Input +* L: lua environment +* sock: pointer to socket structure to be used +\*-------------------------------------------------------------------------*/ +static void push_server(lua_State *L, p_sock sock) +{ + lua_pushusertag(L, (void *) sock, get_tag(L, SERVER_TAG)); +} + +/*=========================================================================*\ +* WinSock2 specific functions. +\*=========================================================================*/ +#ifdef WIN32 +/*-------------------------------------------------------------------------*\ +* Initializes WinSock2 library. +* Returns +* 1 in case of success. 0 in case of error. +\*-------------------------------------------------------------------------*/ +static int wsock_open(void) +{ + WORD wVersionRequested;WSADATA wsaData;int err; + wVersionRequested = MAKEWORD( 2, 0 ); + err = WSAStartup( wVersionRequested, &wsaData ); + if ( err != 0 ) { + return 0; + } + if ( LOBYTE( wsaData.wVersion ) != 2 || + HIBYTE( wsaData.wVersion ) != 0 ) { + WSACleanup( ); + return 0; + } + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Gets time in ms, relative to system startup. +* Returns +* time in ms. +\*-------------------------------------------------------------------------*/ +static int get_time(void) +{ + return GetTickCount(); +} + +/*-------------------------------------------------------------------------*\ +* Put socket into blocking mode. +\*-------------------------------------------------------------------------*/ +static void set_blocking(p_sock sock) +{ + u_long argp = 0; + if (!sock->blocking) { + ioctlsocket(sock->sock, FIONBIO, &argp); + sock->blocking = 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Put socket into non-blocking mode. +\*-------------------------------------------------------------------------*/ +static void set_nonblocking(p_sock sock) +{ + u_long argp = 1; + if (sock->blocking) { + ioctlsocket(sock->sock, FIONBIO, &argp); + sock->blocking = 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last host manipulation error. +\*-------------------------------------------------------------------------*/ +static char *host_strerror(void) +{ + switch (WSAGetLastError()) { + case HOST_NOT_FOUND: return "host not found"; + case NO_ADDRESS: return "unable to resolve host name"; + case NO_RECOVERY: return "name server error"; + case TRY_AGAIN: return "name server unavailable, try again later."; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last socket manipulation error. +\*-------------------------------------------------------------------------*/ +static char *sock_strerror(void) +{ + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEMFILE: return "descriptor table is full"; + case WSAENOBUFS: return "insufficient buffer space"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last bind operation error. +\*-------------------------------------------------------------------------*/ +static char *bind_strerror(void) +{ + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEADDRINUSE: return "address already in use"; + case WSAEINVAL: return "socket already bound"; + case WSAENOBUFS: return "too many connections"; + case WSAEFAULT: return "invalid address"; + case WSAENOTSOCK: return "not a socket descriptor"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last connect operationerror. +\*-------------------------------------------------------------------------*/ +static char *connect_strerror(void) +{ + switch (WSAGetLastError()) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEADDRINUSE: return "address already in use"; + case WSAEADDRNOTAVAIL: return "address unavailable"; + case WSAECONNREFUSED: return "connection refused"; + case WSAENETUNREACH: return "network is unreachable"; + default: return "unknown error"; + } +} +#else + +/*=========================================================================*\ +* BSD specific functions. +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Gets time in ms, relative to system startup. +* Returns +* time in ms. +\*-------------------------------------------------------------------------*/ +static int get_time(void) +{ + struct tms t; + return (times(&t)*1000)/CLK_TCK; +} + +/*-------------------------------------------------------------------------*\ +* Put socket into blocking mode. +\*-------------------------------------------------------------------------*/ +static void set_blocking(p_sock sock) +{ + if (!sock->blocking) { + int flags = fcntl(sock->sock, F_GETFL, 0); + flags &= (~(O_NONBLOCK)); + fcntl(sock->sock, F_SETFL, flags); + sock->blocking = 1; + } +} + +/*-------------------------------------------------------------------------*\ +* Put socket into non-blocking mode. +\*-------------------------------------------------------------------------*/ +static void set_nonblocking(p_sock sock) +{ + if (sock->blocking) { + int flags = fcntl(sock->sock, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(sock->sock, F_SETFL, flags); + sock->blocking = 0; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last host manipulation error. +\*-------------------------------------------------------------------------*/ +static char *host_strerror(void) +{ + switch (h_errno) { + case HOST_NOT_FOUND: return "host not found"; + case NO_ADDRESS: return "unable to resolve host name"; + case NO_RECOVERY: return "name server error"; + case TRY_AGAIN: return "name server unavailable, try again later"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last socket manipulation error. +\*-------------------------------------------------------------------------*/ +static char *sock_strerror(void) +{ + switch (errno) { + case EACCES: return "access denied"; + case EMFILE: return "descriptor table is full"; + case ENFILE: return "too many open files"; + case ENOBUFS: return "insuffucient buffer space"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last bind command error. +\*-------------------------------------------------------------------------*/ +static char *bind_strerror(void) +{ + switch (errno) { + case EBADF: return "invalid descriptor"; + case EINVAL: return "socket already bound"; + case EACCES: return "access denied"; + case ENOTSOCK: return "not a socket descriptor"; + case EADDRINUSE: return "address already in use"; + case EADDRNOTAVAIL: return "address unavailable"; + case ENOMEM: return "out of memory"; + default: return "unknown error"; + } +} + +/*-------------------------------------------------------------------------*\ +* Returns a string describing the last connect error. +\*-------------------------------------------------------------------------*/ +static char *connect_strerror(void) +{ + switch (errno) { + case EBADF: return "invalid descriptor"; + case ENOTSOCK: return "not a socket descriptor"; + case EADDRNOTAVAIL: return "address not availabe"; + case ETIMEDOUT: return "connection timed out"; + case ECONNREFUSED: return "connection refused"; + case EACCES: return "access denied"; + case ENETUNREACH: return "network is unreachable"; + case EADDRINUSE: return "address already in use"; + default: return "unknown error"; + } +} + +#endif + +/*=========================================================================*\ +* Module exported functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Initializes the library interface with Lua and the socket library. +* Defines the symbols exported to Lua. +\*-------------------------------------------------------------------------*/ +void lua_socklibopen(lua_State *L) +{ + int client_tag, server_tag; + static struct luaL_reg funcs[] = { + {"connect", net_connect}, + {"bind", net_bind}, + {"listen", net_listen}, + {"accept", net_accept}, + {"close", net_close}, + {"send", net_send}, + {"receive", net_receive}, + {"timeout", net_timeout} + }; + int i; + +#ifdef WIN32 + wsock_open(); +#endif + /* declare new Lua tags for used userdata values */ + client_tag = lua_newtag(L); + server_tag = lua_newtag(L); + /* Lua exported functions */ + for (i = 0; i < sizeof(funcs)/sizeof(funcs[0]); i++) { + lua_pushnumber(L, client_tag); + lua_pushnumber(L, server_tag); + lua_pushcclosure(L, funcs[i].func, 2 ); + lua_setglobal(L, funcs[i].name ); + } + /* fallbacks */ + lua_pushnumber(L, client_tag); + lua_pushnumber(L, server_tag); + lua_pushcclosure(L, client_gettable, 2); + lua_settagmethod(L, client_tag, "gettable"); + + lua_pushnumber(L, client_tag); + lua_pushnumber(L, server_tag); + lua_pushcclosure(L, server_gettable, 2); + lua_settagmethod(L, server_tag, "gettable"); + + lua_pushnumber(L, client_tag); + lua_pushnumber(L, server_tag); + lua_pushcclosure(L, sock_gc, 2); + lua_settagmethod(L, client_tag, "gc"); + + lua_pushnumber(L, client_tag); + lua_pushnumber(L, server_tag); + lua_pushcclosure(L, sock_gc, 2); + lua_settagmethod(L, server_tag, "gc"); + + /* avoid stupid compiler warnings */ + (void) set_blocking; + +#ifndef WIN32 + /* avoid getting killed by a SIGPIPE signal */ + handle_sigpipe(); +#endif + +#ifdef _DEBUG +/* test support functions */ +lua_pushcfunction(L, net_sleep); lua_setglobal(L, "sleep"); +lua_pushcfunction(L, net_time); lua_setglobal(L, "time"); +#endif +} + +/*=========================================================================*\ +* Lua2c and c2lua stack auxiliary functions +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Checks if argument is a client socket, printing an error message in +* case of error +* Input +* numArg: argument position in lua2c stack +* Returns +* pointer to client socket, or doesn't return in case of error +\*-------------------------------------------------------------------------*/ +static p_sock check_client(lua_State *L, int numArg) +{ + p_sock sock; + lua_Object o = lua_getparam(L, numArg); + luaL_arg_check(L, lua_tag(L, o) == get_tag(L, CLIENT_TAG), + numArg, "client socket expected"); + sock = (p_sock) lua_getuserdata(L, o); + if (sock->sock < 0) + lua_error(L, "operation on closed socket"); + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Checks if argument is a server socket, printing an error message in +* case of error +* Input +* numArg: argument position in lua2c stack +* Returns +* pointer to server socket, or doesn't return in case of error +\*-------------------------------------------------------------------------*/ +static p_sock check_server(lua_State *L, int numArg) +{ + p_sock sock; + lua_Object o = lua_getparam(L, numArg); + luaL_arg_check(L, lua_tag(L, o) == get_tag(L, SERVER_TAG), + numArg, "server socket expected"); + sock = (p_sock) lua_getuserdata(L, o); + if (sock->sock < 0) + lua_error(L, "operation on closed socket"); + return sock; +} + +/*-------------------------------------------------------------------------*\ +* Checks if argument is a socket, printing an error message in +* case of error +* Input +* numArg: argument position in lua2c stack +* Returns +* pointer to socket, or doesn't return in case of error +\*-------------------------------------------------------------------------*/ +static p_sock check_sock(lua_State *L, int numArg) +{ + lua_Object o = lua_getparam(L, numArg); + luaL_arg_check(L, (lua_tag(L, o) == get_tag(L, CLIENT_TAG)) || + (lua_tag(L, o) == get_tag(L, SERVER_TAG)), numArg, "socket expected"); + return lua_getuserdata(L, o); +}