Haiku で msxterm をビルドする ― Rust のマイナーOSサポートを追いかけた記録

みなさん、こんにちは。

私は「MSX0」というIoTマイコンを持っていて、日頃からプロトタイピング用として楽しく活用しています。当サイトでも何度か記事にしていますので、よかったら覗いてみてください。

そんな私が普段の開発用に使っているのが、msxterm というターミナルアプリです。

msxterm は MSX0 と TCP/IP やシリアル接続するための CUI ターミナルソフトで、Rust で書かれています。公式には Windows / macOS / Linux 対応となっているのですが、「Rust が使えるなら Haiku OS 上でもビルドして動かせるのでは……?」と思い立ち、挑戦してみることにしました。

作業自体はごく普通に cargo install --path ./ を叩くところからスタートしたのですが、そこから怒涛のビルドエラー!さらに「ビルドは通ったのに実行すると機能が壊れている」という、なんとも厄介な問題まで発生してしまいました。Rust のマイナーターゲット開発でよくある「沼」に見事にはまってしまったので、その試行錯誤の過程を記録として残しておきます。

最終的な成果物は以下のリポジトリにまとめてあります。


開発環境

今回の開発環境です。つい先日リリースされたばかりの、Haiku R1/beta6(x86_64)を使っています。

  • OS: Haiku R1/beta6(x86_64) hrev59866+79
  • Rust: ツールチェイン導入済み(HaikuDepot で rust_bin をインストールし、msxtermcargo install --path ./ で入れる想定)
まずはHaikuにrustを入れる
まずはHaikuにrustを入れる

ちなみに、ここからのHaiku OSでの作業自体は、Linux 上で動かしている Claude Code から Haiku にリモートログインして行いました。


第1段階 – nix クレートの API が見つからない!

まずは素直にクローンしてビルドしてみます。

$ git clone https://github.com/akio-se/msxterm.git
$ cd ~/msxterm
$ cargo install --path ./

すると、いきなり次のようなコンパイルエラー(exit code 101)で止まってしまいました。

error[E0432]: unresolved import `nix::sys::termios::SpecialCharacterIndices`
  --> rustyline-12.0.0/src/tty/unix.rs:16:39
   |
16 | use nix::sys::termios::{self, SetArg, SpecialCharacterIndices as SCI, Termios};
   |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `SpecialCharacterIndices` in `sys::termios`

error[E0433]: failed to resolve: could not find `ioctl_read_bad` in `nix`
  --> rustyline-12.0.0/src/tty/unix.rs:35:6
   |
35 | nix::ioctl_read_bad!(win_size, libc::TIOCGWINSZ, libc::winsize);

どうやら msxterm が依存している rustyline v12.0.0 が、内部で使っている nix クレートの API を解決できていないようです。

rustylineCargo.toml を見ると nix = "0.26">=0.26.0, <0.27.0)を指定しているのですが、実際に解決された nix v0.26.4 には、なんと Haiku 向けの SpecialCharacterIndices が実装されていませんでした。

調べてみると、nix クレートに Haiku 向けの SpecialCharacterIndices が追加されたのは v0.28.0 からでした。つまり「nix のバージョンを固定し直す」だけでは解決せず、rustyline 側で要求バージョンを 0.28 以上に上げているリリースまで遡って探す必要がありました。

rustylinenix 要求バージョン状況
12.0.00.26エラー
13.0.00.27エラー
14.0.00.28ここから Haiku の SpecialCharacterIndices が使える!
18.0.1(最新)0.31.2最新版

まずは rustyline18.0.1 に引き上げ、main.rs で使っている API(DefaultEditor / ExternalPrinter / Configurer など)が壊れていないことを確認した上で再ビルドしてみました。


第2段階 – nix クレート自体が Haiku 向けに ioctl を提供していない

rustyline のバージョンを上げると SpecialCharacterIndices のエラーは無事に消えましたが、今度は別のエラーが残りました。

error[E0433]: failed to resolve: could not find `ioctl_read_bad` in `nix`
  --> rustyline-18.0.1/src/tty/unix.rs:42:6
   |
42 | nix::ioctl_read_bad!(win_size, libc::TIOCGWINSZ, libc::winsize);

今度はバージョンの問題ではありませんでした。nixsys/mod.rs を覗いてみると、ioctl モジュール自体が次のように対象 OS を絞ってコンパイルされるようになっていたのです。

#[cfg(any(
    bsd,
    linux_android,
    solarish,
    target_os = "fuchsia",
    target_os = "redox",
    target_os = "cygwin",
))]
#[cfg(feature = "ioctl")]
#[macro_use]
pub mod ioctl;

ご覧の通り、リストの中に target_os = "haiku" が入っていません!つまり、nix のバージョンをいくら変えても Haiku では ioctl モジュールごとコンパイルされない という、上流(ライブラリ側)の未対応が根本原因でした。

興味深いことに、同じ nix クレート内で ioctl 番号体系(BSD 方式)を実装しているサブモジュールでは、既に Haiku がサポート対象に入っていました。

#[cfg(any(bsd, solarish, target_os = "haiku",))]
mod bsd;

