From a81627160baa3e9a4322656df431141dfe9839ae Mon Sep 17 00:00:00 2001 From: Sebastien Merle Date: Wed, 30 Oct 2024 08:50:28 +0100 Subject: [PATCH] Validate the firmware when packing and include manifest in the update --- CHANGELOG.md | 3 ++ rebar.config | 3 +- rebar.lock | 13 +++--- src/grisp_tools_pack.erl | 91 ++++++++++++++++++++++++++++++++++++++-- 4 files changed, 100 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1a2fc8..ecee546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,9 @@ and this project adheres to - The deploy command now generate a MANIFEST files that contains information about the deployed software in a term file that can be read with file:consult/1. +- The pack command now validates the firmware by creating an image, writing the + firmware and trying to mount it. If the firmware contains a software build + manifest, it will also be included in the update manifest. ## [2.7.1] - 2024-10-11 diff --git a/rebar.config b/rebar.config index 8248c80..3d3d6fb 100644 --- a/rebar.config +++ b/rebar.config @@ -4,7 +4,8 @@ bbmustache, hackney, edifa, - grisp_update_packager + %FIXME: Revert to HEX package when released + {grisp_update_packager, {git, "https://github.com/grisp/grisp_update_packager.git", {branch, "main"}}} ]}. {project_plugins, [rebar3_ex_doc]}. {hex, [{doc, ex_doc}]}. diff --git a/rebar.lock b/rebar.lock index a0cb1a2..908cdcd 100644 --- a/rebar.lock +++ b/rebar.lock @@ -3,7 +3,10 @@ {<<"certifi">>,{pkg,<<"certifi">>,<<"2.12.0">>},1}, {<<"edifa">>,{pkg,<<"edifa">>,<<"1.0.0">>},0}, {<<"erlexec">>,{pkg,<<"erlexec">>,<<"2.0.7">>},1}, - {<<"grisp_update_packager">>,{pkg,<<"grisp_update_packager">>,<<"1.0.0">>},0}, + {<<"grisp_update_packager">>, + {git,"https://github.com/grisp/grisp_update_packager.git", + {ref,"ce9de02d36a8b3c8596c72ce60b380386df19818"}}, + 0}, {<<"hackney">>,{pkg,<<"hackney">>,<<"1.20.1">>},0}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},1}, {<<"mapz">>,{pkg,<<"mapz">>,<<"2.4.0">>},0}, @@ -14,14 +17,13 @@ {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.7">>},1}, {<<"termseal">>,{pkg,<<"termseal">>,<<"0.1.1">>},1}, {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},1}, - {<<"uuid">>,{pkg,<<"uuid_erl">>,<<"2.0.7">>},1}]}. + {<<"uuid">>,{pkg,<<"uuid_erl">>,<<"2.0.4">>},1}]}. [ {pkg_hash,[ {<<"bbmustache">>, <<"0CABDCE0DB9FE6D3318131174B9F2B351328A4C0AFBEB3E6E99BB0E02E9B621D">>}, {<<"certifi">>, <<"2D1CCA2EC95F59643862AF91F001478C9863C2AC9CB6E2F89780BFD8DE987329">>}, {<<"edifa">>, <<"0F1A01A0C79B7135F334B3FCEEB624F0574C5ED3E4554B06C8664AADA6A339C8">>}, {<<"erlexec">>, <<"76D0BC7487929741B5BB9F74DA2AF5DAF1492134733CF9A05C7AAA278B6934C5">>}, - {<<"grisp_update_packager">>, <<"0532CCD0955398FAC4E1DE90FE85DB941CA609A2F4E066CFFE01ECE41DCCE119">>}, {<<"hackney">>, <<"8D97AEC62DDDDD757D128BFD1DF6C5861093419F8F7A4223823537BAD5D064E2">>}, {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>}, {<<"mapz">>, <<"77A8E38B69BAB16C5D3EBD44E6C619F8AF1F1598B0CAAE301D266605A0865756">>}, @@ -32,13 +34,12 @@ {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>}, {<<"termseal">>, <<"C9D93D4FF638EE99F9377D3438FC7AD132D2901EBBAF10C54F8DEA1D7E24D61C">>}, {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}, - {<<"uuid">>, <<"B2078D2CC814F53AFA52D36C91E08962C7E7373585C623F4C0EA6DFB04B2AF94">>}]}, + {<<"uuid">>, <<"77C3E3EE1E1701A2856CE945846D7CEB71931C60633A305D0B0FEAE03B2B3B5C">>}]}, {pkg_hash_ext,[ {<<"bbmustache">>, <<"688B33A4D5CC2D51F575ADF0B3683FC40A38314A2F150906EDCFC77F5B577B3B">>}, {<<"certifi">>, <<"EE68D85DF22E554040CDB4BE100F33873AC6051387BAF6A8F6CE82272340FF1C">>}, {<<"edifa">>, <<"A1E010561E7D236A24C668D95626BE2BFE082ED0331CE1E6798BE0CD43F59A7B">>}, {<<"erlexec">>, <<"AF2DD940BB8E32F5AA40A65CB455DCAA18F5334FD3507E9BFD14A021E9630897">>}, - {<<"grisp_update_packager">>, <<"47BFDF6FADBED4B8342205A812198CF913E0223A98A775CAAE5D2FB5D5CF751C">>}, {<<"hackney">>, <<"FE9094E5F1A2A2C0A7D10918FEE36BFEC0EC2A979994CFF8CFE8058CD9AF38E3">>}, {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>}, {<<"mapz">>, <<"4B68DF5CF0522E0D6545DF7B681BC052865CDB78405AD4CC9C55FE45EE7B25BE">>}, @@ -49,5 +50,5 @@ {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>}, {<<"termseal">>, <<"466280936214AF1894FC431642E83341B7D13580A3F3485820A2D300C5CAEB49">>}, {<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}, - {<<"uuid">>, <<"4E4C5CA3461DC47C5E157ED42AA3981A053B7A186792AF972A27B14A9489324E">>}]} + {<<"uuid">>, <<"7A4CCD1C151D9B88B4383FA802BCCF9BCB3754B7F53D7CAA164D51A14A6652E4">>}]} ]. diff --git a/src/grisp_tools_pack.erl b/src/grisp_tools_pack.erl index bae7b02..a9c350c 100644 --- a/src/grisp_tools_pack.erl +++ b/src/grisp_tools_pack.erl @@ -86,9 +86,15 @@ package(State) -> grisp_tools_util:weave(State, [ fun expand_bootloader/1, fun expand_system/1, + fun create_image/1, + fun create_partitions/1, + fun copy_firmware/1, + fun extract_manifest/1, + fun close_image/1, fun build_package/1, fun cleanup/1 ], [ + fun close_image/1, fun cleanup/1 ]). @@ -106,12 +112,71 @@ expand_system(State = #{system := SysPath}) expand_system(State) -> State. -build_package(State = #{package := PackageFile}) -> +create_image(State = #{temp_dir := TempDir}) -> + Opts = edifa_opts(State, #{temp_dir => TempDir}), + ImageFile = filename:join(TempDir, "emmc.img"), + case edifa:create(ImageFile, ?GRISP2_IMAGE_SIZE, Opts) of + {ok, Pid, State2} -> + State2#{edifa_pid => Pid}; + {error, Reason, State2} -> + event(State2, [{error, Reason}]) + end. + +create_partitions(State = #{edifa_pid := Pid}) -> + Opts = edifa_opts(State), + case edifa:partition(Pid, mbr, ?GRISP2_PARTITIONS, Opts) of + {ok, [_, _] = Partitions, State2} -> + State2#{partitions => Partitions}; + {error, Reason, State2} -> + event(State2, [{error, Reason}]) + end. + +copy_firmware(State = #{edifa_pid := Pid, system := ExpPath}) -> + Opts = edifa_opts(State, #{ + count => ?GRISP2_SYSTEM_SIZE, + seek => ?GRISP2_RESERVED_SIZE + }), + case edifa:write(Pid, ExpPath, Opts) of + {ok, State2} -> State2; + {error, Reason, State2} -> + event(State2, [{error, Reason}]) + end. + +extract_manifest(State = #{edifa_pid := Pid, partitions := [PartId | _]}) -> + Opts = edifa_opts(State), + case edifa:mount(Pid, PartId, Opts) of + {error, Reason, State2} -> + event(State2, [{error, Reason}]); + {ok, MountPoint, State2} -> + ManifestPath = filename:join(MountPoint, "MANIFEST"), + Manifest = case file:consult(ManifestPath) of + {error, _Reason} -> undefined; + {ok, Term} -> Term + end, + Opts2 = edifa_opts(State2), + case edifa:unmount(Pid, PartId, Opts2) of + {error, Reason, State2} -> + event(State2, [{error, Reason}]); + {ok, State3} -> + event(State3#{manifest => Manifest}, + [{manifest, Manifest}]) + end + end. + +close_image(State = #{edifa_pid := Pid}) -> + case edifa:close(Pid, edifa_opts(State)) of + {ok, State2} -> maps:remove(edifa_pid, State2); + {error, Reason, State2} -> + event(State2, [{error, Reason}]) + end. + +build_package(State = #{package := PackageFile, manifest := Manifest}) -> PackagerOpts1 = maps:with([name, version, block_size, key_file, system, bootloader], State), PackagerOpts2 = PackagerOpts1#{ tarball => true, - mbr => ?GRISP2_PARTITIONS + mbr => ?GRISP2_PARTITIONS, + manifest => Manifest }, case grisp_update_packager:package(PackageFile, PackagerOpts2) of ok -> event(State, [{done, PackageFile}]); @@ -119,7 +184,7 @@ build_package(State = #{package := PackageFile}) -> end. cleanup(State) -> - cleanup_temp_dir(State). + cleanup_temp_dir(cleanup_image(State)). cleanup_temp_dir(State = #{temp_dir := TempDir, cleanup_temp_dir := true}) -> {_, State2} = shell(State, "rm -rf '~s'", [TempDir]), @@ -127,9 +192,29 @@ cleanup_temp_dir(State = #{temp_dir := TempDir, cleanup_temp_dir := true}) -> cleanup_temp_dir(State) -> State. +cleanup_image(State = #{edifa_pid := Pid}) -> + case edifa:close(Pid, edifa_opts(State)) of + {ok, State2} -> maps:remove(edifa_pid, State2); + {error, _Reason, State2} -> maps:remove(edifa_pid, State2) + end; +cleanup_image(State) -> + State. + %--- Internal ------------------------------------------------------------------ +edifa_opts(State) -> + edifa_opts(State, #{}). + +edifa_opts(State, Opts) -> + Opts#{ + log_handler => fun edifa_log_hanler/2, + log_state => State + }. + +edifa_log_hanler(Event, State) -> + event(State, [Event]). + shell(State, Fmt, Args) -> Cmd = binary_to_list(iolist_to_binary(io_lib:format(Fmt, Args))), {{ok, Output}, State2} = grisp_tools_util:shell(State, Cmd),