1329500Scem/*-
2329500Scem * Copyright (c) 2018 Conrad Meyer <cem@FreeBSD.org>
3329500Scem * All rights reserved.
4329500Scem *
5329500Scem * Redistribution and use in source and binary forms, with or without
6329500Scem * modification, are permitted provided that the following conditions
7329500Scem * are met:
8329500Scem * 1. Redistributions of source code must retain the above copyright
9329500Scem *    notice, this list of conditions and the following disclaimer.
10329500Scem * 2. Redistributions in binary form must reproduce the above copyright
11329500Scem *    notice, this list of conditions and the following disclaimer in the
12329500Scem *    documentation and/or other materials provided with the distribution.
13329500Scem *
14329500Scem * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15329500Scem * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16329500Scem * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17329500Scem * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18329500Scem * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19329500Scem * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20329500Scem * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21329500Scem * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22329500Scem * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23329500Scem * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24329500Scem * SUCH DAMAGE.
25329500Scem *
26329500Scem * Portions derived from https://github.com/keplerproject/luafilesystem under
27329500Scem * the terms of the MIT license:
28329500Scem *
29329500Scem * Copyright (c) 2003-2014 Kepler Project.
30329500Scem *
31329500Scem * Permission is hereby granted, free of charge, to any person
32329500Scem * obtaining a copy of this software and associated documentation
33329500Scem * files (the "Software"), to deal in the Software without
34329500Scem * restriction, including without limitation the rights to use, copy,
35329500Scem * modify, merge, publish, distribute, sublicense, and/or sell copies
36329500Scem * of the Software, and to permit persons to whom the Software is
37329500Scem * furnished to do so, subject to the following conditions:
38329500Scem *
39329500Scem * The above copyright notice and this permission notice shall be
40329500Scem * included in all copies or substantial portions of the Software.
41329500Scem *
42329500Scem * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
43329500Scem * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44329500Scem * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
45329500Scem * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
46329500Scem * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
47329500Scem * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
48329500Scem * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
49329500Scem * SOFTWARE.
50329500Scem */
51329500Scem
52329500Scem#include <sys/cdefs.h>
53329500Scem__FBSDID("$FreeBSD: stable/11/libexec/flua/modules/lfs.c 354833 2019-11-18 23:21:13Z kevans $");
54329500Scem
55354833Skevans#ifndef _STANDALONE
56354833Skevans#include <sys/stat.h>
57354833Skevans#include <dirent.h>
58354833Skevans#include <errno.h>
59354833Skevans#include <unistd.h>
60354833Skevans#include <stdio.h>
61354833Skevans#include <string.h>
62354833Skevans#endif
63354833Skevans
64329500Scem#include <lua.h>
65329500Scem#include "lauxlib.h"
66329500Scem#include "lfs.h"
67354833Skevans
68354833Skevans#ifdef _STANDALONE
69329500Scem#include "lstd.h"
70329500Scem#include "lutils.h"
71329500Scem#include "bootstrap.h"
72354833Skevans#endif
73329500Scem
74329500Scem#ifndef nitems
75329500Scem#define	nitems(x)	(sizeof((x)) / sizeof((x)[0]))
76329500Scem#endif
77329500Scem
78329500Scem/*
79329500Scem * The goal is to emulate a subset of the upstream Lua FileSystem library, as
80329500Scem * faithfully as possible in the boot environment.  Only APIs that seem useful
81329500Scem * need to emulated.
82329500Scem *
83329500Scem * Example usage:
84329500Scem *
85329500Scem *     for file in lfs.dir("/boot") do
86329500Scem *         print("\t"..file)
87329500Scem *     end
88329500Scem *
89329500Scem * Prints:
90329500Scem *     .
91329500Scem *     ..
92329500Scem * (etc.)
93329500Scem *
94329500Scem * The other available API is lfs.attributes(), which functions somewhat like
95329649Scem * stat(2) and returns a table of values.  Example code:
96329500Scem *
97329649Scem *     attrs, errormsg, errorcode = lfs.attributes("/boot")
98329649Scem *     if attrs == nil then
99329649Scem *         print(errormsg)
100329649Scem *         return errorcode
101329649Scem *     end
102329649Scem *
103329649Scem *     for k, v in pairs(attrs) do
104329500Scem *         print(k .. ":\t" .. v)
105329500Scem *     end
106329649Scem *     return 0
107329500Scem *
108329649Scem * Prints (on success):
109329500Scem *     gid:    0
110329500Scem *     change: 140737488342640
111329500Scem *     mode:   directory
112329500Scem *     rdev:   0
113329500Scem *     ino:    4199275
114329500Scem *     dev:    140737488342544
115329500Scem *     modification:   140737488342576
116329500Scem *     size:   512
117329500Scem *     access: 140737488342560
118329500Scem *     permissions:    755
119329500Scem *     nlink:  58283552
120329500Scem *     uid:    1001
121329500Scem */
122329500Scem
123329500Scem#define DIR_METATABLE "directory iterator metatable"
124329500Scem
125329500Scemstatic int
126329500Scemlua_dir_iter_next(lua_State *L)
127329500Scem{
128329500Scem	struct dirent *entry;
129329500Scem	DIR *dp, **dpp;
130329500Scem
131329500Scem	dpp = (DIR **)luaL_checkudata(L, 1, DIR_METATABLE);
132329500Scem	dp = *dpp;
133329500Scem	luaL_argcheck(L, dp != NULL, 1, "closed directory");
134329500Scem
135354833Skevans#ifdef _STANDALONE
136329500Scem	entry = readdirfd(dp->fd);
137354833Skevans#else
138354833Skevans	entry = readdir(dp);
139354833Skevans#endif
140329500Scem	if (entry == NULL) {
141329500Scem		closedir(dp);
142329500Scem		*dpp = NULL;
143329500Scem		return 0;
144329500Scem	}
145329500Scem
146329500Scem	lua_pushstring(L, entry->d_name);
147329500Scem	return 1;
148329500Scem}
149329500Scem
150329500Scemstatic int
151329500Scemlua_dir_iter_close(lua_State *L)
152329500Scem{
153329500Scem	DIR *dp, **dpp;
154329500Scem
155329500Scem	dpp = (DIR **)lua_touserdata(L, 1);
156329500Scem	dp = *dpp;
157329500Scem	if (dp == NULL)
158329500Scem		return 0;
159329500Scem
160329500Scem	closedir(dp);
161329500Scem	*dpp = NULL;
162329500Scem	return 0;
163329500Scem}
164329500Scem
165329500Scemstatic int
166329500Scemlua_dir(lua_State *L)
167329500Scem{
168329500Scem	const char *path;
169329500Scem	DIR *dp;
170329500Scem
171329500Scem	if (lua_gettop(L) != 1) {
172329500Scem		lua_pushnil(L);
173329500Scem		return 1;
174329500Scem	}
175329500Scem
176329500Scem	path = luaL_checkstring(L, 1);
177329500Scem	dp = opendir(path);
178329500Scem	if (dp == NULL) {
179329500Scem		lua_pushnil(L);
180329500Scem		return 1;
181329500Scem	}
182329500Scem
183329500Scem	lua_pushcfunction(L, lua_dir_iter_next);
184329500Scem	*(DIR **)lua_newuserdata(L, sizeof(DIR **)) = dp;
185329500Scem	luaL_getmetatable(L, DIR_METATABLE);
186329500Scem	lua_setmetatable(L, -2);
187329500Scem	return 2;
188329500Scem}
189329500Scem
190329500Scemstatic void
191329500Scemregister_metatable(lua_State *L)
192329500Scem{
193329500Scem	/*
194329500Scem	 * Create so-called metatable for iterator object returned by
195329500Scem	 * lfs.dir().
196329500Scem	 */
197329500Scem	luaL_newmetatable(L, DIR_METATABLE);
198329500Scem
199329500Scem	lua_newtable(L);
200329500Scem	lua_pushcfunction(L, lua_dir_iter_next);
201329500Scem	lua_setfield(L, -2, "next");
202329500Scem	lua_pushcfunction(L, lua_dir_iter_close);
203329500Scem	lua_setfield(L, -2, "close");
204329500Scem
205329500Scem	/* Magically associate anonymous method table with metatable. */
206329500Scem	lua_setfield(L, -2, "__index");
207329500Scem	/* Implement magic destructor method */
208329500Scem	lua_pushcfunction(L, lua_dir_iter_close);
209329500Scem	lua_setfield(L, -2, "__gc");
210329500Scem
211329500Scem	lua_pop(L, 1);
212329500Scem}
213329500Scem
214329500Scem#define PUSH_INTEGER(lname, stname)				\
215329500Scemstatic void							\
216329500Scempush_st_ ## lname (lua_State *L, struct stat *sb)		\
217329500Scem{								\
218329500Scem	lua_pushinteger(L, (lua_Integer)sb->st_ ## stname);	\
219329500Scem}
220329500ScemPUSH_INTEGER(dev, dev)
221329500ScemPUSH_INTEGER(ino, ino)
222329500ScemPUSH_INTEGER(nlink, nlink)
223329500ScemPUSH_INTEGER(uid, uid)
224329500ScemPUSH_INTEGER(gid, gid)
225329500ScemPUSH_INTEGER(rdev, rdev)
226329500ScemPUSH_INTEGER(access, atime)
227329500ScemPUSH_INTEGER(modification, mtime)
228329500ScemPUSH_INTEGER(change, ctime)
229329500ScemPUSH_INTEGER(size, size)
230329500Scem#undef PUSH_INTEGER
231329500Scem
232329500Scemstatic void
233329500Scempush_st_mode(lua_State *L, struct stat *sb)
234329500Scem{
235329500Scem	const char *mode_s;
236329500Scem	mode_t mode;
237329500Scem
238329500Scem	mode = (sb->st_mode & S_IFMT);
239329500Scem	if (S_ISREG(mode))
240329500Scem		mode_s = "file";
241329500Scem	else if (S_ISDIR(mode))
242329500Scem		mode_s = "directory";
243329500Scem	else if (S_ISLNK(mode))
244329500Scem		mode_s = "link";
245329500Scem	else if (S_ISSOCK(mode))
246329500Scem		mode_s = "socket";
247329500Scem	else if (S_ISFIFO(mode))
248329500Scem		mode_s = "fifo";
249329500Scem	else if (S_ISCHR(mode))
250329500Scem		mode_s = "char device";
251329500Scem	else if (S_ISBLK(mode))
252329500Scem		mode_s = "block device";
253329500Scem	else
254329500Scem		mode_s = "other";
255329500Scem
256329500Scem	lua_pushstring(L, mode_s);
257329500Scem}
258329500Scem
259329500Scemstatic void
260329500Scempush_st_permissions(lua_State *L, struct stat *sb)
261329500Scem{
262329500Scem	char buf[20];
263329500Scem
264329500Scem	/*
265329500Scem	 * XXX
266329500Scem	 * Could actually format as "-rwxrwxrwx" -- do we care?
267329500Scem	 */
268329500Scem	snprintf(buf, sizeof(buf), "%o", sb->st_mode & ~S_IFMT);
269329500Scem	lua_pushstring(L, buf);
270329500Scem}
271329500Scem
272329500Scem#define PUSH_ENTRY(n)	{ #n, push_st_ ## n }
273329500Scemstruct stat_members {
274329500Scem	const char *name;
275329500Scem	void (*push)(lua_State *, struct stat *);
276329500Scem} members[] = {
277329500Scem	PUSH_ENTRY(mode),
278329500Scem	PUSH_ENTRY(dev),
279329500Scem	PUSH_ENTRY(ino),
280329500Scem	PUSH_ENTRY(nlink),
281329500Scem	PUSH_ENTRY(uid),
282329500Scem	PUSH_ENTRY(gid),
283329500Scem	PUSH_ENTRY(rdev),
284329500Scem	PUSH_ENTRY(access),
285329500Scem	PUSH_ENTRY(modification),
286329500Scem	PUSH_ENTRY(change),
287329500Scem	PUSH_ENTRY(size),
288329500Scem	PUSH_ENTRY(permissions),
289329500Scem};
290329500Scem#undef PUSH_ENTRY
291329500Scem
292329500Scemstatic int
293329500Scemlua_attributes(lua_State *L)
294329500Scem{
295329500Scem	struct stat sb;
296329500Scem	const char *path, *member;
297329500Scem	size_t i;
298329500Scem	int rc;
299329500Scem
300329500Scem	path = luaL_checkstring(L, 1);
301329500Scem	if (path == NULL) {
302329500Scem		lua_pushnil(L);
303329649Scem		lua_pushfstring(L, "cannot convert first argument to string");
304329649Scem		lua_pushinteger(L, EINVAL);
305329649Scem		return 3;
306329500Scem	}
307329500Scem
308329500Scem	rc = stat(path, &sb);
309329500Scem	if (rc != 0) {
310329500Scem		lua_pushnil(L);
311329500Scem		lua_pushfstring(L,
312329500Scem		    "cannot obtain information from file '%s': %s", path,
313329500Scem		    strerror(errno));
314329500Scem		lua_pushinteger(L, errno);
315329500Scem		return 3;
316329500Scem	}
317329500Scem
318329500Scem	if (lua_isstring(L, 2)) {
319329500Scem		member = lua_tostring(L, 2);
320329500Scem		for (i = 0; i < nitems(members); i++) {
321329500Scem			if (strcmp(members[i].name, member) != 0)
322329500Scem				continue;
323329500Scem
324329500Scem			members[i].push(L, &sb);
325329500Scem			return 1;
326329500Scem		}
327329500Scem		return luaL_error(L, "invalid attribute name '%s'", member);
328329500Scem	}
329329500Scem
330329500Scem	/* Create or reuse existing table */
331329500Scem	lua_settop(L, 2);
332329500Scem	if (!lua_istable(L, 2))
333329500Scem		lua_newtable(L);
334329500Scem
335329500Scem	/* Export all stat data to caller */
336329500Scem	for (i = 0; i < nitems(members); i++) {
337329500Scem		lua_pushstring(L, members[i].name);
338329500Scem		members[i].push(L, &sb);
339329500Scem		lua_rawset(L, -3);
340329500Scem	}
341329500Scem	return 1;
342329500Scem}
343329500Scem
344354833Skevans#ifndef _STANDALONE
345354833Skevans#define	lfs_mkdir_impl(path)	(mkdir((path), \
346354833Skevans    S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | \
347354833Skevans    S_IROTH | S_IXOTH))
348354833Skevans
349354833Skevansstatic int
350354833Skevanslua_mkdir(lua_State *L)
351354833Skevans{
352354833Skevans	const char *path;
353354833Skevans	int error, serrno;
354354833Skevans
355354833Skevans	path = luaL_checkstring(L, 1);
356354833Skevans	if (path == NULL) {
357354833Skevans		lua_pushnil(L);
358354833Skevans		lua_pushfstring(L, "cannot convert first argument to string");
359354833Skevans		lua_pushinteger(L, EINVAL);
360354833Skevans		return 3;
361354833Skevans	}
362354833Skevans
363354833Skevans	error = lfs_mkdir_impl(path);
364354833Skevans	if (error == -1) {
365354833Skevans		/* Save it; unclear what other libc functions may be invoked */
366354833Skevans		serrno = errno;
367354833Skevans		lua_pushnil(L);
368354833Skevans		lua_pushfstring(L, strerror(serrno));
369354833Skevans		lua_pushinteger(L, serrno);
370354833Skevans		return 3;
371354833Skevans	}
372354833Skevans
373354833Skevans	lua_pushboolean(L, 1);
374354833Skevans	return 1;
375354833Skevans}
376354833Skevans
377354833Skevansstatic int
378354833Skevanslua_rmdir(lua_State *L)
379354833Skevans{
380354833Skevans	const char *path;
381354833Skevans	int error, serrno;
382354833Skevans
383354833Skevans	path = luaL_checkstring(L, 1);
384354833Skevans	if (path == NULL) {
385354833Skevans		lua_pushnil(L);
386354833Skevans		lua_pushfstring(L, "cannot convert first argument to string");
387354833Skevans		lua_pushinteger(L, EINVAL);
388354833Skevans		return 3;
389354833Skevans	}
390354833Skevans
391354833Skevans	error = rmdir(path);
392354833Skevans	if (error == -1) {
393354833Skevans		/* Save it; unclear what other libc functions may be invoked */
394354833Skevans		serrno = errno;
395354833Skevans		lua_pushnil(L);
396354833Skevans		lua_pushfstring(L, strerror(serrno));
397354833Skevans		lua_pushinteger(L, serrno);
398354833Skevans		return 3;
399354833Skevans	}
400354833Skevans
401354833Skevans	lua_pushboolean(L, 1);
402354833Skevans	return 1;
403354833Skevans}
404354833Skevans#endif
405354833Skevans
406329500Scem#define REG_SIMPLE(n)	{ #n, lua_ ## n }
407329500Scemstatic const struct luaL_Reg fslib[] = {
408329500Scem	REG_SIMPLE(attributes),
409329500Scem	REG_SIMPLE(dir),
410354833Skevans#ifndef _STANDALONE
411354833Skevans	REG_SIMPLE(mkdir),
412354833Skevans	REG_SIMPLE(rmdir),
413354833Skevans#endif
414329500Scem	{ NULL, NULL },
415329500Scem};
416329500Scem#undef REG_SIMPLE
417329500Scem
418329500Scemint
419329500Scemluaopen_lfs(lua_State *L)
420329500Scem{
421329500Scem	register_metatable(L);
422329500Scem	luaL_newlib(L, fslib);
423329500Scem	return 1;
424329500Scem}
425