NervesGithubUpdater.Fwup (nerves_github_updater v0.1.0)

Copy Markdown View Source

Streams a .fw to fwup --apply --framing via an Erlang Port.

Library-shape: no host dependencies.

Wire protocol

Both directions are length-prefixed: [4-byte BE length | payload]. Input from us:

  • [length | bytes] records carrying .fw payload

  • [0] (zero-length record) marks end-of-stream

Output from fwup (each record's payload begins with a 2-byte type code):

  • PR + 2-byte BE percent — progress
  • OK + 2 bytes reserved — success
  • ER + UTF-8 error message — error
  • WN + UTF-8 warning — non-fatal warning

We deliberately do not pass --exit-handshake: in framing mode with an Erlang Port, the handshake byte deadlocks because fwup doesn't see EOF on stdin while the Port is alive. fwup exits cleanly on the zero-length terminator without the handshake.

Source for the framing format: nerves_firmware_http and ssh_subsystem_fwup use the same parser. We rely on Port.command/2's blocking semantics for back-pressure: when fwup's stdin buffer is full, the write blocks until fwup drains.

Caller contract

Callers MUST serialize apply/2 invocations. Each call spawns one fwup process against devpath; concurrent applies to the same device are undefined.

Accepted risks

  • No --exit-handshake (see above) — the final ER text can race behind :exit_status, so an error exit may report a nil/stale message.
  • :stderr_to_stdout merges stderr into the framed stream. A chatty stderr line could theoretically desynchronize frame parsing or stall it. Accepted because fwup in framing mode keeps stderr quiet in practice.

Summary

Functions

Applies fw_path via fwup, blocking until fwup exits.

Types

opt()

@type opt() ::
  {:devpath, String.t() | nil}
  | {:task, String.t()}
  | {:progress, (term() -> any())}
  | {:chunk_size, pos_integer()}
  | {:fwup_path, String.t() | nil}
  | {:extra_args, [String.t()]}

Functions

apply(fw_path, opts \\ [])

@spec apply(Path.t(), [opt()]) :: :ok | {:error, term()}

Applies fw_path via fwup, blocking until fwup exits.

Options

  • :devpath — block device to write to. Required for real hardware; for the host-side smoke test it can be a path to a file used as a target.
  • :task — the fwup task name (default "upgrade").
  • :progress — callback invoked as {:flashing, percent} while fwup applies the firmware.
  • :chunk_size — read chunk size for fw_path (default 65536).
  • :fwup_path — explicit path to the fwup executable. Defaults to System.find_executable("fwup").
  • :extra_args — extra fwup CLI args appended after the fixed args (default []). Hook for future delta-update fwup flags like --unsafe.

Returns :ok on exit status 0; {:error, term()} otherwise.