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