Sync module files

This commit is contained in:
Carsten Schmiemann 2022-11-20 18:48:10 +01:00
parent 0db69f901c
commit 6c8ca654bc
10 changed files with 1409 additions and 2171 deletions

View file

@ -353,19 +353,19 @@ config OVMS_VEHICLE_CAN_RX_QUEUE_SIZE
help help
The size of the CAN bus RX queue (at the vehicle component). The size of the CAN bus RX queue (at the vehicle component).
config OVMS_VEHICLE_RENAULTZOE_PH2_OBD config OVMS_VEHICLE_RENAULTZOE_PH2_OBD
bool "Include support for Renault Zoe PH2 vehicles via OBD port (read-only)" bool "Include support for Renault Zoe PH2 vehicles via OBD port (read-only)"
default y default y
depends on OVMS depends on OVMS
help help
Enable to include support for Renault Zoe Ph2 vehicles via OBD port (read-only). Enable to include support for Renault Zoe Ph2 vehicles via OBD port (read-only).
config OVMS_VEHICLE_RENAULTZOE_PH2_CAN config OVMS_VEHICLE_RENAULTZOE_PH2_CAN
bool "Include support for Renault Zoe PH2 vehicles via direct CAN access after Core Can Gateway" bool "Include support for Renault Zoe PH2 vehicles via direct CAN access after Core Can Gateway"
default y default y
depends on OVMS depends on OVMS
help help
Enable to include support for Renault Zoe Ph2 vehicles via direct CAN access after Core Can Gateway. Enable to include support for Renault Zoe Ph2 vehicles via direct CAN access after Core Can Gateway.
endmenu # Vehicle Support endmenu # Vehicle Support

View file

