Plan 9ネイティブのCライブラリはPOSIXに準拠しておらず、独自の習慣がある。例えばfopenやfcloseなどは使わずopenやcloseシステムコールを使う。バッファリングが必要であればBiobufを使いなさいという態度を取る。Goの標準パッケージに名残が残っているので、知っていれば雰囲気は伝わると思う。*1
Plan 9の環境変数は/env以下にファイルとして提供されていて、プロセス毎に異なる内容が扱える*2。これらの変数は配列もサポートしていて、配列の場合は\0
で区切られたバイト列として表現される。例えばhome='/usr/lufia'
の場合、/env/homeには"/usr/lufia"
と保存されていて、path=(. /bin)
('.'と'/bin'の配列)の場合/env/pathの内容は".\0/bin"
となる。
ANSI/POSIX環境
Plan 9にはネイティブ環境の他に、Unixツールを移植するためだけにANSI/POSIX環境(APE)も用意されている。このライブラリはネイティブとは分けて、ヘッダは/sys/include/ape/に、アーカイブは/386/lib/ape/や/amd64/lib/ape/などアーキテクチャ毎に置かれている。POSIXでは、getenvやputenvで設定した環境変数はenviron配列からも同じ内容が参照できなければならないという制約があり、environ配列は"home=/usr/lufia"
のような"name=value"
形式の文字列配列と定義されているため、ANSI/POSIX環境では/envを使わずメモリ上の配列を操作する方針をとる。だからgetenvはenviron配列を検索するだけの関数で、putenvはenviron配列を更新するだけの関数として用意されている。*3
他のプロセスへ環境変数を引き渡す時は、単純にexecveの引数にenviron配列を渡すだけで良い。execveは渡されたenviron配列を使って/envを再構築する。APEライブラリを使ったプログラムは、mainを実行する前に/envをenviron配列へ読み込む。こうするとPlan 9ネイティブなプログラムは/envを参照すればよいし、APEライブラリを使って書かれたプログラムでは引き続きenviron配列として参照できるようになる。これらの処理は以下のソースコードに書かれている。
ただし、Cの文字列は\0
を文字列の終わりと扱ってしまうため、ネイティブ環境で配列となっている環境変数をそのまま扱うと(1つ目の\0
で終わってしまって)2つ目以降の要素を扱うことができない。ネイティブ環境の場合は/env以下にファイルがあるため、stat等でファイルサイズを取れば長さが分かるけれども、environ配列はCの文字列になってしまうため、本来の長さを知る手段がない。なので_envsetupは\0
を0x1
に置き換えていて、execveは/envへ書き戻す前に0x1
を\0
へ戻す実装となっていた。
そういった理由から、Plan 9のANSI/POSIX環境で、getenvを使って得た文字列の途中に0x1
が見つかった場合、その文字列は配列として扱ってあげる必要がある。
バグ
余談だけどANSI/POSIX環境のgetenvはenviron配列の要素を指すポインタを返す。putenvはenviron配列を更新するが、getenvが返したポインタがまだ参照されている可能性があるため、以前の値を変更してはいけない。なので何度もputenvを呼び出ししていると、以前の値で使っていたメモリは消すことも再利用することもできずそのままリークする。setenvも最終的にはputenvと同じなので、同じようにリークする。
Plan 9ネイティブのgetenvはmallocした値を返すので、使い終わったらユーザ側で適切にfreeしなければならない。