e25ee296c9
This eliminates a lot of stat() calls that happen when lualoader renders the menu with the default settings, and greatly speeds up rendering on my laptop. ftype is nil if loader/loader.efi hasn't been updated yet, falling back to lfs.attributes() to test. This is technically incompatible with lfs, but not in a particularly terrible way. Reviewed-by: cem MFC-after: 4 days Differential Revision: https://reviews.freebsd.org/D27542
451 lines
10 KiB
C
451 lines
10 KiB
C
/*-
|
|
* 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>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#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;
|
|
}
|