@ -34,6 +34,7 @@ static const char *TAG = "boot";
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/xtensa_api.h" #include "freertos/xtensa_api.h"
#include "rom/rtc.h" #include "rom/rtc.h"
#include "rom/uart.h"
#include "soc/rtc_cntl_reg.h" #include "soc/rtc_cntl_reg.h"
#include "esp_system.h" #include "esp_system.h"
#include "esp_panic.h" #include "esp_panic.h"
@ -165,10 +166,18 @@ void boot_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc,
writer->printf("\nLast crash: "); writer->printf("\nLast crash: ");
if (boot_data.crash_data.is_abort) if (boot_data.crash_data.is_abort)
{ {
// Software controlled panic:
writer->printf("abort() was called on core %d\n", boot_data.crash_data.core_id); writer->printf("abort() was called on core %d\n", boot_data.crash_data.core_id);
if (boot_data.stack_overflow_taskname[0])
writer->printf(" Stack overflow in task %s\n", boot_data.stack_overflow_taskname);
else if (boot_data.curr_task[0].name[0] && !boot_data.curr_task[0].stackfree)
writer->printf(" Pending stack overflow in task %s\n", boot_data.curr_task[0].name);
else if (boot_data.curr_task[1].name[0] && !boot_data.curr_task[1].stackfree)
writer->printf(" Pending stack overflow in task %s\n", boot_data.curr_task[1].name);
} }
else else
{ {
// Hardware exception:
int exccause = boot_data.crash_data.reg[19]; int exccause = boot_data.crash_data.reg[19];
writer->printf("%s exception on core %d\n", writer->printf("%s exception on core %d\n",
(exccause < NUM_EDESCS) ? edesc[exccause] : "Unknown", boot_data.crash_data.core_id); (exccause < NUM_EDESCS) ? edesc[exccause] : "Unknown", boot_data.crash_data.core_id);
@ -176,18 +185,29 @@ void boot_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc,
for (int i=0; i<24; i++) for (int i=0; i<24; i++)
writer->printf(" %s: 0x%08lx%s", sdesc[i], boot_data.crash_data.reg[i], ((i+1)%4) ? "" : "\n"); writer->printf(" %s: 0x%08lx%s", sdesc[i], boot_data.crash_data.reg[i], ((i+1)%4) ? "" : "\n");
} }
for (int core = 0; core < portNUM_PROCESSORS; core++)
{
if (boot_data.curr_task[core].name[0])
writer->printf(" Current task on core %d: %s, %u stack bytes free\n",
core, boot_data.curr_task[core].name, boot_data.curr_task[core].stackfree);
}
writer->printf(" Backtrace:\n "); writer->printf(" Backtrace:\n ");
for (int i=0; i<OVMS_BT_LEVELS && boot_data.crash_data.bt[i].pc; i++) for (int i=0; i<OVMS_BT_LEVELS && boot_data.crash_data.bt[i].pc; i++)
writer->printf(" 0x%08lx", boot_data.crash_data.bt[i].pc); writer->printf(" 0x%08lx", boot_data.crash_data.bt[i].pc);
if (boot_data.curr_event_name[0]) if (boot_data.curr_event_name[0])
{ {
writer->printf("\n Event: %s@%s %u secs", boot_data.curr_event_name, boot_data.curr_event_handler, writer->printf("\n Event: %s@%s %u secs", boot_data.curr_event_name, boot_data.curr_event_handler,
boot_data.curr_event_runtime); boot_data.curr_event_runtime);
} }
if (MyBoot.GetResetReason() == ESP_RST_TASK_WDT) if (MyBoot.GetResetReason() == ESP_RST_TASK_WDT)
{ {
writer->printf("\n WDT tasks: %s", boot_data.wdt_tasknames); writer->printf("\n WDT tasks: %s", boot_data.wdt_tasknames);
} }
writer->printf("\n Version: %s\n", StdMetrics.ms_m_version->AsString("").c_str()); writer->printf("\n Version: %s\n", StdMetrics.ms_m_version->AsString("").c_str());
writer->printf("\n Hardware: %s\n", StdMetrics.ms_m_hardware->AsString("").c_str()); writer->printf("\n Hardware: %s\n", StdMetrics.ms_m_hardware->AsString("").c_str());
} }
@ -265,14 +285,12 @@ Boot::Boot()
boot_data.boot_count++; boot_data.boot_count++;
ESP_LOGI(TAG, "Boot #%d reasons for CPU0=%d and CPU1=%d",boot_data.boot_count,cpu0,cpu1); ESP_LOGI(TAG, "Boot #%d reasons for CPU0=%d and CPU1=%d",boot_data.boot_count,cpu0,cpu1);
m_resetreason = boot_data.reset_hint;
ESP_LOGI(TAG, "Reset reason %s (%d)", GetResetReasonName(), GetResetReason());
if (boot_data.soft_reset) if (boot_data.soft_reset)
{ {
boot_data.crash_count_total = 0; boot_data.crash_count_total = 0;
boot_data.crash_count_early = 0; boot_data.crash_count_early = 0;
m_bootreason = BR_SoftReset; m_bootreason = BR_SoftReset;
m_resetreason = ESP_RST_SW;
ESP_LOGI(TAG, "Soft reset by user"); ESP_LOGI(TAG, "Soft reset by user");
} }
else if (boot_data.firmware_update) else if (boot_data.firmware_update)
@ -281,6 +299,7 @@ Boot::Boot()
boot_data.crash_count_early = 0; boot_data.crash_count_early = 0;
m_bootreason = BR_FirmwareUpdate; m_bootreason = BR_FirmwareUpdate;
ESP_LOGI(TAG, "Firmware update reset"); ESP_LOGI(TAG, "Firmware update reset");
m_resetreason = ESP_RST_SW;
} }
else if (!boot_data.stable_reached) else if (!boot_data.stable_reached)
{ {
@ -288,24 +307,33 @@ Boot::Boot()
boot_data.crash_count_early++; boot_data.crash_count_early++;
m_bootreason = BR_EarlyCrash; m_bootreason = BR_EarlyCrash;
ESP_LOGE(TAG, "Early crash #%d detected", boot_data.crash_count_early); ESP_LOGE(TAG, "Early crash #%d detected", boot_data.crash_count_early);
m_resetreason = boot_data.reset_hint;
ESP_LOGI(TAG, "Reset reason %s (%d)", GetResetReasonName(), GetResetReason());
} }
else else
{ {
boot_data.crash_count_total++; boot_data.crash_count_total++;
m_bootreason = BR_Crash; m_bootreason = BR_Crash;
ESP_LOGE(TAG, "Crash #%d detected", boot_data.crash_count_total); ESP_LOGE(TAG, "Crash #%d detected", boot_data.crash_count_total);
m_resetreason = boot_data.reset_hint;
ESP_LOGI(TAG, "Reset reason %s (%d)", GetResetReasonName(), GetResetReason());
} }
} }
m_crash_count_early = boot_data.crash_count_early; m_crash_count_early = boot_data.crash_count_early;
m_stack_overflow = boot_data.stack_overflow;
if (!m_stack_overflow)
boot_data.stack_overflow_taskname[0] = 0;
boot_data.bootreason_cpu0 = cpu0; boot_data.bootreason_cpu0 = cpu0;
boot_data.bootreason_cpu1 = cpu1; boot_data.bootreason_cpu1 = cpu1;
boot_data.reset_hint = ESP_RST_UNKNOWN;
// reset flags: // reset flags:
boot_data.soft_reset = false; boot_data.soft_reset = false;
boot_data.firmware_update = false; boot_data.firmware_update = false;
boot_data.stable_reached = false; boot_data.stable_reached = false;
boot_data.stack_overflow = false;
boot_data.crc = boot_data.calc_crc(); boot_data.crc = boot_data.calc_crc();
@ -351,6 +379,8 @@ const char* Boot::GetBootReasonName()
const char* Boot::GetResetReasonName() const char* Boot::GetResetReasonName()
{ {
if (m_stack_overflow)
return "Stack overflow";
return (m_resetreason >= 0 && m_resetreason < NUM_RESETREASONS) return (m_resetreason >= 0 && m_resetreason < NUM_RESETREASONS)
? resetreason_name[m_resetreason] ? resetreason_name[m_resetreason]
: "Unknown reset reason"; : "Unknown reset reason";
@ -448,6 +478,52 @@ bool Boot::IsShuttingDown()
return m_shutting_down; return m_shutting_down;
} }
/*
* Direct UART output utils borrowed from esp32/panic.c
*/
static void panicPutChar(char c)
{
while (((READ_PERI_REG(UART_STATUS_REG(CONFIG_CONSOLE_UART_NUM)) >> UART_TXFIFO_CNT_S)&UART_TXFIFO_CNT) >= 126) ;
WRITE_PERI_REG(UART_FIFO_REG(CONFIG_CONSOLE_UART_NUM), c);
}
static void panicPutStr(const char *c)
{
int x = 0;
while (c[x] != 0)
{
panicPutChar(c[x]);
x++;
}
}
/*
* This function is called by task_wdt_isr function (ISR for when TWDT times out).
* It can be redefined in user code to handle twdt events.
* Note: It has the same limitations as the interrupt function.
* Do not use ESP_LOGI functions inside.
*/
extern "C" void esp_task_wdt_isr_user_handler(void)
{
panicPutStr("\r\n[OVMS] ***TWDT***\r\n");
// Save TWDT task info:
esp_task_wdt_get_trigger_tasknames(boot_data.wdt_tasknames, sizeof(boot_data.wdt_tasknames));
}
/*
* This function is called if FreeRTOS detects a stack overflow.
*/
extern "C" void vApplicationStackOverflowHook( TaskHandle_t xTask, signed char *pcTaskName )
{
panicPutStr("\r\n[OVMS] ***ERROR*** A stack overflow in task ");
panicPutStr((char *)pcTaskName);
panicPutStr(" has been detected.\r\n");
strlcpy(boot_data.stack_overflow_taskname, (const char*)pcTaskName, sizeof(boot_data.stack_overflow_taskname));
boot_data.stack_overflow = true;
abort();
}
void Boot::ErrorCallback(XtExcFrame *frame, int core_id, bool is_abort) void Boot::ErrorCallback(XtExcFrame *frame, int core_id, bool is_abort)
{ {
boot_data.reset_hint = ovms_reset_reason_get_hint(); boot_data.reset_hint = ovms_reset_reason_get_hint();
@ -495,8 +571,28 @@ void Boot::ErrorCallback(XtExcFrame *frame, int core_id, bool is_abort)
boot_data.curr_event_runtime = 0; boot_data.curr_event_runtime = 0;
} }
// Save TWDT task info: // Save current tasks:
esp_task_wdt_get_trigger_tasknames(boot_data.wdt_tasknames, sizeof(boot_data.wdt_tasknames)); panicPutStr("\r\n[OVMS] Current tasks: ");
for (int core=0; core<portNUM_PROCESSORS; core++)
{
TaskHandle_t task = xTaskGetCurrentTaskHandleForCPU(core);
if (task)
{
char *name = pcTaskGetTaskName(task);
uint32_t stackfree = uxTaskGetStackHighWaterMark(task);
if (core > 0) panicPutChar('|');
panicPutStr(name);
strlcpy(boot_data.curr_task[core].name, name, sizeof(boot_data.curr_task[core].name));
boot_data.curr_task[core].stackfree = stackfree;
if (!stackfree) boot_data.stack_overflow = true;
}
else
{
boot_data.curr_task[core].name[0] = 0;
boot_data.curr_task[core].stackfree = 0;
}
}
panicPutStr("\r\n");
boot_data.crc = boot_data.calc_crc(); boot_data.crc = boot_data.calc_crc();
} }
@ -514,6 +610,8 @@ void Boot::NotifyDebugCrash()
// ,<curr_event_name>,<curr_event_handler>,<curr_event_runtime> // ,<curr_event_name>,<curr_event_handler>,<curr_event_runtime>
// ,<wdt_tasknames> // ,<wdt_tasknames>
// ,<hardware_info> // ,<hardware_info>
// ,<stack_overflow_task>
// ,<core0_task>,<core0_stackfree>,<core1_task>,<core1_stackfree>
StringWriter buf; StringWriter buf;
buf.reserve(2048); buf.reserve(2048);
@ -557,6 +655,20 @@ void Boot::NotifyDebugCrash()
buf.append(","); buf.append(",");
buf.append(mp_encode(StdMetrics.ms_m_hardware->AsString(""))); buf.append(mp_encode(StdMetrics.ms_m_hardware->AsString("")));
// Stack overflow task:
std::string name = boot_data.stack_overflow_taskname;
buf.append(",");
buf.append(mp_encode(name));
// Current tasks:
for (int i = 0; i < 2; i++)
{
name = boot_data.curr_task[i].name;
buf.append(",");
buf.append(mp_encode(name));
buf.printf(",%u", boot_data.curr_task[i].stackfree);
}
MyNotify.NotifyString("data", "debug.crash", buf.c_str()); MyNotify.NotifyString("data", "debug.crash", buf.c_str());
} }
} }

