From c60ce077293214dceb499fe5ce2819b5c34e8a40 Mon Sep 17 00:00:00 2001 From: Tobias Ulmer Date: Wed, 23 Oct 2013 17:50:11 +0200 Subject: [PATCH] Rewrite luafile Turn into a "modern" module like the rest, document, add error handling and remove all methods not in use by current code. Remove object-style access like f:close() to increase grepability of the code. Rename methods to their C equivalents. Signed-off-by: Tobias Ulmer --- generic/luafile.lua | 321 ++++++++++++++++++++++++++++++++----------- generic/luafile_ll.c | 279 +++++++++++++++++++++++-------------- 2 files changed, 417 insertions(+), 183 deletions(-) diff --git a/generic/luafile.lua b/generic/luafile.lua index 2038138..53d9814 100644 --- a/generic/luafile.lua +++ b/generic/luafile.lua @@ -29,139 +29,306 @@ ]] local luafile = {} +local e2lib = require("e2lib") +local err = require("err") +local luafile_ll = require("luafile_ll") local strict = require("strict") -require("luafile_ll") + +--- Numeric constant for stdin. +luafile.STDIN = 0; +--- Numeric constant for stdout. +luafile.STDOUT = 1; +--- Numeric constant for sterr. +luafile.STDERR = 2; + +--- Check whether a luafile object is valid and contains an open file. +-- @param luafile File object. +-- @return True on success, false on error. +-- @return Error object on failure. +local function valid_open_luafile(luafile) + local msg = "Internal luafile error: Please report this error:" + + if type(luafile) ~= "table" then + return false, err.new("%s invalid object", msg) + end + + if type(luafile.file) == "boolean" and not luafile.file then + return false, + err.new("%s no open file", msg) + end + + if type(luafile.file) ~= "userdata" then + return false, err.new("%s invalid internal field structure") + end + + return true +end --- Create new file object. --- @return File object. This functions always succeeds. +-- @return File object. This function always succeeds. function luafile.new() - local f = {} - local meta = { __index = luafile } - setmetatable(f, meta) - return f + local luafile = {} + luafile.file = false + return strict.lock(luafile) end --- Open a file. -- @param path Path to file (string). -- @param mode Mode string of r, r+, w, w+, a or a+. See fopen(3) for details. --- @return File object on success, nil on error. -function luafile.open(path, mode) - local f = luafile.new() - f.file = luafile_ll.fopen(path, mode) - if f.file then - return f - end - return nil +-- @return File object on success, false on error. +-- @return Error object on failure. +function luafile.fopen(path, mode) + local f, handle, errstring + + handle, errstring = luafile_ll.fopen(path, mode) + if not handle then + return false, err.new("could not open file %q with mode %q: %s", + path, mode, errstring) + end + + f = luafile.new() + f.file = handle + return f end --- Open a file descriptor. -- @param fd Valid UNIX file descriptor (number). -- @param mode Mode string of r, r+, w, w+, a or a+. See fdopen(3) for details. --- @return File object on success, nil on error. +-- @return File object on success, false on error. +-- @return Error object on failure. function luafile.fdopen(fd, mode) - local f = luafile.new() - f.file = luafile_ll.fdopen(fd, mode) - if f.file then - return f + local f, handle, errstring + + handle, errstring = luafile_ll.fdopen(fd, mode) + if not handle then + return false, + err.new("could not open file descriptor %d with mode %q: %s", + fd, mode, errstring) end - return nil + + f = luafile.new() + f.file = handle + return f end --- Close a file object. -- @param luafile File object. -- @return True on success, false on error. -function luafile.close(luafile) - if luafile and luafile.file then - if luafile_ll.fclose(luafile.file) then - luafile.file = nil - return true - end +-- @return Error object on failure. +function luafile.fclose(luafile) + local rc, re, errstring + + rc, re = valid_open_luafile(luafile) + if not rc then + return false, re + end + + rc, errstring = luafile_ll.fclose(luafile.file) + luafile.file = false + if not rc then + return false, err.new("error closing file: %s", errstring) end - return false + + return true end --- Read a file. -function luafile.read(luafile) - if luafile and luafile.file then - return luafile_ll.fread(luafile.file) +-- @param luafile File object. +-- @return File data as a string, or false on error. May be up to 16K bytes +-- large and contain embedded zero's. On EOF an empty string is returned. +-- @return Error object on failure. +function luafile.fread(luafile) + local rc, re, errstring, buffer + + rc, re = valid_open_luafile(luafile) + if not rc then + return false, re end - return nil -end ---- Write buffer to a file. -function luafile.write(luafile, buffer) - if luafile and luafile.file and buffer then - return luafile_ll.fwrite(luafile.file, buffer) + buffer, errstring = luafile_ll.fread(luafile.file) + if not buffer then + return false, err.new("error reading file: %s", errstring) end - return nil + + return buffer end ---- Read line from a file. -function luafile.readline(luafile) - if luafile and luafile.file then - return luafile_ll.fgets(luafile.file) +--- Read character from file. +-- @param luafile File object. +-- @return Character as a string, string of length 0 on EOF, or false on error. +-- @return Error object on failure. +function luafile.fgetc(luafile) + local rc, re, errstring, char + + rc, re = valid_open_luafile(luafile) + if not rc then + return false, re + end + + char, errstring = luafile_ll.fgetc(luafile.file) + if not char then + return false, err.new("error reading character from file: %s", + errstring) end - return nil + + + return char end ---- Seek in a file. -function luafile.seek(luafile, offset) - if luafile and luafile.file and offset then - return luafile_ll.fseek(luafile.file, offset) +--- Write buffer to a file. +-- @param luafile File object. +-- @param buffer Data string to be written. May contain embedded zero's. +-- @return True on success, False on error. +-- @return Error object on failure. +function luafile.fwrite(luafile, buffer) + local rc, re, errstring + + rc, re = valid_open_luafile(luafile) + if not rc then + return false, rc + end + + rc, errstring = luafile_ll.fwrite(luafile.file, buffer) + if not rc then + return false, err.new("error writing file: %s", errstring) end - return nil + + return true end ---- Flush file buffers. -function luafile.flush(luafile) - if luafile and luafile.file then - return luafile_ll.fflush(luafile.file) +--- Read line from a file. +-- @param file File object. +-- @return Line of data, potentially including a new-line character at the end +-- but no further. Returns the empty string on end-of-file, or false in +-- case of an error. +-- @return Error object on failure. +function luafile.readline(file) + local rc, re, line, char + + --rc, re = valid_open_luafile(file) + --if not rc then + -- return false, rc + --end + + line = "" + while true do + char, re = luafile.fgetc(file) + if not char then + return false, re + elseif char == "\0" then + -- fgets cannot handle embedded zeros, causing mayhem in C. + -- We could do this in Lua, but lets signal an error till + -- we have a use case. + return false, err.new("got NUL character while reading line") + elseif char == "\n" or char == "" then + line = line..char -- retain newline just like fgets does. + return line + end + + line = line..char end - return nil end ---- Return file descriptor of a file. +--- Return file descriptor of a file object. +-- @param luafile File object. +-- @return Integer file descriptor of the file descriptor. This method does not +-- have an error condition. If passed an invalid or closed file object, it calls +-- e2lib.abort() signaling an internal error. function luafile.fileno(luafile) - if luafile and luafile.file then - return luafile_ll.fileno(luafile.file) + local rc, re, fd, errstring + + rc, re = valid_open_luafile(luafile) + if not rc then + e2lib.abort(re) + end + + fd, errstring = luafile_ll.fileno(luafile.file) + if not fd then + e2lib.abort(err.new("%s", errstring)) end - return nil + + return fd end --- Test for end of file. -function luafile.eof(luafile) - if luafile and luafile.file then - return luafile_ll.feof(luafile.file) +-- @param luafile File object. +-- @return True on end-of-file, false otherwise. feof calls +-- e2lib.abort() when used with an invalid file object. +function luafile.feof(luafile) + local rc, re + + rc, re = valid_open_luafile(luafile) + if not rc then + e2lib.abort(re) end - return nil + + rc, re = luafile_ll.feof(luafile.file) + if not rc and re then + e2lib.abort(err.new("%s", re)) + end + + return rc end ---- Set buffer size used internally. +--- Enable line buffer mode. See setbuf(3) for details. setlinebuf has no +-- error conditions. If an invalid file object is passed, it calls +-- e2lib.abort() terminating the process. +-- @param luafile File object function luafile.setlinebuf(luafile) - if luafile and luafile.file then - return luafile_ll.setlinebuf(luafile.file) + local errstring, rc, re + + rc, re = valid_open_luafile(luafile) + if not rc then + e2lib.abort(re) end - return nil -end ---- Create a pipe. -function luafile.pipe() - local rc, r, w = luafile_ll.pipe() - local fr, fw + rc, errstring = luafile_ll.setlinebuf(luafile.file) if not rc then - return false, nil, nil + e2lib.abort(err.new("%s", errstring)) end - fr = luafile.fdopen(r, "r") - fw = luafile.fdopen(w, "w") - return rc, fr, fw end ---- Duplicate a file descriptor. +--- Duplicate a file descriptor. See dup(2) for details. +-- @param oldfd File descriptor to duplicate. +-- @param newfd Duplicated file descritor. If the file descriptor was open +-- before the call, it's closed automatically. +-- @return True on success, false on error. +-- @return Error object on failure. function luafile.dup2(oldfd, newfd) - if oldfd and newfd then - return luafile_ll.dup2(oldfd, newfd) + local rc, errstring + + rc, errstring = luafile_ll.dup2(oldfd, newfd) + if not rc then + return false, + err.new("duplicating file descriptor failed: %s", errstring) end - return nil + + return true +end + + +--- Create a new UNIX pipe(2) between two file objects. +-- @return File object in read mode, or false on error. +-- @return File object in write mode, or error object on failure. +function luafile.pipe() + local fd1, fd2, fr, fw, re + + fd1, fd2 = luafile_ll.pipe() + if not fd1 then + return false, err.new("failed creating pipe: %s", fd2) + end + + fr, re = luafile.fdopen(fd1, "r") + if not fr then + return false, re + end + + fw,re = luafile.fdopen(fd2, "w") + if not fw then + return false, re + end + + return fr, fw end diff --git a/generic/luafile_ll.c b/generic/luafile_ll.c index b3c6994..a0a9630 100644 --- a/generic/luafile_ll.c +++ b/generic/luafile_ll.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -49,18 +50,22 @@ lua_fopen(lua_State *lua) file = luaL_checkstring(lua, 1); mode = luaL_checkstring(lua, 2); + f = fopen(file, mode); if (f == NULL) { - lua_pushnil(lua); - } else { - fd = fileno(f); - if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) { - lua_pushfstring(lua, "%s: fcntl(%d): %s: %s", __func__, - fd, file, strerror(errno)); - lua_error(lua); - } - lua_pushlightuserdata(lua, (void *)f); + lua_pushboolean(lua, 0); + lua_pushstring(lua, strerror(errno)); + return 2; + } + + fd = fileno(f); + if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) { + lua_pushfstring(lua, "%s: fcntl(%d): %s: %s", __func__, + fd, file, strerror(errno)); + lua_error(lua); } + + lua_pushlightuserdata(lua, f); return 1; } @@ -68,14 +73,22 @@ static int lua_fclose(lua_State *lua) { FILE *f; - int rc; - f = (FILE *)lua_topointer(lua, 1); - if(f) { - rc = fclose(f); - lua_pushboolean(lua, (rc == 0)); - } else { + + f = lua_touserdata(lua, 1); + if (f == NULL) { + lua_pushboolean(lua, 0); + lua_pushstring(lua, + "lua_fclose: one or more arguments of wrong type/missing"); + return 2; + } + + if (fclose(f) == EOF) { lua_pushboolean(lua, 0); + lua_pushstring(lua, strerror(errno)); + return 2; } + + lua_pushboolean(lua, 1); return 1; } @@ -85,14 +98,18 @@ lua_fdopen(lua_State *lua) FILE *f; int fd; const char *mode; + fd = luaL_checkinteger(lua, 1); mode = luaL_checkstring(lua, 2); + f = fdopen(fd, mode); - if(f == NULL) { - lua_pushnil(lua); - } else { - lua_pushlightuserdata(lua, (void *)f); + if (f == NULL) { + lua_pushboolean(lua, 0); + lua_pushstring(lua, strerror(errno)); + return 2; } + + lua_pushlightuserdata(lua, f); return 1; } @@ -101,16 +118,35 @@ lua_fwrite(lua_State *lua) { FILE *f; const char *b; - int n = 0, rc; - f = (FILE *)lua_topointer(lua, 1); - b = luaL_checkstring(lua, 2); - if(!f || !b) { + size_t sz, ret; + + f = lua_touserdata(lua, 1); + b = lua_tolstring(lua, 2, &sz); + if (f == NULL || b == NULL) { lua_pushboolean(lua, 0); - return 1; + lua_pushstring(lua, + "lua_fwrite: one or more arguments of wrong type/missing"); + return 2; + } + + ret = fwrite(b, 1, sz, f); + if (ret != sz) { + if (ferror(f)) { + lua_pushboolean(lua, 0); + lua_pushstring(lua, strerror(errno)); + return 2; + } + + if (feof(f)) { + /* What does end of file on write mean? + * Signal an error */ + lua_pushboolean(lua, 0); + lua_pushstring(lua, "lua_fwrite: end of file"); + return 2; + } } - n = strlen(b); - rc = fwrite(b, 1, n, f); - lua_pushboolean(lua, (rc == n)); + + lua_pushboolean(lua, 1); return 1; } @@ -118,81 +154,89 @@ static int lua_fread(lua_State *lua) { char buf[16384]; - int rc; + size_t ret; FILE *f; - f = (FILE *)lua_topointer(lua, 1); - rc = fread(buf, 1, sizeof(buf), f); - if(rc>0) { - lua_pushlstring(lua, buf, rc); - } else if (rc == 0) { - lua_pushstring(lua, ""); - } else { - lua_pushnil(lua); - } - return 1; -} -static int -lua_fgets(lua_State *lua) -{ - FILE *f; - char buf[16384], *rc; - f = (FILE *)lua_topointer(lua, 1); - if(!f) { - lua_pushnil(lua); - return 1; + f = lua_touserdata(lua, 1); + if (f == NULL) { + lua_pushboolean(lua, 0); + lua_pushstring(lua, + "lua_fread: one or more arguments of wrong type/missing"); + return 2; } - rc = fgets(buf, sizeof(buf), f); - if(!rc) { - lua_pushnil(lua); - return 1; + + + ret = fread(buf, 1, sizeof(buf), f); + if (ret != sizeof(buf)) { + if (ferror(f)) { + lua_pushboolean(lua, 0); + lua_pushstring(lua, strerror(errno)); + return 2; + } + + if (ret <= 0 && feof(f)) { + /* ret <= 0: do not discard data on short reads, + * only signal EOF when all data is returned. */ + lua_pushstring(lua, ""); + return 1; + } } - lua_pushstring(lua, buf); + + lua_pushlstring(lua, buf, ret); return 1; } static int -lua_fseek(lua_State *lua) +lua_fgetc(lua_State *L) { - int rc; - long offset; FILE *f; - f = (FILE *)lua_topointer(lua, 1); - offset = luaL_checklong(lua, 2); - if(!f) { - lua_pushboolean(lua, 0); - return 1; + int c; + char ch; + + f = lua_touserdata(L, 1); + if (f == NULL) { + lua_pushboolean(L, 0); + lua_pushstring(L, + "lua_fgetc: argument of wrong type or missing"); + return 2; } - rc = fseek(f, offset, SEEK_SET); - lua_pushboolean(lua, rc == 0); - return 1; -} -static int -lua_fflush(lua_State *lua) -{ - int rc; - FILE *f; - f = (FILE *)lua_topointer(lua, 1); - if(!f) { - lua_pushnil(lua); - return 1; + c = fgetc(f); + if (c == EOF) { + if (feof(f)) { + lua_pushstring(L, ""); + return 1; + } + + if (ferror(f)) { + lua_pushboolean(L, 0); + lua_pushstring(L, strerror(errno)); + return 2; + } + } - rc = fflush(f); - lua_pushboolean(lua, rc == 0); + + ch = (char)c; + lua_pushlstring(L, &ch, 1); return 1; } static int -lua_pipe(lua_State *lua) +lua_pipe(lua_State *L) { int fd[2]; int rc; + rc = pipe(fd); - lua_pushboolean(lua, rc == 0); - lua_pushnumber(lua, fd[0]); - lua_pushnumber(lua, fd[1]); - return 3; + if (rc != 0) { + lua_pushboolean(L, 0); + lua_pushstring(L, strerror(errno)); + return 2; + } + + lua_pushnumber(L, fd[0]); + lua_pushnumber(L, fd[1]); + return 2; } static int @@ -200,10 +244,13 @@ lua_fileno(lua_State *lua) { FILE *f; int fd; - f = (FILE *)lua_topointer(lua, 1); - if(!f) { - lua_pushnil(lua); - return 1; + + f = lua_touserdata(lua, 1); + if (f == NULL) { + lua_pushboolean(lua, 0); + lua_pushstring(lua, + "lua_fileno: one or more arguments of wrong type/missing"); + return 2; } fd = fileno(f); lua_pushinteger(lua, fd); @@ -211,17 +258,19 @@ lua_fileno(lua_State *lua) } static int -lua_eof(lua_State *lua) +lua_feof(lua_State *lua) { FILE *f; - int eof; - f = (FILE *)lua_topointer(lua, 1); - if(!f) { - lua_pushnil(lua); - return 1; + + f = lua_touserdata(lua, 1); + if (f == NULL) { + lua_pushboolean(lua, 0); + lua_pushstring(lua, + "lua_feof: arguments wrong type or missing"); + return 2; } - eof = feof(f); - lua_pushboolean(lua, eof); + + lua_pushboolean(lua, feof(f)); return 1; } @@ -229,11 +278,15 @@ static int lua_setlinebuf(lua_State *lua) { FILE *f; - f = (FILE *)lua_topointer(lua, 1); - if(!f) { + + f = lua_touserdata(lua, 1); + if (!f) { lua_pushboolean(lua, 0); - return 1; + lua_pushstring(lua, "lua_setlinebuf: one or more arguments " + "of wrong type/missing"); + return 2; } + setlinebuf(f); lua_pushboolean(lua, 1); return 1; @@ -243,10 +296,18 @@ static int lua_dup2(lua_State *lua) { int oldfd, newfd, rc; + oldfd = luaL_checkinteger(lua, 1); newfd = luaL_checkinteger(lua, 2); + rc = dup2(oldfd, newfd); - lua_pushboolean(lua, (rc == 0)); + if (rc < 0) { + lua_pushboolean(lua, 0); + lua_pushstring(lua, strerror(errno)); + return 2; + } + + lua_pushboolean(lua, 1); return 1; } @@ -300,21 +361,27 @@ static luaL_Reg lib[] = { { "fclose", lua_fclose }, { "fwrite", lua_fwrite }, { "fread", lua_fread }, - { "fseek", lua_fseek }, - { "fflush", lua_fflush }, - { "fileno", lua_fileno }, - { "feof", lua_eof }, - { "fgets", lua_fgets }, - { "setlinebuf", lua_setlinebuf }, + { "fgetc", lua_fgetc }, { "pipe", lua_pipe }, + { "setlinebuf", lua_setlinebuf }, + { "feof", lua_feof }, + { "fileno", lua_fileno }, { "dup2", lua_dup2 }, { "cloexec", lua_cloexec }, { NULL, NULL } }; -int luaopen_luafile_ll(lua_State *lua) +int +luaopen_luafile_ll(lua_State *lua) { - luaL_register(lua, "luafile_ll", lib); - return 1; + luaL_Reg *next; + + lua_newtable(lua); + for (next = lib; next->name != NULL; next++) { + lua_pushcfunction(lua, next->func); + lua_setfield(lua, -2, next->name); + } + + return 1; } -- 2.39.5