/*- * Copyright (c) 2018 Conrad Meyer <cem@FreeBSD.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Portions derived from https://github.com/keplerproject/luafilesystem under * the terms of the MIT license: * * Copyright (c) 2003-2014 Kepler Project. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, * modify, merge, publish, distribute, sublicense, and/or sell copies * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include <sys/cdefs.h> #ifndef _STANDALONE #include <sys/stat.h> #include <dirent.h> #include <errno.h> #include <unistd.h> #include <stdio.h> #include <string.h> #endif #include <lua.h> #include "lauxlib.h" #include "lfs.h" #ifdef _STANDALONE #include "lstd.h" #include "lutils.h" #include "bootstrap.h" #endif #ifndef nitems #define nitems(x) (sizeof((x)) / sizeof((x)[0])) #endif /* * The goal is to emulate a subset of the upstream Lua FileSystem library, as * faithfully as possible in the boot environment. Only APIs that seem useful * need to emulated. * * Example usage: * * for file in lfs.dir("/boot") do * print("\t"..file) * end * * Prints: * . * .. * (etc.) * * The other available API is lfs.attributes(), which functions somewhat like * stat(2) and returns a table of values. Example code: * * attrs, errormsg, errorcode = lfs.attributes("/boot") * if attrs == nil then * print(errormsg) * return errorcode * end * * for k, v in pairs(attrs) do * print(k .. ":\t" .. v) * end * return 0 * * Prints (on success): * gid: 0 * change: 140737488342640 * mode: directory * rdev: 0 * ino: 4199275 * dev: 140737488342544 * modification: 140737488342576 * size: 512 * access: 140737488342560 * permissions: 755 * nlink: 58283552 * uid: 1001 */ #define DIR_METATABLE "directory iterator metatable" static int lua_dir_iter_pushtype(lua_State *L __unused, const struct dirent *ent __unused) { /* * This is a non-standard extension to luafilesystem for loader's * benefit. The extra stat() calls to determine the entry type can * be quite expensive on some systems, so this speeds up enumeration of * /boot greatly by providing the type up front. * * This extension is compatible enough with luafilesystem, in that we're * just using an extra return value for the iterator. */ #ifdef _STANDALONE lua_pushinteger(L, ent->d_type); return 1; #else return 0; #endif } static int lua_dir_iter_next(lua_State *L) { struct dirent *entry; DIR *dp, **dpp; dpp = (DIR **)luaL_checkudata(L, 1, DIR_METATABLE); dp = *dpp; luaL_argcheck(L, dp != NULL, 1, "closed directory"); #ifdef _STANDALONE entry = readdirfd(dp->fd); #else entry = readdir(dp); #endif if (entry == NULL) { closedir(dp); *dpp = NULL; return 0; } lua_pushstring(L, entry->d_name); return 1 + lua_dir_iter_pushtype(L, entry); } static int lua_dir_iter_close(lua_State *L) { DIR *dp, **dpp; dpp = (DIR **)lua_touserdata(L, 1); dp = *dpp; if (dp == NULL) return 0; closedir(dp); *dpp = NULL; return 0; } static int lua_dir(lua_State *L) { const char *path; DIR *dp; if (lua_gettop(L) != 1) { lua_pushnil(L); return 1; } path = luaL_checkstring(L, 1); dp = opendir(path); if (dp == NULL) { lua_pushnil(L); return 1; } lua_pushcfunction(L, lua_dir_iter_next); *(DIR **)lua_newuserdata(L, sizeof(DIR **)) = dp; luaL_getmetatable(L, DIR_METATABLE); lua_setmetatable(L, -2); return 2; } static void register_metatable(lua_State *L) { /* * Create so-called metatable for iterator object returned by * lfs.dir(). */ luaL_newmetatable(L, DIR_METATABLE); lua_newtable(L); lua_pushcfunction(L, lua_dir_iter_next); lua_setfield(L, -2, "next"); lua_pushcfunction(L, lua_dir_iter_close); lua_setfield(L, -2, "close"); /* Magically associate anonymous method table with metatable. */ lua_setfield(L, -2, "__index"); /* Implement magic destructor method */ lua_pushcfunction(L, lua_dir_iter_close); lua_setfield(L, -2, "__gc"); lua_pop(L, 1); } #define PUSH_INTEGER(lname, stname) \ static void \ push_st_ ## lname (lua_State *L, struct stat *sb) \ { \ lua_pushinteger(L, (lua_Integer)sb->st_ ## stname); \ } PUSH_INTEGER(dev, dev) PUSH_INTEGER(ino, ino) PUSH_INTEGER(nlink, nlink) PUSH_INTEGER(uid, uid) PUSH_INTEGER(gid, gid) PUSH_INTEGER(rdev, rdev) PUSH_INTEGER(access, atime) PUSH_INTEGER(modification, mtime) PUSH_INTEGER(change, ctime) PUSH_INTEGER(size, size) #undef PUSH_INTEGER static void push_st_mode(lua_State *L, struct stat *sb) { const char *mode_s; mode_t mode; mode = (sb->st_mode & S_IFMT); if (S_ISREG(mode)) mode_s = "file"; else if (S_ISDIR(mode)) mode_s = "directory"; else if (S_ISLNK(mode)) mode_s = "link"; else if (S_ISSOCK(mode)) mode_s = "socket"; else if (S_ISFIFO(mode)) mode_s = "fifo"; else if (S_ISCHR(mode)) mode_s = "char device"; else if (S_ISBLK(mode)) mode_s = "block device"; else mode_s = "other"; lua_pushstring(L, mode_s); } static void push_st_permissions(lua_State *L, struct stat *sb) { char buf[20]; /* * XXX * Could actually format as "-rwxrwxrwx" -- do we care? */ snprintf(buf, sizeof(buf), "%o", sb->st_mode & ~S_IFMT); lua_pushstring(L, buf); } #define PUSH_ENTRY(n) { #n, push_st_ ## n } struct stat_members { const char *name; void (*push)(lua_State *, struct stat *); } members[] = { PUSH_ENTRY(mode), PUSH_ENTRY(dev), PUSH_ENTRY(ino), PUSH_ENTRY(nlink), PUSH_ENTRY(uid), PUSH_ENTRY(gid), PUSH_ENTRY(rdev), PUSH_ENTRY(access), PUSH_ENTRY(modification), PUSH_ENTRY(change), PUSH_ENTRY(size), PUSH_ENTRY(permissions), }; #undef PUSH_ENTRY static int lua_attributes(lua_State *L) { struct stat sb; const char *path, *member; size_t i; int rc; path = luaL_checkstring(L, 1); if (path == NULL) { lua_pushnil(L); lua_pushfstring(L, "cannot convert first argument to string"); lua_pushinteger(L, EINVAL); return 3; } rc = stat(path, &sb); if (rc != 0) { lua_pushnil(L); lua_pushfstring(L, "cannot obtain information from file '%s': %s", path, strerror(errno)); lua_pushinteger(L, errno); return 3; } if (lua_isstring(L, 2)) { member = lua_tostring(L, 2); for (i = 0; i < nitems(members); i++) { if (strcmp(members[i].name, member) != 0) continue; members[i].push(L, &sb); return 1; } return luaL_error(L, "invalid attribute name '%s'", member); } /* Create or reuse existing table */ lua_settop(L, 2); if (!lua_istable(L, 2)) lua_newtable(L); /* Export all stat data to caller */ for (i = 0; i < nitems(members); i++) { lua_pushstring(L, members[i].name); members[i].push(L, &sb); lua_rawset(L, -3); } return 1; } #ifndef _STANDALONE #define lfs_mkdir_impl(path) (mkdir((path), \ S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | \ S_IROTH | S_IXOTH)) static int lua_mkdir(lua_State *L) { const char *path; int error, serrno; path = luaL_checkstring(L, 1); if (path == NULL) { lua_pushnil(L); lua_pushfstring(L, "cannot convert first argument to string"); lua_pushinteger(L, EINVAL); return 3; } error = lfs_mkdir_impl(path); if (error == -1) { /* Save it; unclear what other libc functions may be invoked */ serrno = errno; lua_pushnil(L); lua_pushfstring(L, strerror(serrno)); lua_pushinteger(L, serrno); return 3; } lua_pushboolean(L, 1); return 1; } static int lua_rmdir(lua_State *L) { const char *path; int error, serrno; path = luaL_checkstring(L, 1); if (path == NULL) { lua_pushnil(L); lua_pushfstring(L, "cannot convert first argument to string"); lua_pushinteger(L, EINVAL); return 3; } error = rmdir(path); if (error == -1) { /* Save it; unclear what other libc functions may be invoked */ serrno = errno; lua_pushnil(L); lua_pushfstring(L, strerror(serrno)); lua_pushinteger(L, serrno); return 3; } lua_pushboolean(L, 1); return 1; } #endif #define REG_SIMPLE(n) { #n, lua_ ## n } static const struct luaL_Reg fslib[] = { REG_SIMPLE(attributes), REG_SIMPLE(dir), #ifndef _STANDALONE REG_SIMPLE(mkdir), REG_SIMPLE(rmdir), #endif { NULL, NULL }, }; #undef REG_SIMPLE int luaopen_lfs(lua_State *L) { register_metatable(L); luaL_newlib(L, fslib); #ifdef _STANDALONE /* Non-standard extension for loader, used with lfs.dir(). */ lua_pushinteger(L, DT_DIR); lua_setfield(L, -2, "DT_DIR"); #endif return 1; }