Skip to content

Commit b8c28e0

Browse files
committed
Change burn task to Upgrade by default
This changes the `mix burn` (and subsequently `mix firmware.burn`) to use the FWUP `upgrade` task by default. If the upgrade fails because the device does not actually have upgradable firmware on it, then the task will fallback to using the `complete` task to overwrite the device. You can also now specify `--overwrite` to force the `complete` task and wipe the data on the device with the new firmware. see #679
1 parent 6f6f223 commit b8c28e0

File tree

2 files changed

+121
-45
lines changed

2 files changed

+121
-45
lines changed

lib/mix/nerves/fwup_stream.ex

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
defmodule Mix.Nerves.FwupStream do
2+
@moduledoc """
3+
IO Stream for Fwup
4+
5+
This functions the same as IO.Stream to push fwup IO to stdio, but
6+
it also captures the IO for cases where you want to check the
7+
output programatically as well.
8+
"""
9+
10+
defstruct device: :standard_io, line_or_bytes: :line, raw: true, output: ""
11+
12+
def new(), do: %__MODULE__{}
13+
14+
defimpl Collectable do
15+
def into(%{output: output} = stream) do
16+
{[output], collect(stream)}
17+
end
18+
19+
defp collect(%{device: device, raw: raw} = stream) do
20+
fn
21+
acc, {:cont, x} ->
22+
case raw do
23+
true -> IO.binwrite(device, x)
24+
false -> IO.write(device, x)
25+
end
26+
27+
[acc | x]
28+
29+
acc, _ ->
30+
%{stream | output: IO.iodata_to_binary(acc)}
31+
end
32+
end
33+
end
34+
end

lib/mix/tasks/burn.ex

Lines changed: 87 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
defmodule Mix.Tasks.Burn do
22
use Mix.Task
33
import Mix.Nerves.Utils
4-
alias Mix.Nerves.Preflight
4+
alias Mix.Nerves.{FwupStream, Preflight}
55
alias Nerves.Utils.WSL
66

7-
@switches [device: :string, task: :string, firmware: :string]
7+
@switches [device: :string, task: :string, firmware: :string, overwrite: :boolean]
88
@aliases [d: :device, t: :task, i: :firmware]
99

