callisto/lfs.c
2024-04-06 11:47:15 +13:00

572 lines
9.6 KiB
C

/*
* Callisto - standalone scripting platform for Lua 5.4
* Copyright (c) 2023-2024 Jeremy Baxter.
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <lua/lauxlib.h>
#include <lua/lua.h>
#include "util.h"
/* wrapped DIR structure with the
* absolute path of the directory */
struct wdir {
DIR *d;
const char *path;
};
/* clang-format off */
struct {
mode_t mode;
char name[8];
} modes[] = {
/* file types */
{S_IFMT, "IFMT"},
{S_IFBLK, "IFBLK"},
{S_IFCHR, "IFCHR"},
{S_IFIFO, "IFIFO"},
{S_IFREG, "IFREG"},
{S_IFDIR, "IFDIR"},
{S_IFLNK, "IFLNK"},
{S_IFSOCK, "IFSOCK"},
/* file permissions */
{S_IRWXU, "IRWXU"},
{S_IRUSR, "IRUSR"},
{S_IWUSR, "IWUSR"},
{S_IXUSR, "IXUSR"},
{S_IRWXG, "IRWXG"},
{S_IRGRP, "IRGRP"},
{S_IWGRP, "IWGRP"},
{S_IXGRP, "IXGRP"},
{S_IRWXO, "IRWXO"},
{S_IROTH, "IROTH"},
{S_IWOTH, "IWOTH"},
{S_IXOTH, "IXOTH"},
{S_ISUID, "ISUID"},
{S_ISGID, "ISGID"},
{S_ISVTX, "ISVTX"},
{0, {0}}
};
/* clang-format on */
static int
fs_basename(lua_State *L)
{
const char *ret;
ret = basename((char *)luaL_checkstring(L, 1));
if (ret == NULL)
return lfail(L);
lua_pushstring(L, ret);
return 1;
}
static int
fs_copy(lua_State *L)
{
struct stat sb;
const char *source; /* parameter 1 (string) */
const char *target; /* parameter 2 (string) */
char readbuf[512];
ssize_t ret;
int sfd, tfd;
source = luaL_checkstring(L, 1);
target = luaL_checkstring(L, 2);
/* get the source file's mode */
if (stat(source, &sb) == -1)
return lfail(L);
sfd = open(source, O_RDONLY);
tfd = open(target, O_WRONLY | O_CREAT | O_TRUNC, sb.st_mode);
if (sfd == -1 || tfd == -1)
return lfail(L);
for (;;) {
ret = read(sfd, readbuf, 512);
switch (ret) {
case 0: /* end-of-file */
goto finish;
break;
case -1: /* error */
close(sfd);
close(tfd);
return lfail(L);
break;
}
ret = write(tfd, readbuf, ret);
if (ret == -1) {
close(sfd);
close(tfd);
return lfail(L);
}
}
finish:
close(sfd);
close(tfd);
lua_pushboolean(L, 1);
return 1;
}
static int
fs_dirname(lua_State *L)
{
const char *ret;
ret = dirname((char *)luaL_checkstring(L, 1));
if (ret == NULL)
return lfail(L);
lua_pushstring(L, ret);
return 1;
}
static int fs_status(lua_State *);
static int
entries_iter(lua_State *L)
{
struct wdir *w;
struct dirent *ent;
char cwd[PATH_MAX];
w = (struct wdir *)lua_touserdata(L, lua_upvalueindex(1));
ent = readdir(w->d);
if (!ent) {
closedir(w->d);
w->d = NULL;
return 0;
}
getcwd(cwd, PATH_MAX);
if (chdir(w->path) == -1)
return lfail(L);
lua_pushcfunction(L, fs_status);
lua_pushstring(L, ent->d_name);
lua_call(L, 1, 1);
if (chdir(cwd) == -1)
return lfail(L);
return 1;
}
static int
entries_gc(lua_State *L)
{
struct wdir *w;
w = (struct wdir *)lua_touserdata(L, 1);
if (w->d)
closedir(w->d);
return 0;
}
static int
fs_entries(lua_State *L)
{
struct wdir *w;
const char *path; /* parameter 1 (string) */
path = luaL_optstring(L, 1, ".");
w = (struct wdir *)lua_newuserdatauv(L, sizeof(struct wdir), 0);
w->d = opendir(path);
w->path = path;
if (!(w->d))
return luaL_error(L, "%s: %s", path, strerror(errno));
if (luaL_newmetatable(L, "directory entry")) {
lua_pushcfunction(L, entries_gc);
lua_setfield(L, -2, "__gc");
}
lua_setmetatable(L, -2);
lua_pushcclosure(L, entries_iter, 1);
return 1;
}
static int
fs_exists(lua_State *L)
{
const char *path; /* parameter 1 (string) */
int ret;
path = luaL_checkstring(L, 1);
ret = access(path, F_OK);
if (ret == -1)
return lfail(L);
lua_pushboolean(L, ret == 0);
return 1;
}
static int fs_type(lua_State *);
static int
istype(lua_State *L, char *type)
{
luaL_checkstring(L, 1);
lua_pushcfunction(L, fs_type);
lua_pushvalue(L, 1);
lua_call(L, 1, 1);
lua_pushstring(L, type);
lua_pushboolean(L, lua_compare(L, -1, -2, LUA_OPEQ));
return 1;
}
static int
fs_isdirectory(lua_State *L)
{
return istype(L, "directory");
}
static int
fs_isfile(lua_State *L)
{
return istype(L, "file");
}
static int
fs_issymlink(lua_State *L)
{
return istype(L, "symlink");
}
static int
fs_mkpath(lua_State *L)
{
/*
* Code derived from OpenBSD mkdir(1)
*/
struct stat sb;
const char *path; /* path to make */
char *slash;
mode_t mode; /* file mode of terminal directory */
mode_t dir_mode; /* file mode of intermediate directories */
int done;
path = luaL_checkstring(L, 1);
mode = dir_mode = 0755;
slash = (char *)path;
for (;;) {
slash += strspn(slash, "/");
slash += strcspn(slash, "/");
done = (*slash == '\0');
*slash = '\0';
if (mkdir(path, done ? mode : dir_mode) == 0) {
if (mode > 0755 && chmod(path, mode) == -1)
return lfail(L);
} else {
int mkdir_errno = errno;
if (stat(path, &sb) == -1) {
/* Not there; use mkdir()s errno */
errno = mkdir_errno;
return lfail(L);
}
if (!S_ISDIR(sb.st_mode)) {
/* Is there, but isn't a directory */
errno = ENOTDIR;
return lfail(L);
}
}
if (done)
break;
*slash = '/';
}
lua_pushboolean(L, 1);
return 1;
}
static int
fs_mkdir(lua_State *L)
{
if (mkdir(luaL_checkstring(L, 1), 0755) == 0)
return lfail(L);
lua_pushboolean(L, 1);
return 1;
}
static int
fs_move(lua_State *L)
{
int ret;
const char *src; /* parameter 1 (string) */
const char *dest; /* parameter 2 (string) */
src = luaL_checkstring(L, 1);
dest = luaL_checkstring(L, 2);
ret = rename(src, dest);
if (ret == 0) {
lua_pushboolean(L, 1);
return 1;
}
return lfail(L);
}
static int
recursiveremove(lua_State *L, const char *path)
{
DIR *d;
struct dirent *ent;
char *fullname;
size_t len, nlen, plen;
if ((d = opendir(path)) == NULL) {
if (errno != ENOTDIR)
return lfail(L);
/* if it's a file... */
if (unlink(path) == -1)
return lfail(L);
lua_pushboolean(L, 1);
return 1;
}
while ((ent = readdir(d)) != NULL) {
if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0)
continue;
plen = strlen(path);
nlen = strlen(ent->d_name);
/* path + name + slash in between + null terminator */
len = plen + nlen + 2;
fullname = malloc(len * sizeof(char));
strbcpy(fullname, path, len);
fullname[plen] = '/';
fullname[plen + 1] = 0;
strbcat(fullname, ent->d_name, len);
if (ent->d_type == DT_DIR) {
/* if rmdir succeeded, free fullname and
* proceed with next entry */
if (rmdir(fullname) != -1)
goto next;
if (errno == ENOTEMPTY) /* if the directory is not empty... */
recursiveremove(L, fullname);
else /* if some other error occurred */
return lfail(L);
} else {
if (unlink(fullname) == -1)
return lfail(L);
}
next:
free(fullname);
}
closedir(d);
if (rmdir(path) == -1)
return lfail(L);
lua_pushboolean(L, 1);
return 1;
}
static int
fs_remove(lua_State *L)
{
const char *path; /* parameter 1 (string) */
path = luaL_checkstring(L, 1);
return recursiveremove(L, path);
}
static int
fs_rmdir(lua_State *L)
{
const char *dir; /* parameter 1 (string) */
int ret;
dir = luaL_checkstring(L, 1);
ret = rmdir(dir);
if (ret == 0) {
lua_pushboolean(L, 1);
return 1;
}
return lfail(L);
}
static int
fs_status(lua_State *L)
{
struct stat s;
const char *path; /* parameter 1 (string) */
path = luaL_checkstring(L, 1);
if (lstat(path, &s) == -1)
return lfail(L);
lua_createtable(L, 0, 7);
lua_pushvalue(L, 1);
lua_setfield(L, -2, "path");
lua_pushinteger(L, s.st_mode);
lua_setfield(L, -2, "mode");
lua_pushinteger(L, s.st_uid);
lua_setfield(L, -2, "uid");
lua_pushinteger(L, s.st_gid);
lua_setfield(L, -2, "gid");
lua_pushinteger(L, s.st_atim.tv_sec);
lua_setfield(L, -2, "accessdate");
lua_pushinteger(L, s.st_mtim.tv_sec);
lua_setfield(L, -2, "modifydate");
lua_pushinteger(L, s.st_ctim.tv_sec);
lua_setfield(L, -2, "chdate");
return 1;
}
static int
fs_type(lua_State *L)
{
struct stat sb;
const char *path; /* parameter 1 (string) */
path = luaL_checkstring(L, 1);
if (lstat(path, &sb) == -1)
return lfail(L);
switch (sb.st_mode & S_IFMT) {
case S_IFBLK:
lua_pushstring(L, "block");
break;
case S_IFCHR:
lua_pushstring(L, "character");
break;
case S_IFDIR:
lua_pushstring(L, "directory");
break;
case S_IFIFO:
lua_pushstring(L, "fifo");
break;
case S_IFLNK:
lua_pushstring(L, "symlink");
break;
case S_IFREG:
lua_pushstring(L, "file");
break;
case S_IFSOCK:
lua_pushstring(L, "socket");
break;
}
return 1;
}
static int
fs_workdir(lua_State *L)
{
const char *workdir; /* parameter 1 (string) */
char buffer[512]; /* buffer used by getcwd() */
char *ret;
if (lua_isnoneornil(L, 1)) { /* if first argument was not given... */
ret = getcwd(buffer, 512);
if (ret != NULL) {
lua_pushstring(L, buffer);
return 1;
}
return lfail(L);
}
workdir = luaL_checkstring(L, 1);
if (chdir(workdir) == 0) {
lua_pushboolean(L, 1);
return 1;
}
return lfail(L);
}
/* clang-format off */
static const luaL_Reg fslib[] = {
{"basename", fs_basename},
{"copy", fs_copy},
{"dirname", fs_dirname},
{"entries", fs_entries},
{"exists", fs_exists},
{"isdirectory", fs_isdirectory},
{"isfile", fs_isfile},
{"issymlink", fs_issymlink},
{"mkdir", fs_mkdir},
{"mkpath", fs_mkpath},
{"move", fs_move},
{"remove", fs_remove},
{"rmdir", fs_rmdir},
{"status", fs_status},
{"type", fs_type},
{"workdir", fs_workdir},
{NULL, NULL}
};
int
luaopen_fs(lua_State *L)
{
int i;
luaL_newlib(L, fslib);
for (i = 0; modes[i].name[0] != 0; i++) {
lua_pushinteger(L, modes[i].mode);
lua_setfield(L, -2, modes[i].name);
}
return 1;
}