// SPDX-License-Identifier: LGPL-3.0-or-later /* OS extensions for Lua, module implementation. * * Copyright: 2022, The DoubleFourteen Code Forge * Author: Lorenzo Cogotti */ #include "xconf.h" #include "osx.h" #include "osx_dir.h" #include "luacompat.h" #include "lua.h" #include "lualib.h" #include "lauxlib.h" #include #include #include #include #include #ifdef _WIN32 #include #include #include #include #include #include #include typedef struct __stat64 os_stat_type; #ifndef S_ISDIR #define S_ISDIR(mode) ((mode)&_S_IFDIR) #endif #ifndef S_ISREG #define S_ISREG(mode) ((mode)&_S_IFREG) #endif #define fileno(fh) _fileno(fh) #define stat(path, buf) _stat64(path, buf) #define fstat(fd, buf) _fstat64(fd, buf) #define mkdir(path, mode) _mkdir(path) #define getcwd(buf, len) _getcwd(buf, len) #define chdir(path) _chdir(path) #define getdrive() _getdrive() #define chdrive(drive) _chdrive(drive) #ifndef _O_WTEXT #define _O_WTEXT 0x10000 #endif #ifndef _O_U16TEXT #define _O_U16TEXT 0x20000 #endif #ifndef _O_U8TEXT #define _O_U8TEXT 0x40000 #endif #define setmode(fd, mode) _setmode(fd, mode) static int truncate(const char *path, __int64 size) { int fd; if (_sopen_s(&fd, path, _O_WRONLY|_O_BINARY|_O_NOINHERIT, _SH_DENYRW, 0) != 0) return -1; int ec = _chsize_s(fd, size); _close(fd); return ec; } #define ftruncate(fd, size) _chsize_s(fd, size) #define fsync(fd) _commit(fd) #else #include #include #include #include typedef struct stat os_stat_type; #define _O_BINARY 0 #define _O_TEXT 0 #define _O_WTEXT 0 #define _O_U16TEXT 0 #define _O_U8TEXT 0 static inline int getdrive(void) { return 0; } static inline int chdrive(int drive) { (void) drive; errno = ENOTSUP; return -1; } static inline int setmode(int fd, int mode) { (void) fd, (void) mode; errno = ENOTSUP; return -1; } #endif #ifdef __linux__ #define sys_posix_fadvise(fd, advice) posix_fadvise(fd, 0, 0, advice) #else // Stub posix_fadvise() #define POSIX_FADV_NORMAL 0 #define POSIX_FADV_SEQUENTIAL 0 #define POSIX_FADV_RANDOM 0 #define POSIX_FADV_NOREUSE 0 static inline int sys_posix_fadvise(int fd, int advice) { (void) fd; (void) advice; errno = ENOTSUP; return -1; } #endif static int os_pusherror(lua_State *L, const char *path) { int err = errno; lua_pushboolean(L, false); if (path) lua_pushfstring(L, "%s: %s", path, strerror(err)); else lua_pushstring(L, strerror(err)); lua_pushinteger(L, err); return 3; } static int os_pushfail(lua_State *L, const char *path) { int err = errno; luaL_pushfail(L); if (path) lua_pushfstring(L, "%s: %s", path, strerror(err)); else lua_pushstring(L, strerror(err)); lua_pushinteger(L, err); return 3; } static int os_result(lua_State *L, int ec, const char *path) { if (ec == 0) { lua_pushboolean(L, true); return 1; } return os_pusherror(L, path); } static df_os_dir *todir(lua_State *L) { df_os_dir *dir = luaL_checkudata(L, 1, DF_LUA_DIRHANDLE); if (dir->closeflag) luaL_error(L, "attempt to use a closed directory"); return dir; } static bool os_dir_filter(lua_State *L, const char *name) { // Preserve errno across filter calls int err = errno; lua_pushvalue(L, 2); lua_pushstring(L, name); lua_call(L, 1, 1); errno = err; bool result = lua_toboolean(L, -1); lua_pop(L, 1); return result; } static int os_dir_iter(lua_State *L); static int os_dir(lua_State *L) { const char *path = luaL_checkstring(L, 1); df_os_dir *dir = lua_newuserdata(L, sizeof(*dir)); dir->hn = os_opendir(path); dir->closeflag = false; if (!dir->hn) luaL_error(L, "%s: %s", path, strerror(errno)); luaL_setmetatable(L, DF_LUA_DIRHANDLE); lua_pushcclosure(L, os_dir_iter, 2); return 1; } static int os_dir_iter(lua_State *L) { const char *path = lua_tostring(L, lua_upvalueindex(1)); df_os_dir *dir = lua_touserdata(L, lua_upvalueindex(2)); const char *filename; errno = 0; nextfilename: filename = os_readdir(dir->hn); if (!filename) { // Error or end of directory if (errno != 0) luaL_error(L, lua_pushfstring(L, "%s: %s", path, strerror(errno))); return 0; } // Skip dot and dotdot if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) goto nextfilename; // New directory entry lua_pushstring(L, filename); return 1; } static int os_dir_read(lua_State *L) { df_os_dir *dir = todir(L); errno = 0; const char *filename = os_readdir(dir->hn); if (!filename) return (errno == 0) ? 0 : os_pusherror(L, NULL); lua_pushstring(L, filename); return 1; } static int os_dir_close(lua_State *L) { df_os_dir *dir = todir(L); os_closedir(dir->hn); dir->closeflag = true; return 0; } static int os_dir_gc(lua_State *L) { df_os_dir *dir = luaL_checkudata(L, 1, DF_LUA_DIRHANDLE); if (!dir->closeflag) os_dir_close(L); return 0; } static int os_listfiles(lua_State *L) { const char *path = luaL_checkstring(L, 1); bool dofilter = false; if (!lua_isnoneornil(L, 2)) { luaL_checktype(L, 2, LUA_TFUNCTION); dofilter = true; } df_os_dirhn hn = os_opendir(path); if (!hn) return os_pushfail(L, path); lua_newtable(L); const char *filename; int i = 1; int err; errno = 0; while ((filename = os_readdir(hn)) != NULL) { if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) continue; if (dofilter && !os_dir_filter(L, filename)) continue; lua_pushinteger(L, i++); lua_pushstring(L, filename); lua_settable(L, -3); } err = errno; os_closedir(hn); if (err != 0) return os_pushfail(L, path); return 1; } static int os_stat(lua_State *L) { const char *path = NULL; os_stat_type buf; int ec; if (lua_isstring(L, 1)) { path = lua_tostring(L, 1); ec = stat(path, &buf); } else { luaL_Stream *stream = luaL_checkudata(L, 1, LUA_FILEHANDLE); ec = fstat(fileno(stream->f), &buf); } if (ec != 0) return os_pushfail(L, path); lua_createtable(L, 0, 3); if (S_ISREG(buf.st_mode)) { lua_pushliteral(L, "regular"); lua_setfield(L, -2, "type"); lua_pushinteger(L, buf.st_size); lua_setfield(L, -2, "size"); lua_pushinteger(L, (lua_Integer) buf.st_mtime); lua_setfield(L, -2, "mtime"); } else if (S_ISDIR(buf.st_mode)) { lua_pushliteral(L, "directory"); lua_setfield(L, -2, "type"); lua_pushinteger(L, (lua_Integer) buf.st_mtime); lua_setfield(L, -2, "mtime"); } else { lua_pushstring(L, "other"); lua_setfield(L, -2, "type"); } return 1; } static int os_mkdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); int ec = mkdir(path, 0666); return os_result(L, ec, path); } static int os_getcwd(lua_State *L) { char *p = NULL; size_t size = 128; while (true) { char *np = realloc(p, size); if (!np) goto fail; p = np; if (getcwd(p, size)) break; // got working directory if (errno != ERANGE) goto fail; size <<= 1; if (size == 0) // PARANOID goto fail; } lua_pushstring(L, p); free(p); return 1; fail: free(p); return os_pusherror(L, NULL); } static int os_chdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); int ec = chdir(path); return os_result(L, ec, path); } static int os_getdrive(lua_State *L) { errno = 0; int drive = getdrive(); if (drive == 0) { if (errno != 0) return os_pusherror(L, NULL); lua_pushliteral(L, ""); return 1; } char letter = 'A' + drive - 1; lua_pushlstring(L, &letter, 1); return 1; } static int os_chdrive(lua_State *L) { size_t len; const char *s = luaL_checklstring(L, 1, &len); if (len != 1) luaL_argerror(L, 1, "invalid drive"); int drive = *s - 'A' + 1; if (drive < 1 || drive > 26) luaL_argerror(L, 1, "invalid drive"); int ec = chdrive(drive); return os_result(L, ec, NULL); } static int os_setmode(lua_State *L) { static const char *modes[] = { "binary", "text", "u8text", "u16text", "wtext", NULL }; static const int imodes[] = { _O_BINARY, _O_TEXT, _O_U8TEXT, _O_U16TEXT, _O_WTEXT }; luaL_Stream *stream = luaL_checkudata(L, 1, LUA_FILEHANDLE); int i = luaL_checkoption(L, 2, NULL, modes); int ec = setmode(fileno(stream->f), imodes[i]); return os_result(L, ec, NULL); } static int os_chsize(lua_State *L) { lua_Integer size = luaL_checkinteger(L, 2); if (size < 0) size = 0; const char *path = NULL; int ec; if (lua_isstring(L, 1)) { path = lua_tostring(L, 1); ec = truncate(path, size); } else { luaL_Stream *stream = luaL_checkudata(L, 1, LUA_FILEHANDLE); ec = ftruncate(fileno(stream->f), size); } return os_result(L, ec, path); } static int os_fadvise(lua_State *L) { static const char *hints[] = { "normal", "sequential", "random", "noreuse", NULL }; static const int ihints[] = { POSIX_FADV_NORMAL, POSIX_FADV_SEQUENTIAL, POSIX_FADV_RANDOM, POSIX_FADV_NOREUSE }; luaL_Stream *stream = luaL_checkudata(L, 1, LUA_FILEHANDLE); int i = luaL_checkoption(L, 2, NULL, hints); int ec = sys_posix_fadvise(fileno(stream->f), ihints[i]); return os_result(L, ec, NULL); } static int os_commit(lua_State *L) { luaL_Stream *stream = luaL_checkudata(L, 1, LUA_FILEHANDLE); int ec = fsync(fileno(stream->f)); return os_result(L, ec, NULL); } static void createmeta(lua_State *L) { static const luaL_Reg metameth[] = { { "__index", NULL }, // placeholder { "__gc", os_dir_gc }, #if LUA_VERSION_NUM >= 504 { "__close", os_dir_gc}, #endif { NULL, NULL } }; static const luaL_Reg meth[] = { { "read", os_dir_read }, { "close", os_dir_close }, { NULL, NULL } }; luaL_newmetatable(L, DF_LUA_DIRHANDLE); luaL_setfuncs(L, metameth, 0); luaL_newlibtable(L, meth); luaL_setfuncs(L, meth, 0); lua_setfield(L, -2, "__index"); lua_pop(L, 1); } DF_OSXMOD_API int luaopen_osx(lua_State *L) { static const luaL_Reg funcs[] = { { "listfiles", os_listfiles }, { "dir", os_dir }, { "stat", os_stat }, { "mkdir", os_mkdir }, { "getcwd", os_getcwd }, { "chdir", os_chdir }, { "getdrive", os_getdrive }, { "chdrive", os_chdrive }, { "setmode", os_setmode }, { "chsize", os_chsize }, { "fadvise", os_fadvise }, { "commit", os_commit }, { NULL, NULL } }; createmeta(L); luaL_newlib(L, funcs); lua_createtable(L, 0, 1); luaL_requiref(L, LUA_OSLIBNAME, luaopen_os, false); lua_setfield(L, -2, "__index"); lua_setmetatable(L, -2); #ifdef _WIN32 lua_pushliteral(L, "\\"); #else lua_pushliteral(L, "/"); #endif lua_setfield(L, -2, "sep"); return 1; }