1/*-
2 * Copyright (c) 2018 Conrad Meyer <cem@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 * Portions derived from https://github.com/keplerproject/luafilesystem under
27 * the terms of the MIT license:
28 *
29 * Copyright (c) 2003-2014 Kepler Project.
30 *
31 * Permission is hereby granted, free of charge, to any person
32 * obtaining a copy of this software and associated documentation
33 * files (the "Software"), to deal in the Software without
34 * restriction, including without limitation the rights to use, copy,
35 * modify, merge, publish, distribute, sublicense, and/or sell copies
36 * of the Software, and to permit persons to whom the Software is
37 * furnished to do so, subject to the following conditions:
38 *
39 * The above copyright notice and this permission notice shall be
40 * included in all copies or substantial portions of the Software.
41 *
42 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
43 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
44 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
45 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
46 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
47 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
48 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
49 * SOFTWARE.
50 */
51
52#include <sys/cdefs.h>
53#ifndef _STANDALONE
54#include <sys/stat.h>
55#include <dirent.h>
56#include <errno.h>
57#include <unistd.h>
58#include <stdio.h>
59#include <string.h>
60#endif
61
62#include <lua.h>
63#include "lauxlib.h"
64#include "lfs.h"
65
66#ifdef _STANDALONE
67#include "lstd.h"
68#include "lutils.h"
69#include "bootstrap.h"
70#endif
71
72#ifndef nitems
73#define	nitems(x)	(sizeof((x)) / sizeof((x)[0]))
74#endif
75
76/*
77 * The goal is to emulate a subset of the upstream Lua FileSystem library, as
78 * faithfully as possible in the boot environment.  Only APIs that seem useful
79 * need to emulated.
80 *
81 * Example usage:
82 *
83 *     for file in lfs.dir("/boot") do
84 *         print("\t"..file)
85 *     end
86 *
87 * Prints:
88 *     .
89 *     ..
90 * (etc.)
91 *
92 * The other available API is lfs.attributes(), which functions somewhat like
93 * stat(2) and returns a table of values.  Example code:
94 *
95 *     attrs, errormsg, errorcode = lfs.attributes("/boot")
96 *     if attrs == nil then
97 *         print(errormsg)
98 *         return errorcode
99 *     end
100 *
101 *     for k, v in pairs(attrs) do
102 *         print(k .. ":\t" .. v)
103 *     end
104 *     return 0
105 *
106 * Prints (on success):
107 *     gid:    0
108 *     change: 140737488342640
109 *     mode:   directory
110 *     rdev:   0
111 *     ino:    4199275
112 *     dev:    140737488342544
113 *     modification:   140737488342576
114 *     size:   512
115 *     access: 140737488342560
116 *     permissions:    755
117 *     nlink:  58283552
118 *     uid:    1001
119 */
120
121#define DIR_METATABLE "directory iterator metatable"
122
123static int
124lua_dir_iter_pushtype(lua_State *L __unused, const struct dirent *ent __unused)
125{
126
127	/*
128	 * This is a non-standard extension to luafilesystem for loader's
129	 * benefit.  The extra stat() calls to determine the entry type can
130	 * be quite expensive on some systems, so this speeds up enumeration of
131	 * /boot greatly by providing the type up front.
132	 *
133	 * This extension is compatible enough with luafilesystem, in that we're
134	 * just using an extra return value for the iterator.
135	 */
136#ifdef _STANDALONE
137	lua_pushinteger(L, ent->d_type);
138	return 1;
139#else
140	return 0;
141#endif
142}
143
144static int
145lua_dir_iter_next(lua_State *L)
146{
147	struct dirent *entry;
148	DIR *dp, **dpp;
149
150	dpp = (DIR **)luaL_checkudata(L, 1, DIR_METATABLE);
151	dp = *dpp;
152	luaL_argcheck(L, dp != NULL, 1, "closed directory");
153
154#ifdef _STANDALONE
155	entry = readdirfd(dp->fd);
156#else
157	entry = readdir(dp);
158#endif
159	if (entry == NULL) {
160		closedir(dp);
161		*dpp = NULL;
162		return 0;
163	}
164
165	lua_pushstring(L, entry->d_name);
166	return 1 + lua_dir_iter_pushtype(L, entry);
167}
168
169static int
170lua_dir_iter_close(lua_State *L)
171{
172	DIR *dp, **dpp;
173
174	dpp = (DIR **)lua_touserdata(L, 1);
175	dp = *dpp;
176	if (dp == NULL)
177		return 0;
178
179	closedir(dp);
180	*dpp = NULL;
181	return 0;
182}
183
184static int
185lua_dir(lua_State *L)
186{
187	const char *path;
188	DIR *dp;
189
190	if (lua_gettop(L) != 1) {
191		lua_pushnil(L);
192		return 1;
193	}
194
195	path = luaL_checkstring(L, 1);
196	dp = opendir(path);
197	if (dp == NULL) {
198		lua_pushnil(L);
199		return 1;
200	}
201
202	lua_pushcfunction(L, lua_dir_iter_next);
203	*(DIR **)lua_newuserdata(L, sizeof(DIR **)) = dp;
204	luaL_getmetatable(L, DIR_METATABLE);
205	lua_setmetatable(L, -2);
206	return 2;
207}
208
209static void
210register_metatable(lua_State *L)
211{
212	/*
213	 * Create so-called metatable for iterator object returned by
214	 * lfs.dir().
215	 */
216	luaL_newmetatable(L, DIR_METATABLE);
217
218	lua_newtable(L);
219	lua_pushcfunction(L, lua_dir_iter_next);
220	lua_setfield(L, -2, "next");
221	lua_pushcfunction(L, lua_dir_iter_close);
222	lua_setfield(L, -2, "close");
223
224	/* Magically associate anonymous method table with metatable. */
225	lua_setfield(L, -2, "__index");
226	/* Implement magic destructor method */
227	lua_pushcfunction(L, lua_dir_iter_close);
228	lua_setfield(L, -2, "__gc");
229
230	lua_pop(L, 1);
231}
232
233#define PUSH_INTEGER(lname, stname)				\
234static void							\
235push_st_ ## lname (lua_State *L, struct stat *sb)		\
236{								\
237	lua_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_INTEGER
250
251static void
252push_st_mode(lua_State *L, struct stat *sb)
253{
254	const char *mode_s;
255	mode_t mode;
256
257	mode = (sb->st_mode & S_IFMT);
258	if (S_ISREG(mode))
259		mode_s = "file";
260	else if (S_ISDIR(mode))
261		mode_s = "directory";
262	else if (S_ISLNK(mode))
263		mode_s = "link";
264	else if (S_ISSOCK(mode))
265		mode_s = "socket";
266	else if (S_ISFIFO(mode))
267		mode_s = "fifo";
268	else if (S_ISCHR(mode))
269		mode_s = "char device";
270	else if (S_ISBLK(mode))
271		mode_s = "block device";
272	else
273		mode_s = "other";
274
275	lua_pushstring(L, mode_s);
276}
277
278static void
279push_st_permissions(lua_State *L, struct stat *sb)
280{
281	char buf[20];
282
283	/*
284	 * XXX
285	 * Could actually format as "-rwxrwxrwx" -- do we care?
286	 */
287	snprintf(buf, sizeof(buf), "%o", sb->st_mode & ~S_IFMT);
288	lua_pushstring(L, buf);
289}
290
291#define PUSH_ENTRY(n)	{ #n, push_st_ ## n }
292struct stat_members {
293	const char *name;
294	void (*push)(lua_State *, struct stat *);
295} members[] = {
296	PUSH_ENTRY(mode),
297	PUSH_ENTRY(dev),
298	PUSH_ENTRY(ino),
299	PUSH_ENTRY(nlink),
300	PUSH_ENTRY(uid),
301	PUSH_ENTRY(gid),
302	PUSH_ENTRY(rdev),
303	PUSH_ENTRY(access),
304	PUSH_ENTRY(modification),
305	PUSH_ENTRY(change),
306	PUSH_ENTRY(size),
307	PUSH_ENTRY(permissions),
308};
309#undef PUSH_ENTRY
310
311static int
312lua_attributes(lua_State *L)
313{
314	struct stat sb;
315	const char *path, *member;
316	size_t i;
317	int rc;
318
319	path = luaL_checkstring(L, 1);
320	if (path == NULL) {
321		lua_pushnil(L);
322		lua_pushfstring(L, "cannot convert first argument to string");
323		lua_pushinteger(L, EINVAL);
324		return 3;
325	}
326
327	rc = stat(path, &sb);
328	if (rc != 0) {
329		lua_pushnil(L);
330		lua_pushfstring(L,
331		    "cannot obtain information from file '%s': %s", path,
332		    strerror(errno));
333		lua_pushinteger(L, errno);
334		return 3;
335	}
336
337	if (lua_isstring(L, 2)) {
338		member = lua_tostring(L, 2);
339		for (i = 0; i < nitems(members); i++) {
340			if (strcmp(members[i].name, member) != 0)
341				continue;
342
343			members[i].push(L, &sb);
344			return 1;
345		}
346		return luaL_error(L, "invalid attribute name '%s'", member);
347	}
348
349	/* Create or reuse existing table */
350	lua_settop(L, 2);
351	if (!lua_istable(L, 2))
352		lua_newtable(L);
353
354	/* Export all stat data to caller */
355	for (i = 0; i < nitems(members); i++) {
356		lua_pushstring(L, members[i].name);
357		members[i].push(L, &sb);
358		lua_rawset(L, -3);
359	}
360	return 1;
361}
362
363#ifndef _STANDALONE
364#define	lfs_mkdir_impl(path)	(mkdir((path), \
365    S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | \
366    S_IROTH | S_IXOTH))
367
368static int
369lua_mkdir(lua_State *L)
370{
371	const char *path;
372	int error, serrno;
373
374	path = luaL_checkstring(L, 1);
375	if (path == NULL) {
376		lua_pushnil(L);
377		lua_pushfstring(L, "cannot convert first argument to string");
378		lua_pushinteger(L, EINVAL);
379		return 3;
380	}
381
382	error = lfs_mkdir_impl(path);
383	if (error == -1) {
384		/* Save it; unclear what other libc functions may be invoked */
385		serrno = errno;
386		lua_pushnil(L);
387		lua_pushfstring(L, strerror(serrno));
388		lua_pushinteger(L, serrno);
389		return 3;
390	}
391
392	lua_pushboolean(L, 1);
393	return 1;
394}
395
396static int
397lua_rmdir(lua_State *L)
398{
399	const char *path;
400	int error, serrno;
401
402	path = luaL_checkstring(L, 1);
403	if (path == NULL) {
404		lua_pushnil(L);
405		lua_pushfstring(L, "cannot convert first argument to string");
406		lua_pushinteger(L, EINVAL);
407		return 3;
408	}
409
410	error = rmdir(path);
411	if (error == -1) {
412		/* Save it; unclear what other libc functions may be invoked */
413		serrno = errno;
414		lua_pushnil(L);
415		lua_pushfstring(L, strerror(serrno));
416		lua_pushinteger(L, serrno);
417		return 3;
418	}
419
420	lua_pushboolean(L, 1);
421	return 1;
422}
423#endif
424
425#define REG_SIMPLE(n)	{ #n, lua_ ## n }
426static const struct luaL_Reg fslib[] = {
427	REG_SIMPLE(attributes),
428	REG_SIMPLE(dir),
429#ifndef _STANDALONE
430	REG_SIMPLE(mkdir),
431	REG_SIMPLE(rmdir),
432#endif
433	{ NULL, NULL },
434};
435#undef REG_SIMPLE
436
437int
438luaopen_lfs(lua_State *L)
439{
440	register_metatable(L);
441	luaL_newlib(L, fslib);
442#ifdef _STANDALONE
443	/* Non-standard extension for loader, used with lfs.dir(). */
444	lua_pushinteger(L, DT_DIR);
445	lua_setfield(L, -2, "DT_DIR");
446#endif
447	return 1;
448}
449