1010
@shortdoc "Write a firmware image to an SDCard"
@@ -13,8 +13,9 @@ defmodule Mix.Tasks.Burn do
1313
Writes the generated firmware image to an attached SDCard or file.
1414
1515
By default, this task detects attached SDCards and then invokes `fwup`
16-
to overwrite the contents of the selected SDCard with the new image.
17-
Data on the SDCard will be lost, so be careful.
16+
to upgrade the contents of the selected SDCard with the new image.
17+
If the upgrade to the next parition fails, it will then attempt to
18+
completely overwrite the SDCard with the new image.
1819
1920
## Command line options
2021
@@ -29,10 +30,14 @@ defmodule Mix.Tasks.Burn do
2930
convention, the `complete` task writes everything to the SDCard including
3031
bootloader and application data partitions. The `upgrade` task only
3132
modifies the parts of the SDCard required to run the new software.
33+
Defaults to `upgrade`
3234
3335
* `--firmware <name>` - (Optional) The path to the fw file to use.
3436
Defaults to `<image_path>/<otp_app>.fw`
3537
38+
* `--overwrite` - (Optional) Overwrite the contents of the SDCard by
39+
forcing the `complete` task. Defaults to `false`
40+
3641
## Examples
3742
3843
```
@@ -71,56 +76,39 @@ defmodule Mix.Tasks.Burn do
7176
dev -> dev
7277
end
7378

79+
task = if opts[:overwrite], do: "complete", else: opts[:task] || "upgrade"
80+
7481
set_provisioning(firmware_config[:provisioning])
75-
burn(fw, dev, opts, argv)
82+
83+
burn(fw, dev, task, argv)
7684

7785
# Remove the temporary .fw file
7886
WSL.cleanup_file(fw, firmware_location)
7987
end
8088

81-
defp burn(fw, dev, opts, argv) do
82-
task = opts[:task] || "complete"
89+
defp burn(fw, dev, task, argv) do
8390
args = ["-a", "-i", fw, "-t", task, "-d", dev] ++ argv
8491

85-
{cmd, args} =
86-
case :os.type() do
87-
{_, :darwin} ->
88-
{"fwup", args}
89-
90-
{_, :linux} ->
91-
if WSL.running_on_wsl?() do
92-
WSL.admin_powershell_command("fwup", Enum.join(args, " "))
93-
else
94-
fwup = System.find_executable("fwup")
95-
96-
case File.stat(dev) do
97-
{:ok, %File.Stat{access: :read_write}} ->
98-
{"fwup", args}
99-
100-
{:error, :enoent} ->
101-
case File.touch(dev, System.os_time(:second)) do
102-
:ok ->
103-
{"fwup", args}
104-
105-
{:error, :eacces} ->
106-
elevate_user()
107-
{"sudo", provision_env() ++ [fwup] ++ args}
108-
end
109-
110-
_ ->
111-
elevate_user()
112-
{"sudo", provision_env() ++ [fwup] ++ args}
113-
end
114-
end
115-
116-
{_, :nt} ->
117-
{"fwup", args}
118-
119-
{_, type} ->
120-
raise "Unable to burn firmware on your host #{inspect(type)}"
121-
end
92+
os = get_os!()
12293

123-
shell(cmd, args)
94+
{cmd, args} = cmd_and_args_for_os(os, args, dev)
95+
96+
shell(cmd, args, stream: FwupStream.new())
97+
|> format_result(task)
98+
|> case do
99+
:failed_not_upgradable ->
100+
Mix.shell().info("""
101+
#{IO.ANSI.yellow()}
102+
Device #{dev} either doesn't have firmware on it or has incompatible firmware.
103+
Going to burn the whole MicroSD card so that it's in a factory-default state.
104+
#{IO.ANSI.default_color()}
105+
""")
106+
107+
burn(fw, dev, "complete", argv)
108+
109+
result ->
110+
result
111+
end
124112
end
125113

126114
# Requests an elevation of user through askpass
@@ -157,4 +145,58 @@ defmodule Mix.Tasks.Burn do
157145
Nerves.Env.firmware_path()
158146
end
159147
end
148+
149+
defp get_os!() do
150+
case :os.type() do
151+
{_, :linux} ->
152+
if WSL.running_on_wsl?(), do: :wsl, else: :linux
153+
154+
{_, os} when os in [:darwin, :nt] ->
155+
os
156+
157+
{_, os} ->
158+
raise "Unable to burn firmware on your host #{inspect(os)}"
159+
end
160+
end
161+
162+
defp cmd_and_args_for_os(:linux, args, dev) do
163+
fwup = System.find_executable("fwup")
164+
165+
case File.stat(dev) do
166+
{:ok, %File.Stat{access: :read_write}} ->
167+
{"fwup", args}
168+
169+
{:error, :enoent} ->
170+
case File.touch(dev, System.os_time(:second)) do
171+
:ok ->
172+
{"fwup", args}
173+
174+
{:error, :eacces} ->
175+
elevate_user()
176+
{"sudo", provision_env() ++ [fwup] ++ args}
177+
end
178+
179+
_ ->
180+
elevate_user()
181+
{"sudo", provision_env() ++ [fwup] ++ args}
182+
end
183+
end
184+
185+
defp cmd_and_args_for_os(:wsl, args, _dev) do
186+
WSL.admin_powershell_command("fwup", Enum.join(args, " "))
187+
end
188+
189+
defp cmd_and_args_for_os(_os, args, _dev), do: {"fwup", args}
190+
191+
defp format_result({_, 0}, _task), do: :ok
192+
193+
defp format_result({%FwupStream{output: o}, _}, "upgrade") do
194+
if o =~ ~r/fwup: Expecting platform=#{mix_target()} and/ do
195+
:failed_not_upgradable
196+
else
197+
:failed
198+
end
199+
end
200+
201+
defp format_result(_result, _task), do: :failed
160202
end

0 commit comments

Comments
 (0)