file.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. // Module for interfacing with file system
  2. #include "module.h"
  3. #include "lauxlib.h"
  4. #include "lmem.h"
  5. #include "platform.h"
  6. #include "c_types.h"
  7. #include "vfs.h"
  8. #include "c_string.h"
  9. #include <alloca.h>
  10. #define FILE_READ_CHUNK 1024
  11. // use this time/date in absence of a timestamp
  12. #define FILE_TIMEDEF_YEAR 1970
  13. #define FILE_TIMEDEF_MON 01
  14. #define FILE_TIMEDEF_DAY 01
  15. #define FILE_TIMEDEF_HOUR 00
  16. #define FILE_TIMEDEF_MIN 00
  17. #define FILE_TIMEDEF_SEC 00
  18. static int file_fd = 0;
  19. static int file_fd_ref = LUA_NOREF;
  20. static int rtc_cb_ref = LUA_NOREF;
  21. typedef struct _file_fd_ud {
  22. int fd;
  23. } file_fd_ud;
  24. static void table2tm( lua_State *L, vfs_time *tm )
  25. {
  26. int idx = lua_gettop( L );
  27. // extract items from table
  28. lua_getfield( L, idx, "year" );
  29. lua_getfield( L, idx, "mon" );
  30. lua_getfield( L, idx, "day" );
  31. lua_getfield( L, idx, "hour" );
  32. lua_getfield( L, idx, "min" );
  33. lua_getfield( L, idx, "sec" );
  34. tm->year = luaL_optint( L, ++idx, FILE_TIMEDEF_YEAR );
  35. tm->mon = luaL_optint( L, ++idx, FILE_TIMEDEF_MON );
  36. tm->day = luaL_optint( L, ++idx, FILE_TIMEDEF_DAY );
  37. tm->hour = luaL_optint( L, ++idx, FILE_TIMEDEF_HOUR );
  38. tm->min = luaL_optint( L, ++idx, FILE_TIMEDEF_MIN );
  39. tm->sec = luaL_optint( L, ++idx, FILE_TIMEDEF_SEC );
  40. // remove items from stack
  41. lua_pop( L, 6 );
  42. }
  43. static sint32_t file_rtc_cb( vfs_time *tm )
  44. {
  45. sint32_t res = VFS_RES_ERR;
  46. if (rtc_cb_ref != LUA_NOREF) {
  47. lua_State *L = lua_getstate();
  48. lua_rawgeti( L, LUA_REGISTRYINDEX, rtc_cb_ref );
  49. lua_call( L, 0, 1 );
  50. if (lua_type( L, lua_gettop( L ) ) == LUA_TTABLE) {
  51. table2tm( L, tm );
  52. res = VFS_RES_OK;
  53. }
  54. // pop item returned by callback
  55. lua_pop( L, 1 );
  56. }
  57. return res;
  58. }
  59. // Lua: on()
  60. static int file_on(lua_State *L)
  61. {
  62. enum events{
  63. ON_RTC = 0
  64. };
  65. const char *const eventnames[] = {"rtc", NULL};
  66. int event = luaL_checkoption(L, 1, "rtc", eventnames);
  67. switch (event) {
  68. case ON_RTC:
  69. luaL_unref(L, LUA_REGISTRYINDEX, rtc_cb_ref);
  70. if ((lua_type(L, 2) == LUA_TFUNCTION) ||
  71. (lua_type(L, 2) == LUA_TLIGHTFUNCTION)) {
  72. lua_pushvalue(L, 2); // copy argument (func) to the top of stack
  73. rtc_cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
  74. vfs_register_rtc_cb(file_rtc_cb);
  75. } else {
  76. rtc_cb_ref = LUA_NOREF;
  77. vfs_register_rtc_cb(NULL);
  78. }
  79. break;
  80. default:
  81. break;
  82. }
  83. return 0;
  84. }
  85. // Lua: close()
  86. static int file_close( lua_State* L )
  87. {
  88. int need_pop = FALSE;
  89. file_fd_ud *ud;
  90. if (lua_type( L, 1 ) != LUA_TUSERDATA) {
  91. // fall back to last opened file
  92. if (file_fd_ref != LUA_NOREF) {
  93. lua_rawgeti( L, LUA_REGISTRYINDEX, file_fd_ref );
  94. // top of stack is now default file descriptor
  95. ud = (file_fd_ud *)luaL_checkudata(L, -1, "file.obj");
  96. lua_pop( L, 1 );
  97. } else {
  98. // no default file currently opened
  99. return 0;
  100. }
  101. } else {
  102. ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj");
  103. }
  104. // unref default file descriptor
  105. luaL_unref( L, LUA_REGISTRYINDEX, file_fd_ref );
  106. file_fd_ref = LUA_NOREF;
  107. if(ud->fd){
  108. vfs_close(ud->fd);
  109. // mark as closed
  110. ud->fd = 0;
  111. }
  112. return 0;
  113. }
  114. static int file_obj_free( lua_State *L )
  115. {
  116. file_fd_ud *ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj");
  117. if (ud->fd) {
  118. // close file if it's still open
  119. vfs_close(ud->fd);
  120. ud->fd = 0;
  121. }
  122. return 0;
  123. }
  124. // Lua: format()
  125. static int file_format( lua_State* L )
  126. {
  127. size_t len;
  128. file_close(L);
  129. if( !vfs_format() )
  130. {
  131. NODE_ERR( "\n*** ERROR ***: unable to format. FS might be compromised.\n" );
  132. NODE_ERR( "It is advised to re-flash the NodeMCU image.\n" );
  133. luaL_error(L, "Failed to format file system");
  134. }
  135. else{
  136. NODE_ERR( "format done.\n" );
  137. }
  138. return 0;
  139. }
  140. static int file_fscfg (lua_State *L)
  141. {
  142. uint32_t phys_addr, phys_size;
  143. vfs_fscfg("/FLASH", &phys_addr, &phys_size);
  144. lua_pushinteger (L, phys_addr);
  145. lua_pushinteger (L, phys_size);
  146. return 2;
  147. }
  148. // Lua: open(filename, mode)
  149. static int file_open( lua_State* L )
  150. {
  151. size_t len;
  152. // unref last file descriptor to allow gc'ing if not kept by user script
  153. luaL_unref( L, LUA_REGISTRYINDEX, file_fd_ref );
  154. file_fd_ref = LUA_NOREF;
  155. const char *fname = luaL_checklstring( L, 1, &len );
  156. const char *basename = vfs_basename( fname );
  157. luaL_argcheck(L, c_strlen(basename) <= FS_OBJ_NAME_LEN && c_strlen(fname) == len, 1, "filename invalid");
  158. const char *mode = luaL_optstring(L, 2, "r");
  159. file_fd = vfs_open(fname, mode);
  160. if(!file_fd){
  161. lua_pushnil(L);
  162. } else {
  163. file_fd_ud *ud = (file_fd_ud *) lua_newuserdata( L, sizeof( file_fd_ud ) );
  164. ud->fd = file_fd;
  165. luaL_getmetatable( L, "file.obj" );
  166. lua_setmetatable( L, -2 );
  167. // store reference to opened file
  168. lua_pushvalue( L, -1 );
  169. file_fd_ref = luaL_ref( L, LUA_REGISTRYINDEX );
  170. }
  171. return 1;
  172. }
  173. // Lua: list()
  174. static int file_list( lua_State* L )
  175. {
  176. vfs_dir *dir;
  177. if (dir = vfs_opendir("")) {
  178. lua_newtable( L );
  179. struct vfs_stat stat;
  180. while (vfs_readdir(dir, &stat) == VFS_RES_OK) {
  181. lua_pushinteger(L, stat.size);
  182. lua_setfield(L, -2, stat.name);
  183. }
  184. vfs_closedir(dir);
  185. return 1;
  186. }
  187. return 0;
  188. }
  189. static int get_file_obj( lua_State *L, int *argpos )
  190. {
  191. if (lua_type( L, 1 ) == LUA_TUSERDATA) {
  192. file_fd_ud *ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj");
  193. *argpos = 2;
  194. return ud->fd;
  195. } else {
  196. *argpos = 1;
  197. return file_fd;
  198. }
  199. }
  200. #define GET_FILE_OBJ int argpos; \
  201. int fd = get_file_obj( L, &argpos );
  202. static int file_seek (lua_State *L)
  203. {
  204. GET_FILE_OBJ;
  205. static const int mode[] = {VFS_SEEK_SET, VFS_SEEK_CUR, VFS_SEEK_END};
  206. static const char *const modenames[] = {"set", "cur", "end", NULL};
  207. if(!fd)
  208. return luaL_error(L, "open a file first");
  209. int op = luaL_checkoption(L, argpos, "cur", modenames);
  210. long offset = luaL_optlong(L, ++argpos, 0);
  211. op = vfs_lseek(fd, offset, mode[op]);
  212. if (op < 0)
  213. lua_pushnil(L); /* error */
  214. else
  215. lua_pushinteger(L, vfs_tell(fd));
  216. return 1;
  217. }
  218. // Lua: exists(filename)
  219. static int file_exists( lua_State* L )
  220. {
  221. size_t len;
  222. const char *fname = luaL_checklstring( L, 1, &len );
  223. const char *basename = vfs_basename( fname );
  224. luaL_argcheck(L, c_strlen(basename) <= FS_OBJ_NAME_LEN && c_strlen(fname) == len, 1, "filename invalid");
  225. struct vfs_stat stat;
  226. lua_pushboolean(L, vfs_stat((char *)fname, &stat) == VFS_RES_OK ? 1 : 0);
  227. return 1;
  228. }
  229. // Lua: remove(filename)
  230. static int file_remove( lua_State* L )
  231. {
  232. size_t len;
  233. const char *fname = luaL_checklstring( L, 1, &len );
  234. const char *basename = vfs_basename( fname );
  235. luaL_argcheck(L, c_strlen(basename) <= FS_OBJ_NAME_LEN && c_strlen(fname) == len, 1, "filename invalid");
  236. vfs_remove((char *)fname);
  237. return 0;
  238. }
  239. // Lua: flush()
  240. static int file_flush( lua_State* L )
  241. {
  242. GET_FILE_OBJ;
  243. if(!fd)
  244. return luaL_error(L, "open a file first");
  245. if(vfs_flush(fd) == 0)
  246. lua_pushboolean(L, 1);
  247. else
  248. lua_pushnil(L);
  249. return 1;
  250. }
  251. // Lua: rename("oldname", "newname")
  252. static int file_rename( lua_State* L )
  253. {
  254. size_t len;
  255. const char *oldname = luaL_checklstring( L, 1, &len );
  256. const char *basename = vfs_basename( oldname );
  257. luaL_argcheck(L, c_strlen(basename) <= FS_OBJ_NAME_LEN && c_strlen(oldname) == len, 1, "filename invalid");
  258. const char *newname = luaL_checklstring( L, 2, &len );
  259. basename = vfs_basename( newname );
  260. luaL_argcheck(L, c_strlen(basename) <= FS_OBJ_NAME_LEN && c_strlen(newname) == len, 2, "filename invalid");
  261. if(0 <= vfs_rename( oldname, newname )){
  262. lua_pushboolean(L, 1);
  263. } else {
  264. lua_pushboolean(L, 0);
  265. }
  266. return 1;
  267. }
  268. // Lua: stat(filename)
  269. static int file_stat( lua_State* L )
  270. {
  271. size_t len;
  272. const char *fname = luaL_checklstring( L, 1, &len );
  273. luaL_argcheck( L, c_strlen(fname) <= FS_OBJ_NAME_LEN && c_strlen(fname) == len, 1, "filename invalid" );
  274. struct vfs_stat stat;
  275. if (vfs_stat( (char *)fname, &stat ) != VFS_RES_OK) {
  276. lua_pushnil( L );
  277. return 1;
  278. }
  279. lua_createtable( L, 0, 7 );
  280. lua_pushinteger( L, stat.size );
  281. lua_setfield( L, -2, "size" );
  282. lua_pushstring( L, stat.name );
  283. lua_setfield( L, -2, "name" );
  284. lua_pushboolean( L, stat.is_dir );
  285. lua_setfield( L, -2, "is_dir" );
  286. lua_pushboolean( L, stat.is_rdonly );
  287. lua_setfield( L, -2, "is_rdonly" );
  288. lua_pushboolean( L, stat.is_hidden );
  289. lua_setfield( L, -2, "is_hidden" );
  290. lua_pushboolean( L, stat.is_sys );
  291. lua_setfield( L, -2, "is_sys" );
  292. lua_pushboolean( L, stat.is_arch );
  293. lua_setfield( L, -2, "is_arch" );
  294. // time stamp as sub-table
  295. lua_createtable( L, 0, 6 );
  296. lua_pushinteger( L, stat.tm_valid ? stat.tm.year : FILE_TIMEDEF_YEAR );
  297. lua_setfield( L, -2, "year" );
  298. lua_pushinteger( L, stat.tm_valid ? stat.tm.mon : FILE_TIMEDEF_MON );
  299. lua_setfield( L, -2, "mon" );
  300. lua_pushinteger( L, stat.tm_valid ? stat.tm.day : FILE_TIMEDEF_DAY );
  301. lua_setfield( L, -2, "day" );
  302. lua_pushinteger( L, stat.tm_valid ? stat.tm.hour : FILE_TIMEDEF_HOUR );
  303. lua_setfield( L, -2, "hour" );
  304. lua_pushinteger( L, stat.tm_valid ? stat.tm.min : FILE_TIMEDEF_MIN );
  305. lua_setfield( L, -2, "min" );
  306. lua_pushinteger( L, stat.tm_valid ? stat.tm.sec : FILE_TIMEDEF_SEC );
  307. lua_setfield( L, -2, "sec" );
  308. lua_setfield( L, -2, "time" );
  309. return 1;
  310. }
  311. // g_read()
  312. static int file_g_read( lua_State* L, int n, int16_t end_char, int fd )
  313. {
  314. static char *heap_mem = NULL;
  315. // free leftover memory
  316. if (heap_mem) {
  317. luaM_free(L, heap_mem);
  318. heap_mem = NULL;
  319. }
  320. if(n <= 0)
  321. n = FILE_READ_CHUNK;
  322. if(end_char < 0 || end_char >255)
  323. end_char = EOF;
  324. if(!fd)
  325. return luaL_error(L, "open a file first");
  326. char *p;
  327. int i;
  328. if (n > LUAL_BUFFERSIZE) {
  329. // get buffer from heap
  330. p = heap_mem = luaM_malloc(L, n);
  331. } else {
  332. // small chunks go onto the stack
  333. p = alloca(n);
  334. }
  335. n = vfs_read(fd, p, n);
  336. // bypass search if no end character provided
  337. if (n > 0 && end_char != EOF) {
  338. for (i = 0; i < n; ++i)
  339. if (p[i] == end_char)
  340. {
  341. ++i;
  342. break;
  343. }
  344. } else {
  345. i = n;
  346. }
  347. if (i == 0 || n == VFS_RES_ERR) {
  348. if (heap_mem) {
  349. luaM_free(L, heap_mem);
  350. heap_mem = NULL;
  351. }
  352. return 0;
  353. }
  354. vfs_lseek(fd, -(n - i), VFS_SEEK_CUR);
  355. lua_pushlstring(L, p, i);
  356. if (heap_mem) {
  357. luaM_free(L, heap_mem);
  358. heap_mem = NULL;
  359. }
  360. return 1;
  361. }
  362. // Lua: read()
  363. // file.read() will read all byte in file
  364. // file.read(10) will read 10 byte from file, or EOF is reached.
  365. // file.read('q') will read until 'q' or EOF is reached.
  366. static int file_read( lua_State* L )
  367. {
  368. unsigned need_len = FILE_READ_CHUNK;
  369. int16_t end_char = EOF;
  370. size_t el;
  371. GET_FILE_OBJ;
  372. if( lua_type( L, argpos ) == LUA_TNUMBER )
  373. {
  374. need_len = ( unsigned )luaL_checkinteger( L, argpos );
  375. }
  376. else if(lua_isstring(L, argpos))
  377. {
  378. const char *end = luaL_checklstring( L, argpos, &el );
  379. if(el!=1){
  380. return luaL_error( L, "wrong arg range" );
  381. }
  382. end_char = (int16_t)end[0];
  383. }
  384. return file_g_read(L, need_len, end_char, fd);
  385. }
  386. // Lua: readline()
  387. static int file_readline( lua_State* L )
  388. {
  389. GET_FILE_OBJ;
  390. return file_g_read(L, FILE_READ_CHUNK, '\n', fd);
  391. }
  392. // Lua: write("string")
  393. static int file_write( lua_State* L )
  394. {
  395. GET_FILE_OBJ;
  396. if(!fd)
  397. return luaL_error(L, "open a file first");
  398. size_t l, rl;
  399. const char *s = luaL_checklstring(L, argpos, &l);
  400. rl = vfs_write(fd, s, l);
  401. if(rl==l)
  402. lua_pushboolean(L, 1);
  403. else
  404. lua_pushnil(L);
  405. return 1;
  406. }
  407. // Lua: writeline("string")
  408. static int file_writeline( lua_State* L )
  409. {
  410. GET_FILE_OBJ;
  411. if(!fd)
  412. return luaL_error(L, "open a file first");
  413. size_t l, rl;
  414. const char *s = luaL_checklstring(L, argpos, &l);
  415. rl = vfs_write(fd, s, l);
  416. if(rl==l){
  417. rl = vfs_write(fd, "\n", 1);
  418. if(rl==1)
  419. lua_pushboolean(L, 1);
  420. else
  421. lua_pushnil(L);
  422. }
  423. else{
  424. lua_pushnil(L);
  425. }
  426. return 1;
  427. }
  428. // Lua: fsinfo()
  429. static int file_fsinfo( lua_State* L )
  430. {
  431. u32_t total, used;
  432. if (vfs_fsinfo("", &total, &used)) {
  433. return luaL_error(L, "file system failed");
  434. }
  435. NODE_DBG("total: %d, used:%d\n", total, used);
  436. if(total>0x7FFFFFFF || used>0x7FFFFFFF || used > total)
  437. {
  438. return luaL_error(L, "file system error");
  439. }
  440. lua_pushinteger(L, total-used);
  441. lua_pushinteger(L, used);
  442. lua_pushinteger(L, total);
  443. return 3;
  444. }
  445. typedef struct {
  446. vfs_vol *vol;
  447. } volume_type;
  448. // Lua: vol = file.mount("/SD0")
  449. static int file_mount( lua_State *L )
  450. {
  451. const char *ldrv = luaL_checkstring( L, 1 );
  452. int num = luaL_optint( L, 2, -1 );
  453. volume_type *vol = (volume_type *)lua_newuserdata( L, sizeof( volume_type ) );
  454. if (vol->vol = vfs_mount( ldrv, num )) {
  455. /* set its metatable */
  456. luaL_getmetatable(L, "vfs.vol");
  457. lua_setmetatable(L, -2);
  458. return 1;
  459. } else {
  460. // remove created userdata
  461. lua_pop( L, 1 );
  462. return 0;
  463. }
  464. }
  465. // Lua: success = file.chdir("/SD0/")
  466. static int file_chdir( lua_State *L )
  467. {
  468. const char *path = luaL_checkstring( L, 1 );
  469. lua_pushboolean( L, 0 <= vfs_chdir( path ) );
  470. return 1;
  471. }
  472. static int file_vol_umount( lua_State *L )
  473. {
  474. volume_type *vol = luaL_checkudata( L, 1, "file.vol" );
  475. luaL_argcheck( L, vol, 1, "volume expected" );
  476. lua_pushboolean( L, 0 <= vfs_umount( vol->vol ) );
  477. // invalidate vfs descriptor, it has been free'd anyway
  478. vol->vol = NULL;
  479. return 1;
  480. }
  481. static const LUA_REG_TYPE file_obj_map[] =
  482. {
  483. { LSTRKEY( "close" ), LFUNCVAL( file_close ) },
  484. { LSTRKEY( "read" ), LFUNCVAL( file_read ) },
  485. { LSTRKEY( "readline" ), LFUNCVAL( file_readline ) },
  486. { LSTRKEY( "write" ), LFUNCVAL( file_write ) },
  487. { LSTRKEY( "writeline" ), LFUNCVAL( file_writeline ) },
  488. { LSTRKEY( "seek" ), LFUNCVAL( file_seek ) },
  489. { LSTRKEY( "flush" ), LFUNCVAL( file_flush ) },
  490. { LSTRKEY( "__gc" ), LFUNCVAL( file_obj_free ) },
  491. { LSTRKEY( "__index" ), LROVAL( file_obj_map ) },
  492. { LNILKEY, LNILVAL }
  493. };
  494. static const LUA_REG_TYPE file_vol_map[] =
  495. {
  496. { LSTRKEY( "umount" ), LFUNCVAL( file_vol_umount )},
  497. //{ LSTRKEY( "getfree" ), LFUNCVAL( file_vol_getfree )},
  498. //{ LSTRKEY( "getlabel" ), LFUNCVAL( file_vol_getlabel )},
  499. //{ LSTRKEY( "__gc" ), LFUNCVAL( file_vol_free ) },
  500. { LSTRKEY( "__index" ), LROVAL( file_vol_map ) },
  501. { LNILKEY, LNILVAL }
  502. };
  503. // Module function map
  504. static const LUA_REG_TYPE file_map[] = {
  505. { LSTRKEY( "list" ), LFUNCVAL( file_list ) },
  506. { LSTRKEY( "open" ), LFUNCVAL( file_open ) },
  507. { LSTRKEY( "close" ), LFUNCVAL( file_close ) },
  508. { LSTRKEY( "write" ), LFUNCVAL( file_write ) },
  509. { LSTRKEY( "writeline" ), LFUNCVAL( file_writeline ) },
  510. { LSTRKEY( "read" ), LFUNCVAL( file_read ) },
  511. { LSTRKEY( "readline" ), LFUNCVAL( file_readline ) },
  512. #ifdef BUILD_SPIFFS
  513. { LSTRKEY( "format" ), LFUNCVAL( file_format ) },
  514. { LSTRKEY( "fscfg" ), LFUNCVAL( file_fscfg ) },
  515. #endif
  516. { LSTRKEY( "remove" ), LFUNCVAL( file_remove ) },
  517. { LSTRKEY( "seek" ), LFUNCVAL( file_seek ) },
  518. { LSTRKEY( "flush" ), LFUNCVAL( file_flush ) },
  519. { LSTRKEY( "rename" ), LFUNCVAL( file_rename ) },
  520. { LSTRKEY( "exists" ), LFUNCVAL( file_exists ) },
  521. { LSTRKEY( "fsinfo" ), LFUNCVAL( file_fsinfo ) },
  522. { LSTRKEY( "on" ), LFUNCVAL( file_on ) },
  523. { LSTRKEY( "stat" ), LFUNCVAL( file_stat ) },
  524. #ifdef BUILD_FATFS
  525. { LSTRKEY( "mount" ), LFUNCVAL( file_mount ) },
  526. { LSTRKEY( "chdir" ), LFUNCVAL( file_chdir ) },
  527. #endif
  528. { LNILKEY, LNILVAL }
  529. };
  530. int luaopen_file( lua_State *L ) {
  531. luaL_rometatable( L, "file.vol", (void *)file_vol_map );
  532. luaL_rometatable( L, "file.obj", (void *)file_obj_map );
  533. return 0;
  534. }
  535. NODEMCU_MODULE(FILE, "file", file_map, luaopen_file);