diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..705ce5b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +*.so +*.so.* diff --git a/doc/reference.html b/doc/reference.html index 31f6ecf..8da3956 100644 --- a/doc/reference.html +++ b/doc/reference.html @@ -42,9 +42,9 @@ Support, Manual">
DNS (in socket)@@ -108,9 +108,9 @@ Support, Manual"> MIME-toip, +gethostname, tohostname, -gethostname. +toip.
high-level: -normalize, decode, encode, +normalize, stuff, wrap.@@ -120,10 +120,10 @@ Support, Manual"> dot, eol, qp, -wrp, -qpwrp. +qpwrp, unb64, unqp, +wrp. @@ -142,6 +142,8 @@ Support, Manual">
Socket+bind, +connect, _DEBUG, dns, gettime, @@ -169,11 +171,16 @@ Support, Manual"> bind, close, connect, +dirty, +getfd, +getoption, getpeername, getsockname, getstats, +listen, receive, send, +setfd, setoption, setstats, settimeout, diff --git a/doc/socket.html b/doc/socket.html index 490ab50..2267b4a 100644 --- a/doc/socket.html +++ b/doc/socket.html @@ -244,6 +244,10 @@ method or accept might block forever. it to select, it will be ignored. ++Using select with non-socket objects: Any object that implements getfd and dirty can be used with select, allowing objects from other libraries to be used within a socket.select driven loop. +
+diff --git a/doc/tcp.html b/doc/tcp.html index d1d2154..f59d7ac 100644 --- a/doc/tcp.html +++ b/doc/tcp.html @@ -397,7 +397,40 @@ disables the Nagle's algorithm for the connection.
-The method returns 1 in case of success, or nil otherwise. +The method returns 1 in case of success, or nil +followed by an error message otherwise. +
+ ++Note: The descriptions above come from the man pages. +
+ + + ++client:getoption(option)
+ +
+server:getoption(option) ++Gets options for the TCP object. +See setoption for description of the +option names and values. +
+ ++Option is a string with the option name. +
+ +
+ +- 'keepalive' +
- 'linger' +
- 'reuseaddr' +
- 'tcp-nodelay' +
+The method returns the option value in case of success, or +nil followed by an error message otherwise.
@@ -508,6 +541,66 @@ This is the default mode; This function returns 1.
+ + ++master:dirty()
+ +
+client:dirty()
+server:dirty() ++Check the read buffer status. +
+ ++Returns true if there is any data in the read buffer, false otherwise. +
+ ++Note: This is an internal method, any use is unlikely to be portable. +
+ + + ++master:getfd()
+ +
+client:getfd()
+server:getfd() ++Returns the underling socket descriptor or handle associated to the object. +
+ ++The descriptor or handle. In case the object has been closed, the return will be -1. +
+ ++Note: This is an internal method, any use is unlikely to be portable. +
+ + + ++master:setfd(fd)
+ +
+client:setfd(fd)
+server:setfd(fd) ++Sets the underling socket descriptor or handle associated to the object. The current one is simply replaced, not closed, and no other change to the object state is made. +
+ ++No return value. +
+ ++Note: This is an internal method, any use is unlikely to be portable. +
+diff --git a/gem/makefile b/gem/makefile index d2f0c93..a4287c2 100644 --- a/gem/makefile +++ b/gem/makefile @@ -12,12 +12,3 @@ clean: pdf: ltn012.pdf open ltn012.pdf - -test: gem.so - - -gem.o: gem.c - gcc -c -o gem.o -Wall -ansi -W -O2 gem.c - -gem.so: gem.o - export MACOSX_DEPLOYMENT_TARGET="10.3"; gcc -bundle -undefined dynamic_lookup -o gem.so gem.o diff --git a/makefile b/makefile index 4275474..8e9e967 100644 --- a/makefile +++ b/makefile @@ -6,10 +6,19 @@ PLATS= macosx linux win32 # all: $(PLAT) -$(PLATS) none install local clean: +$(PLATS) none install install-unix local clean: @cd src; $(MAKE) $@ -test: dummy +test: lua test/hello.lua -.PHONY: dummy +install-both: + touch src/*.c + @cd src; $(MAKE) $(PLAT) LUAV=5.1 + @cd src; $(MAKE) install-unix LUAV=5.1 + touch src/*.c + @cd src; $(MAKE) $(PLAT) LUAV=5.2 + @cd src; $(MAKE) install-unix LUAV=5.2 + +.PHONY: test + diff --git a/src/makefile b/src/makefile index b7c22da..a38ff98 100644 --- a/src/makefile +++ b/src/makefile @@ -1,30 +1,35 @@ PLAT?=macosx +LUAV?=5.1 +prefix=/usr/local +#prefix=/opt/local +#prefix=. -INSTALL_DATA=cp -INSTALL_EXEC=cp -#INSTALL_TOP=/opt/local -INSTALL_TOP=./ - +LUAINC_macosx=/usr/local/include #LUAINC_macosx=/opt/local/include -LUAINC_macosx=../../../../projects/lua_env/luaenv/lua_versions/lua-5.2.0-beta/src +#LUAINC_macosx=../../../../projects/lua_env/luaenv/lua_versions/lua-5.2.0-beta/src #LUAINC_macosx=../../../../projects/lua_env/luaenv/lua_versions/lua-5.1.4/src -LUAINC_linux=/usr/include/lua5.1 +#LUAINC_linux=/usr/local/include/lua$(LUAV) +LUAINC_linux=/usr/include/lua$(LUAV) +#LUAINC_linux=/usr/local/include + LUAINC_win32="../../lua-5.1.3/src" LUALIB_win32="../../lua-5.1.3" #------ # Install directories # -#INSTALL_TOP_SHARE=$(INSTALL_TOP)/share/lua/5.1 -#INSTALL_TOP_LIB=$(INSTALL_TOP)/lib/lua/5.1 -INSTALL_TOP_SHARE=$(INSTALL_TOP)/share/lua/5.2 -INSTALL_TOP_LIB=$(INSTALL_TOP)/lib/lua/5.2 + +INSTALL_DATA=cp +INSTALL_EXEC=cp +INSTALL_TOP=$(DESTDIR)$(prefix) + +INSTALL_TOP_SHARE=$(INSTALL_TOP)/share/lua/$(LUAV) +INSTALL_TOP_LIB=$(INSTALL_TOP)/lib/lua/$(LUAV) INSTALL_SOCKET_SHARE=$(INSTALL_TOP_SHARE)/socket INSTALL_SOCKET_LIB=$(INSTALL_TOP_LIB)/socket -#INSTALL_MIME_SHARE=$(INSTALL_TOP_SHARE)/mime -INSTALL_MIME_SHARE=$(INSTALL_TOP_SHARE)/foo/mime +INSTALL_MIME_SHARE=$(INSTALL_TOP_SHARE)/mime INSTALL_MIME_LIB=$(INSTALL_TOP_LIB)/mime #------ @@ -78,7 +83,7 @@ LDFLAGS_win32= /nologo /link /NOLOGO /DLL /INCREMENTAL:NO \ /MANIFESTFILE:"intermediate.manifest" \ /MANIFESTUAC:"level='asInvoker' uiAccess='false'" \ /SUBSYSTEM:WINDOWS /OPT:REF /OPT:ICF /DYNAMICBASE:NO \ - /MACHINE:X86 ws2_32.lib lua5.1.lib /OUT: + /MACHINE:X86 ws2_32.lib lua$(LUAV).lib /OUT: LD_win32=cl SOCKET_win32=wsocket.obj @@ -97,6 +102,7 @@ MIME_V=1.0.3 SOCKET_SO=socket.$(SO).$(SOCKET_V) MIME_SO=mime.$(SO).$(MIME_V) UNIX_SO=unix.$(SO) +SERIAL_SO=serial.$(SO) SOCKET=$(SOCKET_$(PLAT)) #------ @@ -118,7 +124,7 @@ SOCKET_OBJS= \ timeout.$(O) \ buffer.$(O) \ io.$(O) \ - auxiliar.$(O) \ + auxiliar.$(O) \ options.$(O) \ inet.$(O) \ $(SOCKET) \ @@ -147,6 +153,19 @@ UNIX_OBJS=\ unix.$(O) \ lua_typeerror.$(O) +#------ +# Modules belonging to serial (device streams) +# +SERIAL_OBJS:=\ + buffer.$(O) \ + auxiliar.$(O) \ + options.$(O) \ + timeout.$(O) \ + io.$(O) \ + usocket.$(O) \ + serial.$(O) \ + lua_typeerror.$(O) + #------ # Files to install # @@ -169,13 +188,13 @@ TO_TOP_SHARE= \ default: $(PLAT) macosx: - $(MAKE) all PLAT=macosx + $(MAKE) all-unix PLAT=macosx win32: $(MAKE) all PLAT=win32 linux: - $(MAKE) all PLAT=linux + $(MAKE) all-unix PLAT=linux none: @echo "Please run" @@ -191,9 +210,14 @@ $(SOCKET_SO): $(SOCKET_OBJS) $(MIME_SO): $(MIME_OBJS) $(LD) $(MIME_OBJS) $(LDFLAGS)$@ +all-unix: all $(UNIX_SO) $(SERIAL_SO) + $(UNIX_SO): $(UNIX_OBJS) $(LD) $(UNIX_OBJS) $(LDFLAGS)$@ +$(SERIAL_SO): $(SERIAL_OBJS) + $(LD) $(SERIAL_OBJS) $(LDFLAGS)$@ + install: mkdir -p $(INSTALL_TOP_SHARE) $(INSTALL_DATA) $(TO_TOP_SHARE) $(INSTALL_TOP_SHARE) @@ -204,12 +228,16 @@ install: mkdir -p $(INSTALL_MIME_LIB) $(INSTALL_EXEC) $(MIME_SO) $(INSTALL_MIME_LIB)/core.$(SO) +install-unix: install + $(INSTALL_EXEC) $(UNIX_SO) $(INSTALL_SOCKET_LIB)/$(UNIX_SO) + $(INSTALL_EXEC) $(SERIAL_SO) $(INSTALL_SOCKET_LIB)/$(SERIAL_SO) + local: $(MAKE) install INSTALL_TOP_LIB=.. INSTALL_TOP_SHARE=.. clean: rm -f $(SOCKET_SO) $(SOCKET_OBJS) - rm -f $(MIME_SO) $(UNIX_SO) $(MIME_OBJS) $(UNIX_OBJS) + rm -f $(MIME_SO) $(UNIX_SO) $(SERIAL_SO) $(MIME_OBJS) $(UNIX_OBJS) .PHONY: all $(PLATS) default clean echo none @@ -228,6 +256,8 @@ mime.$(O): mime.c mime.h options.$(O): options.c auxiliar.h options.h socket.h io.h \ timeout.h usocket.h inet.h select.$(O): select.c socket.h io.h timeout.h usocket.h select.h +serial.$(O): serial.c auxiliar.h socket.h io.h timeout.h usocket.h \ + options.h unix.h buffer.h tcp.$(O): tcp.c auxiliar.h socket.h io.h timeout.h usocket.h \ inet.h options.h tcp.h buffer.h timeout.$(O): timeout.c auxiliar.h timeout.h diff --git a/src/mime.lua b/src/mime.lua index 169eda2..218b38a 100644 --- a/src/mime.lua +++ b/src/mime.lua @@ -11,7 +11,6 @@ local base = _G local ltn12 = require("ltn12") local mime = require("mime.core") -local io = require("io") local string = require("string") module("mime") diff --git a/src/options.c b/src/options.c index 801adf9..ab9e621 100644 --- a/src/options.c +++ b/src/options.c @@ -21,6 +21,8 @@ static int opt_setboolean(lua_State *L, p_socket ps, int level, int name); static int opt_getboolean(lua_State *L, p_socket ps, int level, int name); static int opt_set(lua_State *L, p_socket ps, int level, int name, void *val, int len); +static int opt_get(lua_State *L, p_socket ps, int level, int name, + void *val, int* len); /*=========================================================================*\ * Exported functions @@ -60,23 +62,43 @@ int opt_set_reuseaddr(lua_State *L, p_socket ps) return opt_setboolean(L, ps, SOL_SOCKET, SO_REUSEADDR); } +int opt_get_reuseaddr(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_REUSEADDR); +} + /* enables reuse of local port */ int opt_set_reuseport(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, SOL_SOCKET, SO_REUSEPORT); } +int opt_get_reuseport(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_REUSEPORT); +} + /* disables the Naggle algorithm */ int opt_set_tcp_nodelay(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, IPPROTO_TCP, TCP_NODELAY); } +int opt_get_tcp_nodelay(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, IPPROTO_TCP, TCP_NODELAY); +} + int opt_set_keepalive(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, SOL_SOCKET, SO_KEEPALIVE); } +int opt_get_keepalive(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, SOL_SOCKET, SO_KEEPALIVE); +} + int opt_set_dontroute(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, SOL_SOCKET, SO_DONTROUTE); @@ -114,6 +136,21 @@ int opt_set_linger(lua_State *L, p_socket ps) return opt_set(L, ps, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof(li)); } +int opt_get_linger(lua_State *L, p_socket ps) +{ + struct linger li; /* obj, name */ + int len = sizeof(li); + int err = opt_get(L, ps, SOL_SOCKET, SO_LINGER, (char *) &li, &len); + if (err) + return err; + lua_newtable(L); + lua_pushboolean(L, li.l_onoff); + lua_setfield(L, -2, "on"); + lua_pushinteger(L, li.l_linger); + lua_setfield(L, -2, "timeout"); + return 1; +} + int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps) { int val = (int) luaL_checknumber(L, 3); /* obj, name, int */ @@ -184,6 +221,19 @@ static int opt_setmembership(lua_State *L, p_socket ps, int level, int name) return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); } +static +int opt_get(lua_State *L, p_socket ps, int level, int name, void *val, int* len) +{ + socklen_t socklen = *len; + if (getsockopt(*ps, level, name, (char *) val, &socklen) < 0) { + lua_pushnil(L); + lua_pushstring(L, "getsockopt failed"); + return 2; + } + *len = socklen; + return 0; +} + static int opt_set(lua_State *L, p_socket ps, int level, int name, void *val, int len) { @@ -199,12 +249,10 @@ int opt_set(lua_State *L, p_socket ps, int level, int name, void *val, int len) static int opt_getboolean(lua_State *L, p_socket ps, int level, int name) { int val = 0; - socklen_t len = sizeof(val); - if (getsockopt(*ps, level, name, (char *) &val, &len) < 0) { - lua_pushnil(L); - lua_pushstring(L, "getsockopt failed"); - return 2; - } + int len = sizeof(val); + int err = opt_get(L, ps, level, name, (char *) &val, &len); + if (err) + return err; lua_pushboolean(L, val); return 1; } diff --git a/src/options.h b/src/options.h index 70364fc..55447f7 100644 --- a/src/options.h +++ b/src/options.h @@ -33,14 +33,18 @@ int opt_set_ip_multicast_loop(lua_State *L, p_socket ps); int opt_set_ip_add_membership(lua_State *L, p_socket ps); int opt_set_ip_drop_membersip(lua_State *L, p_socket ps); int opt_set_ip6_v6only(lua_State *L, p_socket ps); -/* invokes the appropriate option handler */ -int opt_meth_setoption(lua_State *L, p_opt opt, p_socket ps); /* supported options for getoption */ +int opt_get_reuseaddr(lua_State *L, p_socket ps); +int opt_get_tcp_nodelay(lua_State *L, p_socket ps); +int opt_get_keepalive(lua_State *L, p_socket ps); +int opt_get_linger(lua_State *L, p_socket ps); +int opt_get_reuseaddr(lua_State *L, p_socket ps); int opt_get_ip_multicast_loop(lua_State *L, p_socket ps); int opt_get_ip_multicast_if(lua_State *L, p_socket ps); + /* invokes the appropriate option handler */ +int opt_meth_setoption(lua_State *L, p_opt opt, p_socket ps); int opt_meth_getoption(lua_State *L, p_opt opt, p_socket ps); - #endif diff --git a/src/serial.c b/src/serial.c new file mode 100644 index 0000000..5b09e76 --- /dev/null +++ b/src/serial.c @@ -0,0 +1,183 @@ +/*=========================================================================*\ +* Serial stream +* LuaSocket toolkit +\*=========================================================================*/ +#include+ +#include "lua.h" +#include "lauxlib.h" + +#include "auxiliar.h" +#include "socket.h" +#include "options.h" +#include "unix.h" +#include + +/* +Reuses userdata definition from unix.h, since it is useful for all +stream-like objects. + +If we stored the serial path for use in error messages or userdata +printing, we might need our own userdata definition. + +Group usage is semi-inherited from unix.c, but unnecessary since we +have only one object type. +*/ + +/*=========================================================================*\ +* Internal function prototypes +\*=========================================================================*/ +static int global_create(lua_State *L); +static int meth_send(lua_State *L); +static int meth_receive(lua_State *L); +static int meth_close(lua_State *L); +static int meth_settimeout(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); +static int meth_dirty(lua_State *L); +static int meth_getstats(lua_State *L); +static int meth_setstats(lua_State *L); + +/* serial object methods */ +static luaL_Reg serial_methods[] = { + {"__gc", meth_close}, + {"__tostring", auxiliar_tostring}, + {"close", meth_close}, + {"dirty", meth_dirty}, + {"getfd", meth_getfd}, + {"getstats", meth_getstats}, + {"setstats", meth_setstats}, + {"receive", meth_receive}, + {"send", meth_send}, + {"setfd", meth_setfd}, + {"settimeout", meth_settimeout}, + {NULL, NULL} +}; + +/* our socket creation function */ +static luaL_Reg func[] = { + {"serial", global_create}, + {NULL, NULL} +}; + + +/*-------------------------------------------------------------------------*\ +* Initializes module +\*-------------------------------------------------------------------------*/ +LUASOCKET_API int luaopen_socket_serial(lua_State *L) { + /* create classes */ + auxiliar_newclass(L, "serial{client}", serial_methods); + /* create class groups */ + auxiliar_add2group(L, "serial{client}", "serial{any}"); + /* make sure the function ends up in the package table */ + luaL_openlib(L, "socket", func, 0); + /* return the function instead of the 'socket' table */ + lua_pushstring(L, "serial"); + lua_gettable(L, -2); + return 1; +} + +/*=========================================================================*\ +* Lua methods +\*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Just call buffered IO methods +\*-------------------------------------------------------------------------*/ +static int meth_send(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_send(L, &un->buf); +} + +static int meth_receive(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_receive(L, &un->buf); +} + +static int meth_getstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_getstats(L, &un->buf); +} + +static int meth_setstats(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkclass(L, "serial{client}", 1); + return buffer_meth_setstats(L, &un->buf); +} + +/*-------------------------------------------------------------------------*\ +* Select support methods +\*-------------------------------------------------------------------------*/ +static int meth_getfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + lua_pushnumber(L, (int) un->sock); + return 1; +} + +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + un->sock = (t_socket) luaL_checknumber(L, 2); + return 0; +} + +static int meth_dirty(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + lua_pushboolean(L, !buffer_isempty(&un->buf)); + return 1; +} + +/*-------------------------------------------------------------------------*\ +* Closes socket used by object +\*-------------------------------------------------------------------------*/ +static int meth_close(lua_State *L) +{ + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + socket_destroy(&un->sock); + lua_pushnumber(L, 1); + return 1; +} + + +/*-------------------------------------------------------------------------*\ +* Just call tm methods +\*-------------------------------------------------------------------------*/ +static int meth_settimeout(lua_State *L) { + p_unix un = (p_unix) auxiliar_checkgroup(L, "serial{any}", 1); + return timeout_meth_settimeout(L, &un->tm); +} + +/*=========================================================================*\ +* Library functions +\*=========================================================================*/ + + +/*-------------------------------------------------------------------------*\ +* Creates a serial object +\*-------------------------------------------------------------------------*/ +static int global_create(lua_State *L) { + const char* path = luaL_checkstring(L, 1); + + /* allocate unix object */ + p_unix un = (p_unix) lua_newuserdata(L, sizeof(t_unix)); + + /* open serial device */ + t_socket sock = open(path, O_NOCTTY|O_RDWR); + + /*printf("open %s on %d\n", path, sock);*/ + + if (sock < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + lua_pushnumber(L, errno); + return 3; + } + /* set its type as client object */ + auxiliar_setclass(L, "serial{client}", -1); + /* initialize remaining structure fields */ + socket_setnonblocking(&sock); + un->sock = sock; + io_init(&un->io, (p_send) socket_write, (p_recv) socket_read, + (p_error) socket_ioerror, &un->sock); + timeout_init(&un->tm, -1, -1); + buffer_init(&un->buf, &un->io, &un->tm); + return 1; +} diff --git a/src/socket.h b/src/socket.h index e325952..63573de 100644 --- a/src/socket.h +++ b/src/socket.h @@ -67,6 +67,9 @@ const char *socket_strerror(int err); int socket_send(p_socket ps, const char *data, size_t count, size_t *sent, p_timeout tm); int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm); +int socket_write(p_socket ps, const char *data, size_t count, + size_t *sent, p_timeout tm); +int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm); const char *socket_ioerror(p_socket ps, int err); int socket_gethostbyaddr(const char *addr, socklen_t len, struct hostent **hp); diff --git a/src/tcp.c b/src/tcp.c index 19ee73c..3146467 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -33,6 +33,7 @@ static int meth_shutdown(lua_State *L); static int meth_receive(lua_State *L); static int meth_accept(lua_State *L); static int meth_close(lua_State *L); +static int meth_getoption(lua_State *L); static int meth_setoption(lua_State *L); static int meth_settimeout(lua_State *L); static int meth_getfd(lua_State *L); @@ -49,6 +50,7 @@ static luaL_Reg tcp_methods[] = { {"connect", meth_connect}, {"dirty", meth_dirty}, {"getfd", meth_getfd}, + {"getoption", meth_getoption}, {"getpeername", meth_getpeername}, {"getsockname", meth_getsockname}, {"getstats", meth_getstats}, @@ -66,6 +68,14 @@ static luaL_Reg tcp_methods[] = { }; /* socket option handlers */ +static t_opt optget[] = { + {"keepalive", opt_get_keepalive}, + {"reuseaddr", opt_get_reuseaddr}, + {"tcp-nodelay", opt_get_tcp_nodelay}, + {"linger", opt_get_linger}, + {NULL, NULL} +}; + static t_opt optset[] = { {"keepalive", opt_set_keepalive}, {"reuseaddr", opt_set_reuseaddr}, @@ -130,6 +140,12 @@ static int meth_setstats(lua_State *L) { /*-------------------------------------------------------------------------*\ * Just call option handler \*-------------------------------------------------------------------------*/ +static int meth_getoption(lua_State *L) +{ + p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); + return opt_meth_getoption(L, optget, &tcp->sock); +} + static int meth_setoption(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); diff --git a/src/udp.c b/src/udp.c index 13007fb..386051a 100644 --- a/src/udp.c +++ b/src/udp.c @@ -184,6 +184,9 @@ static int meth_receive(lua_State *L) { count = MIN(count, sizeof(buffer)); timeout_markstart(tm); err = socket_recv(&udp->sock, buffer, count, &got, tm); + /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ + if (err == IO_CLOSED) + err = IO_DONE; if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, udp_strerror(err)); @@ -208,6 +211,9 @@ static int meth_receivefrom(lua_State *L) { count = MIN(count, sizeof(buffer)); err = socket_recvfrom(&udp->sock, buffer, count, &got, (SA *) &addr, &addr_len, tm); + /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ + if (err == IO_CLOSED) + err = IO_DONE; if (err == IO_DONE) { lua_pushlstring(L, buffer, got); lua_pushstring(L, inet_ntoa(addr.sin_addr)); diff --git a/src/unix.c b/src/unix.c index b08d325..935d4c3 100644 --- a/src/unix.c +++ b/src/unix.c @@ -39,7 +39,7 @@ static const char *unix_tryconnect(p_unix un, const char *path); static const char *unix_trybind(p_unix un, const char *path); /* unix object methods */ -static luaL_Reg un[] = { +static luaL_Reg unix_methods[] = { {"__gc", meth_close}, {"__tostring", auxiliar_tostring}, {"accept", meth_accept}, @@ -82,9 +82,9 @@ static luaL_Reg func[] = { \*-------------------------------------------------------------------------*/ int luaopen_socket_unix(lua_State *L) { /* create classes */ - auxiliar_newclass(L, "unix{master}", un); - auxiliar_newclass(L, "unix{client}", un); - auxiliar_newclass(L, "unix{server}", un); + auxiliar_newclass(L, "unix{master}", unix_methods); + auxiliar_newclass(L, "unix{client}", unix_methods); + auxiliar_newclass(L, "unix{server}", unix_methods); /* create class groups */ auxiliar_add2group(L, "unix{master}", "unix{any}"); auxiliar_add2group(L, "unix{client}", "unix{any}"); diff --git a/src/unix.h b/src/unix.h index 39fa719..3a7e1e6 100644 --- a/src/unix.h +++ b/src/unix.h @@ -21,6 +21,6 @@ typedef struct t_unix_ { } t_unix; typedef t_unix *p_unix; -int luaopen_socket_unix(lua_State *L); +LUASOCKET_API int luaopen_socket_unix(lua_State *L); #endif /* UNIX_H */ diff --git a/src/usocket.c b/src/usocket.c index fdab123..bf2d19c 100644 --- a/src/usocket.c +++ b/src/usocket.c @@ -16,7 +16,7 @@ /*-------------------------------------------------------------------------*\ * Wait for readable/writable/connected socket with timeout \*-------------------------------------------------------------------------*/ -#ifdef SOCKET_POLL +#ifndef SOCKET_SELECT #include #define WAITFD_R POLLIN @@ -49,6 +49,7 @@ int socket_waitfd(p_socket ps, int sw, p_timeout tm) { fd_set rfds, wfds, *rp, *wp; struct timeval tv, *tp; double t; + if (*ps >= FD_SETSIZE) return EINVAL; if (timeout_iszero(tm)) return IO_TIMEOUT; /* optimize timeout == 0 case */ do { /* must set bits within loop, because select may have modifed them */ @@ -213,14 +214,13 @@ int socket_send(p_socket ps, const char *data, size_t count, for ( ;; ) { long put = (long) send(*ps, data, count, 0); /* if we sent anything, we are done */ - if (put > 0) { + if (put >= 0) { *sent = put; return IO_DONE; } err = errno; - /* send can't really return 0, but EPIPE means the connection was - closed */ - if (put == 0 || err == EPIPE) return IO_CLOSED; + /* EPIPE means the connection was closed */ + if (err == EPIPE) return IO_CLOSED; /* we call was interrupted, just try again */ if (err == EINTR) continue; /* if failed fatal reason, report error */ @@ -243,12 +243,12 @@ int socket_sendto(p_socket ps, const char *data, size_t count, size_t *sent, if (*ps == SOCKET_INVALID) return IO_CLOSED; for ( ;; ) { long put = (long) sendto(*ps, data, count, 0, addr, len); - if (put > 0) { + if (put >= 0) { *sent = put; return IO_DONE; } err = errno; - if (put == 0 || err == EPIPE) return IO_CLOSED; + if (err == EPIPE) return IO_CLOSED; if (err == EINTR) continue; if (err != EAGAIN) return err; if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; @@ -301,6 +301,66 @@ int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, return IO_UNKNOWN; } + +/*-------------------------------------------------------------------------*\ +* Write with timeout +* +* socket_read and socket_write are cut-n-paste of socket_send and socket_recv, +* with send/recv replaced with write/read. We can't just use write/read +* in the socket version, because behaviour when size is zero is different. +\*-------------------------------------------------------------------------*/ +int socket_write(p_socket ps, const char *data, size_t count, + size_t *sent, p_timeout tm) +{ + int err; + *sent = 0; + /* avoid making system calls on closed sockets */ + if (*ps == SOCKET_INVALID) return IO_CLOSED; + /* loop until we send something or we give up on error */ + for ( ;; ) { + long put = (long) write(*ps, data, count); + /* if we sent anything, we are done */ + if (put >= 0) { + *sent = put; + return IO_DONE; + } + err = errno; + /* EPIPE means the connection was closed */ + if (err == EPIPE) return IO_CLOSED; + /* we call was interrupted, just try again */ + if (err == EINTR) continue; + /* if failed fatal reason, report error */ + if (err != EAGAIN) return err; + /* wait until we can send something or we timeout */ + if ((err = socket_waitfd(ps, WAITFD_W, tm)) != IO_DONE) return err; + } + /* can't reach here */ + return IO_UNKNOWN; +} + +/*-------------------------------------------------------------------------*\ +* Read with timeout +* See note for socket_write +\*-------------------------------------------------------------------------*/ +int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm) { + int err; + *got = 0; + if (*ps == SOCKET_INVALID) return IO_CLOSED; + for ( ;; ) { + long taken = (long) read(*ps, data, count); + if (taken > 0) { + *got = taken; + return IO_DONE; + } + err = errno; + if (taken == 0) return IO_CLOSED; + if (err == EINTR) continue; + if (err != EAGAIN) return err; + if ((err = socket_waitfd(ps, WAITFD_R, tm)) != IO_DONE) return err; + } + return IO_UNKNOWN; +} + /*-------------------------------------------------------------------------*\ * Put socket into blocking mode \*-------------------------------------------------------------------------*/ @@ -360,7 +420,7 @@ const char *socket_strerror(int err) { case ECONNABORTED: return "closed"; case ECONNRESET: return "closed"; case ETIMEDOUT: return "timeout"; - default: return strerror(errno); + default: return strerror(err); } } diff --git a/test/find-connect-limit b/test/find-connect-limit new file mode 100755 index 0000000..ad0c3f5 --- /dev/null +++ b/test/find-connect-limit @@ -0,0 +1,32 @@ +#!/usr/bin/env lua +--[[ +Find out how many TCP connections we can make. + +Use ulimit to increase the max number of descriptors: + +ulimit -n 10000 +ulimit -n + +You'll probably need to be root to do this. +]] + +require "socket" + +host = arg[1] or "google.com" +port = arg[2] or 80 + +connections = {} + +repeat + c = assert(socket.connect(hostip or host, 80)) + table.insert(connections, c) + + if not hostip then + hostip = c:getpeername() + print("resolved", host, "to", hostip) + end + + print("connection #", #connections, c, "fd", c:getfd()) + +until false + diff --git a/test/tcp-getoptions b/test/tcp-getoptions new file mode 100755 index 0000000..f9b3d1b --- /dev/null +++ b/test/tcp-getoptions @@ -0,0 +1,41 @@ +#!/usr/bin/env lua + +require"socket" + +port = 8765 + +function options(o) + print("options for", o) + + for _, opt in ipairs{"keepalive", "reuseaddr", "tcp-nodelay"} do + print("getoption", opt, o:getoption(opt)) + end + + print("getoption", "linger", + "on", o:getoption("linger").on, + "timeout", o:getoption("linger").timeout) +end + +local m = socket.tcp() + +options(m) + +assert(m:bind("*", port)) +assert(m:listen()) + +options(m) + +m:close() + +local m = socket.bind("*", port) + +options(m) + +local c = socket.connect("localhost", port) + +options(c) + +local s = m:accept() + +options(s) + diff --git a/test/testsrvr.lua b/test/testsrvr.lua index 7ddff6e..ff31442 100644 --- a/test/testsrvr.lua +++ b/test/testsrvr.lua @@ -7,7 +7,12 @@ while 1 do print("server: waiting for client connection..."); control = assert(server:accept()); while 1 do - command = assert(control:receive()); + command, emsg = control:receive(); + if emsg == "closed" then + control:close() + break + end + assert(command, emsg) assert(control:send(ack)); print(command); (load(command))(); diff --git a/test/udp-zero-length-send b/test/udp-zero-length-send new file mode 100755 index 0000000..a594944 --- /dev/null +++ b/test/udp-zero-length-send @@ -0,0 +1,25 @@ +#!/usr/bin/lua + +--[[ +Show that luasocket returns an error message on zero-length UDP sends, +even though the send is valid, and in fact the UDP packet is sent +to the peer: + +% sudo tcpdump -i lo -n +tcpdump: verbose output suppressed, use -v or -vv for full protocol decode +listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes +13:40:16.652808 IP 127.0.0.1.56573 > 127.0.0.1.5432: UDP, length 0 + +]] + +require"socket" + +s = assert(socket.udp()) +r = assert(socket.udp()) +assert(r:setsockname("*", 5432)) +assert(s:setpeername("127.0.0.1", 5432)) + +ssz, emsg = s:send("") + +print(ssz == 0 and "OK" or "FAIL",[[send:("")]], ssz, emsg) + diff --git a/test/udp-zero-length-send-recv b/test/udp-zero-length-send-recv new file mode 100755 index 0000000..541efd4 --- /dev/null +++ b/test/udp-zero-length-send-recv @@ -0,0 +1,37 @@ +#!/usr/bin/lua + +--[[ +Show that luasocket returns an error message on zero-length UDP sends, +even though the send is valid, and in fact the UDP packet is sent +to the peer: + +% sudo tcpdump -i lo -n +tcpdump: verbose output suppressed, use -v or -vv for full protocol decode +listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes +13:40:16.652808 IP 127.0.0.1.56573 > 127.0.0.1.5432: UDP, length 0 + +]] + +require"socket" + +s = assert(socket.udp()) +r = assert(socket.udp()) +assert(r:setsockname("*", 5432)) +assert(s:setpeername("127.0.0.1", 5432)) + +ok, emsg = s:send("") +if ok ~= 0 then + print("send of zero failed with:", ok, emsg) +end + +assert(r:settimeout(2)) + +ok, emsg = r:receive() + +if not ok or string.len(ok) ~= 0 then + print("fail - receive of zero failed with:", ok, emsg) + os.exit(1) +end + +print"ok" +