// 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 #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) static int flocking(FILE *fh, int mode, __int64 ofs, __int64 len) { if (ofs < 0 || len < 0 || len > LONG_MAX) { errno = ERANGE; return -1; } _lock_file(fh); if (_fseeki64(fh, ofs, SEEK_SET) != 0) goto fail; if (len == 0) { // NOTE: seek may flush write buffers, so file length must be taken now len = _filelengthi64(_fileno(fh)); if (len < 0) goto fail; len -= ofs; if (len < 0) len = 0; if (len > LONG_MAX) { errno = ERANGE; goto fail; } } if (_locking(_fileno(fh), mode, len) != 0) goto fail; _unlock_file(fh); return 0; fail: _unlock_file(fh); return -1; } #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; } #define _LK_NBRLCK F_RDLCK #define _LK_NBLCK F_WRLCK #define _LK_UNLCK F_UNLCK static int flocking(FILE *fh, int mode, off_t ofs, off_t len) { if (ofs < 0 || len < 0) { errno = ERANGE; return -1; } flockfile(fh); if (fseeko(fh, ofs, SEEK_SET) != 0) goto fail; struct flock lk = { .l_whence = SEEK_SET, .l_type = mode, .l_start = ofs, .l_len = len // NOTE: 0 has special meaning }; if (fcntl(fileno(fh), F_SETLK, &lk) != 0) goto fail; funlockfile(fh); return 0; fail: funlockfile(fh); return -1; } #endif #ifdef __linux__ #define 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 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_open(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) return os_pushfail(L, path); luaL_setmetatable(L, DF_LUA_DIRHANDLE); return 1; } static int os_dir_tostring(lua_State *L) { df_os_dir *dir = luaL_checkudata(L, 1, DF_LUA_DIRHANDLE); if (dir->closeflag) { lua_pushliteral(L, "directory (closed)"); } else { df_os_dirhn hn = dir->hn; void *p; #ifdef _WIN32 p = (void *) hn->dirh; #else p = hn; #endif lua_pushfstring(L, "directory (%p)", p); } 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_dir_iter(lua_State *L); static int os_dir(lua_State *L) { lua_pushcfunction(L, os_dir_open); lua_pushvalue(L, 1); lua_call(L, 1, 2); if (lua_isnil(L, -2)) lua_error(L); lua_pop(L, 1); 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_readdir(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_dolistfiles(lua_State *L, bool closeflag, const char *path) { df_os_dir *dir = todir(L); bool dofilter = false; if (!lua_isnoneornil(L, 2)) { luaL_checktype(L, 2, LUA_TFUNCTION); dofilter = true; } char *filename; int i = 1; lua_newtable(L); errno = 0; while ((filename = os_readdir(dir->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); } int err = errno; if (closeflag) os_dir_close(L); if (err != 0) { errno = err; return os_pushfail(L, path); } return 1; } static int os_dir_listfiles(lua_State *L) { return os_dir_dolistfiles(L, false, NULL); } static int os_listfiles(lua_State *L) { const char *path = luaL_checkstring(L, 1); if (lua_isnone(L, 2)) lua_pushnil(L); lua_pushcfunction(L, os_dir_open); lua_pushvalue(L, 1); lua_call(L, 1, 2); if (lua_isnil(L, -2)) lua_error(L); lua_copy(L, -2, 1); lua_pop(L, 2); return os_dir_dolistfiles(L, true, path); } 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) { #if LUA_VERSION_NUM >= 502 luaL_Buffer buf; char *p; size_t size = 128; luaL_buffinit(L, &buf); while (true) { p = luaL_prepbuffsize(&buf, size); if (getcwd(p, size)) break; // got working directory if (errno != ERANGE) return os_pusherror(L, NULL); size <<= 1; if (size == 0) { // PARANOID errno = ERANGE; return os_pusherror(L, NULL); } } luaL_pushresultsize(&buf, strlen(p)); return 1; #else // Unfortunately this leaks memory if Lua ever decides to longjmp(). // After 12 years since Lua 5.2 introduction, // that's what we get with LuaJIT. char *buf = NULL; size_t size = 128; while (true) { char *p = realloc(buf, size); if (!p) goto fail; buf = p; if (getcwd(buf, size)) break; // got working directory if (errno != ERANGE) goto fail; size <<= 1; if (size == 0) { // PARANOID errno = ERANGE; goto fail; } } lua_pushstring(L, buf); free(buf); return 1; fail: free(buf); return os_pusherror(L, NULL); #endif } 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 = 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 int os_locking(lua_State *L) { static const char *modes[] = { "r", "w", "u", NULL }; static const int imodes[] = { _LK_NBRLCK, _LK_NBLCK, _LK_UNLCK }; luaL_Stream *stream = luaL_checkudata(L, 1, LUA_FILEHANDLE); int i = luaL_checkoption(L, 2, NULL, modes); lua_Integer ofs = luaL_optinteger(L, 3, 0); lua_Integer len = luaL_optinteger(L, 4, 0); int ec = flocking(stream->f, imodes[i], ofs, len); return os_result(L, ec, NULL); } static void createmeta(lua_State *L) { static const luaL_Reg metameth[] = { { "__index", NULL }, // placeholder { "__tostring", os_dir_tostring }, { "__gc", os_dir_gc }, #if LUA_VERSION_NUM >= 504 { "__close", os_dir_gc}, #endif { NULL, NULL } }; static const luaL_Reg meth[] = { { "readdir", os_dir_readdir }, { "listfiles", os_dir_listfiles }, { "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 }, { "opendir", os_dir_open }, { "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 }, { "locking", os_locking }, { 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; }