Nerves

2020/07/12

Nerves Project の システム部(OS,bootloader?, elixer起動までの構築)

個々のライブラリ群?だけをみていても理解できないっぽい。 Nerves examplesを参照していくとつながるかもしれん。

platform依存部分(Apache License 2.0):

nerves_system_x86_64

  defp deps do
    [
      {:nerves, "~> 1.5.0", runtime: false},
      {:nerves_system_br, "1.9.1", runtime: false},
      {:nerves_toolchain_x86_64_unknown_linux_musl, "1.2.0", runtime: false},
      {:nerves_system_linter, "~> 0.3.0", runtime: false},
      {:ex_doc, "~> 0.18", only: [:dev, :test], runtime: false}
    ]
end

nerves (Apache License 2.0)

nerves

  defp deps do
    [
      {:distillery, "~> 2.1", optional: true, runtime: false},
      {:jason, "~> 1.0", optional: true},
      {:ex_doc, "~> 0.19", only: [:test, :dev], runtime: false},
      {:dialyxir, "~> 1.0.0-rc.3", only: [:test, :dev], runtime: false},
      {:nerves_bootstrap, "~> 1.0", only: [:test, :dev]},
      {:plug, "~> 1.4", only: :test},
      {:plug_cowboy, "~> 1.0", only: :test}
    ]
  end

パーティション

以下がデフォルト. fwup.conf で構成を変えられる模様. SDカードを raw dataアクセスしてしまっているのかな..

FILE: nerves/docs/Advanced Configuration.md

 +----------------------------+
 | MBR                        |
 +----------------------------+
 | Firmware configuration data|
 | (formatted as uboot env)   |
 +----------------------------+
 | p0*: Boot A        (FAT32) |
 | zImage, bootcode.bin,      |
 | config.txt, etc.           |
 +----------------------------+
 | p0*: Boot B        (FAT32) |
 +----------------------------+
 | p1*: Rootfs A   (squashfs) |
 +----------------------------+
 | p1*: Rootfs B   (squashfs) |
 +----------------------------+
 | p2: Application     (EXT4) |
 +----------------------------+

ファイルシステムを触りたければ squashfsを展開/再圧縮してね、ってなってる. br2触らしたくないんだね.

→ "Cunstomizing Systems.md"に br2触る方法があるね..

nerves_bootstrap (Apache License v2)

  defp deps do
    [
      {:ex_doc, "~> 0.19", only: [:dev, :test], runtime: false}
    ]
  end
FILE: nerves_bootstrap/aliases.ex

このあたりで mix targetを追加している様子. "deps.get", "deps.update", "deps.loadpaths", "deps.compile", "run" かな? mixのビルドシステムに対して 追加している感じやろか..

Buildroot(external tree)共通部 (GPLV2)

nerves_system_br

Nerves Systemのビルドに必要な環境を Docker file で提供している。

これを使えばよかったのでは... fwupも NAT超えで使えるはずだしなぁ.

nerves_app

nerves new で雛形を作る

depsに以下が設定されている.. システム起動語に必要なのは nerves_system_* と nerves_runtime, nerves_init_gadget (USB targetとして使う場合)

      {:nerves, "~> 1.5.0", runtime: false},
      {:shoehorn, "~> 0.6"},
      {:ring_logger, "~> 0.6"},
      {:toolshed, "~> 0.2"},

      # Dependencies for all targets except :host
      {:nerves_runtime, "~> 0.6", targets: @all_targets},
      {:nerves_init_gadget, "~> 0.4", targets: @all_targets},

      # Dependencies for specific targets
      {:nerves_system_rpi, "~> 1.8", runtime: false, targets: :rpi},
      {:nerves_system_rpi0, "~> 1.8", runtime: false, targets: :rpi0},
      {:nerves_system_rpi2, "~> 1.8", runtime: false, targets: :rpi2},
      {:nerves_system_rpi3, "~> 1.8", runtime: false, targets: :rpi3},
      {:nerves_system_rpi3a, "~> 1.8", runtime: false, targets: :rpi3a},
      {:nerves_system_rpi4, "~> 1.8", runtime: false, targets: :rpi4},
      {:nerves_system_bbb, "~> 2.3", runtime: false, targets: :bbb},
      {:nerves_system_x86_64, "~> 1.8", runtime: false, targets: :x86_64},

Nerves hacks

2020/07/12 Nerves

Nerves Project

本家は上記リンクからたどれます

このサイトでは 個人的に調査したり, 動かしてみたりした問いのメモを残していく予定です. ローカルに散らかしておくと見失ってしまうので, マサカリをいただいたりして自分の認識誤りを正してもらえることを期待しています..

発信する分, 何らかのメリット(feedback,寄付)は享受したいものですね!

日本国内リソース

cnnpassのグループがあります. いくつかの Nerves関連サイト, Elixir関連サイトを除いてみると, 参考リンクが出てきます. 複数からリンクしているところが有力 or 根っこだと思うとよいかもですね.

fwup-README.md

2020/07/11 Nervesfwup

referto: https://github.com/fhunleth/fwup.git at 2020/03/11頃参照

Overview

fwup は, 組み込みLinuxベースの組込みシステムのための, 設定可能なイメージベースのソフトウェア更新ユーティリティです. fwup は, 主に所望のルートファイルシステム(RFS)を一度に更新するための, ソフトウェア更新戦略(ストラテジ)をサポートします. これには, AパーティションとBパーティションの間の前方/後方へのスワッピング, リカバリパーティション, さまざまなトライアルアップデート/フェイルバックシナリオなどのストラテジが含まれます. すべてのソフトウェア更新情報は, ZIP圧縮に結合され, 必要に応じて暗号的に署名されることがあります. fwup は最小の依存正とランタイムを要求します. 失敗のシナリオを簡単に推論できるように, スクリプトはわざと制限されています. ソフトウェア更新アーカイブの配布は, 機能ではありません. ユーザは, 必要に応じて, 外部メディア・ネットワークからのストリーム・ Ansibleのようなツールを使ったスクリプトから, 更新を実行するために, fwup を呼び出すことができます.