View file

@ -62,6 +62,12 @@ typedef struct
} bt[OVMS_BT_LEVELS]; } bt[OVMS_BT_LEVELS];
} crash_data_t; } crash_data_t;
typedef struct
{
char name[16];
uint32_t stackfree;
} task_info_t;
typedef struct typedef struct
{ {
// data consistency: // data consistency:
@ -83,6 +89,9 @@ typedef struct
char curr_event_handler[16]; // … MyEvents.m_current_callback->m_caller char curr_event_handler[16]; // … MyEvents.m_current_callback->m_caller
uint16_t curr_event_runtime; // … monotonictime-MyEvents.m_current_started uint16_t curr_event_runtime; // … monotonictime-MyEvents.m_current_started
char wdt_tasknames[32]; // Pipe (|) separated list of the tasks that triggered the TWDT char wdt_tasknames[32]; // Pipe (|) separated list of the tasks that triggered the TWDT
bool stack_overflow;
char stack_overflow_taskname[16];
task_info_t curr_task[portNUM_PROCESSORS];
} boot_data_t; } boot_data_t;
extern boot_data_t boot_data; extern boot_data_t boot_data;
@ -128,6 +137,7 @@ class Boot
bootreason_t m_bootreason; bootreason_t m_bootreason;
esp_reset_reason_t m_resetreason; esp_reset_reason_t m_resetreason;
unsigned int m_crash_count_early; unsigned int m_crash_count_early;
bool m_stack_overflow;
}; };
extern Boot MyBoot; extern Boot MyBoot;

