@@ -16,6 +16,10 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
1616 @ very_safe_version "1.13.0"
1717 @ oldest_version Version . parse! ( "0.2.0" )
1818
19+ # If a payload is smaller than this there is no point to do a delta, the overhead takes more space
20+ # in the best case
21+ @ delta_overhead_limit 22
22+
1923 @ impl NervesHub.Firmwares.UpdateTool
2024 def get_firmware_metadata_from_file ( filepath ) do
2125 with { :ok , firmware_metadata } <- FwupUtil . metadata ( filepath ) ,
@@ -61,7 +65,7 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
6165 { :ok , output }
6266
6367 { :error , reason } ->
64- Logger . warning ( "Could not create a firmware delta : #{ inspect ( reason ) } " ,
68+ Logger . warning ( "Firmware delta creation failed : #{ inspect ( reason ) } " ,
6569 source_url: source_url ,
6670 target_url: target_url
6771 )
@@ -158,7 +162,7 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
158162 { :ok , all_delta_files } <- delta_files ( deltas ) do
159163 Logger . info ( "Generating delta for files: #{ Enum . join ( all_delta_files , ", " ) } " )
160164
161- _ =
165+ file_list =
162166 for absolute <- Path . wildcard ( target_work_dir <> "/**" ) , not File . dir? ( absolute ) do
163167 path = Path . relative_to ( absolute , target_work_dir )
164168
@@ -168,62 +172,107 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
168172 |> Path . dirname ( )
169173 |> File . mkdir_p! ( )
170174
171- _ =
172- case path do
173- "meta." <> _ ->
174- File . cp! ( Path . join ( target_work_dir , path ) , Path . join ( output_work_dir , path ) )
175-
176- "data/" <> subpath ->
177- if subpath in all_delta_files do
178- source_filepath = Path . join ( source_work_dir , path )
179- target_filepath = Path . join ( target_work_dir , path )
180-
181- case File . stat ( source_filepath ) do
182- { :ok , % { size: f_source_size } } ->
183- args = [
184- "-A" ,
185- "-S" ,
186- "-f" ,
187- "-s" ,
188- source_filepath ,
189- target_filepath ,
190- output_path
191- ]
192-
193- % { size: f_target_size } = File . stat! ( target_filepath )
194-
195- { _ , 0 } = System . cmd ( "xdelta3" , args , stderr_to_stdout: true , env: [ ] )
196- % { size: f_delta_size } = File . stat! ( output_path )
197-
198- Logger . info (
199- "Generated delta for #{ path } , from #{ Float . round ( f_source_size / 1024 / 1024 , 1 ) } MB to #{ Float . round ( f_target_size / 1024 / 1024 , 1 ) } MB via delta of #{ Float . round ( f_delta_size / 1024 / 1024 , 1 ) } MB"
200- )
201-
202- { :error , :enoent } ->
203- File . cp! ( target_filepath , output_path )
204- end
205- else
206- File . cp! ( Path . join ( target_work_dir , path ) , Path . join ( output_work_dir , path ) )
207- end
208- end
175+ maybe_generate_delta (
176+ path ,
177+ source_work_dir ,
178+ target_work_dir ,
179+ output_work_dir ,
180+ all_delta_files
181+ )
209182 end
183+ |> Enum . reject ( & is_nil ( & 1 ) )
184+
185+ { :ok , delta_zip_path } = Plug.Upload . random_file ( "#{ source_uuid } _#{ target_uuid } _delta.zip" )
186+ _ = File . rm ( delta_zip_path )
187+ _ = File . cp ( target_path , delta_zip_path )
188+
189+ with { true , :changes_in_delta } <- { Enum . any? ( file_list ) , :changes_in_delta } ,
190+ :ok <- update_changed_files ( file_list , delta_zip_path , output_work_dir ) ,
191+ { :ok , % { size: delta_size } } <- File . stat ( delta_zip_path ) ,
192+ { true , :delta_smaller } <- { delta_size < target_size , :delta_smaller } do
193+ { :ok ,
194+ % {
195+ filepath: delta_zip_path ,
196+ size: delta_size ,
197+ source_size: source_size ,
198+ target_size: target_size ,
199+ tool: "fwup" ,
200+ tool_metadata: tool_metadata
201+ } }
202+ else
203+ { false , :changes_in_delta } -> { :error , :no_changes_in_delta }
204+ { false , :delta_smaller } -> { :error , :delta_larger_than_target }
205+ end
206+ end
207+ end
210208
211- { :ok , delta_zip_path } = Plug.Upload . random_file ( "#{ source_uuid } _#{ target_uuid } _delta" )
209+ defp update_changed_files ( file_list , delta_zip_path , output_work_dir ) do
210+ for file <- file_list do
211+ args = [ "-9" , delta_zip_path , String . replace_prefix ( file , "#{ output_work_dir } /" , "" ) ]
212+ { _output , 0 } = System . cmd ( "zip" , args , cd: output_work_dir )
213+ end
212214
213- { :ok , _ } =
214- :zip . create ( to_charlist ( delta_zip_path ) , generate_file_list ( output_work_dir ) , cwd: to_charlist ( output_work_dir ) )
215+ :ok
216+ end
215217
216- { :ok , % { size: size } } = File . stat ( delta_zip_path )
218+ defp maybe_generate_delta ( "meta." <> _ , _ , _ , _ , _ ) do
219+ nil
220+ end
217221
218- { :ok ,
219- % {
220- filepath: delta_zip_path ,
221- size: size ,
222- source_size: source_size ,
223- target_size: target_size ,
224- tool: "fwup" ,
225- tool_metadata: tool_metadata
226- } }
222+ defp maybe_generate_delta (
223+ "data/" <> subpath = path ,
224+ source_work_dir ,
225+ target_work_dir ,
226+ output_work_dir ,
227+ all_delta_files
228+ ) do
229+ output_path = Path . join ( output_work_dir , path )
230+ target_filepath = Path . join ( target_work_dir , path )
231+
232+ if subpath in all_delta_files do
233+ source_filepath = Path . join ( source_work_dir , path )
234+
235+ case File . stat ( source_filepath ) do
236+ { :ok , % { size: f_source_size } } ->
237+ args = [
238+ "-A" ,
239+ "-S" ,
240+ "-f" ,
241+ "-s" ,
242+ source_filepath ,
243+ target_filepath ,
244+ output_path
245+ ]
246+
247+ % { size: f_target_size } = File . stat! ( target_filepath )
248+
249+ if f_target_size < @ delta_overhead_limit do
250+ Logger . info ( "Skipping generating delta for #{ path } it is under 22 bytes." )
251+ nil
252+ else
253+ { _ , 0 } = System . cmd ( "xdelta3" , args , stderr_to_stdout: true , env: [ ] )
254+ % { size: f_delta_size } = File . stat! ( output_path )
255+
256+ if f_delta_size < f_target_size do
257+ Logger . info (
258+ "Generated delta for #{ path } , from #{ Float . round ( f_source_size / 1024 / 1024 , 1 ) } MB to #{ Float . round ( f_target_size / 1024 / 1024 , 1 ) } MB via delta of #{ Float . round ( f_delta_size / 1024 / 1024 , 1 ) } MB"
259+ )
260+
261+ output_path
262+ else
263+ Logger . info (
264+ "Skipping generated delta for #{ path } , delta is larger: #{ Float . round ( f_source_size / 1024 / 1024 , 1 ) } MB to #{ Float . round ( f_target_size / 1024 / 1024 , 1 ) } MB via delta of #{ Float . round ( f_delta_size / 1024 / 1024 , 1 ) } MB"
265+ )
266+
267+ nil
268+ end
269+ end
270+
271+ { :error , :enoent } ->
272+ nil
273+ end
274+ else
275+ nil
227276 end
228277 end
229278
@@ -239,25 +288,6 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
239288 end
240289 end
241290
242- defp generate_file_list ( workdir ) do
243- # firmware archive files order matters:
244- # 1. meta.conf.ed25519 (optional)
245- # 2. meta.conf
246- # 3. other...
247- [
248- "meta.conf.*" ,
249- "meta.conf" ,
250- "data"
251- ]
252- |> Enum . map ( fn glob -> workdir |> Path . join ( glob ) |> Path . wildcard ( ) end )
253- |> List . flatten ( )
254- |> Enum . map ( fn file ->
255- file
256- |> String . replace_prefix ( "#{ workdir } /" , "" )
257- |> to_charlist ( )
258- end )
259- end
260-
261291 defp get_tool_metadata ( meta_conf_path ) do
262292 with { :ok , feature_usage } <- Confuse.Fwup . get_feature_usage ( meta_conf_path ) do
263293 tool_metadata =
0 commit comments