Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change burn task to Upgrade by default #682

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions lib/mix/nerves/fwup_stream.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defmodule Mix.Nerves.FwupStream do
@moduledoc """
IO Stream for Fwup

This functions the same as IO.Stream to push fwup IO to stdio, but
it also captures the IO for cases where you want to check the
output programatically as well.
"""

defstruct device: :standard_io, line_or_bytes: :line, raw: true, output: ""

def new(), do: %__MODULE__{}

defimpl Collectable do
def into(%{output: output} = stream) do
{[output], collect(stream)}
end

defp collect(%{device: device, raw: raw} = stream) do
fn
acc, {:cont, x} ->
case raw do
true -> IO.binwrite(device, x)
false -> IO.write(device, x)
end

[acc | x]

acc, _ ->
%{stream | output: IO.iodata_to_binary(acc)}
end
end
end
end
132 changes: 87 additions & 45 deletions lib/mix/tasks/burn.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
defmodule Mix.Tasks.Burn do
use Mix.Task
import Mix.Nerves.Utils
alias Mix.Nerves.Preflight
alias Mix.Nerves.{FwupStream, Preflight}
alias Nerves.Utils.WSL

@switches [device: :string, task: :string, firmware: :string]
@switches [device: :string, task: :string, firmware: :string, overwrite: :boolean]
@aliases [d: :device, t: :task, i: :firmware]

@shortdoc "Write a firmware image to an SDCard"
Expand All @@ -13,8 +13,9 @@ defmodule Mix.Tasks.Burn do
Writes the generated firmware image to an attached SDCard or file.

By default, this task detects attached SDCards and then invokes `fwup`
to overwrite the contents of the selected SDCard with the new image.
Data on the SDCard will be lost, so be careful.
to upgrade the contents of the selected SDCard with the new image.
If the upgrade to the next parition fails, it will then attempt to
completely overwrite the SDCard with the new image.

## Command line options

Expand All @@ -29,10 +30,14 @@ defmodule Mix.Tasks.Burn do
convention, the `complete` task writes everything to the SDCard including
bootloader and application data partitions. The `upgrade` task only
modifies the parts of the SDCard required to run the new software.
Defaults to `upgrade`

* `--firmware <name>` - (Optional) The path to the fw file to use.
Defaults to `<image_path>/<otp_app>.fw`

* `--overwrite` - (Optional) Overwrite the contents of the SDCard by
forcing the `complete` task. Defaults to `false`

## Examples

```
Expand Down Expand Up @@ -71,56 +76,39 @@ defmodule Mix.Tasks.Burn do
dev -> dev
end

task = if opts[:overwrite], do: "complete", else: opts[:task] || "upgrade"

set_provisioning(firmware_config[:provisioning])
burn(fw, dev, opts, argv)

burn(fw, dev, task, argv)

# Remove the temporary .fw file
WSL.cleanup_file(fw, firmware_location)
end

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

{cmd, args} =
case :os.type() do
{_, :darwin} ->
{"fwup", args}

{_, :linux} ->
if WSL.running_on_wsl?() do
WSL.admin_powershell_command("fwup", Enum.join(args, " "))
else
fwup = System.find_executable("fwup")

case File.stat(dev) do
{:ok, %File.Stat{access: :read_write}} ->
{"fwup", args}

{:error, :enoent} ->
case File.touch(dev, System.os_time(:second)) do
:ok ->
{"fwup", args}

{:error, :eacces} ->
elevate_user()
{"sudo", provision_env() ++ [fwup] ++ args}
end

_ ->
elevate_user()
{"sudo", provision_env() ++ [fwup] ++ args}
end
end

{_, :nt} ->
{"fwup", args}

{_, type} ->
raise "Unable to burn firmware on your host #{inspect(type)}"
end
os = get_os!()

shell(cmd, args)
{cmd, args} = cmd_and_args_for_os(os, args, dev)

shell(cmd, args, stream: FwupStream.new())
|> format_result(task)
|> case do
:failed_not_upgradable ->
Mix.shell().info("""
#{IO.ANSI.yellow()}
Device #{dev} either doesn't have firmware on it or has incompatible firmware.
Going to burn the whole MicroSD card so that it's in a factory-default state.
#{IO.ANSI.default_color()}
""")

burn(fw, dev, "complete", argv)

result ->
result
end
end

# Requests an elevation of user through askpass
Expand Down Expand Up @@ -157,4 +145,58 @@ defmodule Mix.Tasks.Burn do
Nerves.Env.firmware_path()
end
end

defp get_os!() do
case :os.type() do
{_, :linux} ->
if WSL.running_on_wsl?(), do: :wsl, else: :linux

{_, os} when os in [:darwin, :nt] ->
os

{_, os} ->
raise "Unable to burn firmware on your host #{inspect(os)}"
end
end

defp cmd_and_args_for_os(:linux, args, dev) do
fwup = System.find_executable("fwup")

case File.stat(dev) do
{:ok, %File.Stat{access: :read_write}} ->
{"fwup", args}

{:error, :enoent} ->
case File.touch(dev, System.os_time(:second)) do
:ok ->
{"fwup", args}

{:error, :eacces} ->
elevate_user()
{"sudo", provision_env() ++ [fwup] ++ args}
end

_ ->
elevate_user()
{"sudo", provision_env() ++ [fwup] ++ args}
end
end

defp cmd_and_args_for_os(:wsl, args, _dev) do
WSL.admin_powershell_command("fwup", Enum.join(args, " "))
end

defp cmd_and_args_for_os(_os, args, _dev), do: {"fwup", args}

defp format_result({_, 0}, _task), do: :ok

defp format_result({%FwupStream{output: o}, _}, "upgrade") do
if o =~ ~r/fwup: Expecting platform=#{mix_target()} and/ do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This messages comes from whatever the user puts in their fwup.config, so it seems fragile. Does this work with a FAT-formatted MicroSD card? Like one that you get fresh from a store?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, didn't think about that. I will try with a fresh card shortly

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am getting the same message on a newly FAT formatted microSD and USB drive.

I'm wondering if they change here is to not be automatic and just prompt again asking for a confirmation to run the complete task? Or...we just always run complete if the upgrade task fails?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically, even the task can be defined by the user as well and there may not even be an upgrade or complete task. Maybe this is considered a pure convenience and the only support for this "auto" work is with what's defined in official systems which all have this message and these tasks?

:failed_not_upgradable
else
:failed
end
end

defp format_result(_result, _task), do: :failed
end