View file

@ -718,12 +718,25 @@ OvmsConfigParam* OvmsConfig::CachedParam(std::string param)
return *p; return *p;
} }
/**
* ProtectedPath: `true` if the path is protected (e.g. config)
* - Note: path is canonicalized before comparison, in case the path
* does not exist in the filesystem, the result will be `false`
* (i.e.: not protected).
*/
bool OvmsConfig::ProtectedPath(std::string path) bool OvmsConfig::ProtectedPath(std::string path)
{ {
#ifdef CONFIG_OVMS_DEV_CONFIGVFS #ifdef CONFIG_OVMS_DEV_CONFIGVFS
return false; return false;
#else #else
return (path.find(OVMS_CONFIGPATH) != std::string::npos); char canonical_path[PATH_MAX];
char * result = realpath(path.c_str(), canonical_path);
if (NULL == result) {
// If error during path resolution, we consider it as not protected
return false;
}
std::string resolved_path(canonical_path);
return (resolved_path.find(OVMS_CONFIGPATH) != std::string::npos);
#endif // #ifdef CONFIG_OVMS_DEV_CONFIGVFS #endif // #ifdef CONFIG_OVMS_DEV_CONFIGVFS
} }

View file

@ -46,4 +46,8 @@
#define ESP_LOGD( tag, format, ... ) esp_log_write(ESP_LOG_DEBUG, tag, LOG_FORMAT(D, format), esp_log_timestamp(), tag, ##__VA_ARGS__) #define ESP_LOGD( tag, format, ... ) esp_log_write(ESP_LOG_DEBUG, tag, LOG_FORMAT(D, format), esp_log_timestamp(), tag, ##__VA_ARGS__)
#define ESP_LOGV( tag, format, ... ) esp_log_write(ESP_LOG_VERBOSE, tag, LOG_FORMAT(V, format), esp_log_timestamp(), tag, ##__VA_ARGS__) #define ESP_LOGV( tag, format, ... ) esp_log_write(ESP_LOG_VERBOSE, tag, LOG_FORMAT(V, format), esp_log_timestamp(), tag, ##__VA_ARGS__)
#define ESP_BUFFER_LOGI( tag, buffer, buff_len) esp_log_buffer_hexdump_internal( tag, buffer, buff_len, ESP_LOG_INFO)
#define ESP_BUFFER_LOGD( tag, buffer, buff_len) esp_log_buffer_hexdump_internal( tag, buffer, buff_len, ESP_LOG_DEBUG)
#define ESP_BUFFER_LOGV( tag, buffer, buff_len) esp_log_buffer_hexdump_internal( tag, buffer, buff_len, ESP_LOG_VERBOSE)
#endif //#ifndef __OVMS_LOG_H__ #endif //#ifndef __OVMS_LOG_H__