Haiku での実際の ioctl(TIOCGWINSZ) 呼び出しはこの番号体系で正しく扱えるはずなので、単純に ioctl モジュールを有効化する対象 OS リストへの追加漏れだと判断できます(実際、Python の fcntl 経由で flock() や ioctl 系の呼び出しが Haiku 上で普通に動くことも確認できました)。

【対応】nix を fork してパッチを当てる

本家(上流)にこの1行が入るのを待つのも大変なので、暫定対応として nix-rust/nix を fork することにしました。必要なバージョンのタグ(最終的には 0.30.1)からブランチを切り、対象 OS リストに target_os = "haiku" を追加します。

 #[cfg(any(
     bsd,
     linux_android,
     solarish,
     target_os = "fuchsia",
     target_os = "redox",
+    target_os = "haiku",
 ))]
 #[cfg(feature = "ioctl")]
 #[macro_use]
 pub mod ioctl;

あとは Cargo.toml[patch.crates-io] でこの fork 版を参照するように設定すればOKです。全ソースをリポジトリに同梱することなく、スマートに差し替えることができます。

[patch.crates-io]
nix = { git = "https://github.com/taoman26/nix.git", branch = "haiku-ioctl-support" }

これでようやく cargo install --path ./ が最後まで完走し、msxterm --help も無事に動くようになりました。


第3段階 – ビルドは通ったのに、切断時に謎のエラーが発生

「よし、ビルドできた!」と喜んでいたのも束の間。実際に Haiku 上で MSX0 実機に接続してターミナルとして使い、#quit で切断してみると、気になるメッセージが表示されました。

Tcp disconnectlock() not supported

TCP の切断処理自体は正常に行われていたので通信に実害はなさそうだったのですが、どうしても気になったので調べてみることにしました。

まず msxterm 自身のコードには lock() を呼んでいる箇所が見当たりません。エラーメッセージをよく見ると、printer.print("Tcp disconnect") の出力に続いて、改行なしで次のメッセージが連結されていることがわかりました。コードを追うと、#quit 実行後の流れは以下のようになっています。

if line.starts_with("#quit") {
    stream.shutdown(Shutdown::Both).expect("Shutdown Error");
    break 'input;
}
...
// 受信スレッド join 後
match rl.save_history(&args.file) {
    Ok(_) => println!("history save to {}", args.file),
    Err(e) => println!("{}", e.to_string()),
}

タイミング的に履歴保存の処理が怪しい!そこで save_history() のエラー表示を一時的に {:?} (Debug) に変えて再ビルドし、実機で試してみたところ……犯人が判明しました。

DEBUG_ERR: Io(Error { kind: Unsupported, message: "lock() not supported" })

rustyline の履歴保存処理 (history.rs) を確認すると、内部で file.lock()? を呼び出していました。これはサードパーティのクレートではなく、Rust 標準ライブラリが提供する std::fs::File::lock()(比較的新しく安定化された API)です。

fn save(&mut self, path: &Path) -> Result<()> {
    ...
    let file = f?;
    file.lock()?;              // ← ここで失敗していた!
    self.save_to(&file, false)?;
    ...
}

? でエラーが即座に伝播するため、ロックに失敗した時点で履歴の書き込み自体が一度も実行されていませんでした。つまり、セッションを終えても history.txt が一切更新されないという状態だったのです。TCP 通信自体は問題なく行えていたのでスルーしそうになりましたが、地味に困る実質的な機能不全でした。

原因は、Rust 標準ライブラリの File::lock() が Haiku ターゲット向けにはまだ実装されておらず、ErrorKind::Unsupported"lock() not supported" というメッセージを返す仕様になっていたことでした。Tier 3 ターゲットならではの制約ですね。Haiku 本体の flock() システムコール自体は正常に動作するため、OS 側ではなく「Rust 標準ライブラリ側の Haiku 向け実装が追いついていない」という状態でした。

【気づき】rustyline のバージョン選択がここでも影響

rustyline の履歴ロック実装の変遷を調べてみると、次のようになっていました。

rustyline バージョン履歴ファイルのロック方式
12.0.0 〜 17.0.2fd-lock クレート(内部で生の flock() システムコールを使用)
18.0.0 以降Rust 標準ライブラリの std::fs::File::lock()

なんと、第1段階で rustyline を最新の 18.0.1 に上げてしまったことが、この新たな退行(デグレ)を引き起こしていたのです!fd-lock クレートが使っている生のシステムコール呼び出しであれば、Haiku でも問題なく動作します。

【対応】rustyline を 17.0.2 にダウングレード

nix 0.28 以上を要求しつつ、まだ fd-lock を使ってくれている最後のマイナーバージョン系列が 17.x でした。そこで rustyline18.0.1 から 17.0.2 にダウングレードしました。

これに伴い要求される nix のバージョンも 0.31.2 から 0.30.1 に変わったため、fork のブランチも nix v0.30.1 タグから切り直して同じ1行パッチを当て直しました。

再ビルドして検証したところ、今度は #quit 実行時に、

