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