File diff suppressed because it is too large Load diff

View file

@ -38,6 +38,7 @@
#include "ovms_events.h" #include "ovms_events.h"
#include "metrics_standard.h" #include "metrics_standard.h"
#include "ovms_version.h" #include "ovms_version.h"
#include "esp_idf_version.h"
/** /**
* chargestate_code: convert legacy chargestate key to code * chargestate_code: convert legacy chargestate key to code
@ -628,3 +629,100 @@ bool get_buff_string(const uint8_t *data, uint32_t size, uint32_t index, uint32_
strret.assign<const char *>(begin, end); strret.assign<const char *>(begin, end);
return true; return true;
} }
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 0)
/**
* realpath: this implementation is from (a newer version of) newlib, in particular
* https://github.com/espressif/esp-idf/commit/d83ce227aa987d648e1a0dddba32b88846374815#diff-91b1abbc42db113c5dcf9c11c594f0b3e75cae5f6dfba9ef8b68e26bbd422f4a
* - Note: strchrnul has been replaced with strchr as it's also non implemented
* in our versions.
*/
char * realpath(const char *file_name, char *resolved_name)
{
char * out_path = resolved_name;
if (out_path == NULL) {
/* allowed as an extension, allocate memory for the output path */
out_path = (char *)malloc(PATH_MAX);
if (out_path == NULL) {
errno = ENOMEM;
return NULL;
}
}
/* canonical path starts with / */
strlcpy(out_path, "/", PATH_MAX);
/* pointers moving over the input and output path buffers */
const char* in_ptr = file_name;
char* out_ptr = out_path + 1;
/* number of path components in the output buffer */
size_t out_depth = 0;
while (*in_ptr) {
/* "path component" is the part between two '/' path separators.
* locate the next path component in the input path:
*/
const char* end_of_path_component = strchr(in_ptr, '/');
if (NULL == end_of_path_component) {
end_of_path_component = in_ptr + strlen(in_ptr);
}
size_t path_component_len = end_of_path_component - in_ptr;
if (path_component_len == 0 ||
(path_component_len == 1 && in_ptr[0] == '.')) {
/* empty path component or '.' - nothing to do */
} else if (path_component_len == 2 && in_ptr[0] == '.' && in_ptr[1] == '.') {
/* '..' - remove one path component from the output */
if (out_depth == 0) {
/* nothing to remove */
} else if (out_depth == 1) {
/* there is only one path component in output;
* remove it, but keep the leading separator
*/
out_ptr = out_path + 1;
*out_ptr = '\0';
out_depth = 0;
} else {
/* remove last path component and the separator preceding it */
char * prev_sep = strrchr(out_path, '/');
assert(prev_sep > out_path); /* this shouldn't be the leading separator */
out_ptr = prev_sep;
*out_ptr = '\0';
--out_depth;
}
} else {
/* copy path component to output; +1 is for the separator */
if (out_ptr - out_path + 1 + path_component_len > PATH_MAX - 1) {
/* output buffer insufficient */
errno = E2BIG;
goto fail;
} else {
/* add separator if necessary */
if (out_depth > 0) {
*out_ptr = '/';
++out_ptr;
}
memcpy(out_ptr, in_ptr, path_component_len);
out_ptr += path_component_len;
*out_ptr = '\0';
++out_depth;
}
}
/* move input pointer to separator right after this path component */
in_ptr += path_component_len;
if (*in_ptr != '\0') {
/* move past it unless already at the end of the input string */
++in_ptr;
}
}
return out_path;
fail:
if (resolved_name == NULL) {
/* out_path was allocated, free it */
free(out_path);
}
return NULL;
}
#endif

