Elixir_modules
modules
ドキュメント参照したモノ等をコンテンツとして記録しておく..
ドキュメント参照したモノ等をコンテンツとして記録しておく..
個々のライブラリ群?だけをみていても理解できないっぽい。 Nerves examplesを参照していくとつながるかもしれん。
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
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触る方法があるね..
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のビルドシステムに対して 追加している感じやろか..
Nerves Systemのビルドに必要な環境を Docker file で提供している。
これを使えばよかったのでは... fwupも NAT超えで使えるはずだしなぁ.
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},
本家は上記リンクからたどれます
このサイトでは 個人的に調査したり, 動かしてみたりした問いのメモを残していく予定です. ローカルに散らかしておくと見失ってしまうので, マサカリをいただいたりして自分の認識誤りを正してもらえることを期待しています..
発信する分, 何らかのメリット(feedback,寄付)は享受したいものですね!
cnnpassのグループがあります. いくつかの Nerves関連サイト, Elixir関連サイトを除いてみると, 参考リンクが出てきます. 複数からリンクしているところが有力 or 根っこだと思うとよいかもですね.
referto: https://github.com/fhunleth/fwup.git at 2020/03/11頃参照
fwup
は, 組み込みLinuxベースの組込みシステムのための, 設定可能なイメージベースのソフトウェア更新ユーティリティです.
fwup
は, 主に所望のルートファイルシステム(RFS)を一度に更新するための, ソフトウェア更新戦略(ストラテジ)をサポートします.
これには, AパーティションとBパーティションの間の前方/後方へのスワッピング, リカバリパーティション,
さまざまなトライアルアップデート/フェイルバックシナリオなどのストラテジが含まれます.
すべてのソフトウェア更新情報は, ZIP圧縮に結合され, 必要に応じて暗号的に署名されることがあります.
fwup
は最小の依存正とランタイムを要求します.
失敗のシナリオを簡単に推論できるように, スクリプトはわざと制限されています.
ソフトウェア更新アーカイブの配布は, 機能ではありません.
ユーザは, 必要に応じて, 外部メディア・ネットワークからのストリーム・
Ansibleのようなツールを使ったスクリプトから, 更新を実行するために,
fwup
を呼び出すことができます.
機能リストの全てを挙げます:
デバッグと転送を簡単にするために, 標準ZIP圧縮を使います.
たくさんのプラットフォーム上でファームウェア更新を有効にするため, 単純だけど 自由に設定できる言語, と, ファームウェア更新ポリシー
ターゲットのストレージ要求を単純にする ストリーミングファームウェア更新処理
1つのアーカイブがさまざまなターゲット設定を更新できるように, アーカイブごとの複数のファームウェア更新タスクオプション
基本的なディスクパーティショニングと, FATfs操作
人間と機械が読める進捗
Linux, OSX, BSD, Windowsのいずれの開発システム上でも初期化・更新するSDカード. MMCとSDカードは自動的に検出され, アンマウントされます. ログを眺めたり, 手動でアンマウントする必要はありません.
ファームウェアアーカイブの電子署名を作成・検証
Sparse file support to reduce number of bytes that need to be written when initializing large file systems (see section on sparse files)
大きなファイルシステムを書き込む必要があるときに, バイトサイズを圧縮するためのスパースファイルのサポート(スパースファイルの節を参照)
寛容なライセンス (Apache 2.0 License - see end of doc)
広範な回帰テスト済み! テストは機能の簡単な例も提供します。
内部的には, fwup
は 低レベルなディスク書き込みを高速にする最適化が, dd(1)
で簡単に達成できるよりも多いです.
フラッシュ消去ブロックのアライン,
書き込み時間を最小にするための 大きな未使用領域のスキップを可能にします.
開発サイクルの繰り返しが十分に早くできることをゴールとしています.
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.
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
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).
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
fileA 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.
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.
fwup
は Unix設定ライブラリ(libconfuse)を使っています.
したがって その設定は 他のプログラムに似ているところがあります.
設定ファイルはファームウェアアーカイブを作るために使われます.
作成中, fwup
は, 処理した設定ファイルを アーカイブに埋め込みます.
埋め込まれる設定ファイルは, コメントを削除し, 変数が解決されたあと, 有用なメタデータを追加されたものになります.
設定ファイルはスコープブロックに属し, オプションは key = value
構文を使ってセットされます.
加えて, 設定ファイルは include("filename")
を呼び出すことで他のファイルや分割された設定を取り込みます.
ビルドシステムや他のスクリプトへの統合のため, 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")
グローバルスコープでは, 以下のオプションがあります:
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
は, 更新設定ファイルの他の部分から参照できるように,
ユニークな名前を与える必要があります.
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"
}
ときおり, `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"
}
アーカイブを作るとき, 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
セクションは, 宛先メディアの 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
オプションはfwup
が block-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
の節では, 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ブートローダを使っているシステムのために,
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
せく処んは, ユニークな名前を持つ必要があります.
しかし, タスクを探すときには, ファームウェア更新ツールが前方照合を行うだけです.
これはあなたに,ターゲットハードウェアの状態に基づいて評価できる複数のタスクを定義させてくれます.
最初に照合されたタスクが適用されます.
これは, ターゲット上の現在のファームウェアバージョンやターゲットアーキテクチャなどに基づいて 更新手順が異なる場合にとても使いやすいです.
以下の表に, サポートされる制約を列挙します:
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 | リソースを指定されたブロックオフセットへ書き込みます. オプションは cipher と secret を含みます. |
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変数を案セットします. |
スパースファイルは, ファイルシステム上のメタデータ中のみで表現されるギャップを持ったファイルです. すべてのファイルシステムでサポートされていませんが, 一般的に 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
カーネルドライバと適合するディスク暗号化のサポートに制約があります.
これは安静時に読み出されれない方法で, ファイルシステムのデータを書き込むことを可能とします.
fwup
は暗号鍵の処理に対応しておらず, 不適切な処理は暗号化の利点を簡単に損なう可能性があります..fw
アーカイブは暗号化されません. このメカニズムは アーカイブの秘密は他の意味で保護されます.
もちろん, アーカイブ内のデータを暗号化しておくことも可能です.
しかし, デバイス固有の秘密鍵を持つことはできません.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
上記の引数の多くをシステムに適したものに置き換える必要があるでしょう.
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.pub
と fwup-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
に書き込まれたテキストを解析することで実行できます.
ファームウェアを適用するとき, コマンドラインオプションに基づいて進捗状況が更新されます:
0%
から 100%
まで更新されます.-n
) - 進捗は 0\n
から 100\n
のように表示されます.-q
) - 進捗は表示されません.上記はスクリプトでは上手くいきますが, エラーがオペレータに見られることがあるとき,
fwup
は, うまく stdin
/stdout
を使う構造をサポートします.
このオプションを使うためには, あらゆるコマンドに --framing
オプションを指定します.
フレーミング機能は Erlang VMのポートAPIで流行り,
非Erlang VM言語と統合することを比較的簡単にします.
フレーミングは 組み込みのプロセス間通信の欠点に対処します.
たとえば, フレーミングを有効にすることで,
ファイルの最後を通知するために stdout
をクローズする必要なく,
fwup
の stdin
を介して, ファームウェア更新を流し込むことができます.
フレーミングに助けられる他の機能は, どのテキストが一緒になるか,
テキストがエラーメッセージの一部かどうか, を知ることです.
終了ステータスは 依然として成功か失敗を示しますが, 制御アプリケーションは何がおきたのかを知るために プログラムの終了をまつ必要がありません.
--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
をクローズすることを期待します.
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"
よろしい. 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カードとは対象的に)ラップトップ上の一般ファイルに更新を適用して, バイナリエディタで検証しています.
meta.conf
ファイルが変数展開後に見える, あなたの設定が剥ぎ取らたものです.error()
関数を追加する.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.
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.
特別なeMMCブートパーティションは, メインパーティションと同じ方法で更新可能です.
製品の .fwファイルを作るとき, 2つのターゲットを作ります.
メインeMMCを更新するcomplete
ターゲット と mmcblock0boot0
を更新する bootloader
ターゲットです.
製品スクリプトは, fwup
を二度実行します.
最初は complete
ターゲット, そして再び bootloader
ターゲットです.
/dev/mmcblock0boot0
デバイスは カーネルによって読み込み専用に強制されます.
これをアンロックするには, 以下を実行します:
echo 0 > /sys/block/mmcblk0boot0/force_ro
fwupはいくつかの方法をサポートします:
meta-version
にバージョンを格納しています. これはエンドユーザにとって, 使いやすい方法です.meta-vcs-identifier
に git
ハッシュ値を格納します. これは開発者向けです.meta-uuid
にある( ${FWUP_META_UUID}
) fwup
の計算した UUIDを使う.もちろん, fwup `v1.2.1' から 3つ目の方法は常に存在します.
この背景にあるのは, インストールされたファームウェアが目的のファームウェアと一致するかどうかを明確に知るためです.
UUIDは計算されるので, fwupの前のバージョンで生成された .fw
ファイルは UUIDを持ちます.
最初の2つのオプションは, fwup.conf
ファイルへ追加されるべきバージョンを要求します.
通常, 環境変数を使って追加されるので, バージョン番号はハードコードされません.
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"
}
"raw" NANDフラッシュは, UBIのようなウェアレベリングレイヤを要求します. UBI toolchainと fwupとを統合する方法は, UBI Example fwup.conf を参照ください.
以前は 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.
前章までに リスト と マップ というコレクションとして振る舞う型を見てきた。他にもいくつもあるらしい。
共通的なコトは、コレクションが持っている要素?に対して、繰り返し処理ができるということ。 `Enumerableプロトコルを実装しているといえる` という表現をしている。
Streamモジュールは.. 少し先に見たことがある、パイプ演算子を挟んで処理の並列化で前段の終了を待たずにデータを流し込む、遅延処理してくれるようなもの...かな.
詳しくは Elixir schoolのEnumの章を読むとして、おおよそ以下の機能をもつ:
・・・・
期待値
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
期待値
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
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]
TODO: あとで作る? リスト、番号を引数に持つ。
ローカル関数で結果のタプルを引数に渡して呼び出す。要素数を自然数で受けるようにして、前からいくつめかを認識させ、1ずつ減らしてヘッドから要素を取り出していく。結果のタプルを都度更新していけば結果が得られるだろう。
TODO: あとで作る?
splitと似たような実装。結果は要素ひとつにすれば同じように作れる?(共通化は?)
Enumはデータすべてを処理しきる必要があり、パイプ演算子で結合した段それぞれで処理の完了を待つ。ストリーム対応であれば、処理の完了を待たずに逐次出力されたデータを次の段に渡していく。最終的にListに入れる場合などは、すべてのデータが入力されるのを待つ処理が入る。
・・・・そんな感じ?