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