機能リストの全てを挙げます:

  1. デバッグと転送を簡単にするために, 標準ZIP圧縮を使います.

  2. たくさんのプラットフォーム上でファームウェア更新を有効にするため, 単純だけど 自由に設定できる言語, と, ファームウェア更新ポリシー

  3. ターゲットのストレージ要求を単純にする ストリーミングファームウェア更新処理

  4. 1つのアーカイブがさまざまなターゲット設定を更新できるように, アーカイブごとの複数のファームウェア更新タスクオプション

  5. 基本的なディスクパーティショニングと, FATfs操作

  6. 人間と機械が読める進捗

  7. Linux, OSX, BSD, Windowsのいずれの開発システム上でも初期化・更新するSDカード. MMCとSDカードは自動的に検出され, アンマウントされます. ログを眺めたり, 手動でアンマウントする必要はありません.

  8. ファームウェアアーカイブの電子署名を作成・検証

  9. Sparse file support to reduce number of bytes that need to be written when initializing large file systems (see section on sparse files)

  10. 大きなファイルシステムを書き込む必要があるときに, バイトサイズを圧縮するためのスパースファイルのサポート(スパースファイルの節を参照)

  11. 寛容なライセンス (Apache 2.0 License - see end of doc)

  12. 広範な回帰テスト済み! テストは機能の簡単な例も提供します。

内部的には, fwup は 低レベルなディスク書き込みを高速にする最適化が, dd(1) で簡単に達成できるよりも多いです. フラッシュ消去ブロックのアライン, 書き込み時間を最小にするための 大きな未使用領域のスキップを可能にします. 開発サイクルの繰り返しが十分に早くできることをゴールとしています.

Installing

The simplest way to install fwup is via a package manager or installer.

On OSX, fwup is in homebrew:

brew install fwup

On Linux, download and install the appropriate package for your platform:

On Windows, fwup can be installed from chocolatey

choco install fwup

Alternatively, download the fwup executable and place it in your path.

If you're using another platform or prefer to build it yourself, download the latest source code release or clone this repository. Then read one of the following files:

When building from source, please verify that the regression test pass on your system (run make check) before using fwup in production. While the tests usually pass, they have found minor issues in third party libraries in the past that really should be fixed.

Invoking

If you were given a .fw file and just want to install its contents on an SDCard, here's an example run on Linux:

$ sudo fwup example.fw
Use 14.92 GiB memory card found at /dev/sdc? [y/N] y
100%
Elapsed time: 2.736s

If you're on OSX or Windows, leave off the call to sudo.

IMPORTANT: If you're using an older version of fwup, you'll have to specify more arguments. Run: fwup -a -i example.fw -t complete

Here's a list of other options:

Usage: fwup [OPTION]...

Options:
  -a, --apply   Apply the firmware update
  -c, --create  Create the firmware update
  -d <file> Device file for the memory card
  -D, --detect List attached SDCards or MMC devices and their sizes
  -E, --eject Eject removable media after successfully writing firmware.
  --no-eject Do not eject media after writing firmware
  --enable-trim Enable use of the hardware TRIM command
  --exit-handshake Send a Ctrl+Z on exit and wait for stdin to close (Erlang)
  -f <fwup.conf> Specify the firmware update configuration file
  -F, --framing Apply framing on stdin/stdout
  -g, --gen-keys Generate firmware signing keys (fwup-key.pub and fwup-key.priv)
  -i <input.fw> Specify the input firmware update file (Use - for stdin)
  -l, --list   List the available tasks in a firmware update
  -m, --metadata   Print metadata in the firmware update
  -n   Report numeric progress
  -o <output.fw> Specify the output file when creating an update (Use - for stdout)
  -p, --public-key-file <keyfile> A public key file for verifying firmware updates
  --private-key <key> A private key for signing firmware updates
  --progress-low <number> When displaying progress, this is the lowest number (normally 0 for 0%)
  --progress-high <number> When displaying progress, this is the highest number (normally 100 for 100%)
  --public-key <key> A public key for verifying firmware updates
  -q, --quiet   Quiet
  -s, --private-key-file <keyfile> A private key file for signing firmware updates
  -S, --sign Sign an existing firmware file (specify -i and -o)
  --sparse-check <path> Check if the OS and file system supports sparse files at path
  --sparse-check-size <bytes> Hole size to check for --sparse-check
  -t, --task <task> Task to apply within the firmware update
  -u, --unmount Unmount all partitions on device first
  -U, --no-unmount Do not try to unmount partitions on device
  --unsafe Allow unsafe commands (consider applying only signed archives)
  -v, --verbose   Verbose
  -V, --verify  Verify an existing firmware file (specify -i)
  --version Print out the version
  -y   Accept automatically found memory card when applying a firmware update
  -z   Print the memory card that would be automatically detected and exit
  -1   Fast compression (for create)
  -9   Best compression (default)

Examples:

Create a firmware update archive:

  $ fwup -c -f fwup.conf -o myfirmware.fw

Apply the firmware to an attached SDCard. This would normally be run on the host
where it would auto-detect an SDCard and initialize it using the 'complete' task:

  $ fwup -a -i myfirmware.fw -t complete

Apply the firmware update to /dev/sdc and specify the 'upgrade' task:

  $ fwup -a -d /dev/sdc -i myfirmware.fw -t upgrade

Create an image file from a .fw file for use with dd(1):

  $ fwup -a -d myimage.img -i myfirmware.fw -t complete

Generate a public/private key pair:

  $ fwup -g

Store fwup-key.priv in a safe place and fwup-key.pub on the target. To sign
an existing archive run:

  $ fwup -S -s fwup-key.priv -i myfirmware.fw -o signedfirmware.fw

Example usage

The regression tests contain short examples for usage of various script elements and are likely the most helpful to read due to their small size.

