/*-1* Copyright (c) 2018 Conrad Meyer <[email protected]>2* All rights reserved.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer.9* 2. Redistributions in binary form must reproduce the above copyright10* notice, this list of conditions and the following disclaimer in the11* documentation and/or other materials provided with the distribution.12*13* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND14* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE15* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE16* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE17* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL18* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS19* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)20* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT21* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY22* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF23* SUCH DAMAGE.24*25* Portions derived from https://github.com/keplerproject/luafilesystem under26* the terms of the MIT license:27*28* Copyright (c) 2003-2014 Kepler Project.29*30* Permission is hereby granted, free of charge, to any person31* obtaining a copy of this software and associated documentation32* files (the "Software"), to deal in the Software without33* restriction, including without limitation the rights to use, copy,34* modify, merge, publish, distribute, sublicense, and/or sell copies35* of the Software, and to permit persons to whom the Software is36* furnished to do so, subject to the following conditions:37*38* The above copyright notice and this permission notice shall be39* included in all copies or substantial portions of the Software.40*41* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,42* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF43* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND44* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS45* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN46* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN47* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE48* SOFTWARE.49*/5051#include <sys/cdefs.h>52#ifndef _STANDALONE53#include <sys/stat.h>54#include <dirent.h>55#include <errno.h>56#include <unistd.h>57#include <stdio.h>58#include <string.h>59#endif6061#include <lua.h>62#include "lauxlib.h"63#include "lfs.h"6465#ifdef _STANDALONE66#include "lstd.h"67#include "lutils.h"68#include "bootstrap.h"69#endif7071#ifndef nitems72#define nitems(x) (sizeof((x)) / sizeof((x)[0]))73#endif7475/*76* The goal is to emulate a subset of the upstream Lua FileSystem library, as77* faithfully as possible in the boot environment. Only APIs that seem useful78* need to emulated.79*80* Example usage:81*82* for file in lfs.dir("/boot") do83* print("\t"..file)84* end85*86* Prints:87* .88* ..89* (etc.)90*91* The other available API is lfs.attributes(), which functions somewhat like92* stat(2) and returns a table of values. Example code:93*94* attrs, errormsg, errorcode = lfs.attributes("/boot")95* if attrs == nil then96* print(errormsg)97* return errorcode98* end99*100* for k, v in pairs(attrs) do101* print(k .. ":\t" .. v)102* end103* return 0104*105* Prints (on success):106* gid: 0107* change: 140737488342640108* mode: directory109* rdev: 0110* ino: 4199275111* dev: 140737488342544112* modification: 140737488342576113* size: 512114* access: 140737488342560115* permissions: 755116* nlink: 58283552117* uid: 1001118*/119120#define DIR_METATABLE "directory iterator metatable"121122static int123lua_dir_iter_pushtype(lua_State *L __unused, const struct dirent *ent __unused)124{125126/*127* This is a non-standard extension to luafilesystem for loader's128* benefit. The extra stat() calls to determine the entry type can129* be quite expensive on some systems, so this speeds up enumeration of130* /boot greatly by providing the type up front.131*132* This extension is compatible enough with luafilesystem, in that we're133* just using an extra return value for the iterator.134*/135#ifdef _STANDALONE136lua_pushinteger(L, ent->d_type);137return 1;138#else139return 0;140#endif141}142143static int144lua_dir_iter_next(lua_State *L)145{146struct dirent *entry;147DIR *dp, **dpp;148149dpp = (DIR **)luaL_checkudata(L, 1, DIR_METATABLE);150dp = *dpp;151luaL_argcheck(L, dp != NULL, 1, "closed directory");152153#ifdef _STANDALONE154entry = readdirfd(dp->fd);155#else156entry = readdir(dp);157#endif158if (entry == NULL) {159closedir(dp);160*dpp = NULL;161return 0;162}163164lua_pushstring(L, entry->d_name);165return 1 + lua_dir_iter_pushtype(L, entry);166}167168static int169lua_dir_iter_close(lua_State *L)170{171DIR *dp, **dpp;172173dpp = (DIR **)lua_touserdata(L, 1);174dp = *dpp;175if (dp == NULL)176return 0;177178closedir(dp);179*dpp = NULL;180return 0;181}182183static int184lua_dir(lua_State *L)185{186const char *path;187DIR *dp;188189if (lua_gettop(L) != 1) {190lua_pushnil(L);191return 1;192}193194path = luaL_checkstring(L, 1);195dp = opendir(path);196if (dp == NULL) {197lua_pushnil(L);198return 1;199}200201lua_pushcfunction(L, lua_dir_iter_next);202*(DIR **)lua_newuserdata(L, sizeof(DIR **)) = dp;203luaL_getmetatable(L, DIR_METATABLE);204lua_setmetatable(L, -2);205return 2;206}207208static void209register_metatable(lua_State *L)210{211/*212* Create so-called metatable for iterator object returned by213* lfs.dir().214*/215luaL_newmetatable(L, DIR_METATABLE);216217lua_newtable(L);218lua_pushcfunction(L, lua_dir_iter_next);219lua_setfield(L, -2, "next");220lua_pushcfunction(L, lua_dir_iter_close);221lua_setfield(L, -2, "close");222223/* Magically associate anonymous method table with metatable. */224lua_setfield(L, -2, "__index");225/* Implement magic destructor method */226lua_pushcfunction(L, lua_dir_iter_close);227lua_setfield(L, -2, "__gc");228229lua_pop(L, 1);230}231232#define PUSH_INTEGER(lname, stname) \233static void \234push_st_ ## lname (lua_State *L, struct stat *sb) \235{ \236lua_pushinteger(L, (lua_Integer)sb->st_ ## stname); \237}238PUSH_INTEGER(dev, dev)239PUSH_INTEGER(ino, ino)240PUSH_INTEGER(nlink, nlink)241PUSH_INTEGER(uid, uid)242PUSH_INTEGER(gid, gid)243PUSH_INTEGER(rdev, rdev)244PUSH_INTEGER(access, atime)245PUSH_INTEGER(modification, mtime)246PUSH_INTEGER(change, ctime)247PUSH_INTEGER(size, size)248#undef PUSH_INTEGER249250static void251push_st_mode(lua_State *L, struct stat *sb)252{253const char *mode_s;254mode_t mode;255256mode = (sb->st_mode & S_IFMT);257if (S_ISREG(mode))258mode_s = "file";259else if (S_ISDIR(mode))260mode_s = "directory";261else if (S_ISLNK(mode))262mode_s = "link";263else if (S_ISSOCK(mode))264mode_s = "socket";265else if (S_ISFIFO(mode))266mode_s = "fifo";267else if (S_ISCHR(mode))268mode_s = "char device";269else if (S_ISBLK(mode))270mode_s = "block device";271else272mode_s = "other";273274lua_pushstring(L, mode_s);275}276277static void278push_st_permissions(lua_State *L, struct stat *sb)279{280char buf[20];281282/*283* XXX284* Could actually format as "-rwxrwxrwx" -- do we care?285*/286snprintf(buf, sizeof(buf), "%o", sb->st_mode & ~S_IFMT);287lua_pushstring(L, buf);288}289290#define PUSH_ENTRY(n) { #n, push_st_ ## n }291struct stat_members {292const char *name;293void (*push)(lua_State *, struct stat *);294} members[] = {295PUSH_ENTRY(mode),296PUSH_ENTRY(dev),297PUSH_ENTRY(ino),298PUSH_ENTRY(nlink),299PUSH_ENTRY(uid),300PUSH_ENTRY(gid),301PUSH_ENTRY(rdev),302PUSH_ENTRY(access),303PUSH_ENTRY(modification),304PUSH_ENTRY(change),305PUSH_ENTRY(size),306PUSH_ENTRY(permissions),307};308#undef PUSH_ENTRY309310static int311lua_attributes(lua_State *L)312{313struct stat sb;314const char *path, *member;315size_t i;316int rc;317318path = luaL_checkstring(L, 1);319if (path == NULL) {320lua_pushnil(L);321lua_pushfstring(L, "cannot convert first argument to string");322lua_pushinteger(L, EINVAL);323return 3;324}325326rc = stat(path, &sb);327if (rc != 0) {328lua_pushnil(L);329lua_pushfstring(L,330"cannot obtain information from file '%s': %s", path,331strerror(errno));332lua_pushinteger(L, errno);333return 3;334}335336if (lua_isstring(L, 2)) {337member = lua_tostring(L, 2);338for (i = 0; i < nitems(members); i++) {339if (strcmp(members[i].name, member) != 0)340continue;341342members[i].push(L, &sb);343return 1;344}345return luaL_error(L, "invalid attribute name '%s'", member);346}347348/* Create or reuse existing table */349lua_settop(L, 2);350if (!lua_istable(L, 2))351lua_newtable(L);352353/* Export all stat data to caller */354for (i = 0; i < nitems(members); i++) {355lua_pushstring(L, members[i].name);356members[i].push(L, &sb);357lua_rawset(L, -3);358}359return 1;360}361362#ifndef _STANDALONE363#define lfs_mkdir_impl(path) (mkdir((path), \364S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | \365S_IROTH | S_IXOTH))366367static int368lua_mkdir(lua_State *L)369{370const char *path;371int error, serrno;372373path = luaL_checkstring(L, 1);374if (path == NULL) {375lua_pushnil(L);376lua_pushfstring(L, "cannot convert first argument to string");377lua_pushinteger(L, EINVAL);378return 3;379}380381error = lfs_mkdir_impl(path);382if (error == -1) {383/* Save it; unclear what other libc functions may be invoked */384serrno = errno;385lua_pushnil(L);386lua_pushfstring(L, strerror(serrno));387lua_pushinteger(L, serrno);388return 3;389}390391lua_pushboolean(L, 1);392return 1;393}394395static int396lua_rmdir(lua_State *L)397{398const char *path;399int error, serrno;400401path = luaL_checkstring(L, 1);402if (path == NULL) {403lua_pushnil(L);404lua_pushfstring(L, "cannot convert first argument to string");405lua_pushinteger(L, EINVAL);406return 3;407}408409error = rmdir(path);410if (error == -1) {411/* Save it; unclear what other libc functions may be invoked */412serrno = errno;413lua_pushnil(L);414lua_pushfstring(L, strerror(serrno));415lua_pushinteger(L, serrno);416return 3;417}418419lua_pushboolean(L, 1);420return 1;421}422#endif423424#define REG_SIMPLE(n) { #n, lua_ ## n }425static const struct luaL_Reg fslib[] = {426REG_SIMPLE(attributes),427REG_SIMPLE(dir),428#ifndef _STANDALONE429REG_SIMPLE(mkdir),430REG_SIMPLE(rmdir),431#endif432{ NULL, NULL },433};434#undef REG_SIMPLE435436int437luaopen_lfs(lua_State *L)438{439register_metatable(L);440luaL_newlib(L, fslib);441#ifdef _STANDALONE442/* Non-standard extension for loader, used with lfs.dir(). */443lua_pushinteger(L, DT_DIR);444lua_setfield(L, -2, "DT_DIR");445#endif446return 1;447}448449450