1 /*- 2 * Copyright (c) 2018 Conrad Meyer <[email protected]> 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 #include <lua.h> 56 #include "lauxlib.h" 57 #include "lfs.h" 58 #include "lstd.h" 59 #include "lutils.h" 60 #include "bootstrap.h" 61 62 #ifndef nitems 63 #define nitems(x) (sizeof((x)) / sizeof((x)[0])) 64 #endif 65 66 /* 67 * The goal is to emulate a subset of the upstream Lua FileSystem library, as 68 * faithfully as possible in the boot environment. Only APIs that seem useful 69 * need to emulated. 70 * 71 * Example usage: 72 * 73 * for file in lfs.dir("/boot") do 74 * print("\t"..file) 75 * end 76 * 77 * Prints: 78 * . 79 * .. 80 * (etc.) 81 * 82 * The other available API is lfs.attributes(), which functions somewhat like 83 * stat(2) and returns a table of values: 84 * 85 * for k, v in pairs(lfs.attributes("/boot")) do 86 * print(k .. ":\t" .. v) 87 * end 88 * 89 * Prints: 90 * gid: 0 91 * change: 140737488342640 92 * mode: directory 93 * rdev: 0 94 * ino: 4199275 95 * dev: 140737488342544 96 * modification: 140737488342576 97 * size: 512 98 * access: 140737488342560 99 * permissions: 755 100 * nlink: 58283552 101 * uid: 1001 102 */ 103 104 #define DIR_METATABLE "directory iterator metatable" 105 106 static int 107 lua_dir_iter_next(lua_State *L) 108 { 109 struct dirent *entry; 110 DIR *dp, **dpp; 111 112 dpp = (DIR **)luaL_checkudata(L, 1, DIR_METATABLE); 113 dp = *dpp; 114 luaL_argcheck(L, dp != NULL, 1, "closed directory"); 115 116 entry = readdirfd(dp->fd); 117 if (entry == NULL) { 118 closedir(dp); 119 *dpp = NULL; 120 return 0; 121 } 122 123 lua_pushstring(L, entry->d_name); 124 return 1; 125 } 126 127 static int 128 lua_dir_iter_close(lua_State *L) 129 { 130 DIR *dp, **dpp; 131 132 dpp = (DIR **)lua_touserdata(L, 1); 133 dp = *dpp; 134 if (dp == NULL) 135 return 0; 136 137 closedir(dp); 138 *dpp = NULL; 139 return 0; 140 } 141 142 static int 143 lua_dir(lua_State *L) 144 { 145 const char *path; 146 DIR *dp; 147 148 if (lua_gettop(L) != 1) { 149 lua_pushnil(L); 150 return 1; 151 } 152 153 path = luaL_checkstring(L, 1); 154 dp = opendir(path); 155 if (dp == NULL) { 156 lua_pushnil(L); 157 return 1; 158 } 159 160 lua_pushcfunction(L, lua_dir_iter_next); 161 *(DIR **)lua_newuserdata(L, sizeof(DIR **)) = dp; 162 luaL_getmetatable(L, DIR_METATABLE); 163 lua_setmetatable(L, -2); 164 return 2; 165 } 166 167 static void 168 register_metatable(lua_State *L) 169 { 170 /* 171 * Create so-called metatable for iterator object returned by 172 * lfs.dir(). 173 */ 174 luaL_newmetatable(L, DIR_METATABLE); 175 176 lua_newtable(L); 177 lua_pushcfunction(L, lua_dir_iter_next); 178 lua_setfield(L, -2, "next"); 179 lua_pushcfunction(L, lua_dir_iter_close); 180 lua_setfield(L, -2, "close"); 181 182 /* Magically associate anonymous method table with metatable. */ 183 lua_setfield(L, -2, "__index"); 184 /* Implement magic destructor method */ 185 lua_pushcfunction(L, lua_dir_iter_close); 186 lua_setfield(L, -2, "__gc"); 187 188 lua_pop(L, 1); 189 } 190 191 #define PUSH_INTEGER(lname, stname) \ 192 static void \ 193 push_st_ ## lname (lua_State *L, struct stat *sb) \ 194 { \ 195 lua_pushinteger(L, (lua_Integer)sb->st_ ## stname); \ 196 } 197 PUSH_INTEGER(dev, dev) 198 PUSH_INTEGER(ino, ino) 199 PUSH_INTEGER(nlink, nlink) 200 PUSH_INTEGER(uid, uid) 201 PUSH_INTEGER(gid, gid) 202 PUSH_INTEGER(rdev, rdev) 203 PUSH_INTEGER(access, atime) 204 PUSH_INTEGER(modification, mtime) 205 PUSH_INTEGER(change, ctime) 206 PUSH_INTEGER(size, size) 207 #undef PUSH_INTEGER 208 209 static void 210 push_st_mode(lua_State *L, struct stat *sb) 211 { 212 const char *mode_s; 213 mode_t mode; 214 215 mode = (sb->st_mode & S_IFMT); 216 if (S_ISREG(mode)) 217 mode_s = "file"; 218 else if (S_ISDIR(mode)) 219 mode_s = "directory"; 220 else if (S_ISLNK(mode)) 221 mode_s = "link"; 222 else if (S_ISSOCK(mode)) 223 mode_s = "socket"; 224 else if (S_ISFIFO(mode)) 225 mode_s = "fifo"; 226 else if (S_ISCHR(mode)) 227 mode_s = "char device"; 228 else if (S_ISBLK(mode)) 229 mode_s = "block device"; 230 else 231 mode_s = "other"; 232 233 lua_pushstring(L, mode_s); 234 } 235 236 static void 237 push_st_permissions(lua_State *L, struct stat *sb) 238 { 239 char buf[20]; 240 241 /* 242 * XXX 243 * Could actually format as "-rwxrwxrwx" -- do we care? 244 */ 245 snprintf(buf, sizeof(buf), "%o", sb->st_mode & ~S_IFMT); 246 lua_pushstring(L, buf); 247 } 248 249 #define PUSH_ENTRY(n) { #n, push_st_ ## n } 250 struct stat_members { 251 const char *name; 252 void (*push)(lua_State *, struct stat *); 253 } members[] = { 254 PUSH_ENTRY(mode), 255 PUSH_ENTRY(dev), 256 PUSH_ENTRY(ino), 257 PUSH_ENTRY(nlink), 258 PUSH_ENTRY(uid), 259 PUSH_ENTRY(gid), 260 PUSH_ENTRY(rdev), 261 PUSH_ENTRY(access), 262 PUSH_ENTRY(modification), 263 PUSH_ENTRY(change), 264 PUSH_ENTRY(size), 265 PUSH_ENTRY(permissions), 266 }; 267 #undef PUSH_ENTRY 268 269 static int 270 lua_attributes(lua_State *L) 271 { 272 struct stat sb; 273 const char *path, *member; 274 size_t i; 275 int rc; 276 277 path = luaL_checkstring(L, 1); 278 if (path == NULL) { 279 lua_pushnil(L); 280 return 1; 281 } 282 283 rc = stat(path, &sb); 284 if (rc != 0) { 285 lua_pushnil(L); 286 lua_pushfstring(L, 287 "cannot obtain information from file '%s': %s", path, 288 strerror(errno)); 289 lua_pushinteger(L, errno); 290 return 3; 291 } 292 293 if (lua_isstring(L, 2)) { 294 member = lua_tostring(L, 2); 295 for (i = 0; i < nitems(members); i++) { 296 if (strcmp(members[i].name, member) != 0) 297 continue; 298 299 members[i].push(L, &sb); 300 return 1; 301 } 302 return luaL_error(L, "invalid attribute name '%s'", member); 303 } 304 305 /* Create or reuse existing table */ 306 lua_settop(L, 2); 307 if (!lua_istable(L, 2)) 308 lua_newtable(L); 309 310 /* Export all stat data to caller */ 311 for (i = 0; i < nitems(members); i++) { 312 lua_pushstring(L, members[i].name); 313 members[i].push(L, &sb); 314 lua_rawset(L, -3); 315 } 316 return 1; 317 } 318 319 #define REG_SIMPLE(n) { #n, lua_ ## n } 320 static const struct luaL_Reg fslib[] = { 321 REG_SIMPLE(attributes), 322 REG_SIMPLE(dir), 323 { NULL, NULL }, 324 }; 325 #undef REG_SIMPLE 326 327 int 328 luaopen_lfs(lua_State *L) 329 { 330 register_metatable(L); 331 luaL_newlib(L, fslib); 332 return 1; 333 } 334