xref: /freebsd-12.1/stand/liblua/lfs.c (revision 74709ed0)
1 /*-
2  * Copyright (c) 2018 Conrad Meyer <[email protected]>
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 __FBSDID("$FreeBSD$");
54 
55 #include <lua.h>
56 #include "lauxlib.h"
57 #include "lfs.h"
58 #include "lstd.h"
59 #include "lutils.h"
60 #include "bootstrap.h"
61 
62 #ifndef nitems
63 #define	nitems(x)	(sizeof((x)) / sizeof((x)[0]))
64 #endif
65 
66 /*
67  * The goal is to emulate a subset of the upstream Lua FileSystem library, as
68  * faithfully as possible in the boot environment.  Only APIs that seem useful
69  * need to emulated.
70  *
71  * Example usage:
72  *
73  *     for file in lfs.dir("/boot") do
74  *         print("\t"..file)
75  *     end
76  *
77  * Prints:
78  *     .
79  *     ..
80  * (etc.)
81  *
82  * The other available API is lfs.attributes(), which functions somewhat like
83  * stat(2) and returns a table of values:
84  *
85  *     for k, v in pairs(lfs.attributes("/boot")) do
86  *         print(k .. ":\t" .. v)
87  *     end
88  *
89  * Prints:
90  *     gid:    0
91  *     change: 140737488342640
92  *     mode:   directory
93  *     rdev:   0
94  *     ino:    4199275
95  *     dev:    140737488342544
96  *     modification:   140737488342576
97  *     size:   512
98  *     access: 140737488342560
99  *     permissions:    755
100  *     nlink:  58283552
101  *     uid:    1001
102  */
103 
104 #define DIR_METATABLE "directory iterator metatable"
105 
106 static int
107 lua_dir_iter_next(lua_State *L)
108 {
109 	struct dirent *entry;
110 	DIR *dp, **dpp;
111 
112 	dpp = (DIR **)luaL_checkudata(L, 1, DIR_METATABLE);
113 	dp = *dpp;
114 	luaL_argcheck(L, dp != NULL, 1, "closed directory");
115 
116 	entry = readdirfd(dp->fd);
117 	if (entry == NULL) {
118 		closedir(dp);
119 		*dpp = NULL;
120 		return 0;
121 	}
122 
123 	lua_pushstring(L, entry->d_name);
124 	return 1;
125 }
126 
127 static int
128 lua_dir_iter_close(lua_State *L)
129 {
130 	DIR *dp, **dpp;
131 
132 	dpp = (DIR **)lua_touserdata(L, 1);
133 	dp = *dpp;
134 	if (dp == NULL)
135 		return 0;
136 
137 	closedir(dp);
138 	*dpp = NULL;
139 	return 0;
140 }
141 
142 static int
143 lua_dir(lua_State *L)
144 {
145 	const char *path;
146 	DIR *dp;
147 
148 	if (lua_gettop(L) != 1) {
149 		lua_pushnil(L);
150 		return 1;
151 	}
152 
153 	path = luaL_checkstring(L, 1);
154 	dp = opendir(path);
155 	if (dp == NULL) {
156 		lua_pushnil(L);
157 		return 1;
158 	}
159 
160 	lua_pushcfunction(L, lua_dir_iter_next);
161 	*(DIR **)lua_newuserdata(L, sizeof(DIR **)) = dp;
162 	luaL_getmetatable(L, DIR_METATABLE);
163 	lua_setmetatable(L, -2);
164 	return 2;
165 }
166 
167 static void
168 register_metatable(lua_State *L)
169 {
170 	/*
171 	 * Create so-called metatable for iterator object returned by
172 	 * lfs.dir().
173 	 */
174 	luaL_newmetatable(L, DIR_METATABLE);
175 
176 	lua_newtable(L);
177 	lua_pushcfunction(L, lua_dir_iter_next);
178 	lua_setfield(L, -2, "next");
179 	lua_pushcfunction(L, lua_dir_iter_close);
180 	lua_setfield(L, -2, "close");
181 
182 	/* Magically associate anonymous method table with metatable. */
183 	lua_setfield(L, -2, "__index");
184 	/* Implement magic destructor method */
185 	lua_pushcfunction(L, lua_dir_iter_close);
186 	lua_setfield(L, -2, "__gc");
187 
188 	lua_pop(L, 1);
189 }
190 
191 #define PUSH_INTEGER(lname, stname)				\
192 static void							\
193 push_st_ ## lname (lua_State *L, struct stat *sb)		\
194 {								\
195 	lua_pushinteger(L, (lua_Integer)sb->st_ ## stname);	\
196 }
197 PUSH_INTEGER(dev, dev)
198 PUSH_INTEGER(ino, ino)
199 PUSH_INTEGER(nlink, nlink)
200 PUSH_INTEGER(uid, uid)
201 PUSH_INTEGER(gid, gid)
202 PUSH_INTEGER(rdev, rdev)
203 PUSH_INTEGER(access, atime)
204 PUSH_INTEGER(modification, mtime)
205 PUSH_INTEGER(change, ctime)
206 PUSH_INTEGER(size, size)
207 #undef PUSH_INTEGER
208 
209 static void
210 push_st_mode(lua_State *L, struct stat *sb)
211 {
212 	const char *mode_s;
213 	mode_t mode;
214 
215 	mode = (sb->st_mode & S_IFMT);
216 	if (S_ISREG(mode))
217 		mode_s = "file";
218 	else if (S_ISDIR(mode))
219 		mode_s = "directory";
220 	else if (S_ISLNK(mode))
221 		mode_s = "link";
222 	else if (S_ISSOCK(mode))
223 		mode_s = "socket";
224 	else if (S_ISFIFO(mode))
225 		mode_s = "fifo";
226 	else if (S_ISCHR(mode))
227 		mode_s = "char device";
228 	else if (S_ISBLK(mode))
229 		mode_s = "block device";
230 	else
231 		mode_s = "other";
232 
233 	lua_pushstring(L, mode_s);
234 }
235 
236 static void
237 push_st_permissions(lua_State *L, struct stat *sb)
238 {
239 	char buf[20];
240 
241 	/*
242 	 * XXX
243 	 * Could actually format as "-rwxrwxrwx" -- do we care?
244 	 */
245 	snprintf(buf, sizeof(buf), "%o", sb->st_mode & ~S_IFMT);
246 	lua_pushstring(L, buf);
247 }
248 
249 #define PUSH_ENTRY(n)	{ #n, push_st_ ## n }
250 struct stat_members {
251 	const char *name;
252 	void (*push)(lua_State *, struct stat *);
253 } members[] = {
254 	PUSH_ENTRY(mode),
255 	PUSH_ENTRY(dev),
256 	PUSH_ENTRY(ino),
257 	PUSH_ENTRY(nlink),
258 	PUSH_ENTRY(uid),
259 	PUSH_ENTRY(gid),
260 	PUSH_ENTRY(rdev),
261 	PUSH_ENTRY(access),
262 	PUSH_ENTRY(modification),
263 	PUSH_ENTRY(change),
264 	PUSH_ENTRY(size),
265 	PUSH_ENTRY(permissions),
266 };
267 #undef PUSH_ENTRY
268 
269 static int
270 lua_attributes(lua_State *L)
271 {
272 	struct stat sb;
273 	const char *path, *member;
274 	size_t i;
275 	int rc;
276 
277 	path = luaL_checkstring(L, 1);
278 	if (path == NULL) {
279 		lua_pushnil(L);
280 		return 1;
281 	}
282 
283 	rc = stat(path, &sb);
284 	if (rc != 0) {
285 		lua_pushnil(L);
286 		lua_pushfstring(L,
287 		    "cannot obtain information from file '%s': %s", path,
288 		    strerror(errno));
289 		lua_pushinteger(L, errno);
290 		return 3;
291 	}
292 
293 	if (lua_isstring(L, 2)) {
294 		member = lua_tostring(L, 2);
295 		for (i = 0; i < nitems(members); i++) {
296 			if (strcmp(members[i].name, member) != 0)
297 				continue;
298 
299 			members[i].push(L, &sb);
300 			return 1;
301 		}
302 		return luaL_error(L, "invalid attribute name '%s'", member);
303 	}
304 
305 	/* Create or reuse existing table */
306 	lua_settop(L, 2);
307 	if (!lua_istable(L, 2))
308 		lua_newtable(L);
309 
310 	/* Export all stat data to caller */
311 	for (i = 0; i < nitems(members); i++) {
312 		lua_pushstring(L, members[i].name);
313 		members[i].push(L, &sb);
314 		lua_rawset(L, -3);
315 	}
316 	return 1;
317 }
318 
319 #define REG_SIMPLE(n)	{ #n, lua_ ## n }
320 static const struct luaL_Reg fslib[] = {
321 	REG_SIMPLE(attributes),
322 	REG_SIMPLE(dir),
323 	{ NULL, NULL },
324 };
325 #undef REG_SIMPLE
326 
327 int
328 luaopen_lfs(lua_State *L)
329 {
330 	register_metatable(L);
331 	luaL_newlib(L, fslib);
332 	return 1;
333 }
334