/******************************************************************** ** Copyright (c) 2018-2020 Guan Wenliang ** This file is part of the Berry default interpreter. ** skiars@qq.com, https://github.com/Skiars/berry ** See Copyright Notice in the LICENSE file or at ** https://github.com/Skiars/berry/blob/master/LICENSE ********************************************************************/ #include "berry.h" #include "be_mem.h" #include "be_sys.h" // #include #include #include // from https://github.com/eyalroz/cpp-static-block #include "static_block.hpp" // Local pointer for file managment #ifdef USE_UFILESYS #include #include #include "ZipReadFS.h" extern FS *ffsp; FS zip_ufsp(ZipReadFSImplPtr(new ZipReadFSImpl(&ffsp))); #endif // USE_UFILESYS /* this file contains configuration for the file system. */ /* standard input and output */ extern "C" { int strncmp_PP(const char * str1P, const char * str2P, size_t size) { int result = 0; while (size > 0) { char ch1 = pgm_read_byte(str1P++); char ch2 = pgm_read_byte(str2P++); result = ch1 - ch2; if (result != 0 || ch2 == '\0') { break; } size--; } return result; } // char * strchr_P(const char *s, int c) { do { if (pgm_read_byte(s) == c) { return (char*)s; } } while (pgm_read_byte(s++)); return (0); } } // We need to create a local buffer, since we might mess up mqtt_data #ifndef BERRY_LOGSZ #define BERRY_LOGSZ 700 #endif static char * log_berry_buffer = nullptr; static_block { log_berry_buffer = (char*) malloc(BERRY_LOGSZ); if (log_berry_buffer) log_berry_buffer[0] = 0; } extern void berry_log(const char * berry_buf); BERRY_API void be_writebuffer(const char *buffer, size_t length) { if (!log_berry_buffer) return; if (buffer == nullptr || length == 0) { return; } uint32_t idx = 0; while (idx < length) { int32_t cr_pos = -1; // find next occurence of '\n' or '\r' for (uint32_t i = idx; i < length; i++) { if ((pgm_read_byte(&buffer[i]) == '\n') || (pgm_read_byte(&buffer[i]) == '\r')) { cr_pos = i; break; } } uint32_t chars_to_append = (cr_pos >= 0) ? cr_pos - idx : length - idx; // note cr_pos < length snprintf(log_berry_buffer, BERRY_LOGSZ, "%s%.*s", log_berry_buffer, chars_to_append, &buffer[idx]); // append at most `length` chars if (cr_pos >= 0) { // flush berry_log(log_berry_buffer); log_berry_buffer[0] = 0; // clear string } idx += chars_to_append + 1; // skip '\n' } // Serial.write(buffer, length); // be_fwrite(stdout, buffer, length); } // provides MPATH_ constants #include "be_port.h" extern "C" { // this combined action is called from be_path_tasmota_lib.c // by using a single function, we save >200 bytes of flash // by reducing code repetition. int _m_path_action(bvm *vm, int8_t action){ #ifdef USE_UFILESYS // if this changes to not -1, we push it as a bool int res = -1; // this tells us to return the vm, not nil int returnit = 0; // comment out if nil return is OK to save some flash switch (action){ case MPATH_EXISTS: case MPATH_REMOVE: res = 0; break; } int argc = be_top(vm); if (argc >= 1 && be_isstring(vm, 1)) { const char *path = be_tostring(vm, 1); if (path != nullptr) { switch (action){ case MPATH_EXISTS: res = be_isexist(path); break; case MPATH_REMOVE: res = be_unlink(path); break; case MPATH_RMDIR: res = zip_ufsp.rmdir(path); break; case MPATH_MKDIR: res = zip_ufsp.mkdir(path); break; case MPATH_RENAME: { if (argc >= 2 && be_isstring(vm, 2)) { const char *path2 = be_tostring(vm, 2); res = zip_ufsp.rename(path, path2); } else { res = -1; } } break; case MPATH_LISTDIR: be_newobject(vm, "list"); // add our list object and fall through returnit = 1; case MPATH_ISDIR: case MPATH_MODIFIED: { //isdir needs to open the file, listdir does not // we use be_fopen because it pre-pends with '/'. // without this TAS fails to find stuff at boot... File *dir = (File *)be_fopen(path, "r"); if (dir) { String fpath; String fname; switch (action){ case MPATH_LISTDIR: dir->seekDir(0); fpath = dir->getNextFileName(); while (fpath.length() != 0) { fname = fpath.substring(fpath.lastIndexOf("/") + 1); const char * fn = fname.c_str(); be_pushstring(vm, fn); be_data_push(vm, -2); be_pop(vm, 1); fpath = dir->getNextFileName(); } break; case MPATH_ISDIR: // push bool belowthe only one to push an int, so do it here. res = dir->isDirectory(); break; case MPATH_MODIFIED: // the only one to push an int, so do it here. be_pushint(vm, dir->getLastWrite()); returnit = 1; break; } be_fclose(dir); } } break; } // these switch (action){ case MPATH_LISTDIR: // if it was list, pop always be_pop(vm, 1); break; } } // invalid filename -> nil return } // not a string, or no arg -> nil return unless see below // if we get here, and it was exists or remove, return false always // i.e. it's false for no filename or null filename. // if it was a boolean result if (res != -1){ be_pushbool(vm, res); returnit = 1; } if (returnit){ be_return(vm); } #endif // USE_UFILESYS be_return_nil(vm); } } BERRY_API char* be_readstring(char *buffer, size_t size) { if ((size > 0) && (buffer != NULL)) { *buffer = 0; } return buffer; } /* use the standard library implementation file API. */ #ifdef USE_UFILESYS void* be_fopen(const char *filename, const char *modes) { if (filename != nullptr && modes != nullptr) { char fname2[strlen(filename) + 2]; if (filename[0] == '/') { strcpy(fname2, filename); // copy unchanged } else { fname2[0] = '/'; strcpy(fname2 + 1, filename); // prepend with '/' } // Serial.printf("be_fopen filename=%s, modes=%s\n", filename, modes); File f = zip_ufsp.open(fname2, modes); // returns an object, not a pointer if (f) { File * f_ptr = new File(f); // copy to dynamic object *f_ptr = f; // TODO is this necessary? return f_ptr; } } return nullptr; // return fopen(filename, modes); } #endif // USE_UFILESYS #ifdef USE_UFILESYS // Tasmota specific, get the underlying Arduino File File * be_get_arduino_file(void *hfile) { if (hfile != nullptr) { File * f_ptr = (File*) hfile; return f_ptr; } return nullptr; // return fopen(filename, modes); } #endif int be_fclose(void *hfile) { #ifdef USE_UFILESYS // Serial.printf("be_fclose\n"); if (hfile != nullptr) { File * f_ptr = (File*) hfile; f_ptr->close(); delete f_ptr; return 0; } #endif // USE_UFILESYS return -1; // return fclose(hfile); } size_t be_fwrite(void *hfile, const void *buffer, size_t length) { #ifdef USE_UFILESYS // Serial.printf("be_fwrite %d\n", length); if (hfile != nullptr && buffer != nullptr) { File * f_ptr = (File*) hfile; return f_ptr->write((const uint8_t*) buffer, length); } #endif // USE_UFILESYS return 0; // return fwrite(buffer, 1, length, hfile); } size_t be_fread(void *hfile, void *buffer, size_t length) { #ifdef USE_UFILESYS // Serial.printf("be_fread %d\n", length); if (hfile != nullptr && buffer != nullptr) { File * f_ptr = (File*) hfile; int32_t ret = f_ptr->read((uint8_t*) buffer, length); if (ret >= 0) { // Serial.printf("be_fread ret = %d\n", ret); return ret; } } #endif // USE_UFILESYS return 0; // return fread(buffer, 1, length, hfile); } char* be_fgets(void *hfile, void *buffer, int size) { #ifdef USE_UFILESYS if (size <= 2) { return nullptr; } // can't work if size is 2 or less // Serial.printf("be_fgets size=%d hfile=%p buf=%p\n", size, hfile, buffer); uint8_t * buf = (uint8_t*) buffer; if (hfile != nullptr && buffer != nullptr && size > 0) { File * f_ptr = (File*) hfile; int ret = f_ptr->readBytesUntil('\n', buf, size - 2); // Serial.printf("be_fgets ret=%d\n", ret); if (ret >= 0) { buf[ret] = 0; // add string terminator if (ret > 0 && ret < size - 2) { buf[ret] = '\n'; buf[ret+1] = 0; } return (char*) buffer; } } #endif // USE_UFILESYS return nullptr; // return fgets(buffer, size, hfile); } int be_fseek(void *hfile, long offset) { #ifdef USE_UFILESYS // Serial.printf("be_fseek %d\n", offset); if (hfile != nullptr) { File * f_ptr = (File*) hfile; if (f_ptr->seek(offset)) { return 0; // success } } #endif // USE_UFILESYS return -1; // return fseek(hfile, offset, SEEK_SET); } long int be_ftell(void *hfile) { #ifdef USE_UFILESYS // Serial.printf("be_ftell\n"); if (hfile != nullptr) { File * f_ptr = (File*) hfile; return f_ptr->position(); } #endif // USE_UFILESYS return 0; // return ftell(hfile); } long int be_fflush(void *hfile) { #ifdef USE_UFILESYS // Serial.printf("be_fflush\n"); if (hfile != nullptr) { File * f_ptr = (File*) hfile; f_ptr->flush(); } #endif // USE_UFILESYS return 0; // return fflush(hfile); } size_t be_fsize(void *hfile) { #ifdef USE_UFILESYS // Serial.printf("be_fsize\n"); if (hfile != nullptr) { File * f_ptr = (File*) hfile; return f_ptr->size(); } // long int size, offset = be_ftell(hfile); // fseek(hfile, 0L, SEEK_END); // size = ftell(hfile); // fseek(hfile, offset, SEEK_SET); // return size; #endif // USE_UFILESYS return 0; } extern "C" time_t be_last_modified(void *hfile) { #ifdef USE_UFILESYS if (hfile != nullptr) { File * f_ptr = (File*) hfile; return f_ptr->getLastWrite(); } #endif // USE_UFILESYS return 0; } int be_isexist(const char *filename) { #ifdef USE_UFILESYS char fname2[strlen(filename) + 2]; if (filename[0] == '/') { strcpy(fname2, filename); // copy unchanged } else { fname2[0] = '/'; strcpy(fname2 + 1, filename); // prepend with '/' } return zip_ufsp.exists(fname2); #endif // USE_UFILESYS return 0; } int be_unlink(const char *filename) { #ifdef USE_UFILESYS char fname2[strlen(filename) + 2]; if (filename[0] == '/') { strcpy(fname2, filename); // copy unchanged } else { fname2[0] = '/'; strcpy(fname2 + 1, filename); // prepend with '/' } return zip_ufsp.remove(fname2); #endif // USE_UFILESYS return 0; } /* format file system - erase everything */ extern "C" int be_format_fs(void) { #ifdef USE_UFILESYS return LittleFS.format(); #endif // USE_UFILESYS return 0; } #if BE_USE_FILE_SYSTEM #if defined(USE_FATFS) /* FatFs */ int be_isdir(const char *path) { FILINFO fno; FRESULT fr = f_stat(path, &fno); return fr == FR_OK && fno.fattrib & AM_DIR; } int be_isfile(const char *path) { FILINFO fno; FRESULT fr = f_stat(path, &fno); return fr == FR_OK && !(fno.fattrib & AM_DIR); } int be_isexist(const char *path) { FILINFO fno; return f_stat(path, &fno) == FR_OK; } char* be_getcwd(char *buf, size_t size) { FRESULT fr = f_getcwd(buf, (UINT)size); return fr == FR_OK ? buf : NULL; } int be_chdir(const char *path) { return f_chdir(path); } int be_mkdir(const char *path) { return f_mkdir(path); } int be_unlink(const char *filename) { return f_unlink(filename); } int be_dirfirst(bdirinfo *info, const char *path) { info->dir = be_os_malloc(sizeof(DIR)); info->file = be_os_malloc(sizeof(FILINFO)); if (info->dir && info->file) { FRESULT fr = f_opendir(info->dir, path); return fr == FR_OK ? be_dirnext(info) : 1; } be_os_free(info->dir); be_os_free(info->file); info->dir = NULL; info->file = NULL; return 1; } int be_dirnext(bdirinfo *info) { FRESULT fr = f_readdir(info->dir, info->file); info->name = ((FILINFO *)info->file)->fname; return fr != FR_OK || *info->name == '\0'; } int be_dirclose(bdirinfo *info) { if (info->dir) { int res = f_closedir(info->dir) != FR_OK; be_os_free(info->dir); be_os_free(info->file); return res; } return 1; } #elif defined(_MSC_VER) /* MSVC*/ #include #include #include int be_isdir(const char *path) { DWORD type = GetFileAttributes(path); return type != INVALID_FILE_ATTRIBUTES && (type & FILE_ATTRIBUTE_DIRECTORY) != 0; } int be_isfile(const char *path) { DWORD type = GetFileAttributes(path); return type != INVALID_FILE_ATTRIBUTES && (type & FILE_ATTRIBUTE_DIRECTORY) == 0; } int be_isexist(const char *path) { return GetFileAttributes(path) != INVALID_FILE_ATTRIBUTES; } char* be_getcwd(char *buf, size_t size) { return _getcwd(buf, (int)size); } int be_chdir(const char *path) { return _chdir(path); } int be_mkdir(const char *path) { return _mkdir(path); } int be_unlink(const char *filename) { return remove(filename); } int be_dirfirst(bdirinfo *info, const char *path) { char *buf = be_os_malloc(strlen(path) + 3); info->file = be_os_malloc(sizeof(struct _finddata_t)); info->dir = NULL; if (buf && info->file) { struct _finddata_t *cfile = info->file; strcat(strcpy(buf, path), "/*"); info->dir = (void *)_findfirst(buf, cfile); info->name = cfile->name; be_os_free(buf); return (intptr_t)info->dir == -1; } be_os_free(buf); return 1; } int be_dirnext(bdirinfo *info) { struct _finddata_t *cfile = info->file; int res = _findnext((intptr_t)info->dir, cfile) != 0; info->name = cfile->name; return res; } int be_dirclose(bdirinfo *info) { be_os_free(info->file); return _findclose((intptr_t)info->dir) != 0; } #else /* must be POSIX */ #include #include #include int be_isdir(const char *path) { struct stat path_stat; int res = stat(path, &path_stat); return res == 0 && S_ISDIR(path_stat.st_mode); } int be_isfile(const char *path) { struct stat path_stat; int res = stat(path, &path_stat); return res == 0 && !S_ISDIR(path_stat.st_mode); } int be_isexist(const char *path) { struct stat path_stat; return stat(path, &path_stat) == 0; } char* be_getcwd(char *buf, size_t size) { return getcwd(buf, size); } int be_chdir(const char *path) { return chdir(path); } int be_mkdir(const char *path) { #ifdef _WIN32 return mkdir(path); #else return mkdir(path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); #endif } int be_unlink(const char *filename) { return remove(filename); } int be_dirfirst(bdirinfo *info, const char *path) { info->dir = opendir(path); if (info->dir) { return be_dirnext(info); } return 1; } int be_dirnext(bdirinfo *info) { struct dirent *file; info->file = file = readdir(info->dir); if (file) { info->name = file->d_name; return 0; } return 1; } int be_dirclose(bdirinfo *info) { return closedir(info->dir) != 0; } #endif /* POSIX */ #endif /* BE_USE_OS_MODULE || BE_USE_FILE_SYSTEM */