Tcp disconnecthistory save to history.txt

と綺麗に表示され、history.txt にも実行したコマンドが無事に保存されるようになりました!


第4段階 – もうひとつの小さなバグ「シリアルポートのパス判定」

ビルドを通す過程で、msxterm 自体にも Haiku 未対応のコードが1箇所見つかりました。

src/connection.rs 内の is_varid_serial_port() という関数では OS ごとにシリアルポートのパスパターンを判定しているのですが、Windows / Linux / macOS にしか分岐しておらず、その他の OS では定数 SERIAL_PORT_REGEX 自体が存在せずコンパイルエラーになる構造になっていました。

fn is_varid_serial_port(path: &str) -> bool {
    #[cfg(target_os = "windows")]
    const SERIAL_PORT_REGEX: &str = r"^COM\d+$";

    #[cfg(target_os = "linux")]
    const SERIAL_PORT_REGEX: &str = r"^/dev/ttyS\d+$|^/dev/ttyUSB\d+$";

    #[cfg(target_os = "macos")]
    const SERIAL_PORT_REGEX: &str = r"^/dev/cu\.usbserial-\w+$|^/dev/tty\..+$";

    let serial_port_regex = Regex::new(SERIAL_PORT_REGEX).unwrap(); // Haiku では未定義エラー!
    ...
}

Haiku 実機で ls /dev/ports/ を確認したところ pc_serial0pc_serial1 といったデバイスノードが存在することが分かったので、Haiku 用の分岐を追加しました。

#[cfg(target_os = "haiku")]
const SERIAL_PORT_REGEX: &str = r"^/dev/ports/.+$";

遭遇した問題と対応一覧

今回ハマったポイントと対応をまとめると、以下のようになります。

#症状原因対応
1SpecialCharacterIndices が見つからずビルド不可rustyline 12.0.0 が要求する nix 0.26 に Haiku 対応が入っていないrustyline のバージョンを上げる(→別の問題が発覚)
2ioctl_read_bad が見つからずビルド不可nixsys::ioctl モジュール自体が Haiku 向けに有効化されていない(上流の未対応)nix を fork し、対象 OS に target_os = "haiku" を追加。[patch.crates-io] で参照
3msxterm 自体のコンパイルエラーシリアルポート判定が Windows / Linux / macOS のみにしか対応していないHaiku 用のパターン分岐を追加
4ビルドは通るが #quit 時に lock() not supported と出て履歴が保存されないrustyline 18.x が標準ライブラリの File::lock() を使うようになったが、Haiku の std では未実装rustyline を fd-lock を使う最後の系列 17.0.2 にダウングレード

今回は「Haiku は Rust エコシステムにおいて、まだ薄いサポートしか受けていない」という現実に起因する問題がいくつも重なっていました。

特に4番目の問題は、「ビルドが通ったこと」と「機能が正しく動くこと」は全く別物であるということを痛感させられたケースでした。実際に実機で一連の操作(接続 → コマンド送信 → 切断)を試してみないと気づけない罠ですね。


Haiku で msxterm をビルドする手順

以上、オリジナル msxterm からの修正過程を見てきましたが、上記の修正はすべて taoman26/msxterm にマージ済みですので、現在は特別な準備なしで以下のコマンドだけでビルドできます。

$ git clone https://github.com/taoman26/msxterm.git
$ cd msxterm
$ cargo install --path ./

内部的には Cargo.toml[patch.crates-io] 設定によって、nix クレートが自動的にパッチ適用済みの fork 版に差し替えられます。追加の手動作業は必要ありません。

インストールが終わったら

インストールしただけでは、フルパスを指定しないとコマンドを実行できません。システムがコマンドを認識できるようにパスを通しておきましょう。

  1. /boot/home/config/settings/profile を開きます。
  2. ファイルの末尾に、以下の2行を追加します。(ファイルがなければ新規作成してください)
export CARGO_HOME=$HOME/.cargo
export PATH=$CARGO_HOME/bin:$PATH

保存したら、設定を反映させるためにターミナルを一度再起動します。

あとは以下のように指定してMSX0に接続すれば、他の OS とまったく同じように使えます!

$ msxterm {MSX0_IP_ADDRESS}:2223

今後の対応予定

実は #save コマンドを検証している過程で、「ファイルが保存できない場合にアプリごと落ちてしまう」というバグを新しく発見してしまいました……!こちらについても対応予定ですので、次回の記事でご紹介できればと思います。

本日も最後までお読みいただきありがとうございました。

それでは、よい Haiku ライフを!


今回のような技術検証、「うちの環境ではどうなんだろう?」と気になった方はいませんか。

ビューローみかみでは、構想段階の壁打ちからPoC・実装・現場導入まで、現場で「使い続けられる」ものづくりを支援しています。 

技術顧問サービスでは、本記事のような技術的な質問・検証にも継続的にお答えしています。「相談したら契約」ということはありません。システムを作らない判断も含めて、率直にお話しします。

▶ 技術顧問サービスの詳細はこちら

気になることがあればお気軽にご相談ください。

カテゴリ: IoT

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール