/* * Callisto - standalone scripting platform for Lua 5.4 * Copyright (c) 2023-2024 Jeremy Baxter. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }