diff --git a/CHANGELOG.md b/CHANGELOG.md index 6834ff43b1..5d0be1f378 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Stop using deprecated `term_from_int32` on STM32 platform - Stop using deprecated `term_from_int32` on RP2 platform - Stop using deprecated `term_from_int32` on ESP32 platform +- Fixed bug in ESP32 mkimage.sh leading to non-fatal "unary operator expected" error +- Fixed `fd` leak in ESP32 mkimage.erl +- Fixed mkimage.erl unaligned end of data problems that can prevent +flashing with a web-flasher tool +- Fixed misleading error message when a binary overflows its partition +- Fixed mkimage.erl to stop with and error when file:write/2 fails ## [0.7.0-alpha.1] - 2026-04-06 diff --git a/src/platforms/esp32/tools/mkimage.config.in b/src/platforms/esp32/tools/mkimage.config.in index 8c19021d00..20b30ab11a 100644 --- a/src/platforms/esp32/tools/mkimage.config.in +++ b/src/platforms/esp32/tools/mkimage.config.in @@ -18,6 +18,9 @@ % SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later % +%% It is important that segments are always kept in order by offset +%% so mkimage.erl can pad partitons to the correct size. + #{ segments => [ #{ diff --git a/src/platforms/esp32/tools/mkimage.erl b/src/platforms/esp32/tools/mkimage.erl index 4d8d503124..7a4acd9407 100644 --- a/src/platforms/esp32/tools/mkimage.erl +++ b/src/platforms/esp32/tools/mkimage.erl @@ -116,57 +116,83 @@ mkimage(OutputFile, Segments) -> case file:open(OutputFile, [write, binary]) of {ok, Fout} -> - lists:foldl( - fun(Segment, PrevOffset) -> - SegmentOffset = from_hex(maps:get(offset, Segment)), - case PrevOffset of - undefined -> - no_padding; - _ -> - case SegmentOffset > PrevOffset of - true -> - Padding = [ - 16#FF - || _ <- lists:seq(1, SegmentOffset - PrevOffset) - ], - io:format("Padding ~p bytes~n", [SegmentOffset - PrevOffset]), - file:write(Fout, Padding); - false -> - throw( - io_lib:format( - "Error: insufficient space for segment ~p. Over by: ~p bytes~n", - [ - maps:get(name, Segment), PrevOffset - SegmentOffset - ] - ) - ) - end - end, - SegmentPaths = maps:get(path, Segment), - case try_read(SegmentPaths) of - {ok, Data} -> - file:write(Fout, Data), - io:format("Wrote ~s (~p bytes) at offset ~s (~p)~n", [ - maps:get(name, Segment), - byte_size(Data), - maps:get(offset, Segment), - SegmentOffset - ]), - SegmentOffset + byte_size(Data); - {error, Reason} -> - Fmt = - "Failed to read file ~p Reason: ~p." - " Note that a full build is required before running this command.", - throw(io_lib:format(Fmt, [SegmentPaths, Reason])) - end - end, - undefined, - Segments - ); + try + write_segments(Fout, Segments) + after + file:close(Fout) + end; {error, Reason} -> throw(io_lib:format("Failed to open ~s for writing. Reason: ~p", [OutputFile, Reason])) end. +%% @private +write_segments(_Fout, []) -> + ok; +write_segments(Fout, [Segment | Segments]) -> + SegmentOffset = from_hex(maps:get(offset, Segment)), + SegmentPaths = maps:get(path, Segment), + SegmentEnd = + case try_read(SegmentPaths) of + {ok, Data} -> + case file:write(Fout, Data) of + ok -> + ok; + {error, WriteError} -> + Fmt = "Failed to write segment data from ~p to image. Reason: ~p.", + throw(io_lib:format(Fmt, [SegmentPaths, WriteError])) + end, + io:format("Wrote ~s (~p bytes) at offset ~s (~p)~n", [ + maps:get(name, Segment), + byte_size(Data), + maps:get(offset, Segment), + SegmentOffset + ]), + SegmentOffset + byte_size(Data); + {error, Reason} -> + Fmt = + "Failed to read file ~p Reason: ~p." + " Note that a full build is required before running this command.", + throw(io_lib:format(Fmt, [SegmentPaths, Reason])) + end, + if + Segments == [] -> + %% Pad to end on 32-byte alignment, since we don't have a next offset to pad to. + SectorPad = (32 - (SegmentEnd rem 32)) rem 32, + pad_to_size(Fout, SectorPad); + true -> + [PeekNext | _] = Segments, + NextOffset = from_hex(maps:get(offset, PeekNext)), + case SegmentEnd > NextOffset of + true -> + throw( + io_lib:format( + "Error: insufficient space for segment ~p. Overflows partition by: ~p bytes~n", + [maps:get(name, Segment), SegmentEnd - NextOffset] + ) + ); + false -> + PadSize = NextOffset - SegmentEnd, + case PadSize of + 0 -> + ok; + PadSize -> + ok = pad_to_size(Fout, PadSize) + end + end + end, + write_segments(Fout, Segments). + +%% @private +pad_to_size(Fout, PadSize) -> + Padding = [16#FF || _ <- lists:seq(1, PadSize)], + case file:write(Fout, Padding) of + ok -> + io:format("Padded ~p bytes~n", [PadSize]); + {error, Reason} -> + Fmt = "Failed to add ~p bytes padding. Reason: ~p.", + throw(io_lib:format(Fmt, [PadSize, Reason])) + end. + %% @private try_read([]) -> {error, not_found}; diff --git a/src/platforms/esp32/tools/mkimage.sh.in b/src/platforms/esp32/tools/mkimage.sh.in index a0b99bab6f..a645f0dd0f 100644 --- a/src/platforms/esp32/tools/mkimage.sh.in +++ b/src/platforms/esp32/tools/mkimage.sh.in @@ -35,7 +35,9 @@ fail() exit 1 } -if [ "${@}" = "--help" ]; then +opts="${@}" + +if [ "${opts}" = "--help" ]; then escript "@CMAKE_BINARY_DIR@/mkimage.erl" --help exit 0 fi diff --git a/src/platforms/esp32/tools/mkimage_nvs.config.in b/src/platforms/esp32/tools/mkimage_nvs.config.in index efe9e54c2c..bef0f35b60 100644 --- a/src/platforms/esp32/tools/mkimage_nvs.config.in +++ b/src/platforms/esp32/tools/mkimage_nvs.config.in @@ -19,6 +19,9 @@ % SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later % +%% It is important that segments are always kept in order by offset +%% so mkimage.erl can pad partitons to the correct size. + #{ segments => [ #{