View file

@ -351,7 +351,7 @@ INT sign_extend( UINT uvalue, uint8_t signbit)
UINT signmask = UINT(1U) << signbit; UINT signmask = UINT(1U) << signbit;
if ( newuvalue & signmask) if ( newuvalue & signmask)
newuvalue |= ~ (static_cast<uint_t>(signmask) - 1); newuvalue |= ~ (static_cast<uint_t>(signmask) - 1);
return reinterpret_cast<INT &>(uvalue); return reinterpret_cast<INT &>(newuvalue);
} }
/** /**
@ -365,7 +365,7 @@ INT sign_extend( UINT uvalue)
uint_t newuvalue = uvalue; uint_t newuvalue = uvalue;
if ( newuvalue & ( UINT(1U) << SIGNBIT) ) if ( newuvalue & ( UINT(1U) << SIGNBIT) )
newuvalue |= ~((uint_t(1U) << SIGNBIT) - 1); newuvalue |= ~((uint_t(1U) << SIGNBIT) - 1);
return reinterpret_cast<INT &>(uvalue); return reinterpret_cast<INT &>(newuvalue);
} }
/** /**

View file

@ -51,64 +51,145 @@ static const char *TAG = "vfs";
#include "vfsedit.h" #include "vfsedit.h"
#endif // #ifdef CONFIG_OVMS_COMP_EDITOR #endif // #ifdef CONFIG_OVMS_COMP_EDITOR
void vfs_ls(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) /**
{ * 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<std::string> 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<Direntry>` with all possible entries starting at `startpath`.
* if `recurse` is `true`, then all possible directories are recursively entered.
*/
static void read_entries(std::set<Direntry> &entries, std::string startpath, bool recurse = true) {
DIR *dir; DIR *dir;
struct dirent *dp; struct dirent *dp;
char size[64], mod[64], path[PATH_MAX];
struct stat st;
if (argc == 0) if ((dir = opendir(startpath.c_str())) != NULL) {
{ while ((dp = readdir(dir)) != NULL) {
if ((dir = opendir (".")) == NULL) Direntry dirent(startpath, dp);
{ if (dirent.is_skip) {
writer->puts("Error: VFS cannot open directory listing"); continue;
return; }
entries.insert(dirent);
if (recurse && dirent.is_dir && !dirent.is_protected) {
read_entries(entries, dirent.path());
} }
} }
else closedir(dir);
{
if (MyConfig.ProtectedPath(argv[0]))
{
writer->puts("Error: protected path");
return;
}
if ((dir = opendir (argv[0])) == NULL)
{
writer->puts("Error: VFS cannot open directory listing for that directory");
return;
}
}
while ((dp = readdir (dir)) != NULL)
{
snprintf(path, sizeof(path), "%s/%s", (argc==0) ? "." : argv[0], dp->d_name);
stat(path, &st);
int64_t fsize = st.st_size;
int is_dir = S_ISDIR(st.st_mode);
const char *slash = is_dir ? "/" : "";
if (is_dir) {
strcpy(size, "[DIR] ");
} else {
if (fsize < 1024) {
snprintf(size, sizeof(size), "%d ", (int) fsize);
} else if (fsize < 0x100000) {
snprintf(size, sizeof(size), "%.1fk", (double) fsize / 1024.0);
} else if (fsize < 0x40000000) {
snprintf(size, sizeof(size), "%.1fM", (double) fsize / 1048576);
} else {
snprintf(size, sizeof(size), "%.1fG", (double) fsize / 1073741824);
}
}
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&st.st_mtime));
writer->printf("%8.8s %17.17s %s%s\n", size, mod, dp->d_name, slash);
}
closedir(dir);
} }
}
/**
* Fills a `std::set<Direntry>` 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<Direntry> 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) void vfs_cat(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{ {
@ -505,6 +586,7 @@ VfsInit::VfsInit()
OvmsCommand* cmd_vfs = MyCommandApp.RegisterCommand("vfs","Virtual File System framework"); OvmsCommand* cmd_vfs = MyCommandApp.RegisterCommand("vfs","Virtual File System framework");
cmd_vfs->RegisterCommand("ls","VFS Directory Listing",vfs_ls, "[<file>]", 0, 1); cmd_vfs->RegisterCommand("ls","VFS Directory Listing",vfs_ls, "[<file>]", 0, 1);
cmd_vfs->RegisterCommand("rls","VFS Recursive Directory Listing",vfs_rls, "[<file>]", 0, 1);
cmd_vfs->RegisterCommand("cat","VFS Display a file",vfs_cat, "<file>", 1, 1); cmd_vfs->RegisterCommand("cat","VFS Display a file",vfs_cat, "<file>", 1, 1);
cmd_vfs->RegisterCommand("head","VFS Display first 20 lines of a file",vfs_head, "[-nrlines] <file>", 1, 2); cmd_vfs->RegisterCommand("head","VFS Display first 20 lines of a file",vfs_head, "[-nrlines] <file>", 1, 2);
cmd_vfs->RegisterCommand("stat","VFS Status of a file",vfs_stat, "<file>", 1, 1); cmd_vfs->RegisterCommand("stat","VFS Status of a file",vfs_stat, "<file>", 1, 1);

View file

@ -169,6 +169,17 @@ void test_watchdog(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc
writer->puts("Error: We should never get here"); writer->puts("Error: We should never get here");
} }
void test_stackoverflow(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
uint8_t data[256];
memset(data, verbosity & 255, sizeof data);
writer->printf("Stack bytes remaining: %u\n", uxTaskGetStackHighWaterMark(NULL));
vTaskDelay(pdMS_TO_TICKS(250));
test_stackoverflow(verbosity+1, writer, cmd, argc, argv);
// never reached, just to inhibit tail recursion optimization:
writer->printf("%x\n", data[0]);
}
void test_realloc(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv) void test_realloc(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{ {
void* buf; void* buf;
@ -385,6 +396,7 @@ TestFrameworkInit::TestFrameworkInit()
cmd_test->RegisterCommand("chargen","Character generator",test_chargen,"[<#lines>] [<delay_ms>]",0,2); cmd_test->RegisterCommand("chargen","Character generator",test_chargen,"[<#lines>] [<delay_ms>]",0,2);
cmd_test->RegisterCommand("echo", "Test getchar", test_echo); cmd_test->RegisterCommand("echo", "Test getchar", test_echo);
cmd_test->RegisterCommand("watchdog", "Test task spinning (and watchdog firing)", test_watchdog); cmd_test->RegisterCommand("watchdog", "Test task spinning (and watchdog firing)", test_watchdog);
cmd_test->RegisterCommand("stackoverflow", "Test stack overflow detection (crashes)", test_stackoverflow);
cmd_test->RegisterCommand("realloc", "Test memory re-allocations", test_realloc); cmd_test->RegisterCommand("realloc", "Test memory re-allocations", test_realloc);
cmd_test->RegisterCommand("spiram", "Test SPI RAM memory usage", test_spiram); cmd_test->RegisterCommand("spiram", "Test SPI RAM memory usage", test_spiram);
cmd_test->RegisterCommand("strverscmp", "Test strverscmp function", test_strverscmp, "", 2, 2); cmd_test->RegisterCommand("strverscmp", "Test strverscmp function", test_strverscmp, "", 2, 2);