/*-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#endif6970#include "bootstrap.h"7172#ifndef nitems73#define nitems(x) (sizeof((x)) / sizeof((x)[0]))74#endif7576/*77* The goal is to emulate a subset of the upstream Lua FileSystem library, as78* faithfully as possible in the boot environment. Only APIs that seem useful79* need to emulated.80*81* Example usage:82*83* for file in lfs.dir("/boot") do84* print("\t"..file)85* end86*87* Prints:88* .89* ..90* (etc.)91*92* The other available API is lfs.attributes(), which functions somewhat like93* stat(2) and returns a table of values. Example code:94*95* attrs, errormsg, errorcode = lfs.attributes("/boot")96* if attrs == nil then97* print(errormsg)98* return errorcode99* end100*101* for k, v in pairs(attrs) do102* print(k .. ":\t" .. v)103* end104* return 0105*106* Prints (on success):107* gid: 0108* change: 140737488342640109* mode: directory110* rdev: 0111* ino: 4199275112* dev: 140737488342544113* modification: 140737488342576114* size: 512115* access: 140737488342560116* permissions: 755117* nlink: 58283552118* uid: 1001119*/120121#define DIR_METATABLE "directory iterator metatable"122123static int124lua_dir_iter_pushtype(lua_State *L __unused, const struct dirent *ent __unused)125{126127/*128* This is a non-standard extension to luafilesystem for loader's129* benefit. The extra stat() calls to determine the entry type can130* be quite expensive on some systems, so this speeds up enumeration of131* /boot greatly by providing the type up front.132*133* This extension is compatible enough with luafilesystem, in that we're134* just using an extra return value for the iterator.135*/136#ifdef _STANDALONE137lua_pushinteger(L, ent->d_type);138return 1;139#else140return 0;141#endif142}143144static int145lua_dir_iter_next(lua_State *L)146{147struct dirent *entry;148DIR *dp, **dpp;149150dpp = (DIR **)luaL_checkudata(L, 1, DIR_METATABLE);151dp = *dpp;152luaL_argcheck(L, dp != NULL, 1, "closed directory");153154#ifdef _STANDALONE155entry = readdirfd(dp->fd);156#else157entry = readdir(dp);158#endif159if (entry == NULL) {160closedir(dp);161*dpp = NULL;162return 0;163}164165lua_pushstring(L, entry->d_name);166return 1 + lua_dir_iter_pushtype(L, entry);167}168169static int170lua_dir_iter_close(lua_State *L)171{172DIR *dp, **dpp;173174dpp = (DIR **)lua_touserdata(L, 1);175dp = *dpp;176if (dp == NULL)177return 0;178179closedir(dp);180*dpp = NULL;181return 0;182}183184static int185lua_dir(lua_State *L)186{187const char *path;188DIR *dp;189190if (lua_gettop(L) != 1) {191lua_pushnil(L);192return 1;193}194195path = luaL_checkstring(L, 1);196dp = opendir(path);197if (dp == NULL) {198lua_pushnil(L);199return 1;200}201202lua_pushcfunction(L, lua_dir_iter_next);203*(DIR **)lua_newuserdata(L, sizeof(DIR **)) = dp;204luaL_getmetatable(L, DIR_METATABLE);205lua_setmetatable(L, -2);206return 2;207}208209static void210register_metatable(lua_State *L)211{212/*213* Create so-called metatable for iterator object returned by214* lfs.dir().215*/216luaL_newmetatable(L, DIR_METATABLE);217218lua_newtable(L);219lua_pushcfunction(L, lua_dir_iter_next);220lua_setfield(L, -2, "next");221lua_pushcfunction(L, lua_dir_iter_close);222lua_setfield(L, -2, "close");223224/* Magically associate anonymous method table with metatable. */225lua_setfield(L, -2, "__index");226/* Implement magic destructor method */227lua_pushcfunction(L, lua_dir_iter_close);228lua_setfield(L, -2, "__gc");229230lua_pop(L, 1);231}232233#define PUSH_INTEGER(lname, stname) \234static void \235push_st_ ## lname (lua_State *L, struct stat *sb) \236{ \237lua_pushinteger(L, (lua_Integer)sb->st_ ## stname); \238}239PUSH_INTEGER(dev, dev)240PUSH_INTEGER(ino, ino)241PUSH_INTEGER(nlink, nlink)242PUSH_INTEGER(uid, uid)243PUSH_INTEGER(gid, gid)244PUSH_INTEGER(rdev, rdev)245PUSH_INTEGER(access, atime)246PUSH_INTEGER(modification, mtime)247PUSH_INTEGER(change, ctime)248PUSH_INTEGER(size, size)249#undef PUSH_INTEGER250251static void252push_st_mode(lua_State *L, struct stat *sb)253{254const char *mode_s;255mode_t mode;256257mode = (sb->st_mode & S_IFMT);258if (S_ISREG(mode))259mode_s = "file";260else if (S_ISDIR(mode))261mode_s = "directory";262else if (S_ISLNK(mode))263mode_s = "link";264else if (S_ISSOCK(mode))265mode_s = "socket";266else if (S_ISFIFO(mode))267mode_s = "fifo";268else if (S_ISCHR(mode))269mode_s = "char device";270else if (S_ISBLK(mode))271mode_s = "block device";272else273mode_s = "other";274275lua_pushstring(L, mode_s);276}277278static void279push_st_permissions(lua_State *L, struct stat *sb)280{281char buf[20];282283/*284* XXX285* Could actually format as "-rwxrwxrwx" -- do we care?286*/287snprintf(buf, sizeof(buf), "%o", sb->st_mode & ~S_IFMT);288lua_pushstring(L, buf);289}290291#define PUSH_ENTRY(n) { #n, push_st_ ## n }292struct stat_members {293const char *name;294void (*push)(lua_State *, struct stat *);295} members[] = {296PUSH_ENTRY(mode),297PUSH_ENTRY(dev),298PUSH_ENTRY(ino),299PUSH_ENTRY(nlink),300PUSH_ENTRY(uid),301PUSH_ENTRY(gid),302PUSH_ENTRY(rdev),303PUSH_ENTRY(access),304PUSH_ENTRY(modification),305PUSH_ENTRY(change),306PUSH_ENTRY(size),307PUSH_ENTRY(permissions),308};309#undef PUSH_ENTRY310311static int312lua_attributes(lua_State *L)313{314struct stat sb;315const char *path, *member;316size_t i;317int rc;318319path = luaL_checkstring(L, 1);320if (path == NULL) {321lua_pushnil(L);322lua_pushfstring(L, "cannot convert first argument to string");323lua_pushinteger(L, EINVAL);324return 3;325}326327rc = stat(path, &sb);328if (rc != 0) {329lua_pushnil(L);330lua_pushfstring(L,331"cannot obtain information from file '%s': %s", path,332strerror(errno));333lua_pushinteger(L, errno);334return 3;335}336337if (lua_isstring(L, 2)) {338member = lua_tostring(L, 2);339for (i = 0; i < nitems(members); i++) {340if (strcmp(members[i].name, member) != 0)341continue;342343members[i].push(L, &sb);344return 1;345}346return luaL_error(L, "invalid attribute name '%s'", member);347}348349/* Create or reuse existing table */350lua_settop(L, 2);351if (!lua_istable(L, 2))352lua_newtable(L);353354/* Export all stat data to caller */355for (i = 0; i < nitems(members); i++) {356lua_pushstring(L, members[i].name);357members[i].push(L, &sb);358lua_rawset(L, -3);359}360return 1;361}362363#ifndef _STANDALONE364#define lfs_mkdir_impl(path) (mkdir((path), \365S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | \366S_IROTH | S_IXOTH))367368static int369lua_mkdir(lua_State *L)370{371const char *path;372int error, serrno;373374path = luaL_checkstring(L, 1);375if (path == NULL) {376lua_pushnil(L);377lua_pushfstring(L, "cannot convert first argument to string");378lua_pushinteger(L, EINVAL);379return 3;380}381382error = lfs_mkdir_impl(path);383if (error == -1) {384/* Save it; unclear what other libc functions may be invoked */385serrno = errno;386lua_pushnil(L);387lua_pushfstring(L, strerror(serrno));388lua_pushinteger(L, serrno);389return 3;390}391392lua_pushboolean(L, 1);393return 1;394}395396static int397lua_rmdir(lua_State *L)398{399const char *path;400int error, serrno;401402path = luaL_checkstring(L, 1);403if (path == NULL) {404lua_pushnil(L);405lua_pushfstring(L, "cannot convert first argument to string");406lua_pushinteger(L, EINVAL);407return 3;408}409410error = rmdir(path);411if (error == -1) {412/* Save it; unclear what other libc functions may be invoked */413serrno = errno;414lua_pushnil(L);415lua_pushfstring(L, strerror(serrno));416lua_pushinteger(L, serrno);417return 3;418}419420lua_pushboolean(L, 1);421return 1;422}423#endif424425#define REG_SIMPLE(n) { #n, lua_ ## n }426static const struct luaL_Reg fslib[] = {427REG_SIMPLE(attributes),428REG_SIMPLE(dir),429#ifndef _STANDALONE430REG_SIMPLE(mkdir),431REG_SIMPLE(rmdir),432#endif433{ NULL, NULL },434};435#undef REG_SIMPLE436437int438luaopen_lfs(lua_State *L)439{440register_metatable(L);441luaL_newlib(L, fslib);442#ifdef _STANDALONE443/* Non-standard extension for loader, used with lfs.dir(). */444lua_pushinteger(L, DT_DIR);445lua_setfield(L, -2, "DT_DIR");446#endif447return 1;448}449450#ifndef _STANDALONE451FLUA_MODULE(lfs);452#endif453454455