740 lines
15 KiB
C
740 lines
15 KiB
C
// 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 <assert.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/locking.h>
|
|
#include <direct.h>
|
|
#include <fcntl.h>
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#include <share.h>
|
|
#include <limits.h>
|
|
|
|
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 <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
|
|
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;
|
|
}
|