From 3f4f28010aa5065456f1edf97de1ab268cc49944 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Thu, 3 Apr 2025 11:32:49 -0300 Subject: io.write returns number of written bytes on error --- liolib.c | 18 +++++++++++------- ltests.c | 20 ++++++++++++++++++++ manual/manual.of | 3 +++ testes/files.lua | 31 +++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/liolib.c b/liolib.c index a0988db0..3225ed5f 100644 --- a/liolib.c +++ b/liolib.c @@ -662,11 +662,12 @@ static int io_readline (lua_State *L) { static int g_write (lua_State *L, FILE *f, int arg) { int nargs = lua_gettop(L) - arg; - int status = 1; + size_t totalbytes = 0; /* total number of bytes written */ errno = 0; - for (; nargs--; arg++) { + for (; nargs--; arg++) { /* for each argument */ char buff[LUA_N2SBUFFSZ]; const char *s; + size_t numbytes; /* bytes written in one call to 'fwrite' */ size_t len = lua_numbertocstring(L, arg, buff); /* try as a number */ if (len > 0) { /* did conversion work (value was a number)? */ s = buff; @@ -674,12 +675,15 @@ static int g_write (lua_State *L, FILE *f, int arg) { } else /* must be a string */ s = luaL_checklstring(L, arg, &len); - status = status && (fwrite(s, sizeof(char), len, f) == len); + numbytes = fwrite(s, sizeof(char), len, f); + totalbytes += numbytes; + if (numbytes < len) { /* write error? */ + int n = luaL_fileresult(L, 0, NULL); + lua_pushinteger(L, cast_st2S(totalbytes)); + return n + 1; /* return fail, error msg., error code, and counter */ + } } - if (l_likely(status)) - return 1; /* file handle already on stack top */ - else - return luaL_fileresult(L, status, NULL); + return 1; /* no errors; file handle already on stack top */ } diff --git a/ltests.c b/ltests.c index 6a638d05..1517aa88 100644 --- a/ltests.c +++ b/ltests.c @@ -2106,6 +2106,25 @@ static int coresume (lua_State *L) { } } +#if !defined(LUA_USE_POSIX) + +#define nonblock NULL + +#else + +#include +#include + +static int nonblock (lua_State *L) { + FILE *f = cast(luaL_Stream*, luaL_checkudata(L, 1, LUA_FILEHANDLE))->f; + int fd = fileno(f); + int flags = fcntl(fd, F_GETFL, 0); + flags |= O_NONBLOCK; + fcntl(fd, F_SETFL, flags); + return 0; +} +#endif + /* }====================================================== */ @@ -2159,6 +2178,7 @@ static const struct luaL_Reg tests_funcs[] = { {"upvalue", upvalue}, {"externKstr", externKstr}, {"externstr", externstr}, + {"nonblock", nonblock}, {NULL, NULL} }; diff --git a/manual/manual.of b/manual/manual.of index c1a9c138..7cd0d4db 100644 --- a/manual/manual.of +++ b/manual/manual.of @@ -8699,6 +8699,9 @@ Writes the value of each of its arguments to @id{file}. The arguments must be strings or numbers. In case of success, this function returns @id{file}. +Otherwise, it returns four values: +@fail, the error message, the error code, +and the number of bytes it was able to write. } diff --git a/testes/files.lua b/testes/files.lua index 05fae49b..2c802047 100644 --- a/testes/files.lua +++ b/testes/files.lua @@ -696,6 +696,37 @@ do end +if T and T.nonblock then + print("testing failed write") + + -- unable to write anything to /dev/full + local f = io.open("/dev/full", "w") + assert(f:setvbuf("no")) + local _, _, err, count = f:write("abcd") + assert(err > 0 and count == 0) + assert(f:close()) + + -- receiver will read a "few" bytes (enough to empty a large buffer) + local receiver = [[ + lua -e 'assert(io.stdin:setvbuf("no")); assert(#io.read(1e4) == 1e4)' ]] + + local f = io.popen(receiver, "w") + assert(f:setvbuf("no")) + T.nonblock(f) + + -- able to write a few bytes + assert(f:write(string.rep("a", 1e2))) + + -- Unable to write more bytes than the pipe buffer supports. + -- (In Linux, the pipe buffer size is 64K (2^16). Posix requires at + -- least 512 bytes.) + local _, _, err, count = f:write("abcd", string.rep("a", 2^17)) + assert(err > 0 and count >= 512 and count < 2^17) + + assert(f:close()) +end + + if not _soft then print("testing large files (> BUFSIZ)") io.output(file) -- cgit v1.2.3-55-g6feb