やりたかったこと
Unix環境を想定したライブラリをiOSプログラミングで利用したい場合は静的ライブラリとして取り込むのが普通です。しかし、ライブラリ形式になっていないツールを輸入するのにわざわざライブラリ化するのは手間ですよね。
そこで、*.ipa
ファイルの中にARM用の実行バイナリを入れて、iOSアプリ内からposix_spawn(2)
で起動できるのかを確認してみました。アプリと同梱であればAppleのレギュレーション(アプリ外にある実行可能コードの実行禁止)に違反しないのでは?という期待もありました。
結論
jailbreakしていないiOS実機に実行バイナリを持ち込んで起動することはできない。fork(2)
やposix_spawn(2)
など別プロセスを起動するようなシステムコールはiOSサンドボックス内で利用できないと考えられる。
テスト環境
Xcode 7.3.1 + iOS 9.3.2 (iPhone6s)
試したこと
輸入対象としてbannerコマンド(配布元:Cedar Solutions - Utilities)を使いました。標準出力にバナーを出すコマンドです。
$ banner foo
####### ####### #######
# # # # #
# # # # #
##### # # # #
# # # # #
# # # # #
# ####### #######
まずMach-Oの実行バイナリを作ります。「Autotools管理のライブラリをiOS向けにスマートにビルドする方法」で紹介したように、./configure; make
すればiOS/ARM用の実行バイナリを作ることができます。
今回はARM用だけでなくiOSシミュレータ用にx86用も作ってlipo
で合体させました。
$ file banner
banner: Mach-O universal binary with 5 architectures
banner (for architecture i386): Mach-O executable i386
banner (for architecture x86_64): Mach-O 64-bit executable x86_64
banner (for architecture armv7): Mach-O executable arm
banner (for architecture armv7s): Mach-O executable arm
banner (for architecture arm64): Mach-O 64-bit executable
これをXcodeから「Add Files to "プロジェクト名"...」でプロジェクトに取り込みます。取り込んだファイルへのパスは次のように取得できます。
let path = NSBundle.mainBundle().pathForResource("banner",ofType:"")
これをposix_spawn(2)
で起動してみましょう。下記コード中のCStringArray
は記事「SwiftでCのchar **をうまく扱う」で紹介したものです。
let argv = CStringArray([path, "foo", nil])
let status = posix_spawnp(nil, argv.pointers[0], nil, nil, argv.pointers, nil)
if (status != 0) {
print("posix_spawn: " + String.fromCString(strerror(status))! + " (status=\(Int(status)))")
}
試してみると、iOSシミュレータ上とiOS実機とで異なるエラーが帰ってきました。
iOSシミュレータでは次のエラーが帰ってきます。
posix_spawn: Permission denied (status=13)
これは対象のパスに実行可能ビットが立っていないことによるエラーです。実行バイナリをキャッシュディレクトリ以下にコピーすればchmod(2)
で実行可能ビットが立てられるので、無事バイナリを起動できるようになります。
一方、iOS実機では下記エラーが帰ってきます。
posix_spawn: Operation not permitted (status=1)
このエラーは手元のposix_spawn(2)
のマニュアルに載っていませんので、別のレイヤでのエラーだと予想できます。おそらくiOSのサンドボックス環境でかかっている制約でしょうから、ちょっとやそっとの工夫じゃ動きそうにないですね。
おわりに
というわけで、UnixツールをiOSに移植したい場合は静的ライブラリ化するしかない、という当たり前の結論になりました。実験後に検索してみたところ、fork(2)
がiOSではOperation not permitted
を返すという話題が見つかりました。自分の検索力が低かったということで残念です。