diff options
| author | Caleb Maclennan <caleb@alerque.com> | 2026-08-31 11:00:36 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-31 11:00:36 +0300 |
| commit | b836466de5a78e9b4f29ca6c85a2bdecd7c69da9 (patch) | |
| tree | 465b3b3a8121b1625b7ee3c9fba2e1f3cdce8a3c /src | |
| parent | 8f18ce95bb38c7f5c4bef5b3684cf1b0df1fc266 (diff) | |
| parent | 75d638ac9cc613f39ec4513d3dc2e43a6f909da1 (diff) | |
| download | luasocket-b836466de5a78e9b4f29ca6c85a2bdecd7c69da9.tar.gz luasocket-b836466de5a78e9b4f29ca6c85a2bdecd7c69da9.tar.bz2 luasocket-b836466de5a78e9b4f29ca6c85a2bdecd7c69da9.zip | |
Merge pull request #471 from lunarmodules/fix/zero-read
fix(receive): a receive 0 should immediately return, not block
Diffstat (limited to 'src')
| -rw-r--r-- | src/buffer.c | 23 | ||||
| -rw-r--r-- | src/udp.c | 12 | ||||
| -rw-r--r-- | src/usocket.c | 2 | ||||
| -rw-r--r-- | src/wsocket.c | 9 |
4 files changed, 36 insertions, 10 deletions
diff --git a/src/buffer.c b/src/buffer.c index 3d48a09..b7e97f7 100644 --- a/src/buffer.c +++ b/src/buffer.c | |||
| @@ -11,7 +11,7 @@ | |||
| 11 | static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b); | 11 | static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b); |
| 12 | static int recvline(p_buffer buf, luaL_Buffer *b, size_t budget); | 12 | static int recvline(p_buffer buf, luaL_Buffer *b, size_t budget); |
| 13 | static int recvall(p_buffer buf, luaL_Buffer *b, size_t budget); | 13 | static int recvall(p_buffer buf, luaL_Buffer *b, size_t budget); |
| 14 | static int buffer_get(p_buffer buf, const char **data, size_t *count); | 14 | static int buffer_get(p_buffer buf, const char **data, size_t *count, size_t wanted); |
| 15 | static void buffer_skip(p_buffer buf, size_t count); | 15 | static void buffer_skip(p_buffer buf, size_t count); |
| 16 | static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent); | 16 | static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent); |
| 17 | 17 | ||
| @@ -230,15 +230,14 @@ static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent) { | |||
| 230 | static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b) { | 230 | static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b) { |
| 231 | int err = IO_DONE; | 231 | int err = IO_DONE; |
| 232 | size_t total = 0; | 232 | size_t total = 0; |
| 233 | while (err == IO_DONE) { | 233 | do { |
| 234 | size_t count; const char *data; | 234 | size_t count; const char *data; |
| 235 | err = buffer_get(buf, &data, &count); | 235 | err = buffer_get(buf, &data, &count, wanted - total); |
| 236 | count = MIN(count, wanted - total); | 236 | count = MIN(count, wanted - total); |
| 237 | luaL_addlstring(b, data, count); | 237 | luaL_addlstring(b, data, count); |
| 238 | buffer_skip(buf, count); | 238 | buffer_skip(buf, count); |
| 239 | total += count; | 239 | total += count; |
| 240 | if (total >= wanted) break; | 240 | } while (total < wanted && err == IO_DONE); |
| 241 | } | ||
| 242 | return err; | 241 | return err; |
| 243 | } | 242 | } |
| 244 | 243 | ||
| @@ -253,7 +252,7 @@ static int recvall(p_buffer buf, luaL_Buffer *b, size_t budget) { | |||
| 253 | size_t total = 0; | 252 | size_t total = 0; |
| 254 | while (err == IO_DONE) { | 253 | while (err == IO_DONE) { |
| 255 | const char *data; size_t count; | 254 | const char *data; size_t count; |
| 256 | err = buffer_get(buf, &data, &count); | 255 | err = buffer_get(buf, &data, &count, BUF_SIZE); |
| 257 | if (budget && count > budget - total) { /* strictly more than fits */ | 256 | if (budget && count > budget - total) { /* strictly more than fits */ |
| 258 | count = budget - total; | 257 | count = budget - total; |
| 259 | luaL_addlstring(b, data, count); | 258 | luaL_addlstring(b, data, count); |
| @@ -286,7 +285,7 @@ static int recvline(p_buffer buf, luaL_Buffer *b, size_t budget) { | |||
| 286 | size_t total = 0; | 285 | size_t total = 0; |
| 287 | while (err == IO_DONE) { | 286 | while (err == IO_DONE) { |
| 288 | size_t count, pos; const char *data; | 287 | size_t count, pos; const char *data; |
| 289 | err = buffer_get(buf, &data, &count); | 288 | err = buffer_get(buf, &data, &count, BUF_SIZE); |
| 290 | pos = 0; | 289 | pos = 0; |
| 291 | while (pos < count && data[pos] != '\n') { | 290 | while (pos < count && data[pos] != '\n') { |
| 292 | /* we ignore all \r's -- they are consumed but never counted */ | 291 | /* we ignore all \r's -- they are consumed but never counted */ |
| @@ -325,15 +324,19 @@ static void buffer_skip(p_buffer buf, size_t count) { | |||
| 325 | 324 | ||
| 326 | /*-------------------------------------------------------------------------*\ | 325 | /*-------------------------------------------------------------------------*\ |
| 327 | * Return any data available in buffer, or get more data from transport layer | 326 | * Return any data available in buffer, or get more data from transport layer |
| 328 | * if buffer is empty | 327 | * if buffer is empty. 'wanted' is how many more bytes the caller is still |
| 328 | * after; when it is zero, the transport layer is still consulted (so an | ||
| 329 | * already-closed connection is still reported), but no more than zero bytes | ||
| 330 | * are requested from it, so a healthy connection with no data pending can | ||
| 331 | * never block. | ||
| 329 | \*-------------------------------------------------------------------------*/ | 332 | \*-------------------------------------------------------------------------*/ |
| 330 | static int buffer_get(p_buffer buf, const char **data, size_t *count) { | 333 | static int buffer_get(p_buffer buf, const char **data, size_t *count, size_t wanted) { |
| 331 | int err = IO_DONE; | 334 | int err = IO_DONE; |
| 332 | p_io io = buf->io; | 335 | p_io io = buf->io; |
| 333 | p_timeout tm = buf->tm; | 336 | p_timeout tm = buf->tm; |
| 334 | if (buffer_isempty(buf)) { | 337 | if (buffer_isempty(buf)) { |
| 335 | size_t got; | 338 | size_t got; |
| 336 | err = io->recv(io->ctx, buf->data, BUF_SIZE, &got, tm); | 339 | err = io->recv(io->ctx, buf->data, wanted == 0 ? 0 : BUF_SIZE, &got, tm); |
| 337 | buf->first = 0; | 340 | buf->first = 0; |
| 338 | buf->last = got; | 341 | buf->last = got; |
| 339 | } | 342 | } |
| @@ -283,6 +283,7 @@ static int meth_receivefrom(lua_State *L) { | |||
| 283 | lua_pushliteral(L, "out of memory"); | 283 | lua_pushliteral(L, "out of memory"); |
| 284 | return 2; | 284 | return 2; |
| 285 | } | 285 | } |
| 286 | memset(&addr, 0, sizeof(addr)); | ||
| 286 | err = socket_recvfrom(&udp->sock, dgram, wanted, &got, (SA *) &addr, | 287 | err = socket_recvfrom(&udp->sock, dgram, wanted, &got, (SA *) &addr, |
| 287 | &addr_len, tm); | 288 | &addr_len, tm); |
| 288 | /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ | 289 | /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ |
| @@ -292,6 +293,17 @@ static int meth_receivefrom(lua_State *L) { | |||
| 292 | if (wanted > sizeof(buf)) free(dgram); | 293 | if (wanted > sizeof(buf)) free(dgram); |
| 293 | return 2; | 294 | return 2; |
| 294 | } | 295 | } |
| 296 | /* a zero-length request may be satisfied by some kernels (notably | ||
| 297 | * Darwin/BSD) without ever touching the sender's address -- only | ||
| 298 | * resolve it when the OS actually reported one, instead of feeding | ||
| 299 | * getnameinfo() a garbage/zeroed sockaddr. */ | ||
| 300 | if (addr.ss_family != AF_INET && addr.ss_family != AF_INET6) { | ||
| 301 | lua_pushlstring(L, dgram, got); | ||
| 302 | lua_pushnil(L); | ||
| 303 | lua_pushnil(L); | ||
| 304 | if (wanted > sizeof(buf)) free(dgram); | ||
| 305 | return 3; | ||
| 306 | } | ||
| 295 | err = getnameinfo((struct sockaddr *)&addr, addr_len, addrstr, | 307 | err = getnameinfo((struct sockaddr *)&addr, addr_len, addrstr, |
| 296 | INET6_ADDRSTRLEN, portstr, 6, NI_NUMERICHOST | NI_NUMERICSERV); | 308 | INET6_ADDRSTRLEN, portstr, 6, NI_NUMERICHOST | NI_NUMERICSERV); |
| 297 | if (err) { | 309 | if (err) { |
diff --git a/src/usocket.c b/src/usocket.c index 7965db6..e00c43d 100644 --- a/src/usocket.c +++ b/src/usocket.c | |||
| @@ -258,6 +258,7 @@ int socket_recv(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm | |||
| 258 | int err; | 258 | int err; |
| 259 | *got = 0; | 259 | *got = 0; |
| 260 | if (*ps == SOCKET_INVALID) return IO_CLOSED; | 260 | if (*ps == SOCKET_INVALID) return IO_CLOSED; |
| 261 | if (count == 0) return IO_DONE; | ||
| 261 | for ( ;; ) { | 262 | for ( ;; ) { |
| 262 | long taken = (long) recv(*ps, data, count, 0); | 263 | long taken = (long) recv(*ps, data, count, 0); |
| 263 | if (taken > 0) { | 264 | if (taken > 0) { |
| @@ -343,6 +344,7 @@ int socket_read(p_socket ps, char *data, size_t count, size_t *got, p_timeout tm | |||
| 343 | int err; | 344 | int err; |
| 344 | *got = 0; | 345 | *got = 0; |
| 345 | if (*ps == SOCKET_INVALID) return IO_CLOSED; | 346 | if (*ps == SOCKET_INVALID) return IO_CLOSED; |
| 347 | if (count == 0) return IO_DONE; | ||
| 346 | for ( ;; ) { | 348 | for ( ;; ) { |
| 347 | long taken = (long) read(*ps, data, count); | 349 | long taken = (long) read(*ps, data, count); |
| 348 | if (taken > 0) { | 350 | if (taken > 0) { |
diff --git a/src/wsocket.c b/src/wsocket.c index d3af9d4..b2b668c 100644 --- a/src/wsocket.c +++ b/src/wsocket.c | |||
| @@ -247,6 +247,7 @@ int socket_recv(p_socket ps, char *data, size_t count, size_t *got, | |||
| 247 | int err, prev = IO_DONE; | 247 | int err, prev = IO_DONE; |
| 248 | *got = 0; | 248 | *got = 0; |
| 249 | if (*ps == SOCKET_INVALID) return IO_CLOSED; | 249 | if (*ps == SOCKET_INVALID) return IO_CLOSED; |
| 250 | if (count == 0) return IO_DONE; | ||
| 250 | for ( ;; ) { | 251 | for ( ;; ) { |
| 251 | int taken = recv(*ps, data, (int) count, 0); | 252 | int taken = recv(*ps, data, (int) count, 0); |
| 252 | if (taken > 0) { | 253 | if (taken > 0) { |
| @@ -285,6 +286,14 @@ int socket_recvfrom(p_socket ps, char *data, size_t count, size_t *got, | |||
| 285 | } | 286 | } |
| 286 | if (taken == 0) return IO_CLOSED; | 287 | if (taken == 0) return IO_CLOSED; |
| 287 | err = WSAGetLastError(); | 288 | err = WSAGetLastError(); |
| 289 | /* a zero-length request is trivially "too small" for any | ||
| 290 | * non-empty datagram; unlike POSIX, which truncates and succeeds | ||
| 291 | * silently, Windows reports this as WSAEMSGSIZE even though the | ||
| 292 | * datagram -- and its sender's address, already written to addr/ | ||
| 293 | * len above -- was still consumed. Normalize it to match POSIX's | ||
| 294 | * silent-truncation instead of surfacing a platform-specific | ||
| 295 | * error for what is otherwise a successful, if empty, receive. */ | ||
| 296 | if (count == 0 && err == WSAEMSGSIZE) return IO_DONE; | ||
| 288 | /* On UDP, a connreset simply means the previous send failed. | 297 | /* On UDP, a connreset simply means the previous send failed. |
| 289 | * So we try again. | 298 | * So we try again. |
| 290 | * On TCP, it means our socket is now useless, so the error passes. | 299 | * On TCP, it means our socket is now useless, so the error passes. |
