Skip to content

Commit f0289e9

Browse files
joshknshoes
andauthored
Use :zip and Plug.Upload.random_file/1 where appropriate (#2291)
Switch from system shelling out for `zip` and `unzip` and use `:zip` instead. Also, favour `Plug.Upload.random_file/1` for safe cleanup of downloaded or generated files. --------- Co-authored-by: Nate Shoemaker <[email protected]>
1 parent 3542923 commit f0289e9

File tree

3 files changed

+93
-108
lines changed

3 files changed

+93
-108
lines changed

lib/nerves_hub/firmwares.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ defmodule NervesHub.Firmwares do
620620
org = NervesHub.Repo.preload(org, :org_keys)
621621

622622
with {:ok, %{id: org_key_id}} <- verify_signature(filepath, org.org_keys),
623-
{:ok, %{firmware_metadata: fm, tool_metadata: tm} = m} <-
623+
{:ok, %{path: conf_path, firmware_metadata: fm, tool_metadata: tm} = m} <-
624624
update_tool().get_firmware_metadata_from_file(filepath) do
625625
filename = fm.uuid <> ".fw"
626626

@@ -634,7 +634,7 @@ defmodule NervesHub.Firmwares do
634634
misc: fm.misc,
635635
org_id: org_id,
636636
org_key_id: org_key_id,
637-
delta_updatable: update_tool().delta_updatable?(filepath),
637+
delta_updatable: update_tool().delta_updatable?(conf_path),
638638
platform: fm.platform,
639639
product_name: fm.product,
640640
upload_metadata: firmware_upload_config().metadata(org_id, filename),

lib/nerves_hub/firmwares/update_tool/fwup.ex

Lines changed: 82 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
2323
{:ok, tool_metadata} <- get_tool_metadata(meta_conf_path) do
2424
{:ok,
2525
%{
26+
path: meta_conf_path,
2627
firmware_metadata: firmware_metadata,
2728
tool_metadata: tool_metadata,
2829
tool: "fwup",
@@ -49,26 +50,27 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
4950
work_dir = Path.join(System.tmp_dir(), uuid) |> Path.expand()
5051
_ = File.mkdir_p(work_dir)
5152

52-
source_path = Path.join(work_dir, "source.fw") |> Path.expand()
53-
target_path = Path.join(work_dir, "target.fw") |> Path.expand()
54-
55-
dl!(source_url, source_path)
56-
dl!(target_url, target_path)
53+
try do
54+
source_path = Path.join(work_dir, "source.fw") |> Path.expand()
55+
target_path = Path.join(work_dir, "target.fw") |> Path.expand()
5756

58-
output_filename = uuid <> ".fw"
59-
output_path = Path.join(work_dir, output_filename) |> Path.expand()
57+
dl!(source_url, source_path)
58+
dl!(target_url, target_path)
6059

61-
case do_delta_file(source_path, target_path, output_path, work_dir) do
62-
{:ok, output} ->
63-
{:ok, output}
60+
case do_delta_file(source_path, target_path, work_dir) do
61+
{:ok, output} ->
62+
{:ok, output}
6463

65-
{:error, reason} ->
66-
Logger.warning("Could not create a firmware delta: #{inspect(reason)}",
67-
source_url: source_url,
68-
target_url: target_url
69-
)
64+
{:error, reason} ->
65+
Logger.warning("Could not create a firmware delta: #{inspect(reason)}",
66+
source_url: source_url,
67+
target_url: target_url
68+
)
7069

71-
{:error, :delta_not_created}
70+
{:error, :delta_not_created}
71+
end
72+
after
73+
File.rmdir(work_dir)
7274
end
7375
end
7476

@@ -84,14 +86,7 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
8486

8587
@impl NervesHub.Firmwares.UpdateTool
8688
def delta_updatable?(file_path) do
87-
{meta, 0} = System.cmd("unzip", ["-qqp", file_path, "meta.conf"], env: [])
88-
89-
path =
90-
System.tmp_dir!()
91-
|> Path.join("meta.conf")
92-
93-
File.write!(path, meta)
94-
{:ok, feature_usage} = Confuse.Fwup.get_feature_usage(path)
89+
{:ok, feature_usage} = Confuse.Fwup.get_feature_usage(file_path)
9590

9691
feature_usage.raw_deltas? or feature_usage.fat_deltas?
9792
end
@@ -144,7 +139,7 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
144139
end)
145140
end
146141

147-
def do_delta_file(source_path, target_path, output_path, work_dir) do
142+
def do_delta_file(source_path, target_path, work_dir) do
148143
source_work_dir = Path.join(work_dir, "source")
149144
target_work_dir = Path.join(work_dir, "target")
150145
output_work_dir = Path.join(work_dir, "output")
@@ -155,8 +150,8 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
155150
:ok <- File.mkdir_p(output_work_dir),
156151
{:ok, %{size: source_size}} <- File.stat(source_path),
157152
{:ok, %{size: target_size}} <- File.stat(target_path),
158-
{_, 0} <- System.cmd("unzip", ["-qq", source_path, "-d", source_work_dir], env: []),
159-
{_, 0} <- System.cmd("unzip", ["-qq", target_path, "-d", target_work_dir], env: []),
153+
{:ok, _} <- :zip.extract(to_charlist(source_path), cwd: to_charlist(source_work_dir)),
154+
{:ok, _} <- :zip.extract(to_charlist(target_path), cwd: to_charlist(target_work_dir)),
160155
{:ok, source_meta_conf} <- File.read(Path.join(source_work_dir, "meta.conf")),
161156
{:ok, target_meta_conf} <- File.read(Path.join(target_work_dir, "meta.conf")),
162157
{:ok, tool_metadata} <- get_tool_metadata(Path.join(target_work_dir, "meta.conf")),
@@ -215,22 +210,18 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
215210
end
216211
end
217212

218-
# firmware archive files order matters:
219-
# 1. meta.conf.ed25519 (optional)
220-
# 2. meta.conf
221-
# 3. other...
222-
[
223-
"meta.conf.*",
224-
"meta.conf",
225-
"data"
226-
]
227-
|> Enum.each(&add_to_zip(&1, output_work_dir, output_path))
213+
{:ok, delta_zip_path} = Plug.Upload.random_file("generated_delta_zip_file")
214+
215+
{:ok, _} =
216+
:zip.create(to_charlist(delta_zip_path), generate_file_list(output_work_dir),
217+
cwd: to_charlist(output_work_dir)
218+
)
228219

229-
{:ok, %{size: size}} = File.stat(output_path)
220+
{:ok, %{size: size}} = File.stat(delta_zip_path)
230221

231222
{:ok,
232223
%{
233-
filepath: output_path,
224+
filepath: delta_zip_path,
234225
size: size,
235226
source_size: source_size,
236227
target_size: target_size,
@@ -252,6 +243,25 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
252243
end
253244
end
254245

246+
defp generate_file_list(workdir) do
247+
# firmware archive files order matters:
248+
# 1. meta.conf.ed25519 (optional)
249+
# 2. meta.conf
250+
# 3. other...
251+
[
252+
"meta.conf.*",
253+
"meta.conf",
254+
"data"
255+
]
256+
|> Enum.map(fn glob -> workdir |> Path.join(glob) |> Path.wildcard() end)
257+
|> List.flatten()
258+
|> Enum.map(fn file ->
259+
file
260+
|> String.replace_prefix("#{workdir}/", "")
261+
|> to_charlist()
262+
end)
263+
end
264+
255265
defp get_tool_metadata(meta_conf_path) do
256266
with {:ok, feature_usage} <- Confuse.Fwup.get_feature_usage(meta_conf_path) do
257267
tool_metadata =
@@ -269,75 +279,50 @@ defmodule NervesHub.Firmwares.UpdateTool.Fwup do
269279
end
270280
end
271281

272-
defp add_to_zip(glob, workdir, output) do
273-
workdir
274-
|> Path.join(glob)
275-
|> Path.wildcard()
276-
|> case do
277-
[] ->
278-
:ok
279-
280-
paths ->
281-
args = ["-r", "-qq", output | Enum.map(paths, &Path.relative_to(&1, workdir))]
282-
{_, 0} = System.cmd("zip", args, cd: workdir, env: [])
283-
284-
:ok
285-
end
286-
end
287-
288282
defp extract_meta_conf_locally(filepath) do
289-
try do
290-
dir =
291-
System.tmp_dir!()
292-
|> Path.join("nerves-hub-meta-#{System.unique_integer([:positive])}")
283+
{:ok, path} = Plug.Upload.random_file("nerves_hub_meta_conf")
293284

294-
_ = File.mkdir_p(dir)
285+
stream = File.stream!(path)
295286

296-
path = Path.join(dir, "meta.conf")
297-
stream = File.stream!(path)
287+
{:ok, unzip} =
288+
filepath
289+
|> Unzip.LocalFile.open()
290+
|> Unzip.new()
298291

299-
{:ok, unzip} =
300-
filepath
301-
|> Unzip.LocalFile.open()
302-
|> Unzip.new()
292+
_ =
293+
unzip
294+
|> Unzip.file_stream!("meta.conf")
295+
|> Enum.into(stream, &IO.iodata_to_binary/1)
303296

304-
_ =
305-
unzip
306-
|> Unzip.file_stream!("meta.conf")
307-
|> Enum.into(stream, &IO.iodata_to_binary/1)
308-
309-
{:ok, path}
310-
rescue
311-
e ->
312-
Logging.log_message_to_sentry(
313-
"[UpdateTool.Fwup] Extracting meta.conf failed due to: #{inspect(e)}",
314-
%{filepath: filepath}
315-
)
297+
{:ok, path}
298+
rescue
299+
e ->
300+
Logging.log_message_to_sentry(
301+
"[UpdateTool.Fwup] Extracting meta.conf failed due to: #{inspect(e)}",
302+
%{filepath: filepath}
303+
)
316304

317-
Logger.error("Extracting meta.conf failed due to: #{inspect(e)}", filepath: filepath)
318-
{:error, :extract_meta_conf_failed}
319-
end
305+
Logger.error("Extracting meta.conf failed due to: #{inspect(e)}", filepath: filepath)
306+
{:error, :extract_meta_conf_failed}
320307
end
321308

322309
defp download_archive(firmware) do
323-
try do
324-
{:ok, url} = firmware_upload_config().download_file(firmware)
325-
archive_path = Path.join(System.tmp_dir(), "nh-#{firmware.uuid}.fw")
326-
dl!(url, archive_path)
327-
{:ok, archive_path}
328-
rescue
329-
e ->
330-
Logging.log_message_to_sentry(
331-
"[UpdateTool.Fwup] Downloading firmware failed due to: #{inspect(e)}",
332-
%{firmware_uuid: firmware.uuid}
333-
)
310+
{:ok, url} = firmware_upload_config().download_file(firmware)
311+
{:ok, archive_path} = Plug.Upload.random_file("downloaded_firmware_#{firmware.id}")
312+
dl!(url, archive_path)
313+
{:ok, archive_path}
314+
rescue
315+
e ->
316+
Logging.log_message_to_sentry(
317+
"[UpdateTool.Fwup] Downloading firmware failed due to: #{inspect(e)}",
318+
%{firmware_uuid: firmware.uuid}
319+
)
334320

335-
Logger.error("Downloading firmware failed due to: #{inspect(e)}",
336-
firmware_uuid: firmware.uuid
337-
)
321+
Logger.error("Downloading firmware failed due to: #{inspect(e)}",
322+
firmware_uuid: firmware.uuid
323+
)
338324

339-
{:error, :download_firmware_failed}
340-
end
325+
{:error, :download_firmware_failed}
341326
end
342327

343328
defp firmware_upload_config(), do: Application.fetch_env!(:nerves_hub, :firmware_upload)

test/nerves_hub/firmwares/update_tool_test.exs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ defmodule NervesHub.Firmwares.UpdateToolTest do
197197
source_size: ^source_size,
198198
target_size: ^target_size
199199
}} =
200-
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "delta.fw"), Path.join(dir, "work"))
200+
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "work"))
201201

