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
123 static int
lua_dir_iter_pushtype(lua_State * L __unused,const struct dirent * ent __unused)124 lua_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
144 static int
lua_dir_iter_next(lua_State * L)145 lua_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
169 static int
lua_dir_iter_close(lua_State * L)170 lua_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
184 static int
lua_dir(lua_State * L)185 lua_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
209 static void
register_metatable(lua_State * L)210 register_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) \
234 static void \
235 push_st_ ## lname (lua_State *L, struct stat *sb) \
236 { \
237 lua_pushinteger(L, (lua_Integer)sb->st_ ## stname); \
238 }
PUSH_INTEGER(dev,dev)239 PUSH_INTEGER(dev, dev)
240 PUSH_INTEGER(ino, ino)
241 PUSH_INTEGER(nlink, nlink)
242 PUSH_INTEGER(uid, uid)
243 PUSH_INTEGER(gid, gid)
244 PUSH_INTEGER(rdev, rdev)
245 PUSH_INTEGER(access, atime)
246 PUSH_INTEGER(modification, mtime)
247 PUSH_INTEGER(change, ctime)
248 PUSH_INTEGER(size, size)
249 #undef PUSH_INTEGER
250
251 static void
252 push_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
278 static void
push_st_permissions(lua_State * L,struct stat * sb)279 push_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 }
292 struct 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
311 static int
lua_attributes(lua_State * L)312 lua_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
368 static int
lua_mkdir(lua_State * L)369 lua_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
396 static int
lua_rmdir(lua_State * L)397 lua_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 }
426 static 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
437 int
luaopen_lfs(lua_State * L)438 luaopen_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