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