Nerves.Runtime.KV behaviour (nerves_runtime v0.11.8) View Source

Key Value storage for firmware variables provided by fwup.

KV provides functionality to read and modify firmware metadata set by fwup. The firmware metadata contains information such as the active firmware slot, where the application data partition is located, etc. The firmware metadata store is a simple key-value store where both keys and values are stored as strings.

The firmware metadata is stored in the U-boot environment block stored at the beginning of the disk outside of actual partitions. It is not stored redundantly, and it can be susceptible to corruption in case of power failure during writes. For this reason, it is recommended to use the firmware metadata with caution. The access patterns in this module lower the risk of corruption, and risk can be lowered further by storing as little data as possible in the firmware metadata.

UBootEnv can alternatively be used for more direct access to the firmware metadata. However, since KV utilizes caching of the firmware metadata, you should use either KV or UBootEnv, not both.

Examples

Getting all firmware metadata:

iex> Nerves.Runtime.KV.get_all()
%{
  "a.nerves_fw_application_part0_devpath" => "/dev/mmcblk0p3",
  "a.nerves_fw_application_part0_fstype" => "ext4",
  "a.nerves_fw_application_part0_target" => "/root",
  "a.nerves_fw_architecture" => "arm",
  "a.nerves_fw_author" => "The Nerves Team",
  "a.nerves_fw_description" => "",
  "a.nerves_fw_misc" => "",
  "a.nerves_fw_platform" => "rpi0",
  "a.nerves_fw_product" => "test_app",
  "a.nerves_fw_uuid" => "d9492bdb-94de-5288-425e-2de6928ef99c",
  "a.nerves_fw_vcs_identifier" => "",
  "a.nerves_fw_version" => "0.1.0",
  "b.nerves_fw_application_part0_devpath" => "/dev/mmcblk0p3",
  "b.nerves_fw_application_part0_fstype" => "ext4",
  "b.nerves_fw_application_part0_target" => "/root",
  "b.nerves_fw_architecture" => "arm",
  "b.nerves_fw_author" => "The Nerves Team",
  "b.nerves_fw_description" => "",
  "b.nerves_fw_misc" => "",
  "b.nerves_fw_platform" => "rpi0",
  "b.nerves_fw_product" => "test_app",
  "b.nerves_fw_uuid" => "4e08ad59-fa3c-5498-4a58-179b43cc1a25",
  "b.nerves_fw_vcs_identifier" => "",
  "b.nerves_fw_version" => "0.1.1",
  "nerves_fw_active" => "b",
  "nerves_fw_devpath" => "/dev/mmcblk0",
  "nerves_serial_number" => ""
}

Parts of the firmware metadata are global, while others pertain to a specific firmware slot. This is indicated by the key - data which describes firmware of a specific slot have keys prefixed with the name of the firmware slot. In the above example, "nerves_fw_active" and "nerves_serial_number" are global, while "a.nerves_fw_version" and "b.nerves_fw_version" apply to the "a" and "b" firmware slots, respectively.

It is also possible to get firmware metadata that only pertains to the currently active firmware slot:

iex> Nerves.Runtime.KV.get_all_active()
%{
  "nerves_fw_application_part0_devpath" => "/dev/mmcblk0p3",
  "nerves_fw_application_part0_fstype" => "ext4",
  "nerves_fw_application_part0_target" => "/root",
  "nerves_fw_architecture" => "arm",
  "nerves_fw_author" => "The Nerves Team",
  "nerves_fw_description" => "",
  "nerves_fw_misc" => "",
  "nerves_fw_platform" => "rpi0",
  "nerves_fw_product" => "test_app",
  "nerves_fw_uuid" => "4e08ad59-fa3c-5498-4a58-179b43cc1a25",
  "nerves_fw_vcs_identifier" => "",
  "nerves_fw_version" => "0.1.1"
}

Note that get_all_active/0 strips out the a. and b. prefixes.

Further, the two functions get/1 and get_active/1 allow you to get a specific key from the firmware metadata. get/1 requires specifying the entire key name, while get_active/1 will prepend the slot prefix for you:

iex> Nerves.Runtime.KV.get("nerves_fw_active")
"b"
iex> Nerves.Runtime.KV.get("b.nerves_fw_uuid")
"4e08ad59-fa3c-5498-4a58-179b43cc1a25"
iex> Nerves.Runtime.KV.get_active("nerves_fw_uuid")
"4e08ad59-fa3c-5498-4a58-179b43cc1a25"

Aside from reading values from the KV store, it is also possible to write new values to the firmware metadata. New values may either have unique keys, in which case they will be added to the firmware metadata, or re-use a key, in which case they will overwrite the current value with that key:

iex> :ok = Nerves.Runtime.KV.put("my_firmware_key", "my_value")
iex> :ok = Nerves.Runtime.KV.put("nerves_serial_number", "my_new_serial_number")
iex> Nerves.Runtime.KV.get("my_firmware_key")
"my_value"
iex> Nerves.Runtime.KV.get("nerves_serial_number")
"my_new_serial_number"

It is possible to write a collection of values at once, in order to minimize number of writes:

iex> :ok = Nerves.Runtime.KV.put(%{"one_key" => "one_val", "two_key" => "two_val"})
iex> Nerves.Runtime.KV.get("one_key")
"one_val"

Lastly, put_active/1 and put_active/2 allow you to write firmware metadata to the currently active firmware slot without specifying the slot prefix yourself:

iex> :ok = Nerves.Runtime.KV.put_active("nerves_fw_misc", "Nerves is awesome")
iex> Nerves.Runtime.KV.get_active("nerves_fw_misc")
"Nerves is awesome"

Link to this section Summary

Types

The KV store is a string -> string map

Callbacks

Initialize the KV store and return its contents

Persist the updated KV pairs

Functions

Returns a specification to start this module under a supervisor.

Get the key regardless of firmware slot

Get the key for only the active firmware slot

Get all keys regardless of firmware slot

Get all key value pairs for only the active firmware slot

Write a collection of key-value pairs to the firmware metadata

Write a key-value pair to the firmware metadata

Write a collection of key-value pairs to the active firmware slot

Write a key-value pair to the active firmware slot

Start the KV store server

Link to this section Types

Specs

string_map() :: %{required(String.t()) => String.t()}

The KV store is a string -> string map

Since the KV store is backed by things like the U-Boot environment blocks, the keys and values can't be just any string. For example, characters with the value 0 (i.e., NULL) are disallowed. The = sign is also disallowed in keys. Values may have embedded new lines. In general, it's recommended to stick with ASCII values to avoid causing trouble when working with C programs and U-Boot which also access the variables.

Link to this section Callbacks

Specs

init(opts :: any()) :: initial_state :: string_map()

Initialize the KV store and return its contents

This will be called on boot and should return all persisted key/value pairs. The results will be cached and if a change should be persisted, put/1 will be called with the update.

The opts parameter is currently unused, but may supply configuration options in the future.

Specs

put(state :: string_map()) :: :ok | {:error, reason :: any()}

Persist the updated KV pairs

The KV map contains the KV pairs returned by init/1 with any changes made by users of Nerves.Runtime.KV.

Link to this section Functions

Returns a specification to start this module under a supervisor.

See Supervisor.

Specs

get(String.t()) :: String.t() | nil

Get the key regardless of firmware slot

Specs

get_active(String.t()) :: String.t() | nil

Get the key for only the active firmware slot

Specs

get_all() :: string_map()

Get all keys regardless of firmware slot

Specs

get_all_active() :: string_map()

Get all key value pairs for only the active firmware slot

Specs

put(string_map()) :: :ok

Write a collection of key-value pairs to the firmware metadata

Specs

put(String.t(), String.t()) :: :ok

Write a key-value pair to the firmware metadata

Specs

put_active(string_map()) :: :ok

Write a collection of key-value pairs to the active firmware slot

Specs

put_active(String.t(), String.t()) :: :ok

Write a key-value pair to the active firmware slot

Specs

start_link(any()) :: GenServer.on_start()

Start the KV store server