diff options
author | Kartik K. Agaram <vc@akkartik.com> | 2022-01-29 12:11:21 -0800 |
---|---|---|
committer | Kartik K. Agaram <vc@akkartik.com> | 2022-01-29 12:39:53 -0800 |
commit | 24f0781d2bd96ac333337ccb2168fb26b601db7a (patch) | |
tree | 0d4bf969f30bf1575900ac7b0f06c6acb9859831 /src | |
parent | 90fc24ed041736d6cdebc74e5fd2e260e3803c3b (diff) | |
download | teliva-24f0781d2bd96ac333337ccb2168fb26b601db7a.tar.gz |
new library: luafilesystem (lfs)
https://github.com/keplerproject/luafilesystem The new commander.tlv app demonstrates it working.
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile | 3 | ||||
-rw-r--r-- | src/lfs.c | 1183 | ||||
-rw-r--r-- | src/lfs.h | 35 | ||||
-rw-r--r-- | src/linit.c | 1 | ||||
-rw-r--r-- | src/lualib.h | 3 |
5 files changed, 1224 insertions, 1 deletions
diff --git a/src/Makefile b/src/Makefile index 0d359ab..3d2a91e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -26,7 +26,7 @@ LUA_A= liblua.a CORE_O= lapi.o lcode.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o lmem.o \ lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o ltm.o \ lundump.o lvm.o lzio.o \ - kilo.o tlv.o teliva.o + lfs.o kilo.o tlv.o teliva.o LIB_O= lauxlib.o lbaselib.o ldblib.o liolib.o lmathlib.o loslib.o \ ltablib.o lstrlib.o loadlib.o linit.o @@ -189,6 +189,7 @@ lzio.o: lzio.c lua.h luaconf.h llimits.h lmem.h lstate.h lobject.h ltm.h \ lzio.h print.o: print.c ldebug.h lstate.h lua.h luaconf.h lobject.h llimits.h \ ltm.h lzio.h lmem.h lopcodes.h lundump.h +lfs.o: lfs.c lfs.h lua.h lauxlib.h lualib.h kilo.o: kilo.c lua.h teliva.h teliva.o: teliva.c lua.h lauxlib.h lualib.h teliva.h tlv.h diff --git a/src/lfs.c b/src/lfs.c new file mode 100644 index 0000000..29c4f55 --- /dev/null +++ b/src/lfs.c @@ -0,0 +1,1183 @@ +/* +** LuaFileSystem +** Copyright Kepler Project 2003 - 2020 +** (http://keplerproject.github.io/luafilesystem) +** +** File system manipulation library. +** This library offers these functions: +** lfs.attributes (filepath [, attributename | attributetable]) +** lfs.chdir (path) +** lfs.currentdir () +** lfs.dir (path) +** lfs.link (old, new[, symlink]) +** lfs.lock (fh, mode) +** lfs.lock_dir (path) +** lfs.mkdir (path) +** lfs.rmdir (path) +** lfs.setmode (filepath, mode) +** lfs.symlinkattributes (filepath [, attributename]) +** lfs.touch (filepath [, atime [, mtime]]) +** lfs.unlock (fh) +*/ + +#ifndef LFS_DO_NOT_USE_LARGE_FILE +#ifndef _WIN32 +#ifndef _AIX +#define _FILE_OFFSET_BITS 64 /* Linux, Solaris and HP-UX */ +#else +#define _LARGE_FILES 1 /* AIX */ +#endif +#endif +#endif + +#ifdef _WIN32 +#define _WIN32_WINNT 0x600 +#endif + +#ifndef LFS_DO_NOT_USE_LARGE_FILE +#define _LARGEFILE64_SOURCE +#endif + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> +#include <sys/stat.h> + +#ifdef _WIN32 + +#include <direct.h> +#include <windows.h> +#include <io.h> +#include <sys/locking.h> + +#ifdef __BORLANDC__ +#include <utime.h> +#else +#include <sys/utime.h> +#endif + +#include <fcntl.h> + +/* MAX_PATH seems to be 260. Seems kind of small. Is there a better one? */ +#define LFS_MAXPATHLEN MAX_PATH + +#else + +#include <unistd.h> +#include <dirent.h> +#include <fcntl.h> +#include <sys/types.h> +#include <utime.h> +#include <sys/param.h> /* for MAXPATHLEN */ + +#ifdef MAXPATHLEN +#define LFS_MAXPATHLEN MAXPATHLEN +#else +#include <limits.h> /* for _POSIX_PATH_MAX */ +#define LFS_MAXPATHLEN _POSIX_PATH_MAX +#endif + +#endif + +#include "lua.h" +#include "lauxlib.h" +#include "lualib.h" + +#include "lfs.h" + +#define LFS_VERSION "1.8.0" +#define LFS_LIBNAME "lfs" + +#if LUA_VERSION_NUM >= 503 /* Lua 5.3+ */ + +#ifndef luaL_optlong +#define luaL_optlong luaL_optinteger +#endif + +#endif + +#if LUA_VERSION_NUM >= 502 +#define new_lib(L, l) (luaL_newlib(L, l)) +#else +#define new_lib(L, l) (lua_newtable(L), luaL_register(L, NULL, l)) +#endif + +/* Define 'strerror' for systems that do not implement it */ +#ifdef NO_STRERROR +#define strerror(_) "System unable to describe the error" +#endif + +#define DIR_METATABLE "directory metatable" +typedef struct dir_data { + int closed; +#ifdef _WIN32 + intptr_t hFile; + char pattern[MAX_PATH + 1]; +#else + DIR *dir; +#endif +} dir_data; + +#define LOCK_METATABLE "lock metatable" + +#ifdef _WIN32 + +#ifdef __BORLANDC__ +#define lfs_setmode(file, m) (setmode(_fileno(file), m)) +#define STAT_STRUCT struct stati64 +#else +#define lfs_setmode(file, m) (_setmode(_fileno(file), m)) +#define STAT_STRUCT struct _stati64 +#endif + +#ifndef _S_IFLNK +#define _S_IFLNK 0x400 +#endif + +#ifndef S_ISDIR +#define S_ISDIR(mode) (mode&_S_IFDIR) +#endif +#ifndef S_ISREG +#define S_ISREG(mode) (mode&_S_IFREG) +#endif +#ifndef S_ISLNK +#define S_ISLNK(mode) (mode&_S_IFLNK) +#endif +#ifndef S_ISSOCK +#define S_ISSOCK(mode) (0) +#endif +#ifndef S_ISFIFO +#define S_ISFIFO(mode) (0) +#endif +#ifndef S_ISCHR +#define S_ISCHR(mode) (mode&_S_IFCHR) +#endif +#ifndef S_ISBLK +#define S_ISBLK(mode) (0) +#endif + +#define STAT_FUNC _stati64 +#define LSTAT_FUNC lfs_win32_lstat + +#else + +#define _O_TEXT 0 +#define _O_BINARY 0 +#define lfs_setmode(file, m) ((void)file, (void)m, 0) +#define STAT_STRUCT struct stat +#define STAT_FUNC stat +extern int lstat(const char * pathname, struct stat * statbuf); +#define LSTAT_FUNC lstat + +#endif + +#ifdef _WIN32 +#define lfs_mkdir _mkdir +#else +#define lfs_mkdir(path) (mkdir((path), \ + S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH)) +#endif + +#ifdef _WIN32 + +int lfs_win32_pusherror(lua_State * L) +{ + int en = GetLastError(); + lua_pushnil(L); + if (en == ERROR_FILE_EXISTS || en == ERROR_SHARING_VIOLATION) + lua_pushstring(L, "File exists"); + else + lua_pushstring(L, strerror(en)); + return 2; +} + +#define TICKS_PER_SECOND 10000000 +#define EPOCH_DIFFERENCE 11644473600LL +time_t windowsToUnixTime(FILETIME ft) +{ + ULARGE_INTEGER uli; + uli.LowPart = ft.dwLowDateTime; + uli.HighPart = ft.dwHighDateTime; + return (time_t) (uli.QuadPart / TICKS_PER_SECOND - EPOCH_DIFFERENCE); +} + +int lfs_win32_lstat(const char *path, STAT_STRUCT * buffer) +{ + WIN32_FILE_ATTRIBUTE_DATA win32buffer; + if (GetFileAttributesEx(path, GetFileExInfoStandard, &win32buffer)) { + if (!(win32buffer.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { + return STAT_FUNC(path, buffer); + } + buffer->st_mode = _S_IFLNK; + buffer->st_dev = 0; + buffer->st_ino = 0; + buffer->st_nlink = 0; + buffer->st_uid = 0; + buffer->st_gid = 0; + buffer->st_rdev = 0; + buffer->st_atime = windowsToUnixTime(win32buffer.ftLastAccessTime); + buffer->st_mtime = windowsToUnixTime(win32buffer.ftLastWriteTime); + buffer->st_ctime = windowsToUnixTime(win32buffer.ftCreationTime); + buffer->st_size = 0; + return 0; + } else { + return 1; + } +} + +#endif + +/* +** Utility functions +*/ +static int pusherror(lua_State * L, const char *info) +{ + lua_pushnil(L); + if (info == NULL) + lua_pushstring(L, strerror(errno)); + else + lua_pushfstring(L, "%s: %s", info, strerror(errno)); + lua_pushinteger(L, errno); + return 3; +} + +static int pushresult(lua_State * L, int res, const char *info) +{ + if (res == -1) { + return pusherror(L, info); + } else { + lua_pushboolean(L, 1); + return 1; + } +} + + +/* +** This function changes the working (current) directory +*/ +static int change_dir(lua_State * L) +{ + const char *path = luaL_checkstring(L, 1); + if (chdir(path)) { + lua_pushnil(L); + lua_pushfstring(L, "Unable to change working directory to '%s'\n%s\n", + path, chdir_error); + return 2; + } else { + lua_pushboolean(L, 1); + return 1; + } +} + +/* +** This function returns the current directory +** If unable to get the current directory, it returns nil +** and a string describing the error +*/ +static int get_dir(lua_State * L) +{ +#ifdef NO_GETCWD + lua_pushnil(L); + lua_pushstring(L, "Function 'getcwd' not provided by system"); + return 2; +#else + char *path = NULL; + /* Passing (NULL, 0) is not guaranteed to work. + Use a temp buffer and size instead. */ + size_t size = LFS_MAXPATHLEN; /* initial buffer size */ + int result; + while (1) { + char *path2 = realloc(path, size); + if (!path2) { /* failed to allocate */ + result = pusherror(L, "get_dir realloc() failed"); + break; + } + path = path2; + if (getcwd(path, size) != NULL) { + /* success, push the path to the Lua stack */ + lua_pushstring(L, path); + result = 1; + break; + } + if (errno != ERANGE) { /* unexpected error */ + result = pusherror(L, "get_dir getcwd() failed"); + break; + } + /* ERANGE = insufficient buffer capacity, double size and retry */ + size *= 2; + } + free(path); + return result; +#endif +} + +/* +** Check if the given element on the stack is a file and returns it. +*/ +static FILE *check_file(lua_State * L, int idx, const char *funcname) +{ +#if LUA_VERSION_NUM == 501 + FILE **fh = (FILE **) luaL_checkudata(L, idx, "FILE*"); + if (*fh == NULL) { + luaL_error(L, "%s: closed file", funcname); + return 0; + } else + return *fh; +#elif LUA_VERSION_NUM >= 502 && LUA_VERSION_NUM <= 504 + luaL_Stream *fh = (luaL_Stream *) luaL_checkudata(L, idx, "FILE*"); + if (fh->closef == 0 || fh->f == NULL) { + luaL_error(L, "%s: closed file", funcname); + return 0; + } else + return fh->f; +#else +#error unsupported Lua version +#endif +} + + +/* +** +*/ +static int _file_lock(lua_State * L, FILE * fh, const char *mode, + const long start, long len, const char *funcname) +{ + int code; +#ifdef _WIN32 + /* lkmode valid values are: + LK_LOCK Locks the specified bytes. If the bytes cannot be locked, + the program immediately tries again after 1 second. + If, after 10 attempts, the bytes cannot be locked, + the constant returns an error. + LK_NBLCK Locks the specified bytes. If the bytes cannot be locked, + the constant returns an error. + LK_NBRLCK Same as _LK_NBLCK. + LK_RLCK Same as _LK_LOCK. + LK_UNLCK Unlocks the specified bytes, which must have been + previously locked. + + Regions should be locked only briefly and should be unlocked + before closing a file or exiting the program. + + http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_crt__locking.asp + */ + int lkmode; + switch (*mode) { + case 'r': + lkmode = LK_NBLCK; + break; + case 'w': + lkmode = LK_NBLCK; + break; + case 'u': + lkmode = LK_UNLCK; + break; + default: + return luaL_error(L, "%s: invalid mode", funcname); + } + if (!len) { + fseek(fh, 0L, SEEK_END); + len = ftell(fh); + } + fseek(fh, start, SEEK_SET); +#ifdef __BORLANDC__ + code = locking(fileno(fh), lkmode, len); +#else + code = _locking(fileno(fh), lkmode, len); +#endif +#else + struct flock f; + switch (*mode) { + case 'w': + f.l_type = F_WRLCK; + break; + case 'r': + f.l_type = F_RDLCK; + break; + case 'u': + f.l_type = F_UNLCK; + break; + default: + return luaL_error(L, "%s: invalid mode", funcname); + } + f.l_whence = SEEK_SET; + f.l_start = (off_t) start; + f.l_len = (off_t) len; + code = fcntl(fileno(fh), F_SETLK, &f); +#endif + return (code != -1); +} + +#ifdef _WIN32 +typedef struct lfs_Lock { + HANDLE fd; +} lfs_Lock; +static int lfs_lock_dir(lua_State * L) +{ + size_t pathl; + HANDLE fd; + lfs_Lock *lock; + char *ln; + const char *lockfile = "/lockfile.lfs"; + const char *path = luaL_checklstring(L, 1, &pathl); + ln = (char *) malloc(pathl + strlen(lockfile) + 1); + if (!ln) { + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; + } + strcpy(ln, path); + strcat(ln, lockfile); + fd = CreateFile(ln, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL); + free(ln); + if (fd == INVALID_HANDLE_VALUE) { + return lfs_win32_pusherror(L); + } + lock = (lfs_Lock *) lua_newuserdata(L, sizeof(lfs_Lock)); + lock->fd = fd; + luaL_getmetatable(L, LOCK_METATABLE); + lua_setmetatable(L, -2); + return 1; +} + +static int lfs_unlock_dir(lua_State * L) +{ + lfs_Lock *lock = (lfs_Lock *) luaL_checkudata(L, 1, LOCK_METATABLE); + if (lock->fd != INVALID_HANDLE_VALUE) { + CloseHandle(lock->fd); + lock->fd = INVALID_HANDLE_VALUE; + } + return 0; +} +#else +typedef struct lfs_Lock { + char *ln; +} lfs_Lock; +static int lfs_lock_dir(lua_State * L) +{ + lfs_Lock *lock; + size_t pathl; + char *ln; + const char *lockfile = "/lockfile.lfs"; + const char *path = luaL_checklstring(L, 1, &pathl); + lock = (lfs_Lock *) lua_newuserdata(L, sizeof(lfs_Lock)); + ln = (char *) malloc(pathl + strlen(lockfile) + 1); + if (!ln) { + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; + } + strcpy(ln, path); + strcat(ln, lockfile); + if (symlink("lock", ln) == -1) { + free(ln); + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; + } + lock->ln = ln; + luaL_getmetatable(L, LOCK_METATABLE); + lua_setmetatable(L, -2); + return 1; +} + +static int lfs_unlock_dir(lua_State * L) +{ + lfs_Lock *lock = (lfs_Lock *) luaL_checkudata(L, 1, LOCK_METATABLE); + if (lock->ln) { + unlink(lock->ln); + free(lock->ln); + lock->ln = NULL; + } + return 0; +} +#endif + +static int lfs_g_setmode(lua_State * L, FILE * f, int arg) +{ + static const int mode[] = { _O_BINARY, _O_TEXT }; + static const char *const modenames[] = { "binary", "text", NULL }; + int op = luaL_checkoption(L, arg, NULL, modenames); + int res = lfs_setmode(f, mode[op]); + if (res != -1) { + int i; + lua_pushboolean(L, 1); + for (i = 0; modenames[i] != NULL; i++) { + if (mode[i] == res) { + lua_pushstring(L, modenames[i]); + return 2; + } + } + lua_pushnil(L); + return 2; + } else { + return pusherror(L, NULL); + } +} + +static int lfs_f_setmode(lua_State * L) +{ + return lfs_g_setmode(L, check_file(L, 1, "setmode"), 2); +} + +/* +** Locks a file. +** @param #1 File handle. +** @param #2 String with lock mode ('w'rite, 'r'ead). +** @param #3 Number with start position (optional). +** @param #4 Number with length (optional). +*/ +static int file_lock(lua_State * L) +{ + FILE *fh = check_file(L, 1, "lock"); + const char *mode = luaL_checkstring(L, 2); + const long start = (long) luaL_optinteger(L, 3, 0); + long len = (long) luaL_optinteger(L, 4, 0); + if (_file_lock(L, fh, mode, start, len, "lock")) { + lua_pushboolean(L, 1); + return 1; + } else { + lua_pushnil(L); + lua_pushfstring(L, "%s", strerror(errno)); + return 2; + } +} + + +/* +** Unlocks a file. +** @param #1 File handle. +** @param #2 Number with start position (optional). +** @param #3 Number with length (optional). +*/ +static int file_unlock(lua_State * L) +{ + FILE *fh = check_file(L, 1, "unlock"); + const long start = (long) luaL_optinteger(L, 2, 0); + long len = (long) luaL_optinteger(L, 3, 0); + if (_file_lock(L, fh, "u", start, len, "unlock")) { + lua_pushboolean(L, 1); + return 1; + } else { + lua_pushnil(L); + lua_pushfstring(L, "%s", strerror(errno)); + return 2; + } +} + + +/* +** Creates a link. +** @param #1 Object to link to. +** @param #2 Name of link. +** @param #3 True if link is symbolic (optional). +*/ +static int make_link(lua_State * L) +{ + const char *oldpath = luaL_checkstring(L, 1); + const char *newpath = luaL_checkstring(L, 2); +#ifndef _WIN32 + return pushresult(L, + (lua_toboolean(L, 3) ? symlink : link) (oldpath, + newpath), + NULL); +#else + int symbolic = lua_toboolean(L, 3); + STAT_STRUCT oldpathinfo; + int is_dir = 0; + if (STAT_FUNC(oldpath, &oldpathinfo) == 0) { + is_dir = S_ISDIR(oldpathinfo.st_mode) != 0; + } + if (!symbolic && is_dir) { + lua_pushnil(L); + lua_pushstring(L, + "hard links to directories are not supported on Windows"); + return 2; + } + + int result = symbolic ? CreateSymbolicLink(newpath, oldpath, is_dir) + : CreateHardLink(newpath, oldpath, NULL); + + if (result) { + return pushresult(L, result, NULL); + } else { + lua_pushnil(L); + lua_pushstring(L, symbolic ? "make_link CreateSymbolicLink() failed" + : "make_link CreateHardLink() failed"); + return 2; + } +#endif +} + + +/* +** Creates a directory. +** @param #1 Directory path. +*/ +static int make_dir(lua_State * L) +{ + const char *path = luaL_checkstring(L, 1); + return pushresult(L, lfs_mkdir(path), NULL); +} + + +/* +** Removes a directory. +** @param #1 Directory path. +*/ +static int remove_dir(lua_State * L) +{ + const char *path = luaL_checkstring(L, 1); + return pushresult(L, rmdir(path), NULL); +} + + +/* +** Directory iterator +*/ +static int dir_iter(lua_State * L) +{ +#ifdef _WIN32 + struct _finddata_t c_file; +#else + struct dirent *entry; +#endif + dir_data *d = (dir_data *) luaL_checkudata(L, 1, DIR_METATABLE); + luaL_argcheck(L, d->closed == 0, 1, "closed directory"); +#ifdef _WIN32 + if (d->hFile == 0L) { /* first entry */ + if ((d->hFile = _findfirst(d->pattern, &c_file)) == -1L) { + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + d->closed = 1; + return 2; + } else { + lua_pushstring(L, c_file.name); + return 1; + } + } else { /* next entry */ + if (_findnext(d->hFile, &c_file) == -1L) { + /* no more entries => close directory */ + _findclose(d->hFile); + d->closed = 1; + return 0; + } else { + lua_pushstring(L, c_file.name); + return 1; + } + } +#else + if ((entry = readdir(d->dir)) != NULL) { + lua_pushstring(L, entry->d_name); + return 1; + } else { + /* no more entries => close directory */ + closedir(d->dir); + d->closed = 1; + return 0; + } +#endif +} + + +/* +** Closes directory iterators +*/ +static int dir_close(lua_State * L) +{ + dir_data *d = (dir_data *) lua_touserdata(L, 1); +#ifdef _WIN32 + if (!d->closed && d->hFile) { + _findclose(d->hFile); + } +#else + if (!d->closed && d->dir) { + closedir(d->dir); + } +#endif + d->closed = 1; + return 0; +} + + +/* +** Factory of directory iterators +*/ +static int dir_iter_factory(lua_State * L) +{ + const char *path = luaL_checkstring(L, 1); + dir_data *d; + lua_pushcfunction(L, dir_iter); + d = (dir_data *) lua_newuserdata(L, sizeof(dir_data)); + luaL_getmetatable(L, DIR_METATABLE); + lua_setmetatable(L, -2); + d->closed = 0; +#ifdef _WIN32 + d->hFile = 0L; + if (strlen(path) > MAX_PATH - 2) + luaL_error(L, "path too long: %s", path); + else + sprintf(d->pattern, "%s/*", path); +#else + d->dir = opendir(path); + if (d->dir == NULL) + luaL_error(L, "cannot open %s: %s", path, strerror(errno)); +#endif +#if LUA_VERSION_NUM >= 504 + lua_pushnil(L); + lua_pushvalue(L, -2); + return 4; +#else + return 2; +#endif +} + + +/* +** Creates directory metatable. +*/ +static int dir_create_meta(lua_State * L) +{ + luaL_newmetatable(L, DIR_METATABLE); + + /* Method table */ + lua_newtable(L); + lua_pushcfunction(L, dir_iter); + lua_setfield(L, -2, "next"); + lua_pushcfunction(L, dir_close); + lua_setfield(L, -2, "close"); + + /* Metamethods */ + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, dir_close); + lua_setfield(L, -2, "__gc"); + +#if LUA_VERSION_NUM >= 504 + lua_pushcfunction(L, dir_close); + lua_setfield(L, -2, "__close"); +#endif + return 1; +} + + +/* +** Creates lock metatable. +*/ +static int lock_create_meta(lua_State * L) +{ + luaL_newmetatable(L, LOCK_METATABLE); + + /* Method table */ + lua_newtable(L); + lua_pushcfunction(L, lfs_unlock_dir); + lua_setfield(L, -2, "free"); + + /* Metamethods */ + lua_setfield(L, -2, "__index"); + lua_pushcfunction(L, lfs_unlock_dir); + lua_setfield(L, -2, "__gc"); + return 1; +} + + +/* +** Convert the inode protection mode to a string. +*/ +#ifdef _WIN32 +static const char *mode2string(unsigned short mode) +{ +#else +static const char *mode2string(mode_t mode) +{ +#endif + if (S_ISREG(mode)) + return "file"; + else if (S_ISDIR(mode)) + return "directory"; + else if (S_ISLNK(mode)) + return "link"; + else if (S_ISSOCK(mode)) + return "socket"; + else if (S_ISFIFO(mode)) + return "named pipe"; + else if (S_ISCHR(mode)) + return "char device"; + else if (S_ISBLK(mode)) + return "block device"; + else + return "other"; +} + + +/* +** Set access time and modification values for a file. +** @param #1 File path. +** @param #2 Access time in seconds, current time is used if missing. +** @param #3 Modification time in seconds, access time is used if missing. +*/ +static int file_utime(lua_State * L) +{ + const char *file = luaL_checkstring(L, 1); + struct utimbuf utb, *buf; + + if (lua_gettop(L) == 1) /* set to current date/time */ + buf = NULL; + else { + utb.actime = (time_t) luaL_optnumber(L, 2, 0); + utb.modtime = (time_t) luaL_optinteger(L, 3, utb.actime); + buf = &utb; + } + + return pushresult(L, utime(file, buf), NULL); +} + + +/* inode protection mode */ +static void push_st_mode(lua_State * L, STAT_STRUCT * info) +{ + lua_pushstring(L, mode2string(info->st_mode)); +} + +/* device inode resides on */ +static void push_st_dev(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_dev); +} + +/* inode's number */ +static void push_st_ino(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_ino); +} + +/* number of hard links to the file */ +static void push_st_nlink(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_nlink); +} + +/* user-id of owner */ +static void push_st_uid(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_uid); +} + +/* group-id of owner */ +static void push_st_gid(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_gid); +} + +/* device type, for special file inode */ +static void push_st_rdev(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_rdev); +} + +/* time of last access */ +static void push_st_atime(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_atime); +} + +/* time of last data modification */ +static void push_st_mtime(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_mtime); +} + +/* time of last file status change */ +static void push_st_ctime(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_ctime); +} + +/* file size, in bytes */ +static void push_st_size(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_size); +} + +#ifndef _WIN32 +/* blocks allocated for file */ +static void push_st_blocks(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_blocks); +} + +/* optimal file system I/O blocksize */ +static void push_st_blksize(lua_State * L, STAT_STRUCT * info) +{ + lua_pushinteger(L, (lua_Integer) info->st_blksize); +} +#endif + + /* + ** Convert the inode protection mode to a permission list. + */ + +#ifdef _WIN32 +static const char *perm2string(unsigned short mode) +{ + static char perms[10] = "---------"; + int i; + for (i = 0; i < 9; i++) + perms[i] = '-'; + if (mode & _S_IREAD) { + perms[0] = 'r'; + perms[3] = 'r'; + perms[6] = 'r'; + } + if (mode & _S_IWRITE) { + perms[1] = 'w'; + perms[4] = 'w'; + perms[7] = 'w'; + } + if (mode & _S_IEXEC) { + perms[2] = 'x'; + perms[5] = 'x'; + perms[8] = 'x'; + } + return perms; +} +#else +static const char *perm2string(mode_t mode) +{ + static char perms[10] = "---------"; + int i; + for (i = 0; i < 9; i++) + perms[i] = '-'; + if (mode & S_IRUSR) + perms[0] = 'r'; + if (mode & S_IWUSR) + perms[1] = 'w'; + if (mode & S_IXUSR) + perms[2] = 'x'; + if (mode & S_IRGRP) + perms[3] = 'r'; + if (mode & S_IWGRP) + perms[4] = 'w'; + if (mode & S_IXGRP) + perms[5] = 'x'; + if (mode & S_IROTH) + perms[6] = 'r'; + if (mode & S_IWOTH) + perms[7] = 'w'; + if (mode & S_IXOTH) + perms[8] = 'x'; + return perms; +} +#endif + +/* permssions string */ +static void push_st_perm(lua_State * L, STAT_STRUCT * info) +{ + lua_pushstring(L, perm2string(info->st_mode)); +} + +typedef void (*_push_function)(lua_State * L, STAT_STRUCT * info); + +struct _stat_members { + const char *name; + _push_function push; +}; + +struct _stat_members members[] = { + { "mode", push_st_mode }, + { "dev", push_st_dev }, + { "ino", push_st_ino }, + { "nlink", push_st_nlink }, + { "uid", push_st_uid }, + { "gid", push_st_gid }, + { "rdev", push_st_rdev }, + { "access", push_st_atime }, + { "modification", push_st_mtime }, + { "change", push_st_ctime }, + { "size", push_st_size }, + { "permissions", push_st_perm }, +#ifndef _WIN32 + { "blocks", push_st_blocks }, + { "blksize", push_st_blksize }, +#endif + { NULL, NULL } +}; + +/* +** Get file or symbolic link information +*/ +static int _file_info_(lua_State * L, + int (*st)(const char *, STAT_STRUCT *)) +{ + STAT_STRUCT info; + const char *file = luaL_checkstring(L, 1); + int i; + + if (st(file, &info)) { + lua_pushnil(L); + lua_pushfstring(L, "cannot obtain information from file '%s': %s", + file, strerror(errno)); + lua_pushinteger(L, errno); + return 3; + } + if (lua_isstring(L, 2)) { + const char *member = lua_tostring(L, 2); + for (i = 0; members[i].name; i++) { + if (strcmp(members[i].name, member) == 0) { + /* push member value and return */ + members[i].push(L, &info); + return 1; + } + } + /* member not found */ + return luaL_error(L, "invalid attribute name '%s'", member); + } + /* creates a table if none is given, removes extra arguments */ + lua_settop(L, 2); + if (!lua_istable(L, 2)) { + lua_newtable(L); + } + /* stores all members in table on top of the stack */ + for (i = 0; members[i].name; i++) { + lua_pushstring(L, members[i].name); + members[i].push(L, &info); + lua_rawset(L, -3); + } + return 1; +} + + +/* +** Get file information using stat. +*/ +static int file_info(lua_State * L) +{ + return _file_info_(L, STAT_FUNC); +} + + +/* +** Push the symlink target to the top of the stack. +** Assumes the file name is at position 1 of the stack. +** Returns 1 if successful (with the target on top of the stack), +** 0 on failure (with stack unchanged, and errno set). +*/ +static int push_link_target(lua_State * L) +{ + const char *file = luaL_checkstring(L, 1); +#ifdef _WIN32 + HANDLE h = CreateFile(file, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (h == INVALID_HANDLE_VALUE) { + return lfs_win32_pusherror(L); + } +#endif + char *target = NULL; + int tsize, size = 256; /* size = initial buffer capacity */ + int ok = 0; + while (!ok) { + char *target2 = realloc(target, size); + if (!target2) { /* failed to allocate */ + break; + } + target = target2; +#ifdef _WIN32 + tsize = GetFinalPathNameByHandle(h, target, size, FILE_NAME_OPENED); +#else + tsize = readlink(file, target, size); +#endif + if (tsize < 0) { /* a readlink() error occurred */ + break; + } + if (tsize < size) { +#ifdef _WIN32 + if (tsize > 4 && strncmp(target, "\\\\?\\", 4) == 0) { + memmove_s(target, tsize - 3, target + 4, tsize - 3); + tsize -= 4; + } +#endif + ok = 1; + break; + } + /* possibly truncated readlink() result, double size and retry */ + size *= 2; + } + if (ok) { + target[tsize] = '\0'; + lua_pushlstring(L, target, tsize); + } +#ifdef _WIN32 + CloseHandle(h); +#endif + free(target); + return ok; +} + +/* +** Get symbolic link information using lstat. +*/ +static int link_info(lua_State * L) +{ + int ret; + if (lua_isstring(L, 2) && (strcmp(lua_tostring(L, 2), "target") == 0)) { + int ok = push_link_target(L); + return ok ? 1 : pusherror(L, "could not obtain link target"); + } + ret = _file_info_(L, LSTAT_FUNC); + if (ret == 1 && lua_type(L, -1) == LUA_TTABLE) { + int ok = push_link_target(L); + if (ok) { + lua_setfield(L, -2, "target"); + } + } + return ret; +} + + +/* +** Assumes the table is on top of the stack. +*/ +static void set_info(lua_State * L) +{ + lua_pushliteral(L, "Copyright (C) 2003-2017 Kepler Project"); + lua_setfield(L, -2, "_COPYRIGHT"); + lua_pushliteral(L, + "LuaFileSystem is a Lua library developed to complement " + "the set of functions related to file systems offered by " + "the standard Lua distribution"); + lua_setfield(L, -2, "_DESCRIPTION"); + lua_pushliteral(L, "LuaFileSystem " LFS_VERSION); + lua_setfield(L, -2, "_VERSION"); +} + + +static const struct luaL_Reg fslib[] = { + { "attributes", file_info }, + { "chdir", change_dir }, + { "currentdir", get_dir }, + { "dir", dir_iter_factory }, + { "link", make_link }, + { "lock", file_lock }, + { "mkdir", make_dir }, + { "rmdir", remove_dir }, + { "symlinkattributes", link_info }, + { "setmode", lfs_f_setmode }, + { "touch", file_utime }, + { "unlock", file_unlock }, + { "lock_dir", lfs_lock_dir }, + { NULL, NULL }, +}; + +LFS_EXPORT int luaopen_lfs(lua_State * L) +{ + dir_create_meta(L); + lock_create_meta(L); + new_lib(L, fslib); + lua_pushvalue(L, -1); + lua_setglobal(L, LFS_LIBNAME); + set_info(L); + return 1; +} diff --git a/src/lfs.h b/src/lfs.h new file mode 100644 index 0000000..13b60a9 --- /dev/null +++ b/src/lfs.h @@ -0,0 +1,35 @@ +/* +** LuaFileSystem +** Copyright Kepler Project 2003 - 2020 +** (http://keplerproject.github.io/luafilesystem) +*/ + +/* Define 'chdir' for systems that do not implement it */ +#ifdef NO_CHDIR +#define chdir(p) (-1) +#define chdir_error "Function 'chdir' not provided by system" +#else +#define chdir_error strerror(errno) +#endif + +#ifdef _WIN32 +#define chdir(p) (_chdir(p)) +#define getcwd(d, s) (_getcwd(d, s)) +#define rmdir(p) (_rmdir(p)) +#define LFS_EXPORT __declspec (dllexport) +#ifndef fileno +#define fileno(f) (_fileno(f)) +#endif +#else +#define LFS_EXPORT +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + LFS_EXPORT int luaopen_lfs(lua_State * L); + +#ifdef __cplusplus +} +#endif diff --git a/src/linit.c b/src/linit.c index f2fd331..b22d822 100644 --- a/src/linit.c +++ b/src/linit.c @@ -22,6 +22,7 @@ static const luaL_Reg lualibs[] = { {LUA_OSLIBNAME, luaopen_os}, {LUA_STRLIBNAME, luaopen_string}, {LUA_MATHLIBNAME, luaopen_math}, + {LUA_LFSLIBNAME, luaopen_lfs}, {LUA_CURSESLIBNAME, luaopen_curses}, {LUA_SOCKETCORELIBNAME, luaopen_socket_core}, {LUA_MIMECORELIBNAME, luaopen_mime_core}, diff --git a/src/lualib.h b/src/lualib.h index 13a7b96..779f195 100644 --- a/src/lualib.h +++ b/src/lualib.h @@ -33,6 +33,9 @@ LUALIB_API int (luaopen_string) (lua_State *L); #define LUA_MATHLIBNAME "math" LUALIB_API int (luaopen_math) (lua_State *L); +#define LUA_LFSLIBNAME "lfs" +LUALIB_API int (luaopen_lfs) (lua_State *L); + #define LUA_CURSESLIBNAME "curses" LUALIB_API int (luaopen_curses) (lua_State *L); |