diff --git a/CHANGELOG.md b/CHANGELOG.md index 050eacf0..2c490505 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,12 @@ ## Fixed +- Config + - A startup configuration line longer than the command buffer no longer stops the replay: + the offending line is skipped and the following lines are still applied. + - The web UI and the configuration upload now refuse a line the replay cannot take, + instead of writing it to flash. + ## Breaking changes - Config diff --git a/cmd_parser.c b/cmd_parser.c index bfa75b06..a0b0ab3b 100644 --- a/cmd_parser.c +++ b/cmd_parser.c @@ -1962,6 +1962,7 @@ void execute_config(void) __banked save_cmd = 0; uint8_t cmd_idx = 0; + uint8_t skipping = 0; do { flash_region.addr = pos; flash_region.len = FLASH_READ_BURST_SIZE; @@ -1972,20 +1973,23 @@ void execute_config(void) __banked do { if (cmd_idx >= (CMD_BUF_SIZE - 1)) { cmd_buffer[cmd_idx] = NUL; - print_string("ERROR: Command too long: "); + print_string("ERROR: Command too long, skipped: "); print_string_x(cmd_buffer); write_char('\n'); - err_status = ERR_CMD_TOO_LONG; - goto config_done; + cmd_idx = 0; + skipping = 1; } c = flash_buf[cfg_idx++]; if (c == 0 || c == '\n') { - cmd_buffer[cmd_idx] = NUL; - if (cmd_idx) { - cmd_tokenize(); - if (err_status != ERR_OK) - goto config_done; - cmd_parser(); + if (skipping) { + skipping = 0; + } else { + cmd_buffer[cmd_idx] = NUL; + if (cmd_idx) { + cmd_tokenize(); + if (err_status == ERR_OK) + cmd_parser(); + } } if (c == 0) goto config_done; @@ -1993,8 +1997,10 @@ void execute_config(void) __banked continue; } - cmd_buffer[cmd_idx] = c; - cmd_idx++; + if (!skipping) { + cmd_buffer[cmd_idx] = c; + cmd_idx++; + } } while (cfg_idx); pages_left--; diff --git a/html/app.js b/html/app.js index 2dc4e7a1..2d4bd7e2 100644 --- a/html/app.js +++ b/html/app.js @@ -90,6 +90,7 @@ sy_rebooting:"Rebooting, reconnect in about 20 s",sy_bytes:"bytes", cw_title:"Write this startup configuration?",cw_save_title:"Save running configuration to flash?", cw_info:"{n} / 2048 bytes, replayed line by line on every boot", cw_toolarge:"Too large: the config sector accepts at most 2048 bytes. Remove lines first.", +cw_longline:"Line {n} is {m} bytes long: the switch can only replay lines up to 126 bytes and would skip it on boot, so the write is refused. Shorten it first.", cw_unknown:"Not in the known config grammar (will still be written): ",cw_empty:"(empty)", cw_writing:"Writing configuration...",cw_failed:"config write failed: HTTP {n}", cw_verify_fail:"Verification failed: flash content differs from what was sent. Command log NOT cleared.", @@ -201,6 +202,7 @@ sy_rebooting:"再起動中です。約 20 秒後に再接続してください", cw_title:"この起動設定を書き込みますか?",cw_save_title:"実行中の設定をフラッシュに保存しますか?", cw_info:"{n} / 2048 バイト、起動のたびに 1 行ずつ実行されます", cw_toolarge:"サイズ超過: 設定セクタは最大 2048 バイトです。先に行を削除してください。", +cw_longline:"{n} 行目は {m} バイトです: スイッチが起動時に実行できる行は 126 バイトまでで、この行は読み飛ばされるため、書き込みを拒否します。先に短くしてください。", cw_unknown:"既知の設定文法に含まれない行 (そのまま書き込まれます): ",cw_empty:"(空)", cw_writing:"設定を書き込み中...",cw_failed:"設定の書き込みに失敗しました: HTTP {n}", cw_verify_fail:"検証失敗: フラッシュの内容が送信内容と異なります。コマンドログは消去されていません。", @@ -312,6 +314,7 @@ sy_rebooting:"正在重启,约 20 秒后重新连接",sy_bytes:"字节", cw_title:"写入此启动配置?",cw_save_title:"将运行配置保存到 Flash?", cw_info:"{n} / 2048 字节,每次启动时逐行执行", cw_toolarge:"过大: 配置扇区最多容纳 2048 字节。请先删除部分行。", +cw_longline:"第 {n} 行有 {m} 字节: 交换机启动时只能执行不超过 126 字节的行,该行会被跳过,因此拒绝写入。请先缩短该行。", cw_unknown:"不属于已知配置语法的行 (仍会写入): ",cw_empty:"(空)", cw_writing:"正在写入配置...",cw_failed:"配置写入失败: HTTP {n}", cw_verify_fail:"校验失败: Flash 内容与发送内容不一致。命令日志未清除。", @@ -1715,11 +1718,16 @@ function cfgReload(){ cfgParseKnown(x); }).catch(function(){}); } +function cfgLongLine(v){ + var ls=v.split("\n"); + for(var i=0;i126)return{n:i+1,m:n};} + return null; +} function cfgBytes(){ - var n=new Blob([$("cfgedit").value]).size; + var v=$("cfgedit").value,n=new Blob([v]).size; var el=$("cfgbytes"); el.textContent=n+" / 2048 "+t("sy_bytes"); - el.style.color=n>2048?"var(--bad)":""; + el.style.color=(n>2048||cfgLongLine(v))?"var(--bad)":""; return n; } $("cfgedit").addEventListener("input",cfgBytes); @@ -1792,12 +1800,12 @@ function writeConfig(txt,title){ ed.value=txt.replace(/\r\n/g,"\n"); var ok=h("button",{class:"ctl pri",text:t("sy_write"),onclick:function(){closeModal();doWriteConfig(cfgText(ed.value))}}); function refresh(){ - var v=cfgText(ed.value),bytes=new Blob([v]).size; + var v=cfgText(ed.value),bytes=new Blob([v]).size,long=cfgLongLine(v); var unknown=v.split("\n").filter(function(l){return l.trim()&&!isConfCmd(l.trim().replace(/\s+/g," "))}); info.textContent=t("cw_info",{n:bytes}); - ok.disabled=bytes>2048; + ok.disabled=bytes>2048||!!long; warn.style.color=ok.disabled?"var(--bad)":"var(--warn)"; - warn.textContent=ok.disabled?t("cw_toolarge"):(unknown.length?t("cw_unknown")+unknown.join(" | "):""); + warn.textContent=bytes>2048?t("cw_toolarge"):long?t("cw_longline",long):(unknown.length?t("cw_unknown")+unknown.join(" | "):""); } ed.addEventListener("input",refresh);refresh(); modal(title,h("div",null,[info,warn,ed]),[h("button",{class:"ctl",text:t("c_cancel"),onclick:closeModal}),ok]); diff --git a/httpd/httpd.c b/httpd/httpd.c index 4ae741a8..85408e7b 100644 --- a/httpd/httpd.c +++ b/httpd/httpd.c @@ -377,8 +377,8 @@ void gen_random_hex_chars(__xdata uint8_t * b, __xdata uint8_t bytes) static uint8_t config_take(void) { // #386: needs static, otherwise it still lands in SRAM/DSEG - static __xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last; - __xdata uint8_t cfg_bl; + static __xdata uint16_t cfg_pos, cfg_hdr, cfg_body, cfg_end, cfg_last, cfg_i; + __xdata uint8_t cfg_bl, cfg_run; cfg_bl = strlen_x(boundary); @@ -420,6 +420,13 @@ static uint8_t config_take(void) // the payload plus its terminator must fit the sector if (cfg_end - cfg_body + 1 > CONFIG_LEN) return 2; + cfg_run = 0; + for (cfg_i = cfg_body; cfg_i < cfg_end; cfg_i++) { + if (config_buf[cfg_i] == '\n') + cfg_run = 0; + else if (++cfg_run >= CMD_BUF_SIZE - 1) + return 2; + } config_buf[cfg_end] = 0; flash_region.addr = CONFIG_START; flash_sector_erase();