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