202202
assert %{size: ^delta_size} = File.stat!(delta_path)
203203

@@ -243,7 +243,7 @@ defmodule NervesHub.Firmwares.UpdateToolTest do
243243
source_size: ^source_size,
244244
target_size: ^target_size
245245
}} =
246-
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "delta.fw"), Path.join(dir, "work"))
246+
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "work"))
247247

248248
assert %{size: ^delta_size} = File.stat!(delta_path)
249249

@@ -289,7 +289,7 @@ defmodule NervesHub.Firmwares.UpdateToolTest do
289289
source_size: ^source_size,
290290
target_size: ^target_size
291291
}} =
292-
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "delta.fw"), Path.join(dir, "work"))
292+
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "work"))
293293

294294
assert %{size: ^delta_size} = File.stat!(delta_path)
295295

@@ -335,7 +335,7 @@ defmodule NervesHub.Firmwares.UpdateToolTest do
335335
source_size: ^source_size,
336336
target_size: ^target_size
337337
}} =
338-
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "delta.fw"), Path.join(dir, "work"))
338+
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "work"))
339339

340340
assert %{size: ^delta_size} = File.stat!(delta_path)
341341

@@ -403,7 +403,7 @@ defmodule NervesHub.Firmwares.UpdateToolTest do
403403
source_size: ^source_size,
404404
target_size: ^target_size
405405
}} =
406-
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "delta.fw"), Path.join(dir, "work"))
406+
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "work"))
407407