Other examples can be found in the bbb-buildroot-fwup for project for the BeagleBone Black and Raspberry Pi. The [Nerves Project](https://github.com/nerves-project/nerves_system_br) has more examples and is better maintained.

My real world use of fwup involves writing the new firmware to a place on the Flash that's not in current use and then 'flipping' over to it at the very end. The examples tend to reflect that. fwup can also be used to overwrite an installation in place assuming you're using an initramfs, but that doesn't give protection against someone pulling power at a bad time. Also, fwup's one pass over the archive feature means that firmware validation is mostly done on the fly, so you'll want to verify the archive first (see the -V option).

Helper scripts

While not the original use of fwup, it can be convenient to convert other files to .fw files. fwup comes with the following shell script helper:

  • img2fwup - convert a raw image file to a .fw file

A use case for the img2fwup script is to convert a large SDCard image file to one that is compressed and checksumed by fwup for distribution.

Versioning

fwup uses semver for versioning. For example, if you are using 1.0.0, that means no breaking changes until 2.0.0 or new features until 1.1.0. I highly recommend a conservative approach to upgrading fwup once you have devices in the field. For example, if you have 1.0.0 devices in the field, it's ok to update to 1.1.0, but be very careful about using 1.1.0 features in your fwup.conf files. They will be ignored by 1.0.0 devices and that may or may not be what you want.

Configuration file format

fwup は Unix設定ライブラリ(libconfuse)を使っています. したがって その設定は 他のプログラムに似ているところがあります. 設定ファイルはファームウェアアーカイブを作るために使われます. 作成中, fwup は, 処理した設定ファイルを アーカイブに埋め込みます. 埋め込まれる設定ファイルは, コメントを削除し, 変数が解決されたあと, 有用なメタデータを追加されたものになります. 設定ファイルはスコープブロックに属し, オプションは key = value 構文を使ってセットされます. 加えて, 設定ファイルは include("filename") を呼び出すことで他のファイルや分割された設定を取り込みます.

Environment variables

ビルドシステムや他のスクリプトへの統合のため, fwup は設定ファイル内の変数置換を実施します. 環境変数は, ファームウェア更新ファイルを作成する間, ホスト上で解決されることに留意ください. 生成されたファームウェアファイルは含みません.

環境変数は以下のように参照されます:

key = ${ANY_ENVIRONMENT_VARIABLE}

:- シンボルを使うことで, 環境変数のデフォルト値を提供することができます:

key = ${ANY_ENVIRONMENT_VARIABLE:-adefault}

設定ファイルの中で, ファイルをまたいで使われる定数を定義するのに便利です. 定数は, 環境変数とおなじように参照されます. 以下が例です:

define(MY_CONSTANT, 5)

デフォルトでは, 同じ定数の定義の繰り返しは, 定数値を変更しません. いいかえると, 最初の宣言が勝ちます. 最初の定義は, 同じ名前の環境変数から取得できることに注意しましょう. これによりビルドスクリプト内の定数を上書きすることができます.

場合によっては, 環境変数または以前の define の呼び出しによって上書きされることのない定数には, 最後の定義が勝つことが望ましいです. この動作には, define! を使用します:

define!(MY_CONSTANT, "Can't override this")

単純マッチ計算も, define-eval()define-eval!() とを使って反映することができます. たとえば:

define-eval(AN_OFFSET, "${PREVIOUS_OFFSET} + ${PREVIOUS_COUNT}")

これらの2つの関数は リリース 0.10.0 から追加されましたが, これらはファームウェア作成時に評価されます. .fw ファイルは, fwup の旧バージョンにあわせて 作られます.

最後に, file-resource は リソースのサイズを定義した, FWUP_SIZE_<resource_name> と名付けられた変数を定義します.

file-resource zImage {
        host-path = "output/images/zImage"
}

execute("echo zImage size is ${FWUP_SIZE_zImage} bytes")

Global scope

グローバルスコープでは, 以下のオプションがあります:

Key Description
require-fwup-version この更新を適用するために 要求する fwupの最低バージョン
meta-product プロダクト名
meta-description プロダクトあるいはファームウェア更新の概要
meta-version ファームウェアバージョン
meta-author 更新のうしろにいる作者あるいは会社
meta-platform この更新が走るプラットフォーム (rpi,bbbなど)
meta-architecture プラットフォームアーキテクチャ(armなど)
meta-vcs-identifier このイメージを再現するのに使うバージョン管理ID
meta-misc その他の追加データ. 書式と内容はユーザ次第.
meta-creation-date 更新が作られたときのタイムスタンプ(ZIPメタデータからもたらされる). 再現するためには, SOURCE_DATE_EPOCH 環境変数をセットしてください.
meta-fwup-version 更新を作るために使われる fwupのバージョン(廃止予定. 1.2.0以降で追加されなくなっている)
meta-uuid このファームウェアを再現するためのUUID. UUIDは, .fwファイルが(自動生成)作られたあとに, デジタル署名された場合でも, かわりません.

上記オプションの設定のあと, ほかのオプションのために, スコープを作ることが必要です. 現在存在するスコープは:

Scope Description
file-resource アーカイブに含まれなければならないファイルへの参照を定義
mbr 宛先のMBR(master boot record)を定義
gpt 宛先の GPTパーティションを定義
task ファームウェア更新タスクの定義(コマンドラインから -t を使って参照される)

file-resource

file-resource は, アーカイブ中に含まれるべき ホスト上のファイルを与えます. 各々の file-resource は, 更新設定ファイルの他の部分から参照できるように, ユニークな名前を与える必要があります. fwup は, アーカイブ内のファイルの長さと BLAKE2b-256ハッシュ値とを 自動的に記録します. これらのフィールドは 進捗を計算することと, アーカイブコンテンツの検証のために, 内部的に使われます. 標準的な file-resource セクションは 次のようになります:

file-resource zImage {
        host-path = "output/images/zImage"
}

リソースは通常, ファームウェアアーカイブの data ディレクトリ内に保存されます. これは多くのユーザに透過です. 他のソフトウェアと使うために, .fw ファイルを作る必要があるなら, 他の場所にファイルを埋め込むのが使いやすいことがあります. これは絶対パスリソースを与えることで実現できます:

file-resource "/my_custom_metadata" {
        host-path = "path/to/my_custom_metadata_file"
}

Resource concatenation

ときおり, `file-resourceP から複数のファイルを互いに結合する必要があります. raw_write を複数回呼び出して使うことで, これを実現できますが, 事前にファイルオフセットがわからない時や, オフセットが ブロック境界に該当しない場合には機能しません. ほかの選択肢は, 前のステップでファイルを結合することです. もし不便であれば, fwup は, セミコロンで分割された host-path で 複数のパスを与えることを許容します. それらのファイルは, 記述順に結合されます.

file-resource kernel_and_rootfs {
        # Concatenate uImage and the rootfs. OpenWRT mtd splitter will
        # separate them back out at runtime.
        host-path = "output/images/uImage;output/images/rootfs.squashfs"
}

File resource validation checks

アーカイブを作るとき, fwup は単純なエラーを捕まえるため, ファイルリソースの検証チェックを行うことができます. このチェックは, ファイルリソースが宛先に入らないくらい大きすぎたり, ビルドの中断によって末尾が欠けたりする共通的なエラーを捕まえます.

これらのチェックは, 更新を適用するときに適用されないことに注意してください. なぜならば, 実際の長さ(とハッシュ)はアーカイブメタデータ内に記録されており, 検証に使われるからです.

以下のチェックがサポートされています:

Check Description
assert-size-lte ファイルサイズが与えられたもの以下の場合, エラーを報告します.
assert-size-gte ファイルサイズが与えられたもの以上の場合, エラーを報告します.

サイズは 512バイトブロック単位で与えます(fwup の他のものと同様に):

file-resource rootfs.img {
        host-path = "output/images/rootfs.squashfs"
        assert-size-lte = ${ROOTFS_A_PART_COUNT}
}

文字列からのファイル

ときおり, fwup 設定ファイル内で短いファイルを作成することは, ファイルを参照することよりも有用です. file-resource 内の contents キーを使うことで達成できます. 変数置換は, fwup 設定ファイルの他の文字列と同様に, contents 文字列でも行われます.

file-resource short-file.txt {
        contents = "You're looking at a short\n\
file creating by fwup.\n\
When it was made, FOO was ${FOO}.\n"
}

mbr

mbr セクションは, 宛先メディアの Master Boot Recordのコンテンツを指定します. このセクションは, メディア上に存在するファイルシステムを見つけるために, Linuxとブートローダによって読まれるパーティションテーブルを含みます. fdisk のようなツールと比べて, fwup はとても単純なパーティション設定のみをサポートします. fdisk のようなツールは,設定ファイルのブロックオフセットやパーティションサイズを検出するのに使えます. オフセットとサイズは 512 byteブロックで与えられます. 潜在的な mbr定義は以下のとおりです:

mbr mbr-a {
        bootstrap-code-host-path = "path/to/bootstrap-data" # should be 440 bytes
        signature = 0x01020304

        partition 0 {
                block-offset = ${BOOT_PART_OFFSET}
                block-count = ${BOOT_PART_COUNT}
                type = 0x1 # FAT12
                boot = true
        }
        partition 1 {
                block-offset = ${ROOTFS_A_PART_OFFSET}
                block-count = ${ROOTFS_A_PART_COUNT}
                type = 0x83 # Linux
        }
        partition 2 {
                block-offset = ${ROOTFS_B_PART_OFFSET}
                block-count = ${ROOTFS_B_PART_COUNT}
                type = 0x83 # Linux
        }
        partition 3 {
                block-offset = ${APP_PART_OFFSET}
                block-count = ${APP_PART_COUNT}
                type = 0x83 # Linux
        }
}

Intel Edisonや似たようなプラットフォームを使っている場合, fwup は MBR内の OSIPヘッダの生成をサポートします. このヘッダは, ブートローダ(たとえば U-Boot)をメモリ上の何処にロードすべきかという情報を提供します. OSIPとイメージレコード(OSII)オプション名は, 長さ, チェックサム, イメージ数のフィールドは自動的に計算されることを除いて, ヘッダフィールドに直接マップされます. 以下は全てのオプションを示した例です:

mbr mbr-a {
    include-osip = true
    osip-major = 1
    osip-minor = 0
    osip-num-pointers = 1

    osii 0 {
        os-major = 0
        os-minor = 0
        start-block-offset = ${UBOOT_OFFSET}
        ddr-load-address = 0x01100000
        entry-point = 0x01101000
        image-size-blocks = 0x0000c000
        attribute = 0x0f
    }

    partition 0 {
        block-offset = ${ROOTFS_A_PART_OFFSET}
        block-count = ${ROOTFS_A_PART_COUNT}
        type = 0x83 # Linux
    }
}

まれに, ストレージの残りを最終パーティションで埋めることは有用です. ターゲットのストレージサイズが判らない場合に必要で, 可能であれば, コレをできるだけ使う必要があります. expand オプションはfwupblock-count を可能な限り大きくなるように伸ばすことを要求します. expand を使うとき, block-count は, 今は最小のパーティションサイズです. 唯一最後のパーティションが拡張可能です. 例を挙げます:

mbr mbr-a {
        partition 0 {
                block-offset = ${BOOT_PART_OFFSET}
                block-count = ${BOOT_PART_COUNT}
                type = 0x1 # FAT12
                boot = true
        }
        partition 1 {
                block-offset = ${ROOTFS_A_PART_OFFSET}
                block-count = ${ROOTFS_A_PART_COUNT}
                type = 0x83 # Linux
        }
        partition 2 {
                block-offset = ${ROOTFS_B_PART_OFFSET}
                block-count = ${ROOTFS_B_PART_COUNT}
                type = 0x83 # Linux
        }
        partition 3 {
                block-offset = ${APP_PART_OFFSET}
                block-count = ${APP_PART_COUNT}
                type = 0x83 # Linux
                expand = true
        }
}

gpt

gpt の節では, GUIDパーティションテーブル の内容を指定します.

gpt my-gpt {
    # UUID for the entire disk
    guid = b443fbeb-2c93-481b-88b3-0ecb0aeba911

    partition 0 {
        block-offset = ${EFI_PART_OFFSET}
        block-count = ${EFI_PART_COUNT}
        type = c12a7328-f81f-11d2-ba4b-00a0c93ec93b # EFI type UUID
        guid = 5278721d-0089-4768-85df-b8f1b97e6684 # ID for partition 0 (create with uuidgen)
        name = "efi-part.vfat"
    }
    partition 1 {
        block-offset = ${ROOTFS_PART_OFFSET}
        block-count = ${ROOTFS_PART_COUNT}
        type = 44479540-f297-41b2-9af7-d131d5f0458a # Rootfs type UUID
        guid = fcc205c8-2f1c-4dcd-bef4-7b209aa15cca # Another uuidgen'd UUID
        name = "rootfs.ext2"
    }
}

fwup によって, 保護されたMBR と、プライマリ・バックアップGPTテーブルを書かせるために, gpt_write を呼びます.

U-Boot environment

U-Bootブートローダを使っているシステムのために, U-Boot環境ブロックを修正するためのサポートが含まれます. このアドバンテージを得るためには, トップレベルに 環境ブロックをどうするかを記述する uboot-environment セクションを宣言する必要があります.

uboot-environment my_uboot_env {
    block-offset = 2048
    block-count = 16
}

U-boot変数の取得・設定については, taskセクション中の機能を参照ください.

注意: 現時点では, U-Boot環境をサポートする実装は私が使う分だけです. 特に, 冗長な環境, ビッグエンディアンターゲット, raw NANDパーティションへの書き込みをサポートしていません. あなたがこれらを使う場合, これらをサポートするために貢献することを検討してください.

fwup の U-Bootサポートが, あなたのニーズにあわなければ, mkenvimage ユーティリディを使って環境イメージを作ることが可能です. そして, 正しい場所へ raw_write を使って書き込めます. これはおそらく, たくさんの変数をセットするときは, より適切な手段です.

task

task セクションは, ファームウェア更新タスクを指定します. これらのセクションは, 更新が適用される状態と更新を適用するステップを記述するため, ファームウェア更新の主要な部分です. 各 task せく処んは, ユニークな名前を持つ必要があります. しかし, タスクを探すときには, ファームウェア更新ツールが前方照合を行うだけです. これはあなたに,ターゲットハードウェアの状態に基づいて評価できる複数のタスクを定義させてくれます. 最初に照合されたタスクが適用されます. これは, ターゲット上の現在のファームウェアバージョンやターゲットアーキテクチャなどに基づいて 更新手順が異なる場合にとても使いやすいです. 以下の表に, サポートされる制約を列挙します:

Constraint Min fwup version Description
require-fat-file-exists(block_offset, filename) 0.7.0 指定されたFATfsにあるファイルを要求する
require-fat-file-match(block_offset, filename, pattern) 0.14.0 ファイル名の一致と, パターンがファイル内のバイト数と一致することを要求する
require-partition-offset(partition, block_offset) 0.7.0 パーティションのブロックオフセットが 指定された値であることを要求する.
require-path-on-device(path, device) 0.13.0 指定されたパス(たとえば"/")が指定されたパーティションデバイス(たとえば"/dev/mmcblk0p1)にあることを要求する.
require-path-at-offset(path, offset) 0.19.0 指定されたパス(たとえば "/")が, 指定されたブロックオフセット(たとえば1024)にあることを要求する. require-path-on-device と組み合わせます.
require-uboot-variable(my_uboot_env, varname, value) 0.10.0 U-Boot環境で, 変数が指定された値に設定されていることを要求する.

task セクションがイベントハンドラのリストであることを思い出してください. イベントハンドラはスコープとして編成されます. イベントハンドラは, イベントが発生したときに, ファームウェアのアプリケーションが更新する間, 一致します. イベントは初期化, 完了, エラー, アーカイブからのファイル展開を含みます. アーカイブはストリーミングマナーで処理されるので, イベントの順序はファイルがアーカイブに追加された順序に基づいて, 決定的です. あるイベントが 他のイベントの前に発生することが重要である場合は, file-resource セクションが, 望みの順序で 指定されていることを確認してください.

Event Description
on-init タスクが適用されたときに, 送られる最初のイベント
on-finish イベント処理中にエラーが検出されなかったと仮定した最後のイベント
on-error エラーが生じた場合に送られる.したがって, 中間ファイルは削除可能です.
on-resource <resource name> イベント発生のように送られる.現状, これは アーカイブから処理された file-resources を送るようになっています.

イベントスコープには, アクションのリストが含まれています. アクションは, ファイルシステムをフォーマットしたり, ファイルをファイルシステムにコピーしたり, 宛先のナマの場所に書き込むことができます.

Action Min fwup version Description
error(message) 0.12.0 エラーとして ファームウェア更新を直ちに失敗します.
execute(command) 0.16.0 ホスト上でコマンドを実行します. --unsafe フラグを要求します.
fat_mkfs(block_offset, block_count) 0.1.0 指定されたブロックオフセットとカウントに, FATfsを作ります.
fat_write(block_offset, filename) 0.1.0 指定されたブロックオフセットに, リソースをFATfsに書き込みます.
fat_attrib(block_offset, filename, attrib) 0.1.0 ファイル属性を変更します. 属性は "RHS"のような文字列です(R:読み込み専用, H:隠し属性, S:システム).
fat_mv(block_offset, oldname, newname) 0.1.0 FATfs上の指定されたファイルの名前を変更します.
fat_mv!(block_offset, oldname, newname) 0.14.0 すでに新しい名前があったとしても, 指定されたファイルの名前を変更します.
fat_rm(block_offset, filename) 0.1.0 指定されたファイルを削除します.
fat_mkdir(block_offset, filename) 0.2.0 FATfs上にディレクトリを作ります. ディレクトリがすでに存在していた場合でも成功します.
fat_setlabel(block_offset, label) 0.2.0 FATfs上にボリュームラベルをセットします.
fat_touch(block_offset, filename) 0.7.0 ファイルが存在していなければ, 空のファイルを作ります.(Linuxのようにタイムスタンプ更新しません)
gpt_write(gpt) 1.4.0 指定されたGPTをターゲットに書き込みます.
info(message) 0.13.0 情報メッセージを表示します.
mbr_write(mbr) 0.1.0 指定された mbrをターゲットに書き込みます.
path_write(destination_path) 0.16.0 リソースをホスト上のパスへ書き込みます. --unsafe フラグを要求します.
pipe_write(command) 0.16.0 ホスト上で, リソースをパイプでコマンドへ渡します. --unsafe フラグを要求します.
raw_memset(block_offset, block_count, value) 0.10.0 指定されたブロックへ, 指定されたバイトの値を繰り返し書き込みます.
raw_write(block_offset, options) 0.1.0 リソースを指定されたブロックオフセットへ書き込みます. オプションは ciphersecret を含みます.
trim(block_offset, count) 0.15.0 以前に範囲に書き込まれたデータを破棄します. "--enable-trim" が fwupに渡されると, TRIM要求がデバイスに発行されます.
uboot_recover(my_uboot_env) 0.15.0 U-Boot環境(変数領域)が壊れている場合, 最初期化します. 壊れていない場合は なにもしません.
uboot_clearenv(my_uboot_env) 0.10.0 U-Boot環境のクリーン, 変数を開放します.
uboot_setenv(my_uboot_env, name, value) 0.10.0 指定されたU-Boot変数をセットします.
uboot_unsetenv(my_uboot_env, name) 0.10.0 指定されたU-Boot変数を案セットします.

スパースファイル(Sparse files)

スパースファイルは, ファイルシステム上のメタデータ中のみで表現されるギャップを持ったファイルです. すべてのファイルシステムでサポートされていませんが, 一般的に Linuxは良いサポートをしています. スパースファイルを作るのは簡単です. ファイル末尾の位置から後方へシークして, なにかデータを書くのです. ギャップは, ファイルシステムのメタデータ中で, 穴が空いたたように"保存"されます. データは, ホールから読み出したゼロになります. データとホールは, 厳密にファイルシステム上のブロック境界上で開始・終了します. したがって, 小さなギャップは, ホールに保存されるのではなく, ゼロで埋められます.

Why is this important? If you're using fwup to write a large EXT2 partition, you'll find that it contains many gaps. It would be better to just write the EXT2 data and metadata without filling in all of the unused space. Sparse file support in fwup lets you do that. Since EXT2 filesystems legitimately contain long runs of zeros that must be written to Flash, fwup queries the filesystem containing the EXT2 data to find the gaps. Other tools like dd(1) only look for runs of zeros so their sparse file support cannot be used to emulate this. You may see warnings about copying sparse files to Flash and it has to do with tools not writing long runs of zeros. The consequence of fwup querying the filesystem for holes is that this feature only works when firmware update archives are created on operating systems and filesystems that support it. Of course, firmware updates can be applied on systems without support for querying holes in files. Those systems also benefit from not having to write as much to Flash devices. If you instead apply a firmware update to a normal file, though, the OS will likely fill in the gaps with zeros and thus offer no improvement.

There is one VERY important caveat with the sparse file handling: Some zeros in files are important and some are not. If runs in zeros in a file are important and they are written to a file as a "hole", fwup will not write them back. This is catastrophic if the zeros represent things like free blocks on a filesystem. Luckily, the file system formatting utilities write the important zeros to the disk and the OS does not scan bytes to see which ones are runs on zeros and automatically create holes. Programs like dd(1) can do this, though, so it is crucial that you do not run files through dd to make then sparser before passing them to fwup.

To turn this feature off, set skip-holes on the resource to false:

file-resource rootfs.img {
        host-path = "output/images/rootfs.img"
        skip-holes = false
}

ディスク暗号化

raw_write 機能は, Linuxの dm-crypt カーネルドライバと適合するディスク暗号化のサポートに制約があります. これは安静時に読み出されれない方法で, ファイルシステムのデータを書き込むことを可能とします.

  1. fwup は暗号鍵の処理に対応しておらず, 不適切な処理は暗号化の利点を簡単に損なう可能性があります.
  2. .fw アーカイブは暗号化されません. このメカニズムは アーカイブの秘密は他の意味で保護されます. もちろん, アーカイブ内のデータを暗号化しておくことも可能です. しかし, デバイス固有の秘密鍵を持つことはできません.
  3. 最も単純は dm-crypt 暗号は, 現状サポートされます("aes-cbc-plain"). これは知られた定義です. fwup のApacheライセンスのもとに, 組み込むことができる他のモードへのプルリクエストは歓迎します.

インターネット上には, 暗号化ファイルシステムを作り, dm-crypt を用いてファイルシステムをマウントするためのいくつかのチュートリアルがあります. fwup はもっと単純です. 書き込むためのバイト数のブロックを取得し, 暗号化し, 宛先に書き込みます. たとえば, ディスクに暗号化して書き込みたい, SquashFSでフォーマットされたファイルシステムがある場合, 以下の断片があるでしょう:

on-resource fs.squashfs {
    raw_write(${PARTITION_START}, "cipher=aes-cbc-plain", "secret=\${SECRET_KEY}")
}

上記の例では, SECRET_KEY は, ファームウェア更新時にデバイス上で環境変数からくることが期待されます. もちろん, テストするために設定ファイル中にハードコードした暗号鍵を使うこともできます. 鍵は 16進数文字列にエンコードされています.

さて, デバイス上では, SquashFSパーティションをマウントしますが, dm-crypt を使います. 手順は以下のようになるでしょう:

losetup /dev/loop0 /dev/mmcblk0p2
cryptsetup open --type=plain --cipher=aes-cbc-plain --key-file=key.txt /dev/loop0 my-filesystem
mount /dev/mapper/my-filesystem /mnt

上記の引数の多くをシステムに適したものに置き換える必要があるでしょう.

Reproducible builds

It's possible for the system time to be saved in various places when using fwup. This means that an archive with the same contents, but built at different times results in .fw files with different bytes. See reproducible-builds.org for a discussion on this topic.

fwup obeys the SOURCE_DATE_EPOCH environment variable and will force all timestamps to the value of that variable when needed. Set $SOURCE_DATE_EPOCH to the number of seconds since midnight Jan 1, 1970 (run date +%s) to use this feature.

A better way of comparing .fw archives, though, is to use the firmware UUID. The firmware UUID is computed from the contents of the archive rather than the bit-for-bit representation of the .fw file, itself. The firmware UUID is unaffected by timestamps (with or without SOURCE_DATE_EPOCH) or other things like compression algorithm improvements. This is not to say that SOURCE_DATE_EPOCH is not important, but that the UUID is an additional tool for ensuring that firmware updates are reproducible.

ファームウェア認証

ファームウェアアーカイブは, 単純な公開/暗号鍵スキームを使って 認証可能です. 始める前に, fwup -g を実行して, 公開/暗号鍵ペアを作ります. アルゴリズムは Ed25519 が使われます. これは2つのファイルを生成します: fwup-key.pubfwup-key.priv. 秘密鍵 fwup-key.priv, 署名鍵を保持することは重要です.

アーカイブに署名するには, ファームウェアを作るときに, fwupへ -s fwup-key.priv を渡します. 他のオプションは --sign あるいは -S で作ったあとに, ファームウェアアーカイブに署名することです.

アーカイブが署名されたことを確認するためには, コマンドラインでアーカイブを読むコマンドに -p fwup-key.pub を渡します. (たとえば -a, -l, -m

It is important to understand how verification works so that the security of the archive isn't compromised. Firmware updates are applied in one pass to avoid needing a lot of memory or disk space. The consequence of this is that verification is done on the fly. The main metadata for the archive is always verified before any operations occur. Cryptographic hashs (using the BLAKE2b-256 algorithm) of each file contained in the archive is stored in the metadata. The hash for each file is computed on the fly, so a compromised file may not be detected until it has been written to Flash. Since this is obviously bad, the strategy for creating firmware updates is to write them to an unused location first and then switch over at the last possible minute. This is desirable to do anyway, since this strategy also provides some protection against the user disconnecting power midway through the firmware update.

アプリケーションでの整合

多くのユーザは fwup を自分たちのアプリケーションと統合したいと思うことが予想されます.

多くの操作は, 単純に実行可能な fwup を呼び出し, stdout に書き込まれたテキストを解析することで実行できます. ファームウェアを適用するとき, コマンドラインオプションに基づいて進捗状況が更新されます:

  1. 人が読める - これがデフォルトです. 進捗は 0% から 100% まで更新されます.
  2. 数値(-n) - 進捗は 0\n から 100\n のように表示されます.
  3. 静か(-q) - 進捗は表示されません.

上記はスクリプトでは上手くいきますが, エラーがオペレータに見られることがあるとき, fwup は, うまく stdin/stdout を使う構造をサポートします. このオプションを使うためには, あらゆるコマンドに --framing オプションを指定します.

フレーミング機能は Erlang VMのポートAPIで流行り, 非Erlang VM言語と統合することを比較的簡単にします. フレーミングは 組み込みのプロセス間通信の欠点に対処します. たとえば, フレーミングを有効にすることで, ファイルの最後を通知するために stdout をクローズする必要なく, fwupstdin を介して, ファームウェア更新を流し込むことができます. フレーミングに助けられる他の機能は, どのテキストが一緒になるか, テキストがエラーメッセージの一部かどうか, を知ることです. 終了ステータスは 依然として成功か失敗を示しますが, 制御アプリケーションは何がおきたのかを知るために プログラムの終了をまつ必要がありません.

--framing モードにおいて, fwup との すべての通信は, パケットで行われます(バイトストリームではなく). パケットは4バイトの長さフィールドで始まります. 長さは ビッグエンディアン(ネットワークバイトオーダ)の 符号なし整数です. 長さがゼロのパケット(すなわち 4バイトすべてがゼロ)は入力の最後を通知します.

Field Size Description
Length 4 bytes パケット長(ビッグエンディアンの整数)
Data Length bytes ペイロード

入出力パケットは異なるフォーマットです. fwup へ入力するために送る(.fw ファイルを stdioを使って送るときのような)とき, 入力バイト数はパケットに区切られなければなりませんが, 最も便利です. たとえば, 4Kチャンクのバイトで受信した場合 fwup へは最後に長さゼロのパケットを含む, 4Kパケットで送ることができます.

fwup からの全ての出力パケットは, パケット先頭に 2バイトの種別フィールドを持ちます.

Field Size Description
Length 4 bytes Packet length as a big endian integer
Type 2 bytes See below
Data Length-2 bytes Payload

以下の種類が定義されます:

Type 2 byte value Description
Success "OK" コマンドは正常に実行された. ペイロードは2バイトのリザルトコード(現状0が成功). オプションのテキストメッセージが続きます.
Error "ER" 失敗が発生. ペイロードは2バイトのエラーコード(将来予約). テキストメッセージが続きます.
Warning "WN" 警告が発生. ペイロードは2バイトのエラーコード(将来予約). テキストメッセージが続きます.
Progress "PR" 次の2バイトはビッグエンディアン形式で進捗(0~100)

関連するオプションは --exit-handshake です. このプションはErlangのポートプロセス機能と統合するために, Erlang専用に実装されました. 終了を監視するよりも, サブプロセスから最終的なキャラクターが来るのを待つ方が便利である他との統合に役立つ場合があります. Erlangでの問題は, プロセスが終了したというメッセージが, stdoutの最終的な文字を打ち負かすのが簡単なことです. このオプションが有効化されるとき, fwup が終了できる状態のときに, fwup が呼び出したプロセスが stdin をクローズすることを期待します.

FAQ

How do I include a file in the archive that isn't used by fwup

The scenario is that you need to store some metadata or some other information that is useful to another program. For example, you'd like to include some documentation or other notes inside the firmware update archive that the destination device can pull out and present on a UI. To do this, just add file-resource blocks for each file. These blocks don't need to be referenced by an on-resource block.

アーカイブにファームウェアバージョンを取り込むには?

gitを使っている場合, fwup を以下のように起動することができます:

GITDESCRIBE=`git describe` fwup -c -f myupdate.conf -o out.fw

そして, myupdate.conf に以下の行を追加してください.

meta-version = "${GITDESCRIBE}"

ターゲットデバイス上で, -m オプションを使うことでバージョンを確認することができます. 以下に例示します:

$ fwup -m -i out.fw
meta-product = "Awesome product"
meta-description = "A description"
meta-version = "v0.0.1"
meta-author = "Me"
meta-platform = "bbb"
meta-architecture = "arm"
meta-uuid="07a34e75-b7ea-5ed8-b5d9-80c10daf4939"

fwupを使ってできるクールなことはないの?

よろしい. FAQではないけれども, 何らかの理由でこれはクールだと思える人が要るでしょう. わたしの働いている多くのシステムは, sshでネットワークに接続しています. 時々, 私は以下のように更新しています:

$ cat mysoftware.fw | ssh root@192.168.1.20 \
    'fwup -a -U -d /dev/mmcblk2 -t upgrade && reboot'

fwup を介して, パイプでソフトウェア更新する機能は便利です. これは, 何らかの理由で, デバイス上に更新データを保持するのに十分な容量が無いような状況からも, わたしを抜け出させてくれました.

複数の署名鍵を実相するには?

一度に複数の署名鍵を持つ必要があるようないくつかのユースケースは存在します. たとえば, インフラストラクチャですべてのファームウェアの更新が署名されるように強制できますが, QAビルドの公式の安全なパスを全員に強制することはできません.

現状, 各ファームウェアファイルは, 1つの署名しかできません. しかし, 検証(デバイス)は, 複数の公開鍵を指定(-pオプションをくりかえす)することができます. 鍵毎に fwup を呼び出すことができますが, fwupを介してストリーム更新をサポートし, 検証を実行するため, そして, このクリティカルなコードパスを単純にするためにも, 複数の鍵を指定することを推奨します.

どうやってデバッグするの?

私は, (eMMCやSDカードとは対象的に)ラップトップ上の一般ファイルに更新を適用して, バイナリエディタで検証しています.

  1. コンテンツを検査するため, .fw ファイルを展開する. それは一般のZIPファイルで, meta.conf ファイルが変数展開後に見える, あなたの設定が剥ぎ取らたものです.
  2. printfスタイルデバッグを行う為に error() 関数を追加する.
  3. 動作するイメージを見つけ, 一部のセクションの更新をスキップします. たとえば, 一部のプロセッサはMBRの内容に非常にこだわりがあり, パーティションの制約に取り組む前に他のすべてを機能させるのが簡単です.

How do I specify the root partition in Linux

There are a few options. Most people can specify root=/dev/mmcblk0p1 or root=/dev/sda1 or something similar on the kernel commandline and everything will work out fine. On systems with multiple drives and an unpredictable boot order, you can specify root=PARTUUID=01234567-01 where the -01 part corresponds to the 1-based partition index and 01234567 is any signature. In your fwup.conf file's MBR block, specify signature = 0x01234567. A third option is to use an initramfs and not worry about any of this.

How do I get the best performance

In general, fwup writes to Flash memory in large blocks so that the update can occur quickly. Obviously, reducing the amount that needs to get written always helps. Beyond that, most optimizations are platform dependent. Linux caches writes so aggressively that writes to Flash memory are nearly as fast as possible. OSX, on the other hand, does very little caching, so doing things like only working with one FAT filesystem at a time can help. In this case, fwup only caches writes to one FAT filesystem at a time, so mixing them will flush caches. OSX is also slow to unmount disks, so keep in mind that performance can only be so fast on some systems.

/dev/mmcblock0boot0 を更新するにはどうするの?

特別なeMMCブートパーティションは, メインパーティションと同じ方法で更新可能です. 製品の .fwファイルを作るとき, 2つのターゲットを作ります. メインeMMCを更新するcomplete ターゲット と mmcblock0boot0 を更新する bootloader ターゲットです. 製品スクリプトは, fwup を二度実行します. 最初は complete ターゲット, そして再び bootloader ターゲットです.

/dev/mmcblock0boot0 デバイスは カーネルによって読み込み専用に強制されます. これをアンロックするには, 以下を実行します:

echo 0 > /sys/block/mmcblk0boot0/force_ro

ファームウェアバージョンを見つける最良の方法はなんですか.

fwupはいくつかの方法をサポートします:

  1. meta-version にバージョンを格納しています. これはエンドユーザにとって, 使いやすい方法です.
  2. meta-vcs-identifiergit ハッシュ値を格納します. これは開発者向けです.
  3. meta-uuid にある( ${FWUP_META_UUID}fwup の計算した UUIDを使う.

もちろん, fwup `v1.2.1' から 3つ目の方法は常に存在します. この背景にあるのは, インストールされたファームウェアが目的のファームウェアと一致するかどうかを明確に知るためです. UUIDは計算されるので, fwupの前のバージョンで生成された .fw ファイルは UUIDを持ちます.

最初の2つのオプションは, fwup.conf ファイルへ追加されるべきバージョンを要求します. 通常, 環境変数を使って追加されるので, バージョン番号はハードコードされません.

How do I get the firmware metadata formatted as JSON

Use jq!

$ fwup -m -i $FW_FILE | jq -n -R 'reduce inputs as $i ({}; . + ($i | (match("([^=]*)=\"(.*)\"") | .captures | {(.[0].string) : .[1].string})))'
{
  "meta-product": "My Awesome Product",
  "meta-version": "0.1.0",
  "meta-author": "All of us",
  "meta-platform": "imx6",
  "meta-architecture": "arm",
  "meta-creation-date": "2018-11-07T14:46:38Z",
  "meta-uuid": "7add3c6d-230c-5bf1-77ec-5f785e91be40"
}

ナマのNANDフラッシュはどう使えばいいの?

"raw" NANDフラッシュは, UBIのようなウェアレベリングレイヤを要求します. UBI toolchainと fwupとを統合する方法は, UBI Example fwup.conf を参照ください.

fwupはなんと発音しますか

以前は eff-double-you-up と発音していましたが, 同僚や他の人は .fw ファイルを参照するときに "fwup"(一音節)や "fwup-dates" と呼び始めました. いまは一音節を使っています. これにより"a"ではなく"an"が使われているドキュメントで問題がおきました. 気軽にプルリクを送ってください.

ライセンス

このユーティリティは様々なライセンスのソースコードを含みます. コードの大部分は, LICENSE ファイルにみつけけられるような Apache2.0ライセンスです.

そのほかのサードパーティソースコードは, 3rdparty ディレクトリで見つけられます.

The FAT filesystem code (FatFs) comes from elm-chan.org and has the following license:

FatFs module is a generic FAT file system module for small embedded systems. This is a free software that opened for education, research and commercial developments under license policy of following terms.

Copyright (C) 2015, ChaN, all right reserved.

  • The FatFs module is a free software and there is NO WARRANTY.
  • No restriction on use. You can use, modify and redistribute it for personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY.
  • Redistributions of source code must retain the above copyright notice.

fwup uses semver.c for checking versions. semver.c is Copyright (c) Tomás Aparicio and distributed under the MIT License. See LICENSE.

On systems without the function strptime(), a version from Google is included that is distributed under the Apache 2.0 license.

プログラミングElixir第10章~コレクションの処理

概要

  • Enumモジュール
  • Streamモジュール
  • Collectableプロトコル
  • 内包表記 (comprehension)

前章までに リスト と マップ というコレクションとして振る舞う型を見てきた。他にもいくつもあるらしい。

共通的なコトは、コレクションが持っている要素?に対して、繰り返し処理ができるということ。 `Enumerableプロトコルを実装しているといえる` という表現をしている。

Streamモジュールは.. 少し先に見たことがある、パイプ演算子を挟んで処理の並列化で前段の終了を待たずにデータを流し込む、遅延処理してくれるようなもの...かな.

Enum - コレクションの処理

詳しくは Elixir schoolのEnumの章を読むとして、おおよそ以下の機能をもつ:

  • コレクションをリストに変換
  • コレクションを結合する
  • 与えられたコレクションの各要素に、何らかの関数を適用した結果を要素とする新しいコレクションを生成する
  • 位置、基準によって要素を選択(フィルタリング)
  • 要素をソート、比較する
  • コレクションを分割する
  • 要素を連結する
  • 術後演算
  • コレクションをマージする

・・・・

やってみよう

Enum.all を 作る

期待値

iex(5)> Enum.all?( [2,4,6], &(rem(&1,2)==0) )
true

試行錯誤の結果

defmodule MyEnum do
  defp _all([_], false, _), do: false
  defp _all([_], nil, _), do: false
  defp _all([], _, _), do: true
  defp _all([h|t], _, f), do: _all(t, f.(h), f)

  def all?([h|t], f), do: (
    _all(t, f.(h), f)
  )
end

おけ

iex(7)> MyEnum.all?( [2,4,6], &(rem(&1,2)==0) )
true

Enum.eachをつくる

期待値

iex(9)> Enum.each(["some", "example"], fn x -> IO.puts(x) end)
some
example
:ok
defmodule MyEnum do
  defp _each([h], func), do: func.(h)
  defp _each([h|t], func), do: (
    func.(h)
    _each(t,func)
  )

  def each([h|t], func), do: (
    _each([h|t], func)
  )
end
iex(18)> MyEnum.each(["some", "example"], fn x -> IO.puts(x) end)
some
example
:ok

Enum.filter を 作る

defmodule MyEnum do
  defp _filter([], _func, result), do: result
  defp _filter([h|t], func, result), do: (
    if func.(h) do
      _filter(t, func, result++[h])
    else
      _filter(t, func, result)
    end
  )

  def filter([h|t], func), do: (
    _filter([h|t], func, [])
  )
end
iex(31)> MyEnum.filter([1, 2, 3,4,5,6,7], fn x -> rem(x, 2) == 0 end)
[2, 4, 6]

不具合

誤: _filter(t, func, [result|h])
正: _filter(t, func, result++[h])

resultがリストであることを期待して記述したつもり。縦棒は要素を区切るものだったから 誤りではリストと新しいスカラ値の2要素のリストが生成された。

リストに要素を追加する場合は `++` 演算子を使う。

MyEnum.filter([1, 2, 3], fn x -> rem(x, 2) == 0 end)
iex(22)> MyEnum.filter([1, 2, 3], fn x -> rem(x, 2) == 0 end)
[[] | 2]

Enum.split を 作る

TODO: あとで作る? リスト、番号を引数に持つ。

ローカル関数で結果のタプルを引数に渡して呼び出す。要素数を自然数で受けるようにして、前からいくつめかを認識させ、1ずつ減らしてヘッドから要素を取り出していく。結果のタプルを都度更新していけば結果が得られるだろう。

Enum.take を 作る

TODO: あとで作る?

splitと似たような実装。結果は要素ひとつにすれば同じように作れる?(共通化は?)

ストリーム~遅延列挙

Enumはデータすべてを処理しきる必要があり、パイプ演算子で結合した段それぞれで処理の完了を待つ。ストリーム対応であれば、処理の完了を待たずに逐次出力されたデータを次の段に渡していく。最終的にListに入れる場合などは、すべてのデータが入力されるのを待つ処理が入る。

・・・・そんな感じ?