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 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 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 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 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 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 } 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 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 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 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 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 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