408408
assert %{size: ^delta_size} = File.stat!(delta_path)
409409

@@ -443,7 +443,7 @@ defmodule NervesHub.Firmwares.UpdateToolTest do
443443
fw_b = build_fw!(Path.join(dir, "b.fw"), raw_conf_path, data_path_2)
444444

445445
{:error, [err]} =
446-
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "delta.fw"), Path.join(dir, "work"))
446+
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "work"))
447447

448448
assert err =~
449449
"Target uses raw deltas and source firmware uses encryption for the same resource but target firmware has no cipher or"
@@ -467,7 +467,7 @@ defmodule NervesHub.Firmwares.UpdateToolTest do
467467
fw_b = build_fw!(Path.join(dir, "b.fw"), fwup_conf_path, data_path_2)
468468

469469
{:error, :no_delta_support_in_firmware} =
470-
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "delta.fw"), Path.join(dir, "work"))
470+
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "work"))
471471
end
472472

473473
@tag :tmp_dir
@@ -497,7 +497,7 @@ defmodule NervesHub.Firmwares.UpdateToolTest do
497497
source_size: ^source_size,
498498
target_size: ^target_size
499499
}} =
500-
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "delta.fw"), Path.join(dir, "work"))
500+
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "work"))
501501

502502
%{size: ^delta_size} = File.stat!(delta_path)
503503

@@ -561,7 +561,7 @@ defmodule NervesHub.Firmwares.UpdateToolTest do
561561
source_size: ^source_size,
562562
target_size: ^target_size
563563
}} =
564-
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "delta.fw"), Path.join(dir, "work"))
564+
Fwup.do_delta_file(fw_a, fw_b, Path.join(dir, "work"))
565565

566566
assert %{size: ^delta_size} = File.stat!(delta_path)
567567

0 commit comments

Comments
 (0)