/* ; Project: Open Vehicle Monitor System ; Date: 14th March 2017 ; ; Changes: ; 1.0 Initial release ; ; (C) 2011 Michael Stegen / Stegen Electronics ; (C) 2011-2017 Mark Webb-Johnson ; (C) 2011 Sonny Chen @ EPRO/DX ; ; Permission is hereby granted, free of charge, to any person obtaining a copy ; of this software and associated documentation files (the "Software"), to deal ; in the Software without restriction, including without limitation the rights ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ; copies of the Software, and to permit persons to whom the Software is ; furnished to do so, subject to the following conditions: ; ; The above copyright notice and this permission notice shall be included in ; all copies or substantial portions of the Software. ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ; THE SOFTWARE. */ #include "ovms_log.h" static const char *TAG = "vfs"; #include #include #include #include #include #include #include #include #include #include #include "ovms_vfs.h" #include "ovms_config.h" #include "ovms_command.h" #include "ovms_peripherals.h" #include "crypt_md5.h" #ifdef CONFIG_OVMS_COMP_EDITOR #include "vfsedit.h" #endif // #ifdef CONFIG_OVMS_COMP_EDITOR /** * This class is a kind of wrapper around dirent */ class Direntry { public: std::string basepath; // base path of the file / directory (parent dir) - always ending with '/' std::string name; // name of the file / dirctory bool is_dir; // `true`: is a directory, `false`: is a file bool is_protected; // `true`: this path is protected bool is_skip; // `true`: we should not analyze this entry int64_t size; // size time_t mtime; // modification time /** * Default constructor, calls stat() on the file. */ Direntry(std::string basepath, struct dirent *direntry) { struct stat st; this->basepath = basepath + '/'; name = direntry->d_name; is_protected = MyConfig.ProtectedPath(path()); stat(path().c_str(), &st); is_dir = S_ISDIR(st.st_mode); is_skip = (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) || (direntry->d_name[0] == '.'); size = st.st_size; mtime = st.st_mtime; } std::string path() const { return basepath + name; } bool operator<(const Direntry &other) const { static std::less const less; return less(path(), other.path()); } void display(OvmsWriter* writer, bool show_full_path) { char bufsize[64], mod[64]; if (is_dir) { if (is_protected) { strcpy(bufsize, "[P][DIR]"); } else { strcpy(bufsize, "[DIR] "); } } else { if (is_protected) { strcpy(bufsize, "[P] "); } else { if (size < 1024) { snprintf(bufsize, sizeof(bufsize), "%d ", (int) size); } else if (size < 0x100000) { snprintf(bufsize, sizeof(bufsize), "%.1fk", (double) size / 1024.0); } else if (size < 0x40000000) { snprintf(bufsize, sizeof(bufsize), "%.1fM", (double) size / 1048576); } else { snprintf(bufsize, sizeof(bufsize), "%.1fG", (double) size / 1073741824); } } } strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&mtime)); const char *slash = is_dir ? "/" : ""; writer->printf("%8.8s %17.17s %s%s\n", bufsize, mod, show_full_path ? path().c_str() : name.c_str(), slash); } }; /** * Fills a `std::set` with all possible entries starting at `startpath`. * if `recurse` is `true`, then all possible directories are recursively entered. */ static void read_entries(std::set &entries, std::string startpath, bool recurse = true) { DIR *dir; struct dirent *dp; if ((dir = opendir(startpath.c_str())) != NULL) { while ((dp = readdir(dir)) != NULL) { Direntry dirent(startpath, dp); if (dirent.is_skip) { continue; } entries.insert(dirent); if (recurse && dirent.is_dir && !dirent.is_protected) { read_entries(entries, dirent.path()); } } closedir(dir); } } /** * Fills a `std::set` with all possible entries, and display the sorted result. * if `recurse` is `true`: * - all possible directories are recursively entered, * - the display shows the full file path * if `recurse` is `false`: * - only `startpath` is listed, * - the display shows only file / directory name */ static void list_entries(OvmsWriter* writer, std::string startpath, bool recurse = true) { std::set entries; while((startpath.back() == '/') || (startpath.back() == '.')) { startpath.pop_back(); } if (MyConfig.ProtectedPath(startpath)) { writer->puts("Error: protected path"); return; } read_entries(entries, startpath, recurse); for (auto it = entries.begin(); it != entries.end(); it++) { Direntry dirent = *it; dirent.display(writer, recurse); } } /** * Recursive listing of the files and directories starting at `argv[0]` (or '.') * - all possible directories are recursively entered, * - the display shows the full file / directory path * - protected directories are identified and not entered */ void vfs_rls(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { list_entries(writer, (argc==0) ? "." : argv[0], true); } /** * Listing of the files and directories at `argv[0]` (or '.') * - the display shows only the file / directory name * - protected directories are identified */ void vfs_ls(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { list_entries(writer, (argc==0) ? "." : argv[0], false); } void vfs_cat(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (MyConfig.ProtectedPath(argv[0])) { writer->puts("Error: protected path"); return; } FILE* f = fopen(argv[0], "r"); if (f == NULL) { writer->puts("Error: VFS file cannot be opened"); return; } char buf[512]; while(size_t n = fread(buf, sizeof(char), sizeof(buf), f)) { writer->write(buf, n); } fclose(f); } void vfs_head(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { const char *filename = NULL; int nrlines = 20; for (int i=0; iPutUsage(writer); return; } nrlines = v; } else filename = argv[i]; } if (!filename) { writer->puts("Error: no filename given"); return; } if (MyConfig.ProtectedPath(filename)) { writer->puts("Error: protected path"); return; } FILE* f = fopen(filename, "r"); if (f == NULL) { writer->puts("Error: VFS file cannot be opened"); return; } char buf[512]; int k =0; while (fgets(buf,sizeof(buf),f)!=NULL) { writer->write(buf,strlen(buf)); if (++k >= nrlines) break; } fclose(f); } void vfs_stat(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (MyConfig.ProtectedPath(argv[0])) { writer->puts("Error: protected path"); return; } FILE* f = fopen(argv[0], "r"); if (f == NULL) { writer->puts("Error: VFS file cannot be opened"); return; } OVMS_MD5_CTX* md5 = new OVMS_MD5_CTX; OVMS_MD5_Init(md5); int filesize = 0; uint8_t *buf = new uint8_t[512]; while(size_t n = fread(buf, sizeof(char), 512, f)) { filesize += n; OVMS_MD5_Update(md5, buf, n); } uint8_t* rmd5 = new uint8_t[16]; OVMS_MD5_Final(rmd5, md5); char dchecksum[33]; sprintf(dchecksum,"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", rmd5[0],rmd5[1],rmd5[2],rmd5[3],rmd5[4],rmd5[5],rmd5[6],rmd5[7], rmd5[8],rmd5[9],rmd5[10],rmd5[11],rmd5[12],rmd5[13],rmd5[14],rmd5[15]); writer->printf("File %s size is %d and digest %s\n", argv[0],filesize,dchecksum); delete [] rmd5; delete [] buf; fclose(f); } void vfs_rm(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (MyConfig.ProtectedPath(argv[0])) { writer->puts("Error: protected path"); return; } if (unlink(argv[0]) == 0) { writer->puts("VFS File deleted"); } else { writer->puts("Error: Could not delete VFS file"); } } void vfs_mv(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (MyConfig.ProtectedPath(argv[0])) { writer->puts("Error: protected path"); return; } if (MyConfig.ProtectedPath(argv[1])) { writer->puts("Error: protected path"); return; } if (rename(argv[0],argv[1]) == 0) { writer->puts("VFS File renamed"); } else { writer->puts("Error: Could not rename VFS file"); } } void vfs_mkdir(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (MyConfig.ProtectedPath(argv[0])) { writer->puts("Error: protected path"); return; } if (mkdir(argv[0],0) == 0) { writer->puts("VFS directory created"); } else { writer->puts("Error: Could not create VFS directory"); } } void vfs_rmdir(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (MyConfig.ProtectedPath(argv[0])) { writer->puts("Error: protected path"); return; } if (rmdir(argv[0]) == 0) { writer->puts("VFS directory removed"); } else { writer->puts("Error: Could not remove VFS directory"); } } void vfs_cp(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (MyConfig.ProtectedPath(argv[0])) { writer->puts("Error: protected path"); return; } if (MyConfig.ProtectedPath(argv[1])) { writer->puts("Error: protected path"); return; } FILE* f = fopen(argv[0], "r"); if (f == NULL) { writer->puts("Error: VFS source file cannot be opened"); return; } FILE* w = fopen(argv[1], "w"); if (w == NULL) { writer->puts("Error: VFS target file cannot be opened"); fclose(f); return; } char buf[512]; while(size_t n = fread(buf, sizeof(char), sizeof(buf), f)) { fwrite(buf,n,1,w); } fclose(w); fclose(f); writer->puts("VFS copy complete"); } void vfs_append(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { if (MyConfig.ProtectedPath(argv[1])) { writer->puts("Error: protected path"); return; } FILE* w = fopen(argv[1], "a"); if (w == NULL) { writer->puts("Error: VFS target file cannot be opened"); return; } int len = strlen(argv[0]); fwrite(argv[0], len, 1, w); fwrite("\n", 1, 1, w); fclose(w); } class VfsTailCommand : public OvmsCommandTask { using OvmsCommandTask::OvmsCommandTask; public: char* filename = NULL; int nrlines = 0; int fd = -1; char buf[128]; off_t fpos; ssize_t len; OvmsCommandState_t Prepare() { // parse args: for (int i=0; iPutUsage(writer); return OCS_Error; } nrlines = v; } else filename = argv[i]; } // check file: if (!filename || !*filename || MyConfig.ProtectedPath(filename)) { writer->puts("Error: invalid/protected path"); return OCS_Error; } fd = open(filename, O_RDONLY|O_NONBLOCK); if (fd == -1) { writer->puts("Error: VFS file cannot be opened"); return OCS_Error; } // seek nrlines back from end of file: fpos = lseek(fd, 0, SEEK_END); int lcnt = ((nrlines > 0) ? nrlines : 10) + 1; while (fpos > 0 && lcnt > 0) { ssize_t rlen = MIN(fpos, sizeof(buf)); fpos = lseek(fd, fpos-rlen, SEEK_SET); len = read(fd, buf, rlen); for (int i=len-1; i>=0; i--) { if (buf[i] == '\n' && (--lcnt == 0)) { fpos += i + 1; break; } } } // determine run mode: if (nrlines <= 0 && writer->IsInteractive()) { writer->puts("[tail: in follow mode, press Ctrl-C to abort]"); return OCS_RunLoop; } return OCS_RunOnce; } void Service() { while (true) { // Note: the esp-idf libs do currently not provide read/seek into // data appended by other tasks, we need to reopen for each check if (fd == -1) fd = open(filename, O_RDONLY|O_NONBLOCK); if (fd == -1) { writer->puts("[tail: file lost, abort]"); break; } lseek(fd, fpos, SEEK_SET); while ((len = read(fd, buf, sizeof buf)) > 0) writer->write(buf, len); fpos = lseek(fd, 0, SEEK_CUR); close(fd); fd = -1; // done/abort? if (m_state != OCS_RunLoop) break; vTaskDelay(pdMS_TO_TICKS(250)); } } static void Execute(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { VfsTailCommand* me = new VfsTailCommand(verbosity, writer, cmd, argc, argv); if (me) me->Run(); } }; void vfs_df(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) { FATFS *fs; DWORD fre_clust; uint64_t fre_sect, tot_sect; int sector_size; uint64_t size, free; // drive "0:" = /store if (f_getfree("0:", &fre_clust, &fs) == FR_OK) { tot_sect = (fs->n_fatent - 2) * fs->csize; fre_sect = fre_clust * fs->csize; #if FF_MAX_SS != FF_MIN_SS sector_size = fs->ssize; #else sector_size = 512; #endif size = ((uint64_t) tot_sect) * sector_size / 1024; free = ((uint64_t) fre_sect) * sector_size / 1024; writer->printf("/store: Size: %6llu kB Avail: %6llu kB Used: %3d%%\n", size, free, (int)(100 - fre_sect * 100 / tot_sect)); } else { writer->puts("ERROR: can't get df info for /store"); } #ifdef CONFIG_OVMS_COMP_SDCARD // drive "1:" = /sd sdmmc_card_t* card = MyPeripherals->m_sdcard->m_card; if (card && f_getfree("1:", &fre_clust, &fs) == FR_OK) { tot_sect = (fs->n_fatent - 2) * fs->csize; fre_sect = fre_clust * fs->csize; sector_size = card->csd.sector_size; size = ((uint64_t) tot_sect) * sector_size / (1024 * 1024); free = ((uint64_t) fre_sect) * sector_size / (1024 * 1024); writer->printf("/sd : Size: %6llu MB Avail: %6llu MB Used: %3d%%\n", size, free, (int)(100 - fre_sect * 100 / tot_sect)); } #endif // #ifdef CONFIG_OVMS_COMP_SDCARD } class VfsInit { public: VfsInit(); } MyVfsInit __attribute__ ((init_priority (5200))); VfsInit::VfsInit() { ESP_LOGI(TAG, "Initialising VFS (5200)"); OvmsCommand* cmd_vfs = MyCommandApp.RegisterCommand("vfs","Virtual File System framework"); cmd_vfs->RegisterCommand("ls","VFS Directory Listing",vfs_ls, "[]", 0, 1); cmd_vfs->RegisterCommand("rls","VFS Recursive Directory Listing",vfs_rls, "[]", 0, 1); cmd_vfs->RegisterCommand("cat","VFS Display a file",vfs_cat, "", 1, 1); cmd_vfs->RegisterCommand("head","VFS Display first 20 lines of a file",vfs_head, "[-nrlines] ", 1, 2); cmd_vfs->RegisterCommand("stat","VFS Status of a file",vfs_stat, "", 1, 1); cmd_vfs->RegisterCommand("mkdir","VFS Create a directory",vfs_mkdir, "", 1, 1); cmd_vfs->RegisterCommand("rmdir","VFS Delete a directory",vfs_rmdir, "", 1, 1); cmd_vfs->RegisterCommand("rm","VFS Delete a file",vfs_rm, "", 1, 1); cmd_vfs->RegisterCommand("mv","VFS Rename a file",vfs_mv, " ", 2, 2); cmd_vfs->RegisterCommand("cp","VFS Copy a file",vfs_cp, " ", 2, 2); cmd_vfs->RegisterCommand("append","VFS Append a line to a file",vfs_append, " ", 2, 2); cmd_vfs->RegisterCommand("tail","VFS output tail of a file",VfsTailCommand::Execute, "[-nrlines] ", 1, 2); cmd_vfs->RegisterCommand("df","VFS show disk usage", vfs_df, "", 0, 0); #ifdef CONFIG_OVMS_COMP_EDITOR cmd_vfs->RegisterCommand("edit","VFS edit a file",vfs_edit, "", 1, 1); #endif // #ifdef CONFIG_OVMS_COMP_EDITOR }