#author("2025-05-31T12:54:35+09:00;2025-05-31T07:56:42+09:00","default:kaw","kaw")
#author("2025-06-03T23:25:26+09:00;2025-05-31T07:56:42+09:00","default:kaw","kaw")
#topicpath
* usbfadm - 河豚板のUSBメモリ管理ツール [#x7dde876]
RIGHT:EBUG 第93回会合 2025年5月31日 ~
川俣吉広、kaw@on.rim.or.jp

** usbfadmについて [#m98d081f]
usbfadm (USB Flashdrive ADMinistration tool) は、[[河豚板LiveUSB>河豚板]]でUSBメモリを管理するためのユティリティツールです。

このツールは当初、河豚板LiveUSBもまだなかったころ、河豚板LiveCDで[[mfs>man:newfs]]の内容をUSBメモリに保存し次回の起動で読み込むための補助的な存在でした。

現在ではUSBメモリへのデータ保存以外にも、LiveUSBメディアのカスタマイズ・再構築やディスクデバイスの構成変更などを行える、河豚板LiveUSBの総合管理ツールとなっており、[[日本語デスクトップ環境構築>EBUG勉強会/20200229_dtjsetup]]、[[対話的ネットワーク設定>fg3:netconf]]、[[ライブアップデート>EBUG勉強会/20210828_fiupdate]]などを行うツールとならび、河豚板独自の機能を提供しています。~
(なお、このツールには"USB Flashdrive"という名称が使われていますが、実際にはOpenBSDがマウントして読み書きできるてのストレージデバイスに対して利用可能です)
(なお、このツールには"USB Flashdrive"という名称が使われていますが、実際にはUSBメモリだけでなく、ハードディスク、SSH、SDカードなど、OpenBSDがマウントして読み書きできるてのストレージデバイスに対して利用可能です)

今回は、このusbfadmについて見ていきます。
----
目次
#contents

** usbfadmの機能 [#cedb79be]
*** 機能一覧 [#se02b3c5]
|CENTER:コマンド名|CENTER:機能|h
|[[sync>fg2:usbfadm_sync]]|メモリ上のファイルシステムの内容をUSBメモリへ保存(同期)|
|[[newdrive>fg3:liveusb_remaster]]|USBメモリに河豚板のシステムを作成(リマスタリング)|
|[[expand>fg2:usbfadm_expand]]|LiveUSBのデータ保存領域を拡張|
|[[archive>fg3:archive]]|syncで保存したデータをアーカイブ化|
|[[target>fg2:usbfadm_sync]]|sync,archive,expandするデバイス・パーティションを設定|
|[[saveas>fg2:usbfadm_sync]]|USBメモリへの保存データ名を設定|
|[[info>fg2:usbfadm_sync]]|targetで指定したパーティションの状況を表示|
|>|コマンド名クリックで「河豚板ガイド」の説明箇所へ|

***実行例 [#kafd7951]
以下のように、対話形式での利用が基本となっている。実行にはroot権限が必要。
 demohost$ doas usbfadm
 doas (kaw@demohost.honjoji.local) password: 
 
 Welcome to usbfadm.
 USB flash drive administration tool for FuguIta
 
 Version/Arch: 7.6/amd64  (FuguIta-7.6-amd64-202504091)
     Boot mode: usbflash
 Target device: /dev/sd2d
 Data saved as: demohost
 
 readline capability available
 TAB to complete the reserved words
 
 Type ? for help.
 
 sd2d : demohost ->?
 
 Interactive commands are;
     target    -  set the partition for sync, info and expand
     saveas    -  set the name of the data to be saved
     sync      -  sync the target with the current mfs
     archive   -  archive saved directory to *.cpio.gz
     info      -  show info about the target partition
     newdrive  -  make a new FuguIta LiveUSB
     expand    -  expand the target partition as large as possible
     bye, exit, quit
               - end of this utility
 
 Command line options are;
     -r : redo sync non-interactively
         (must run 'sync' at interactive mode before doing this)
     -i : show info about the persistent storage
     -q : quiet mode when redo sync
     -t : trace output (pass -x to shell)
     -d : debug output for newdrive to file 'usbf.debugout'
     -h : print this help
 
 sd2d : demohost ->
上の「sd2d : demohost ->」がコマンドプロンプト。この例では「sd2d」が操作対象のパーティション、「demohost」がsync, archiveを行う際の名称を表している。

** 各機能の説明 [#z85a6422]
*** sync [#d5b022f9]
[[メモリ上のファイルシステムの内容をUSBメモリへ保存(同期)する。>fg2:usbfadm_sync]]

|CENTER:&ref(usbfadm_sync.png,,50%);|
|CENTER:メモリからストレージへの同期|

-保存対象は/ramにマウントされているMFSのディレクトリやファイル
--/ramのさらに下層にマウントされているファイルシステムは対象外
--/ram/tmp (= /tmp)も対象外

-初回は[[pax>man:pax]]でフルコピー。2回目以降は[[rsync>https://rsync.samba.org/]]の差分コピー機能を使用して内容の同期を行い、所要時間を短縮する

-保存したデータは、以降の河豚板起動時に[[起動モード3を指定する>fg2:boot_mode3]]ことで復帰を行う

-保存先のパーティションはtarget、保存名称はsaveasコマンドで予め指定しておく必要がある~
(syncを既に行っており、再実行する場合や、起動モード3で読込んで起動した場合は、target, saveasは以前の設定を引き継ぐ)

-2回目以降の保存は、"usbfadm -r"とすることで、コマンドライン上から直接実行可能~
[[cronなどでバックグラウンド実行を行う>fg2:sync_cron]]ことも想定

***newdrive [#ga041e54]
[[河豚板LiveUSBを新規作成(リマスタリング)する。>fg3:liveusb_remaster]]

#ref(usbfadm_newdrive.png,,50%)

-河豚板LiveDVD/LiveUSBのどちらからでも実行可能

-単に現在動作しているシステムを複製するだけではなく、以下の項目を指定し、生成するイメージをカスタマイズできる
|RIGHT:|CENTER:|CENTER:|c
|カスタマイズ項目|設定内容|amd64でのデフォルト値|h
|生成対象|実デバイス / イメージファイル |なし|
|起動方法|LEFT:Legacy BIOS / UEFI / なし(データ保存専用) / Hybrid|UEFI|
|パーティション&br;テーブル|MBR / GPT|MBR|
|/ramの&br;ファイルシステム|MFS / TMPFS|MFS|
|スワップのサイズ|(0で作成しない)|16MB|
|データ保存領域の&br;サイズ|(0で作成しない)|未使用部分全て|
|データ保存領域の&br;暗号化|なし / あり|なし|
|未使用領域を&br;FATにするか|しない / する|しない|

なお、上の図からわかるように、newdriveでは保存データのコピーは行わないため、元のシステムと同じ環境が必要な場合は、別途、targetを新デバイスに変更した上でsyncを実行する必要がある。

***expand [#y93de7af]
[[デバイスに未使用領域がある場合、データ保存用パーティションをデバイスのサイズ一杯まで拡張する。>fg2:usbfadm_expand]]

河豚板LiveUSBのイメージファイルは、現在、2GBのサイズで作成され、配布されている。~
よって、それ以上のサイズのUSBメモリにイメージを書き込んで使用することになるが、書き込んだだけでは使用するUSBメモリのサイズに関係なく2GBしか領域を使用できない。そして、河豚板のシステムが約1GBを占有しているので、usbfadmでデータを保存できるのは1ギガバイト程度となる。

expandコマンドは、データ保存用パーティションをデバイスのサイズ一杯まで拡張し、デバイスを有効に使用できるようにする。

#ref(usbfadm_expand.png,,50%)

-データ保存用パーティションの拡張には、2つの方法がある
--growfs - パーティション内のデータを保持したまま、拡張を行う

--newfs - パーティションを拡張後、フォーマットを行う。この際、ファイルシステムのパラメータ(ブロックサイズやiノード密度など)は、拡張後のパーティションサイズに適切な値が設定される

-データ保存用パーティションの後に別のパーティションが存在している場合は、拡張はできない。事実上、配布イメージ専用

***archive [#c14beb93]
[[saveasで指定した保存データをアーカイブ化する。>fg3:archive]]

saveasコマンドで指定し、syncコマンドで保存したファイルツリーから*.cpio.gz形式のアーカイブを作成する
(パス名の最大長は、[[tar>man:tar]]よりも[[cpio>man:cpio]]が長いため、cpioを採用した)。

#ref(usbfadm_archive.png,,50%)

このアーカイブファイルは、syncで保存したデータと同様、河豚板の起動モード3で指定して読み込むことができる。

-archive機能利用の想定シナリオ
--アーカイブデータを読み込んで起動することで、毎回同じ状態から操作を開始

--アーカイブデータを環境をカスタマイズするためのベースとして使用

--アーカイブされたデータで、運用環境をやりとりする

-アーカイブ元はMFSではなく、syncで保存済みのデータ
--理由:ファイルツリー全体の整合性を担保するため~
MFS->USBメモリへの保存は、数分~数十分かかる場合があるため、運用中に一部のファイルが書き換わってしまう可能性がある。それを防ぐため

-アーカイブデータからのモード3起動では、usbfadmを起動してもsaveasは設定されない。明示的にsaveasを実行する必要がある
--理由:アーカイブデータから復帰したMFSがsaveasで保存された「より新しい」環境を上書きしてしまわないようにするため
  test.cpio.gzのsaveasが再起動後、"test"に設定されていると...
 
  ----->archive---->sync...reboot...-->sync--->
          |          |             ^    |
          V          V             |    V
     test.cpio.gz   test/          |   test/  "test"が、より古い
          |                        |          test.cpio.gzで
          +------------------------+          上書きされる危険あり!

** 実装上のTopics色々

*** スクリプトの概要 [#g016e032]

usbfadmは、ディスクデバイスを管理するコマンドに対するwrapper scriptという見方もできる。

usbfadmが提供する機能は、[[fdisk>man:fdisk]], [[disklabel>man:disklabel]], [[newfs>man:newfs]], [[bioctl>man:bioctl]]など、ディスクデバイスを扱うコマンドを直接使用することで実行できるが、usbfadmはそれらコマンドの詳細を知ることなく、河豚板LiveUSBの管理を安全かつ容易に実行可能。

usbfadmは、単一の[[ksh>man:ksh]]スクリプトで、概ね以下のような構成になっている。~
参考: [[usbfadmのソースコード (GitHub)>https://github.com/ykaw/FuguIta/blob/master/rdroot/boottmp/usbfadm]]


 #ユティリティ関数群の定義
 echoerr() { ... }
 clear_exit() { ...}
  ...
 
 #各コマンド関数群の定義
 cmd_sync() { ... }
 cmd_archive() { ... }
  ...
 
 #ここからメイン処理
 
 #初期化
 大域変数の定義
 実行環境のチェック
 デフォルト値の設定
 設定ファイルの読込
 コマンドラインオプションの解析
 
 if コマンドラインオプションあり; then
   非対話的処理
   (usbfadm -rなど)
   clear_exit
 fi
 
 #対話的処理
 while :; do
   プロンプトの表示
   コマンドの読込
   case コマンド in
     sync)    cmd_sync;;
     archive) cmd_archive;;
     ...
   esac
 done
 clear_exit

- 対話的処理のコマンドループ以外はstraight forwardな処理

- デバイスに改変を加える場面では、敢えてUnixのtoolbox的アプローチよりも、[[拘束的インターフェース>https://www.amazon.co.jp/UNIX%E3%81%A8%E3%81%84%E3%81%86%E8%80%83%E3%81%88%E6%96%B9%E2%80%95%E3%81%9D%E3%81%AE%E8%A8%AD%E8%A8%88%E6%80%9D%E6%83%B3%E3%81%A8%E5%93%B2%E5%AD%A6-Mike-Gancarz/dp/4274064069]]を採用。利便性・機能性より安全性を優先する

- 対象デバイスをマウント/アンマウントは、コマンド単位で行い、デバイスをいじる直前でマウントし、いじり終ったら即アンマウント。安全性とusbfadm実行中にデバイスを交換する可能性を考慮した結果

- エラー発生時、あるいはキャンセルの指示があった場合はclear_exit()で後片付けしてbail out。リカバリ・リトライなどはしない方針
-- 後片付け
---vnodeデバイスの解除
---対象デバイスのアンマウント
---ロックディレクトリ内の作業ファイル
---及びロックディレクトリの削除
---etc...

''→'' ''ユーザがCtrl-Cなどでusbfadmを強制終了させても、安全に終了できること。副作用なくクリーンに終了すること。''

- ディスクデバイスやパーティションなどの操作対象はユティリティ関数である程度まで抽象化。
-- ユティリティ関数へのAPI的には、パーティションテーブルの形式や起動方法などは隠蔽されている
-- デバイス/パーティションのパラメータなども、なるべく直参照しなくてよい方向をめざす

*** readline機能の付加 [#m98eea2b]

コマンドの読込には、[[rlwrap>https://github.com/hanslub42/rlwrap]]コマンドを利用したrl_wread関数を定義。この関数により、以下の機能が実装されている。
-行編集機能(Emacsバインディング)
-コマンド/デバイス/ファイルなどのTABによる補完
-デフォルト値の提示 (ENTERでデフォルト値入力)
-ヒストリ機能

rl_wread関数は、始めにrlwrapコマンドをダミーで起動し、rlwrapコマンドが正常に使えるか判定する。rlwrapが正常に使用できない場合は、単純なread文にフォールバックする。
 #-------------------
 # read user's input with readline functionality
 # outputs echoed to stdout
 #
 #     usage: rl_wread prompt-str default-str [completion words ....]
 #
 rl_wread () {
     local prompt="$1";  shift
     local default="$1"; shift
     local retval
 
     # check if rlwrap is available
     #   When control tty is missing (in /etc/rc.shutdown for example),
     #   rlwrap in command substitution "$(rlwrap ...) " fails.
     if retval=$(rlwrap true) 2>/dev/null 2>&1 ; then
         echo "$@" > $lockdir/rl_words
         rlwrap -b '' \
                -f $lockdir/rl_words \
                -P "$default" \
                sh -f -c 'echo -n "'"$prompt"'->" >&2 ; read w || echo EOF; echo $w' || echo RL_ERR
     else
         #-------------------
         # fallback to dumb input
         #
         if [[ -z "$default" ]]; then
             echo -n "${prompt}->" >&2
             read w
         else
             echo -n "$prompt [$default] -> " >&2
             read w
             if [[ -z "$w" ]]; then
               w="$default"
             fi
         fi
         echo $w
     fi
 }
使用法: ユーザの入力値は標準出力に返されるので、シェルのコマンド置換でキャプチャする。~
end of fileの場合は文字列「EOF」が、エラーが発生した場合は「RL_ERR」が返される。
 cmd=$(rl_wread "$d : $u " '' quit bye exit sync archive info saveas target newdrive expand help ?)
                    ↑     ↑  ↑
                プロンプト ↑  TABで補完する単語群
                      デフォルト値

*** プラットフォーム依存の処理 [#d2247fb1]
usbfadmは、OSによって抽象化されたデバイスやファイルを扱うので、処理の大部分はプラットフォーム非依存だが、newdrive機能では、起動廻りの処理を扱うのでプラットフォームに依存する部分が存在する。~
この処理は、newdrive()関数内で/etc/fuguita/usbfadm_postproc.shを読み込む(sourceする)ことで対応している。

arm64アーキテクチャの例:~
河豚板/arm64では、newdrive時に[[ラズパイ3/4用にEFI/U-boot関連の設定が必要>EBUG勉強会/20240525_FuguItaISO#s8c29b84]]なので、その処理をusbfadm_postproc.sh.arm64で行っている。
 #
 # post processing for Raspberry Pi 3/4
 # This file is included in usbfadm
 #
 
 notice "Change partition ID and Boot flag for Raspberry Pi..."
 
 if [ "$instsys" = UEFI ]; then
     echo "e 0\n0C\n\n\n\nf 0\nq" | fdisk -e "$scandev"  ← パーティションタイプの変更
 fi
 
 
 # install Raspberry Pi Firmwares and U-Boot binaries
 #
 notice "Copying U-BOOT stuffs..."
 
 if mount -t msdos -o-l /dev/${scandev}i /mnt; then
     tar -xvz -C /mnt -f /usr/fuguita/mdec/bootstuff.$(uname -m).tar.gz  ← ESPにブート関連の
     umount /mnt                                                            ファイルを配置
 fi

*** expand機能の改良: GPTの拡張 [#a98ec6fe]

expand機能は、従来、MBRパーティションにのみ対応していた。河豚板LiveUSBの配布イメージは[[MBR+UEFIで行って>EBUG勉強会/20190223_UEFI_GPT#g2c561a7]]おり、これで必要十分であったが、GPTの場合にもデータ保存領域の拡張が行えるようexpand機能の改良を行った。

usbfadmではパーティションの操作を行う際は、行いたい操作に操作するfdiskやdisklabelのコマンド文字列を生成し、これをパイプライン経由でそれらのコマンドに入力することで実現している。

コーディング例
 fdisk_input="e ${partid_obsd}\nA6\nn\n\n*\nw\nq\n"
   ...
 echo -n "$fdisk_input" | fdisk -e "$scandev" >/dev/null

GPTのパーティション拡張の場合も、概ねこの方針でOKだが、新たに以下の問題が発生する。

- GPTヘッダと呼ばれる部分には、デバイスのサイズが記録されているが、配布イメージをデバイスにベタ書きした場合、ここには実際のデバイスサイズではなく、イメージファイルのサイズが書かれている

- また、配布イメージをデバイスにベタ書きすると、デバイス最後部に配置すべきバックアップGPTがデバイスの中途半端な位置に配置されてしまう

- パーティション情報の変更を行った場合は、GPTヘッダ中のCRC値もそれに合わせて更新する必要がある

#ref(usbfadm_gptexpand.png,,50%)

GPTの再構成方法の概要は以下のとおり

- fdiskコマンドをオプションなしで実行し、現状のパーティション構成を出力。その結果を保存しておく

- fdisk -eで、fdiskを編集モードで起動
-- デバイスをGPTで初期化
--- GPTヘッダに実デバイスのディスクサイズが正しく設定される

--- GPTヘッダのCRC値が再計算される

--- バックアップGPTがディスクの最後尾に配置される

- 最初に保存した本来のパーティション情報を元に、そのパーティション情報を再構成するfdiskのコマンド列を生成し、そのコマンド列でfdiskを実行する
-- 各パーティションの情報がGPT初期化前のものに復帰する~
~
実際の動作としては、fdisk -v sd1を実行したときの出力は以下のようになる。
 Primary GPT:
 Disk: sd1       Usable LBA: 34 to 16777182 [16777216 Sectors]
 GUID: c535a72e-969c-4602-b77c-e7a162b549b0
    #: type                                 [       start:         size ]
       guid                                 name
 ------------------------------------------------------------------------
    1: EFI Sys                              [          64:         1024 ] ←種別、位置、サイズ
       3df0b410-40b7-47be-a406-20da01ece8e4 UEFI Boot                     ←UUID、コメント
    2: Microsoft basic data                 [    12860480:      3916672 ] ←以下同じ
       8b830bfc-2d7f-4f00-9c1f-6c241aba9421 MSDOS FAT
    3: OpenBSD                              [        1088:     12859392 ]
       83b8e6d4-e620-47aa-b1fc-04f7f827fa81 OpenBSD Area
 
 Secondary GPT:
 Disk: sd1       Usable LBA: 34 to 16777182 [16777216 Sectors]
 GUID: c535a72e-969c-4602-b77c-e7a162b549b0
    #: type                                 [       start:         size ]
       guid                                 name
 ------------------------------------------------------------------------
    1: EFI Sys                              [          64:         1024 ]
       3df0b410-40b7-47be-a406-20da01ece8e4 UEFI Boot
    2: Microsoft basic data                 [    12860480:      3916672 ]
       8b830bfc-2d7f-4f00-9c1f-6c241aba9421 MSDOS FAT
    3: OpenBSD                              [        1088:     12859392 ]
       83b8e6d4-e620-47aa-b1fc-04f7f827fa81 OpenBSD Area
 
 MBR:
 Disk: sd1	geometry: 1044/255/63 [16777216 Sectors]
 Offset: 0	Signature: 0xAA55
             Starting         Ending         LBA Info:
  #: id      C   H   S -      C   H   S [       start:        size ]
 -------------------------------------------------------------------------------
  0: EE      0   0   2 -   1044  85   1 [           1:    16777215 ] EFI GPT ← 保護MBR
  1: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
  2: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
  3: 00      0   0   0 -      0   0   0 [           0:           0 ] Unused
cmd_expand関数内では、この出力を以下のようなfdisk編集モードのコマンド列に変換する。
 reinit gpt  ← GPTで初期化
 edit 0      ← 初期化で作成されたパーティションを削除
 0
 edit 1      ← パーティション1の作成
 c12a7328-f81f-11d2-ba4b-00a0c93ec93b  ← UUID (パーティション種別)
 64          ← 開始位置
 1024        ← サイズ
 UEFI Boot   ← コメント
 edit 2      ← パーティション2以下、同様
 ebd0a0a2-b9e5-4433-87c0-68b6b72699c7
 12860480
 3916672
 MSDOS FAT
 edit 3
 824cc7a0-36a8-11e3-890a-952519ad3f61
 1088
 12859392
 OpenBSD Area
 write
 quit
変換されたコマンド列をfdisk -e sd1に入力し、GPTを修正する。

- OpenBSDの領域をバックアップGPTの直前まで拡張

- GPTを実デバイスに書き込んでfdisk終了

なお、この処理はGPTを全面的に書き換えるクリティカルな処理のため、以下のようなエラー対策を実施している。

- 各処理の要所で実行ステータスをチェックし、実デバイスの書き込み前に異常が検出されたらエラーメッセージを表示して終了

- 書き込み後、エラーが発生した場合は、処理前のパーティション情報をファイルに書き出し、この情報を元に手動でパーティションを再構成するように表示して終了

GPT拡張後、disklabelコマンドを呼び出してデータ保存用パーティションの拡張を行う。この部分は従来のMBRパーティションの場合と同じ。

- disklabelコマンドを用いて、[[OpenBSD boundary>EBUG勉強会/20171118_OpenBSD管理入門#content_1_1]]をバックアップGPTの直前まで拡張し、データ保存用パーティション(dパーティション)をOpenBSD boundaryの範囲内いっぱいに拡張

- ディスクラベルを実デバイスに書き込んでdisklabelコマンド終了

** 今後の開発予定 [#p99a7525]

- 現在、新規の機能追加の要望や、不具合の報告などがないため、usbfadmの開発作業は待機状態。今後、必要に応じて作業を行ってゆく

- usbfadmは開発開始から約20年が経過しており、中には、以下のような古い形式のコーディングが残ったままになっている。
 if [ 1 -le $(expr X"$(pwd)" : X$mntdir1) ]; then
     echo
     echoerr 'You are under $mntdir1. Please move to other directory.'
     exit 1
 fi
このような古い形式のコードは、適時、修正してゆく。ただし、拙速な修正はエンバグを招きかねないため、今後の修正・開発作業に併せて、このようなコードのリファクタリングを行ってゆく予定

** おまけ [#fa4b0043]
河豚板 7.7の「日本語デスクトップ環境デモ版」を作ってみたので、使ってみてください。

説明ページ
- [[日本語デスクトップ環境デモ版>FuguIta/BBS#d96a6798]]

- [[FuguIta desktop environment demo version>FuguIta/BBS#ba2ab6a8]]

Front page   Edit Diff History Attach Copy Rename Reload   New Page list Search Recent changes   Help   RSS of recent changes