2014-12-22 12:35:05 +01:00
|
|
|
// Module for interfacing with file system
|
|
|
|
|
2015-12-16 06:04:58 +01:00
|
|
|
#include "module.h"
|
2014-12-22 12:35:05 +01:00
|
|
|
#include "lauxlib.h"
|
2016-10-28 16:32:12 +02:00
|
|
|
#include "lmem.h"
|
2014-12-22 12:35:05 +01:00
|
|
|
#include "platform.h"
|
2020-10-18 20:46:47 +02:00
|
|
|
#include "spiffs/nodemcu_spiffs.h"
|
2014-12-22 12:35:05 +01:00
|
|
|
|
2019-07-23 06:22:38 +02:00
|
|
|
#include <stdint.h>
|
2016-09-05 20:17:13 +02:00
|
|
|
#include "vfs.h"
|
2019-07-21 23:58:21 +02:00
|
|
|
#include <string.h>
|
2014-12-22 12:35:05 +01:00
|
|
|
|
2016-10-28 16:32:12 +02:00
|
|
|
#include <alloca.h>
|
|
|
|
|
|
|
|
#define FILE_READ_CHUNK 1024
|
|
|
|
|
2017-01-17 21:54:23 +01:00
|
|
|
// use this time/date in absence of a timestamp
|
|
|
|
#define FILE_TIMEDEF_YEAR 1970
|
|
|
|
#define FILE_TIMEDEF_MON 01
|
|
|
|
#define FILE_TIMEDEF_DAY 01
|
|
|
|
#define FILE_TIMEDEF_HOUR 00
|
|
|
|
#define FILE_TIMEDEF_MIN 00
|
|
|
|
#define FILE_TIMEDEF_SEC 00
|
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
static int file_fd = 0;
|
2016-11-08 21:02:51 +01:00
|
|
|
static int file_fd_ref = LUA_NOREF;
|
2016-09-05 20:17:13 +02:00
|
|
|
static int rtc_cb_ref = LUA_NOREF;
|
2014-12-22 12:35:05 +01:00
|
|
|
|
2016-11-08 21:02:51 +01:00
|
|
|
typedef struct _file_fd_ud {
|
|
|
|
int fd;
|
|
|
|
} file_fd_ud;
|
2016-09-05 20:17:13 +02:00
|
|
|
|
2020-10-18 20:46:47 +02:00
|
|
|
static void do_flash_mount() {
|
|
|
|
if (!vfs_mount("/FLASH", 0)) {
|
|
|
|
// Failed to mount -- try reformat
|
|
|
|
dbg_printf("Formatting file system. Please wait...\n");
|
|
|
|
if (!vfs_format()) {
|
|
|
|
NODE_ERR( "\n*** ERROR ***: unable to format. FS might be compromised.\n" );
|
|
|
|
NODE_ERR( "It is advised to re-flash the NodeMCU image.\n" );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
static void table2tm( lua_State *L, vfs_time *tm )
|
2014-12-22 12:35:05 +01:00
|
|
|
{
|
2016-09-05 20:17:13 +02:00
|
|
|
int idx = lua_gettop( L );
|
|
|
|
|
|
|
|
// extract items from table
|
|
|
|
lua_getfield( L, idx, "year" );
|
|
|
|
lua_getfield( L, idx, "mon" );
|
|
|
|
lua_getfield( L, idx, "day" );
|
|
|
|
lua_getfield( L, idx, "hour" );
|
|
|
|
lua_getfield( L, idx, "min" );
|
|
|
|
lua_getfield( L, idx, "sec" );
|
|
|
|
|
2017-01-17 21:54:23 +01:00
|
|
|
tm->year = luaL_optint( L, ++idx, FILE_TIMEDEF_YEAR );
|
|
|
|
tm->mon = luaL_optint( L, ++idx, FILE_TIMEDEF_MON );
|
|
|
|
tm->day = luaL_optint( L, ++idx, FILE_TIMEDEF_DAY );
|
|
|
|
tm->hour = luaL_optint( L, ++idx, FILE_TIMEDEF_HOUR );
|
|
|
|
tm->min = luaL_optint( L, ++idx, FILE_TIMEDEF_MIN );
|
|
|
|
tm->sec = luaL_optint( L, ++idx, FILE_TIMEDEF_SEC );
|
2016-09-05 20:17:13 +02:00
|
|
|
|
|
|
|
// remove items from stack
|
|
|
|
lua_pop( L, 6 );
|
|
|
|
}
|
2014-12-22 12:35:05 +01:00
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
static sint32_t file_rtc_cb( vfs_time *tm )
|
|
|
|
{
|
|
|
|
sint32_t res = VFS_RES_ERR;
|
2016-02-14 00:58:04 +01:00
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
if (rtc_cb_ref != LUA_NOREF) {
|
|
|
|
lua_State *L = lua_getstate();
|
2014-12-22 12:35:05 +01:00
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
lua_rawgeti( L, LUA_REGISTRYINDEX, rtc_cb_ref );
|
2020-06-16 09:19:55 +02:00
|
|
|
if (luaL_pcallx( L, 0, 1 ) != LUA_OK)
|
|
|
|
return res;
|
2014-12-22 12:35:05 +01:00
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
if (lua_type( L, lua_gettop( L ) ) == LUA_TTABLE) {
|
|
|
|
table2tm( L, tm );
|
|
|
|
res = VFS_RES_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
// pop item returned by callback
|
|
|
|
lua_pop( L, 1 );
|
2014-12-22 12:35:05 +01:00
|
|
|
}
|
2016-09-05 20:17:13 +02:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lua: on()
|
|
|
|
static int file_on(lua_State *L)
|
|
|
|
{
|
|
|
|
enum events{
|
|
|
|
ON_RTC = 0
|
|
|
|
};
|
|
|
|
const char *const eventnames[] = {"rtc", NULL};
|
|
|
|
|
|
|
|
int event = luaL_checkoption(L, 1, "rtc", eventnames);
|
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case ON_RTC:
|
|
|
|
luaL_unref(L, LUA_REGISTRYINDEX, rtc_cb_ref);
|
|
|
|
|
2020-02-14 10:10:25 +01:00
|
|
|
switch(lua_type(L, 2)) {
|
|
|
|
case LUA_TFUNCTION:
|
2016-09-05 20:17:13 +02:00
|
|
|
lua_pushvalue(L, 2); // copy argument (func) to the top of stack
|
|
|
|
rtc_cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
|
|
vfs_register_rtc_cb(file_rtc_cb);
|
2020-02-14 10:10:25 +01:00
|
|
|
break;
|
|
|
|
case LUA_TNIL:
|
2016-09-05 20:17:13 +02:00
|
|
|
rtc_cb_ref = LUA_NOREF;
|
|
|
|
vfs_register_rtc_cb(NULL);
|
2020-02-14 10:10:25 +01:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
luaL_error(L, "Callback should be function or nil");
|
2016-09-05 20:17:13 +02:00
|
|
|
}
|
2020-02-14 10:10:25 +01:00
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2014-12-22 12:35:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Lua: close()
|
2015-01-05 03:09:51 +01:00
|
|
|
static int file_close( lua_State* L )
|
2014-12-22 12:35:05 +01:00
|
|
|
{
|
2016-11-08 21:02:51 +01:00
|
|
|
int need_pop = FALSE;
|
|
|
|
file_fd_ud *ud;
|
|
|
|
|
|
|
|
if (lua_type( L, 1 ) != LUA_TUSERDATA) {
|
|
|
|
// fall back to last opened file
|
|
|
|
if (file_fd_ref != LUA_NOREF) {
|
|
|
|
lua_rawgeti( L, LUA_REGISTRYINDEX, file_fd_ref );
|
|
|
|
// top of stack is now default file descriptor
|
|
|
|
ud = (file_fd_ud *)luaL_checkudata(L, -1, "file.obj");
|
|
|
|
lua_pop( L, 1 );
|
|
|
|
} else {
|
|
|
|
// no default file currently opened
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj");
|
|
|
|
}
|
|
|
|
|
|
|
|
if(ud->fd){
|
|
|
|
vfs_close(ud->fd);
|
|
|
|
// mark as closed
|
|
|
|
ud->fd = 0;
|
2014-12-22 12:35:05 +01:00
|
|
|
}
|
2019-07-05 07:55:54 +02:00
|
|
|
|
|
|
|
// unref default file descriptor
|
|
|
|
luaL_unref( L, LUA_REGISTRYINDEX, file_fd_ref );
|
|
|
|
file_fd_ref = LUA_NOREF;
|
|
|
|
|
2019-02-17 19:26:29 +01:00
|
|
|
return 0;
|
2014-12-22 12:35:05 +01:00
|
|
|
}
|
|
|
|
|
2016-11-08 21:02:51 +01:00
|
|
|
static int file_obj_free( lua_State *L )
|
|
|
|
{
|
|
|
|
file_fd_ud *ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj");
|
|
|
|
if (ud->fd) {
|
|
|
|
// close file if it's still open
|
|
|
|
vfs_close(ud->fd);
|
|
|
|
ud->fd = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-12-22 12:35:05 +01:00
|
|
|
// Lua: format()
|
2015-01-05 03:09:51 +01:00
|
|
|
static int file_format( lua_State* L )
|
2014-12-22 12:35:05 +01:00
|
|
|
{
|
|
|
|
size_t len;
|
|
|
|
file_close(L);
|
2016-09-05 20:17:13 +02:00
|
|
|
if( !vfs_format() )
|
2014-12-22 12:35:05 +01:00
|
|
|
{
|
2016-06-05 23:10:58 +02:00
|
|
|
NODE_ERR( "\n*** ERROR ***: unable to format. FS might be compromised.\n" );
|
2015-01-07 08:57:17 +01:00
|
|
|
NODE_ERR( "It is advised to re-flash the NodeMCU image.\n" );
|
2016-06-05 23:10:58 +02:00
|
|
|
luaL_error(L, "Failed to format file system");
|
2014-12-22 12:35:05 +01:00
|
|
|
}
|
|
|
|
else{
|
|
|
|
NODE_ERR( "format done.\n" );
|
|
|
|
}
|
2016-09-05 20:17:13 +02:00
|
|
|
return 0;
|
2014-12-22 12:35:05 +01:00
|
|
|
}
|
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
static int file_fscfg (lua_State *L)
|
|
|
|
{
|
|
|
|
uint32_t phys_addr, phys_size;
|
|
|
|
|
|
|
|
vfs_fscfg("/FLASH", &phys_addr, &phys_size);
|
2014-12-22 12:35:05 +01:00
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
lua_pushinteger (L, phys_addr);
|
|
|
|
lua_pushinteger (L, phys_size);
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lua: open(filename, mode)
|
|
|
|
static int file_open( lua_State* L )
|
|
|
|
{
|
|
|
|
size_t len;
|
2016-11-08 21:02:51 +01:00
|
|
|
|
|
|
|
// unref last file descriptor to allow gc'ing if not kept by user script
|
|
|
|
luaL_unref( L, LUA_REGISTRYINDEX, file_fd_ref );
|
|
|
|
file_fd_ref = LUA_NOREF;
|
2016-09-05 20:17:13 +02:00
|
|
|
|
|
|
|
const char *fname = luaL_checklstring( L, 1, &len );
|
|
|
|
const char *basename = vfs_basename( fname );
|
2019-07-21 23:58:21 +02:00
|
|
|
luaL_argcheck(L, strlen(basename) <= FS_OBJ_NAME_LEN && strlen(fname) == len, 1, "filename invalid");
|
2016-09-05 20:17:13 +02:00
|
|
|
|
|
|
|
const char *mode = luaL_optstring(L, 2, "r");
|
|
|
|
|
|
|
|
file_fd = vfs_open(fname, mode);
|
|
|
|
|
|
|
|
if(!file_fd){
|
|
|
|
lua_pushnil(L);
|
|
|
|
} else {
|
2016-11-08 21:02:51 +01:00
|
|
|
file_fd_ud *ud = (file_fd_ud *) lua_newuserdata( L, sizeof( file_fd_ud ) );
|
|
|
|
ud->fd = file_fd;
|
|
|
|
luaL_getmetatable( L, "file.obj" );
|
|
|
|
lua_setmetatable( L, -2 );
|
|
|
|
|
|
|
|
// store reference to opened file
|
|
|
|
lua_pushvalue( L, -1 );
|
|
|
|
file_fd_ref = luaL_ref( L, LUA_REGISTRYINDEX );
|
2016-09-05 20:17:13 +02:00
|
|
|
}
|
2019-02-17 19:26:29 +01:00
|
|
|
return 1;
|
2016-09-05 20:17:13 +02:00
|
|
|
}
|
2014-12-22 12:35:05 +01:00
|
|
|
|
|
|
|
// Lua: list()
|
2015-01-05 03:09:51 +01:00
|
|
|
static int file_list( lua_State* L )
|
2014-12-22 12:35:05 +01:00
|
|
|
{
|
2016-09-05 20:17:13 +02:00
|
|
|
vfs_dir *dir;
|
2018-08-10 17:38:48 +02:00
|
|
|
const char *pattern;
|
|
|
|
struct vfs_stat stat;
|
|
|
|
int pcres;
|
2016-09-05 20:17:13 +02:00
|
|
|
|
2018-08-10 17:38:48 +02:00
|
|
|
lua_settop(L, 1);
|
|
|
|
pattern = luaL_optstring(L, 1, NULL); /* Pattern (arg) or nil (not) at 1 */
|
|
|
|
|
|
|
|
dir = vfs_opendir("");
|
|
|
|
if (dir == NULL) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_newtable( L ); /* Table at 2 */
|
|
|
|
|
|
|
|
if (pattern) {
|
2019-02-17 19:26:29 +01:00
|
|
|
/*
|
2018-08-10 17:38:48 +02:00
|
|
|
* We know that pattern is a string, and so the "match" method will always
|
|
|
|
* exist. No need to check return value here
|
|
|
|
*/
|
|
|
|
luaL_getmetafield( L, 1, "match" ); /* Function at 3 */
|
|
|
|
}
|
|
|
|
|
|
|
|
while (vfs_readdir(dir, &stat) == VFS_RES_OK) {
|
|
|
|
if (pattern) {
|
|
|
|
lua_settop( L, 3 ); /* Ensure nothing else on stack */
|
|
|
|
|
|
|
|
/* Construct and pcall(string.match,name,pattern) */
|
|
|
|
lua_pushvalue( L, 3 );
|
|
|
|
lua_pushstring( L, stat.name );
|
|
|
|
lua_pushvalue( L, 1 );
|
|
|
|
pcres = lua_pcall( L, 2, 1, 0 );
|
|
|
|
if (pcres != 0) {
|
|
|
|
vfs_closedir(dir);
|
|
|
|
lua_error( L );
|
|
|
|
}
|
|
|
|
if (lua_isnil( L, -1 )) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-09-05 20:17:13 +02:00
|
|
|
}
|
2018-08-10 17:38:48 +02:00
|
|
|
lua_pushinteger( L, stat.size );
|
|
|
|
lua_setfield( L, 2, stat.name );
|
2014-12-22 12:35:05 +01:00
|
|
|
}
|
2018-08-10 17:38:48 +02:00
|
|
|
|
|
|
|
/* Shed everything back to Table */
|
|
|
|
lua_settop( L, 2 );
|
|
|
|
vfs_closedir(dir);
|
|
|
|
return 1;
|
2014-12-22 12:35:05 +01:00
|
|
|
}
|
|
|
|
|
2016-11-08 21:02:51 +01:00
|
|
|
static int get_file_obj( lua_State *L, int *argpos )
|
2014-12-22 12:35:05 +01:00
|
|
|
{
|
2016-11-08 21:02:51 +01:00
|
|
|
if (lua_type( L, 1 ) == LUA_TUSERDATA) {
|
|
|
|
file_fd_ud *ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj");
|
|
|
|
*argpos = 2;
|
|
|
|
return ud->fd;
|
|
|
|
} else {
|
|
|
|
*argpos = 1;
|
|
|
|
return file_fd;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define GET_FILE_OBJ int argpos; \
|
|
|
|
int fd = get_file_obj( L, &argpos );
|
|
|
|
|
|
|
|
static int file_seek (lua_State *L)
|
|
|
|
{
|
|
|
|
GET_FILE_OBJ;
|
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
static const int mode[] = {VFS_SEEK_SET, VFS_SEEK_CUR, VFS_SEEK_END};
|
2014-12-22 12:35:05 +01:00
|
|
|
static const char *const modenames[] = {"set", "cur", "end", NULL};
|
2016-11-08 21:02:51 +01:00
|
|
|
if(!fd)
|
2014-12-22 12:35:05 +01:00
|
|
|
return luaL_error(L, "open a file first");
|
2016-11-08 21:02:51 +01:00
|
|
|
int op = luaL_checkoption(L, argpos, "cur", modenames);
|
|
|
|
long offset = luaL_optlong(L, ++argpos, 0);
|
|
|
|
op = vfs_lseek(fd, offset, mode[op]);
|
2015-07-16 08:11:22 +02:00
|
|
|
if (op < 0)
|
2015-02-25 20:34:07 +01:00
|
|
|
lua_pushnil(L); /* error */
|
2014-12-22 12:35:05 +01:00
|
|
|
else
|
2016-11-08 21:02:51 +01:00
|
|
|
lua_pushinteger(L, vfs_tell(fd));
|
2014-12-22 12:35:05 +01:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2016-02-13 23:37:04 +01:00
|
|
|
// Lua: exists(filename)
|
|
|
|
static int file_exists( lua_State* L )
|
|
|
|
{
|
|
|
|
size_t len;
|
2019-02-17 19:26:29 +01:00
|
|
|
const char *fname = luaL_checklstring( L, 1, &len );
|
2016-09-05 20:17:13 +02:00
|
|
|
const char *basename = vfs_basename( fname );
|
2019-07-21 23:58:21 +02:00
|
|
|
luaL_argcheck(L, strlen(basename) <= FS_OBJ_NAME_LEN && strlen(fname) == len, 1, "filename invalid");
|
2016-02-13 23:37:04 +01:00
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
struct vfs_stat stat;
|
|
|
|
lua_pushboolean(L, vfs_stat((char *)fname, &stat) == VFS_RES_OK ? 1 : 0);
|
2016-02-13 23:37:04 +01:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-12-22 12:35:05 +01:00
|
|
|
// Lua: remove(filename)
|
2015-01-05 03:09:51 +01:00
|
|
|
static int file_remove( lua_State* L )
|
2014-12-22 12:35:05 +01:00
|
|
|
{
|
|
|
|
size_t len;
|
2019-02-17 19:26:29 +01:00
|
|
|
const char *fname = luaL_checklstring( L, 1, &len );
|
2016-09-05 20:17:13 +02:00
|
|
|
const char *basename = vfs_basename( fname );
|
2019-07-21 23:58:21 +02:00
|
|
|
luaL_argcheck(L, strlen(basename) <= FS_OBJ_NAME_LEN && strlen(fname) == len, 1, "filename invalid");
|
2016-09-05 20:17:13 +02:00
|
|
|
vfs_remove((char *)fname);
|
|
|
|
return 0;
|
2014-12-22 12:35:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Lua: flush()
|
2015-01-05 03:09:51 +01:00
|
|
|
static int file_flush( lua_State* L )
|
2014-12-22 12:35:05 +01:00
|
|
|
{
|
2016-11-08 21:02:51 +01:00
|
|
|
GET_FILE_OBJ;
|
|
|
|
|
|
|
|
if(!fd)
|
2014-12-22 12:35:05 +01:00
|
|
|
return luaL_error(L, "open a file first");
|
2016-11-08 21:02:51 +01:00
|
|
|
if(vfs_flush(fd) == 0)
|
2014-12-22 12:35:05 +01:00
|
|
|
lua_pushboolean(L, 1);
|
|
|
|
else
|
|
|
|
lua_pushnil(L);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2015-02-11 14:20:54 +01:00
|
|
|
// Lua: rename("oldname", "newname")
|
|
|
|
static int file_rename( lua_State* L )
|
|
|
|
{
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
const char *oldname = luaL_checklstring( L, 1, &len );
|
2016-09-05 20:17:13 +02:00
|
|
|
const char *basename = vfs_basename( oldname );
|
2019-07-21 23:58:21 +02:00
|
|
|
luaL_argcheck(L, strlen(basename) <= FS_OBJ_NAME_LEN && strlen(oldname) == len, 1, "filename invalid");
|
2019-02-17 19:26:29 +01:00
|
|
|
|
|
|
|
const char *newname = luaL_checklstring( L, 2, &len );
|
2016-09-05 20:17:13 +02:00
|
|
|
basename = vfs_basename( newname );
|
2019-07-21 23:58:21 +02:00
|
|
|
luaL_argcheck(L, strlen(basename) <= FS_OBJ_NAME_LEN && strlen(newname) == len, 2, "filename invalid");
|
2015-02-11 14:20:54 +01:00
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
if(0 <= vfs_rename( oldname, newname )){
|
2015-02-11 14:20:54 +01:00
|
|
|
lua_pushboolean(L, 1);
|
|
|
|
} else {
|
|
|
|
lua_pushboolean(L, 0);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2017-01-17 21:54:23 +01:00
|
|
|
// Lua: stat(filename)
|
|
|
|
static int file_stat( lua_State* L )
|
|
|
|
{
|
|
|
|
size_t len;
|
2019-02-17 19:26:29 +01:00
|
|
|
const char *fname = luaL_checklstring( L, 1, &len );
|
2019-07-21 23:58:21 +02:00
|
|
|
luaL_argcheck( L, strlen(fname) <= FS_OBJ_NAME_LEN && strlen(fname) == len, 1, "filename invalid" );
|
2017-01-17 21:54:23 +01:00
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
struct vfs_stat stat;
|
|
|
|
if (vfs_stat( (char *)fname, &stat ) != VFS_RES_OK) {
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_pushnil( L );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_createtable( L, 0, 7 );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushinteger( L, stat.size );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "size" );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushstring( L, stat.name );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "name" );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushboolean( L, stat.is_dir );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "is_dir" );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushboolean( L, stat.is_rdonly );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "is_rdonly" );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushboolean( L, stat.is_hidden );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "is_hidden" );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushboolean( L, stat.is_sys );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "is_sys" );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushboolean( L, stat.is_arch );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "is_arch" );
|
|
|
|
|
|
|
|
// time stamp as sub-table
|
|
|
|
lua_createtable( L, 0, 6 );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushinteger( L, stat.tm_valid ? stat.tm.year : FILE_TIMEDEF_YEAR );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "year" );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushinteger( L, stat.tm_valid ? stat.tm.mon : FILE_TIMEDEF_MON );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "mon" );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushinteger( L, stat.tm_valid ? stat.tm.day : FILE_TIMEDEF_DAY );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "day" );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushinteger( L, stat.tm_valid ? stat.tm.hour : FILE_TIMEDEF_HOUR );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "hour" );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushinteger( L, stat.tm_valid ? stat.tm.min : FILE_TIMEDEF_MIN );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "min" );
|
|
|
|
|
2017-03-29 13:58:40 +02:00
|
|
|
lua_pushinteger( L, stat.tm_valid ? stat.tm.sec : FILE_TIMEDEF_SEC );
|
2017-01-17 21:54:23 +01:00
|
|
|
lua_setfield( L, -2, "sec" );
|
|
|
|
|
|
|
|
lua_setfield( L, -2, "time" );
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2014-12-31 06:53:26 +01:00
|
|
|
// g_read()
|
2016-11-08 21:02:51 +01:00
|
|
|
static int file_g_read( lua_State* L, int n, int16_t end_char, int fd )
|
2014-12-22 12:35:05 +01:00
|
|
|
{
|
2019-04-11 23:17:00 +02:00
|
|
|
int i, j;
|
|
|
|
luaL_Buffer b;
|
|
|
|
char p[LUAL_BUFFERSIZE/2];
|
2016-11-08 21:02:51 +01:00
|
|
|
|
|
|
|
if(!fd)
|
2014-12-22 12:35:05 +01:00
|
|
|
return luaL_error(L, "open a file first");
|
|
|
|
|
2019-04-11 23:17:00 +02:00
|
|
|
luaL_buffinit(L, &b);
|
2014-12-22 12:35:05 +01:00
|
|
|
|
2019-04-11 23:17:00 +02:00
|
|
|
for (j = 0; j < n; j += sizeof(p)) {
|
2019-05-07 17:49:16 +02:00
|
|
|
int nwanted = (n - j >= sizeof(p)) ? sizeof(p) : n - j;
|
2019-04-11 23:17:00 +02:00
|
|
|
int nread = vfs_read(fd, p, nwanted);
|
2016-10-28 16:32:12 +02:00
|
|
|
|
2019-04-11 23:17:00 +02:00
|
|
|
if (nread == VFS_RES_ERR || nread == 0) {
|
2020-09-13 17:37:54 +02:00
|
|
|
if (j > 0) {
|
|
|
|
break;
|
|
|
|
}
|
2019-05-01 19:29:11 +02:00
|
|
|
lua_pushnil(L);
|
2019-04-11 23:17:00 +02:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < nread; ++i) {
|
|
|
|
luaL_addchar(&b, p[i]);
|
|
|
|
if (p[i] == end_char) {
|
2020-09-29 23:16:53 +02:00
|
|
|
vfs_lseek(fd, -nread + i + 1, VFS_SEEK_CUR); //reposition after end char found
|
2019-04-11 23:17:00 +02:00
|
|
|
nread = 0; // force break on outer loop
|
2016-11-19 16:36:00 +01:00
|
|
|
break;
|
|
|
|
}
|
2016-10-28 16:32:12 +02:00
|
|
|
}
|
2014-12-22 12:35:05 +01:00
|
|
|
|
2019-04-11 23:17:00 +02:00
|
|
|
if (nread < nwanted)
|
|
|
|
break;
|
2016-10-28 16:32:12 +02:00
|
|
|
}
|
2019-04-11 23:17:00 +02:00
|
|
|
luaL_pushresult(&b);
|
2016-10-28 16:32:12 +02:00
|
|
|
return 1;
|
2014-12-22 12:35:05 +01:00
|
|
|
}
|
|
|
|
|
2014-12-31 06:53:26 +01:00
|
|
|
// Lua: read()
|
2019-05-07 17:49:16 +02:00
|
|
|
// file.read() will read FILE _CHUNK bytes, or EOF is reached.
|
2014-12-31 06:53:26 +01:00
|
|
|
// file.read(10) will read 10 byte from file, or EOF is reached.
|
2019-02-17 19:26:29 +01:00
|
|
|
// file.read('q') will read until 'q' or EOF is reached.
|
2015-01-05 03:09:51 +01:00
|
|
|
static int file_read( lua_State* L )
|
2014-12-31 06:53:26 +01:00
|
|
|
{
|
2016-10-28 16:32:12 +02:00
|
|
|
unsigned need_len = FILE_READ_CHUNK;
|
2014-12-31 06:53:26 +01:00
|
|
|
int16_t end_char = EOF;
|
|
|
|
size_t el;
|
2016-11-08 21:02:51 +01:00
|
|
|
|
|
|
|
GET_FILE_OBJ;
|
|
|
|
|
|
|
|
if( lua_type( L, argpos ) == LUA_TNUMBER )
|
2014-12-31 06:53:26 +01:00
|
|
|
{
|
2016-11-08 21:02:51 +01:00
|
|
|
need_len = ( unsigned )luaL_checkinteger( L, argpos );
|
2014-12-31 06:53:26 +01:00
|
|
|
}
|
2016-11-08 21:02:51 +01:00
|
|
|
else if(lua_isstring(L, argpos))
|
2014-12-31 06:53:26 +01:00
|
|
|
{
|
2016-11-08 21:02:51 +01:00
|
|
|
const char *end = luaL_checklstring( L, argpos, &el );
|
2014-12-31 06:53:26 +01:00
|
|
|
if(el!=1){
|
|
|
|
return luaL_error( L, "wrong arg range" );
|
|
|
|
}
|
|
|
|
end_char = (int16_t)end[0];
|
|
|
|
}
|
|
|
|
|
2016-11-08 21:02:51 +01:00
|
|
|
return file_g_read(L, need_len, end_char, fd);
|
2014-12-31 06:53:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Lua: readline()
|
2015-01-05 03:09:51 +01:00
|
|
|
static int file_readline( lua_State* L )
|
2014-12-31 06:53:26 +01:00
|
|
|
{
|
2016-11-08 21:02:51 +01:00
|
|
|
GET_FILE_OBJ;
|
|
|
|
|
2017-12-19 18:32:23 +01:00
|
|
|
return file_g_read(L, FILE_READ_CHUNK, '\n', fd);
|
2014-12-31 06:53:26 +01:00
|
|
|
}
|
|
|
|
|
2019-04-11 23:17:00 +02:00
|
|
|
// Lua: getfile(filename)
|
|
|
|
static int file_getfile( lua_State* L )
|
|
|
|
{
|
|
|
|
// Warning this code C calls other file_* routines to avoid duplication code. These
|
2019-05-01 19:29:11 +02:00
|
|
|
// use Lua stack addressing of arguments, so this does Lua stack maniplation to
|
2019-04-11 23:17:00 +02:00
|
|
|
// align these
|
|
|
|
int ret_cnt = 0;
|
|
|
|
lua_settop(L ,1);
|
|
|
|
// Stack [1] = FD
|
2019-05-01 19:29:11 +02:00
|
|
|
file_open(L);
|
2019-04-11 23:17:00 +02:00
|
|
|
// Stack [1] = filename; [2] = FD or nil
|
|
|
|
if (!lua_isnil(L, -1)) {
|
|
|
|
lua_remove(L, 1); // dump filename, so [1] = FD
|
|
|
|
file_fd_ud *ud = (file_fd_ud *)luaL_checkudata(L, 1, "file.obj");
|
2019-05-01 19:29:11 +02:00
|
|
|
ret_cnt = file_g_read(L, LUAI_MAXINT32, EOF, ud->fd);
|
|
|
|
// Stack [1] = FD; [2] = contents if ret_cnt = 1;
|
|
|
|
file_close(L); // leaves Stack unchanged if [1] = FD
|
2019-04-11 23:17:00 +02:00
|
|
|
lua_remove(L, 1); // Dump FD leaving contents as [1] / ToS
|
|
|
|
}
|
|
|
|
return ret_cnt;
|
|
|
|
}
|
|
|
|
|
2014-12-22 12:35:05 +01:00
|
|
|
// Lua: write("string")
|
2015-01-05 03:09:51 +01:00
|
|
|
static int file_write( lua_State* L )
|
2014-12-22 12:35:05 +01:00
|
|
|
{
|
2016-11-08 21:02:51 +01:00
|
|
|
GET_FILE_OBJ;
|
|
|
|
|
|
|
|
if(!fd)
|
2014-12-22 12:35:05 +01:00
|
|
|
return luaL_error(L, "open a file first");
|
|
|
|
size_t l, rl;
|
2016-11-08 21:02:51 +01:00
|
|
|
const char *s = luaL_checklstring(L, argpos, &l);
|
|
|
|
rl = vfs_write(fd, s, l);
|
2014-12-22 12:35:05 +01:00
|
|
|
if(rl==l)
|
|
|
|
lua_pushboolean(L, 1);
|
|
|
|
else
|
|
|
|
lua_pushnil(L);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lua: writeline("string")
|
2015-01-05 03:09:51 +01:00
|
|
|
static int file_writeline( lua_State* L )
|
2014-12-22 12:35:05 +01:00
|
|
|
{
|
2016-11-08 21:02:51 +01:00
|
|
|
GET_FILE_OBJ;
|
|
|
|
|
|
|
|
if(!fd)
|
2014-12-22 12:35:05 +01:00
|
|
|
return luaL_error(L, "open a file first");
|
|
|
|
size_t l, rl;
|
2016-11-08 21:02:51 +01:00
|
|
|
const char *s = luaL_checklstring(L, argpos, &l);
|
|
|
|
rl = vfs_write(fd, s, l);
|
2014-12-22 12:35:05 +01:00
|
|
|
if(rl==l){
|
2016-11-08 21:02:51 +01:00
|
|
|
rl = vfs_write(fd, "\n", 1);
|
2014-12-22 12:35:05 +01:00
|
|
|
if(rl==1)
|
|
|
|
lua_pushboolean(L, 1);
|
|
|
|
else
|
|
|
|
lua_pushnil(L);
|
|
|
|
}
|
|
|
|
else{
|
|
|
|
lua_pushnil(L);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2019-04-11 23:17:00 +02:00
|
|
|
// Lua: getfile(filename)
|
|
|
|
static int file_putfile( lua_State* L )
|
|
|
|
{
|
|
|
|
// Warning this code C calls other file_* routines to avoid duplication code. These
|
2019-05-01 19:29:11 +02:00
|
|
|
// use Lua stack addressing of arguments, so this does Lua stack maniplation to
|
2019-04-11 23:17:00 +02:00
|
|
|
// align these
|
|
|
|
int ret_cnt = 0;
|
|
|
|
lua_settop(L, 2);
|
|
|
|
lua_pushvalue(L, 2); //dup contents onto the ToS [3]
|
|
|
|
lua_pushliteral(L, "w+");
|
|
|
|
lua_replace(L, 2);
|
2019-05-01 19:29:11 +02:00
|
|
|
// Stack [1] = filename; [2] "w+" [3] contents;
|
|
|
|
file_open(L);
|
|
|
|
// Stack [1] = filename; [2] "w+" [3] contents; [4] FD or nil
|
2019-04-11 23:17:00 +02:00
|
|
|
|
|
|
|
if (!lua_isnil(L, -1)) {
|
|
|
|
lua_remove(L, 2); //dump "w+" attribute literal
|
|
|
|
lua_replace(L, 1);
|
2019-05-01 19:29:11 +02:00
|
|
|
// Stack [1] = FD; [2] contents
|
2019-04-11 23:17:00 +02:00
|
|
|
file_write(L);
|
2019-05-01 19:29:11 +02:00
|
|
|
// Stack [1] = FD; [2] contents; [3] result status
|
2019-04-11 23:17:00 +02:00
|
|
|
lua_remove(L, 2); //dump contents
|
|
|
|
file_close(L);
|
|
|
|
lua_remove(L, 1); // Dump FD leaving status as ToS
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
// Lua: fsinfo()
|
|
|
|
static int file_fsinfo( lua_State* L )
|
2016-01-12 07:53:45 +01:00
|
|
|
{
|
2019-07-23 21:46:49 +02:00
|
|
|
uint32_t total, used;
|
2016-09-05 20:17:13 +02:00
|
|
|
if (vfs_fsinfo("", &total, &used)) {
|
|
|
|
return luaL_error(L, "file system failed");
|
|
|
|
}
|
|
|
|
NODE_DBG("total: %d, used:%d\n", total, used);
|
|
|
|
if(total>0x7FFFFFFF || used>0x7FFFFFFF || used > total)
|
|
|
|
{
|
|
|
|
return luaL_error(L, "file system error");
|
|
|
|
}
|
|
|
|
lua_pushinteger(L, total-used);
|
|
|
|
lua_pushinteger(L, used);
|
|
|
|
lua_pushinteger(L, total);
|
|
|
|
return 3;
|
2016-01-12 07:53:45 +01:00
|
|
|
}
|
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
typedef struct {
|
|
|
|
vfs_vol *vol;
|
|
|
|
} volume_type;
|
|
|
|
|
|
|
|
// Lua: vol = file.mount("/SD0")
|
|
|
|
static int file_mount( lua_State *L )
|
|
|
|
{
|
|
|
|
const char *ldrv = luaL_checkstring( L, 1 );
|
|
|
|
int num = luaL_optint( L, 2, -1 );
|
|
|
|
volume_type *vol = (volume_type *)lua_newuserdata( L, sizeof( volume_type ) );
|
|
|
|
|
|
|
|
if (vol->vol = vfs_mount( ldrv, num )) {
|
|
|
|
/* set its metatable */
|
2018-11-16 21:37:18 +01:00
|
|
|
luaL_getmetatable(L, "file.vol");
|
2016-09-05 20:17:13 +02:00
|
|
|
lua_setmetatable(L, -2);
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
// remove created userdata
|
|
|
|
lua_pop( L, 1 );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lua: success = file.chdir("/SD0/")
|
|
|
|
static int file_chdir( lua_State *L )
|
|
|
|
{
|
|
|
|
const char *path = luaL_checkstring( L, 1 );
|
|
|
|
|
|
|
|
lua_pushboolean( L, 0 <= vfs_chdir( path ) );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int file_vol_umount( lua_State *L )
|
|
|
|
{
|
|
|
|
volume_type *vol = luaL_checkudata( L, 1, "file.vol" );
|
|
|
|
luaL_argcheck( L, vol, 1, "volume expected" );
|
|
|
|
|
|
|
|
lua_pushboolean( L, 0 <= vfs_umount( vol->vol ) );
|
|
|
|
|
|
|
|
// invalidate vfs descriptor, it has been free'd anyway
|
|
|
|
vol->vol = NULL;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-27 02:13:38 +02:00
|
|
|
LROT_BEGIN(file_obj, NULL, LROT_MASK_GC_INDEX)
|
|
|
|
LROT_FUNCENTRY( __gc, file_obj_free )
|
|
|
|
LROT_TABENTRY( __index, file_obj )
|
2019-05-08 13:08:20 +02:00
|
|
|
LROT_FUNCENTRY( close, file_close )
|
|
|
|
LROT_FUNCENTRY( read, file_read )
|
|
|
|
LROT_FUNCENTRY( readline, file_readline )
|
|
|
|
LROT_FUNCENTRY( write, file_write )
|
|
|
|
LROT_FUNCENTRY( writeline, file_writeline )
|
|
|
|
LROT_FUNCENTRY( seek, file_seek )
|
|
|
|
LROT_FUNCENTRY( flush, file_flush )
|
2020-04-27 02:13:38 +02:00
|
|
|
LROT_END(file_obj, NULL, LROT_MASK_GC_INDEX)
|
2019-05-08 13:08:20 +02:00
|
|
|
|
|
|
|
|
2020-04-27 02:13:38 +02:00
|
|
|
LROT_BEGIN(file_vol, NULL, LROT_MASK_INDEX)
|
2019-05-08 13:08:20 +02:00
|
|
|
LROT_TABENTRY( __index, file_vol )
|
2020-04-27 02:13:38 +02:00
|
|
|
LROT_FUNCENTRY( umount, file_vol_umount )
|
|
|
|
LROT_END(file_vol, NULL, LROT_MASK_INDEX)
|
2019-05-08 13:08:20 +02:00
|
|
|
|
|
|
|
// Module function map
|
2020-04-27 02:13:38 +02:00
|
|
|
LROT_BEGIN(file, NULL, 0)
|
2019-05-08 13:08:20 +02:00
|
|
|
LROT_FUNCENTRY( list, file_list )
|
|
|
|
LROT_FUNCENTRY( open, file_open )
|
|
|
|
LROT_FUNCENTRY( close, file_close )
|
|
|
|
LROT_FUNCENTRY( write, file_write )
|
|
|
|
LROT_FUNCENTRY( writeline, file_writeline )
|
|
|
|
LROT_FUNCENTRY( read, file_read )
|
|
|
|
LROT_FUNCENTRY( readline, file_readline )
|
2020-04-27 02:13:38 +02:00
|
|
|
#ifdef BUILD_SPIFFS
|
|
|
|
LROT_FUNCENTRY( format, file_format )
|
|
|
|
LROT_FUNCENTRY( fscfg, file_fscfg )
|
|
|
|
#endif
|
2019-05-08 13:08:20 +02:00
|
|
|
LROT_FUNCENTRY( remove, file_remove )
|
|
|
|
LROT_FUNCENTRY( seek, file_seek )
|
|
|
|
LROT_FUNCENTRY( flush, file_flush )
|
|
|
|
LROT_FUNCENTRY( rename, file_rename )
|
|
|
|
LROT_FUNCENTRY( exists, file_exists )
|
|
|
|
LROT_FUNCENTRY( getcontents, file_getfile )
|
|
|
|
LROT_FUNCENTRY( putcontents, file_putfile )
|
|
|
|
LROT_FUNCENTRY( fsinfo, file_fsinfo )
|
|
|
|
LROT_FUNCENTRY( on, file_on )
|
|
|
|
LROT_FUNCENTRY( stat, file_stat )
|
2020-04-27 02:13:38 +02:00
|
|
|
#ifdef BUILD_FATFS
|
|
|
|
LROT_FUNCENTRY( mount, file_mount )
|
|
|
|
LROT_FUNCENTRY( chdir, file_chdir )
|
|
|
|
#endif
|
|
|
|
LROT_END(file, NULL, 0)
|
2019-05-08 13:08:20 +02:00
|
|
|
|
2014-12-22 12:35:05 +01:00
|
|
|
|
2016-09-05 20:17:13 +02:00
|
|
|
int luaopen_file( lua_State *L ) {
|
2020-10-18 20:46:47 +02:00
|
|
|
int startup_option = platform_rcr_get_startup_option();
|
|
|
|
if ((startup_option & STARTUP_OPTION_DELAY_MOUNT) == 0) {
|
|
|
|
do_flash_mount();
|
|
|
|
} else {
|
|
|
|
myspiffs_set_automount(do_flash_mount);
|
2019-05-19 15:38:23 +02:00
|
|
|
}
|
2019-05-08 13:08:20 +02:00
|
|
|
luaL_rometatable( L, "file.vol", LROT_TABLEREF(file_vol));
|
|
|
|
luaL_rometatable( L, "file.obj", LROT_TABLEREF(file_obj));
|
2016-09-05 20:17:13 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-05-08 13:08:20 +02:00
|
|
|
NODEMCU_MODULE(FILE, "file", file, luaopen_file);
|