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 #endif
70
71 #include "bootstrap.h"
72
73 #ifndef nitems
74 #define nitems(x) (sizeof((x)) / sizeof((x)[0]))
75 #endif
76
77 /*
78 * The goal is to emulate a subset of the upstream Lua FileSystem library, as
79 * faithfully as possible in the boot environment. Only APIs that seem useful
80 * need to emulated.
81 *
82 * Example usage:
83 *
84 * for file in lfs.dir("/boot") do
85 * print("\t"..file)
86 * end
87 *
88 * Prints:
89 * .
90 * ..
91 * (etc.)
92 *
93 * The other available API is lfs.attributes(), which functions somewhat like
94 * stat(2) and returns a table of values. Example code:
95 *
96 * attrs, errormsg, errorcode = lfs.attributes("/boot")
97 * if attrs == nil then
98 * print(errormsg)
99 * return errorcode
100 * end
101 *
102 * for k, v in pairs(attrs) do
103 * print(k .. ":\t" .. v)
104 * end
105 * return 0
106 *
107 * Prints (on success):
108 * gid: 0
109 * change: 140737488342640
110 * mode: directory
111 * rdev: 0
112 * ino: 4199275
113 * dev: 140737488342544
114 * modification: 140737488342576
115 * size: 512
116 * access: 140737488342560
117 * permissions: 755
118 * nlink: 58283552
119 * uid: 1001
120 */
121
122 #define DIR_METATABLE "directory iterator metatable"
123
124 static int
lua_dir_iter_pushtype(lua_State * L __unused,const struct dirent * ent __unused)125 lua_dir_iter_pushtype(lua_State *L __unused, const struct dirent *ent __unused)
126 {
127
128 /*
129 * This is a non-standard extension to luafilesystem for loader's
130 * benefit. The extra stat() calls to determine the entry type can
131 * be quite expensive on some systems, so this speeds up enumeration of
132 * /boot greatly by providing the type up front.
133 *
134 * This extension is compatible enough with luafilesystem, in that we're
135 * just using an extra return value for the iterator.
136 */
137 #ifdef _STANDALONE
138 lua_pushinteger(L, ent->d_type);
139 return 1;
140 #else
141 return 0;
142 #endif
143 }
144
145 static int
lua_dir_iter_next(lua_State * L)146 lua_dir_iter_next(lua_State *L)
147 {
148 struct dirent *entry;
149 DIR *dp, **dpp;
150
151 dpp = (DIR **)luaL_checkudata(L, 1, DIR_METATABLE);
152 dp = *dpp;
153 luaL_argcheck(L, dp != NULL, 1, "closed directory");
154
155 #ifdef _STANDALONE
156 entry = readdirfd(dp->fd);
157 #else
158 entry = readdir(dp);
159 #endif
160 if (entry == NULL) {
161 closedir(dp);
162 *dpp = NULL;
163 return 0;
164 }
165
166 lua_pushstring(L, entry->d_name);
167 return 1 + lua_dir_iter_pushtype(L, entry);
168 }
169
170 static int
lua_dir_iter_close(lua_State * L)171 lua_dir_iter_close(lua_State *L)
172 {
173 DIR *dp, **dpp;
174
175 dpp = (DIR **)lua_touserdata(L, 1);
176 dp = *dpp;
177 if (dp == NULL)
178 return 0;
179
180 closedir(dp);
181 *dpp = NULL;
182 return 0;
183 }
184
185 static int
lua_dir(lua_State * L)186 lua_dir(lua_State *L)
187 {
188 const char *path;
189 DIR *dp;
190
191 if (lua_gettop(L) != 1) {
192 lua_pushnil(L);
193 return 1;
194 }
195
196 path = luaL_checkstring(L, 1);
197 dp = opendir(path);
198 if (dp == NULL) {
199 lua_pushnil(L);
200 return 1;
201 }
202
203 lua_pushcfunction(L, lua_dir_iter_next);
204 *(DIR **)lua_newuserdata(L, sizeof(DIR **)) = dp;
205 luaL_getmetatable(L, DIR_METATABLE);
206 lua_setmetatable(L, -2);
207 return 2;
208 }
209
210 static void
register_metatable(lua_State * L)211 register_metatable(lua_State *L)
212 {
213 /*
214 * Create so-called metatable for iterator object returned by
215 * lfs.dir().
216 */
217 luaL_newmetatable(L, DIR_METATABLE);
218
219 lua_newtable(L);
220 lua_pushcfunction(L, lua_dir_iter_next);
221 lua_setfield(L, -2, "next");
222 lua_pushcfunction(L, lua_dir_iter_close);
223 lua_setfield(L, -2, "close");
224
225 /* Magically associate anonymous method table with metatable. */
226 lua_setfield(L, -2, "__index");
227 /* Implement magic destructor method */
228 lua_pushcfunction(L, lua_dir_iter_close);
229 lua_setfield(L, -2, "__gc");
230
231 lua_pop(L, 1);
232 }
233
234 #define PUSH_INTEGER(lname, stname) \
235 static void \
236 push_st_ ## lname (lua_State *L, struct stat *sb) \
237 { \
238 lua_pushinteger(L, (lua_Integer)sb->st_ ## stname); \
239 }
PUSH_INTEGER(dev,dev)240 PUSH_INTEGER(dev, dev)
241 PUSH_INTEGER(ino, ino)
242 PUSH_INTEGER(nlink, nlink)
243 PUSH_INTEGER(uid, uid)
244 PUSH_INTEGER(gid, gid)
245 PUSH_INTEGER(rdev, rdev)
246 PUSH_INTEGER(access, atime)
247 PUSH_INTEGER(modification, mtime)
248 PUSH_INTEGER(change, ctime)
249 PUSH_INTEGER(size, size)
250 #undef PUSH_INTEGER
251
252 static void
253 push_st_mode(lua_State *L, struct stat *sb)
254 {
255 const char *mode_s;
256 mode_t mode;
257
258 mode = (sb->st_mode & S_IFMT);
259 if (S_ISREG(mode))
260 mode_s = "file";
261 else if (S_ISDIR(mode))
262 mode_s = "directory";
263 else if (S_ISLNK(mode))
264 mode_s = "link";
265 else if (S_ISSOCK(mode))
266 mode_s = "socket";
267 else if (S_ISFIFO(mode))
268 mode_s = "fifo";
269 else if (S_ISCHR(mode))
270 mode_s = "char device";
271 else if (S_ISBLK(mode))
272 mode_s = "block device";
273 else
274 mode_s = "other";
275
276 lua_pushstring(L, mode_s);
277 }
278
279 static void
push_st_permissions(lua_State * L,struct stat * sb)280 push_st_permissions(lua_State *L, struct stat *sb)
281 {
282 char buf[20];
283
284 /*
285 * XXX
286 * Could actually format as "-rwxrwxrwx" -- do we care?
287 */
288 snprintf(buf, sizeof(buf), "%o", sb->st_mode & ~S_IFMT);
289 lua_pushstring(L, buf);
290 }
291
292 #define PUSH_ENTRY(n) { #n, push_st_ ## n }
293 struct stat_members {
294 const char *name;
295 void (*push)(lua_State *, struct stat *);
296 } members[] = {
297 PUSH_ENTRY(mode),
298 PUSH_ENTRY(dev),
299 PUSH_ENTRY(ino),
300 PUSH_ENTRY(nlink),
301 PUSH_ENTRY(uid),
302 PUSH_ENTRY(gid),
303 PUSH_ENTRY(rdev),
304 PUSH_ENTRY(access),
305 PUSH_ENTRY(modification),
306 PUSH_ENTRY(change),
307 PUSH_ENTRY(size),
308 PUSH_ENTRY(permissions),
309 };
310 #undef PUSH_ENTRY
311
312 static int
lua_attributes(lua_State * L)313 lua_attributes(lua_State *L)
314 {
315 struct stat sb;
316 const char *path, *member;
317 size_t i;
318 int rc;
319
320 path = luaL_checkstring(L, 1);
321 if (path == NULL) {
322 lua_pushnil(L);
323 lua_pushfstring(L, "cannot convert first argument to string");
324 lua_pushinteger(L, EINVAL);
325 return 3;
326 }
327
328 rc = stat(path, &sb);
329 if (rc != 0) {
330 lua_pushnil(L);
331 lua_pushfstring(L,
332 "cannot obtain information from file '%s': %s", path,
333 strerror(errno));
334 lua_pushinteger(L, errno);
335 return 3;
336 }
337
338 if (lua_isstring(L, 2)) {
339 member = lua_tostring(L, 2);
340 for (i = 0; i < nitems(members); i++) {
341 if (strcmp(members[i].name, member) != 0)
342 continue;
343
344 members[i].push(L, &sb);
345 return 1;
346 }
347 return luaL_error(L, "invalid attribute name '%s'", member);
348 }
349
350 /* Create or reuse existing table */
351 lua_settop(L, 2);
352 if (!lua_istable(L, 2))
353 lua_newtable(L);
354
355 /* Export all stat data to caller */
356 for (i = 0; i < nitems(members); i++) {
357 lua_pushstring(L, members[i].name);
358 members[i].push(L, &sb);
359 lua_rawset(L, -3);
360 }
361 return 1;
362 }
363
364 #ifndef _STANDALONE
365 #define lfs_mkdir_impl(path) (mkdir((path), \
366 S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | \
367 S_IROTH | S_IXOTH))
368
369 static int
lua_mkdir(lua_State * L)370 lua_mkdir(lua_State *L)
371 {
372 const char *path;
373 int error, serrno;
374
375 path = luaL_checkstring(L, 1);
376 if (path == NULL) {
377 lua_pushnil(L);
378 lua_pushfstring(L, "cannot convert first argument to string");
379 lua_pushinteger(L, EINVAL);
380 return 3;
381 }
382
383 error = lfs_mkdir_impl(path);
384 if (error == -1) {
385 /* Save it; unclear what other libc functions may be invoked */
386 serrno = errno;
387 lua_pushnil(L);
388 lua_pushfstring(L, strerror(serrno));
389 lua_pushinteger(L, serrno);
390 return 3;
391 }
392
393 lua_pushboolean(L, 1);
394 return 1;
395 }
396
397 static int
lua_rmdir(lua_State * L)398 lua_rmdir(lua_State *L)
399 {
400 const char *path;
401 int error, serrno;
402
403 path = luaL_checkstring(L, 1);
404 if (path == NULL) {
405 lua_pushnil(L);
406 lua_pushfstring(L, "cannot convert first argument to string");
407 lua_pushinteger(L, EINVAL);
408 return 3;
409 }
410
411 error = rmdir(path);
412 if (error == -1) {
413 /* Save it; unclear what other libc functions may be invoked */
414 serrno = errno;
415 lua_pushnil(L);
416 lua_pushfstring(L, strerror(serrno));
417 lua_pushinteger(L, serrno);
418 return 3;
419 }
420
421 lua_pushboolean(L, 1);
422 return 1;
423 }
424 #endif
425
426 #define REG_SIMPLE(n) { #n, lua_ ## n }
427 static const struct luaL_Reg fslib[] = {
428 REG_SIMPLE(attributes),
429 REG_SIMPLE(dir),
430 #ifndef _STANDALONE
431 REG_SIMPLE(mkdir),
432 REG_SIMPLE(rmdir),
433 #endif
434 { NULL, NULL },
435 };
436 #undef REG_SIMPLE
437
438 int
luaopen_lfs(lua_State * L)439 luaopen_lfs(lua_State *L)
440 {
441 register_metatable(L);
442 luaL_newlib(L, fslib);
443 #ifdef _STANDALONE
444 /* Non-standard extension for loader, used with lfs.dir(). */
445 lua_pushinteger(L, DT_DIR);
446 lua_setfield(L, -2, "DT_DIR");
447 #endif
448 return 1;
449 }
450
451 #ifndef _STANDALONE
452 FLUA_MODULE(lfs);
453 #endif
454