彷徨えるフジワラ

年がら年中さまよってます

終了コードにまつわる話

事の発端は以下の tweet:

devel-ml に流れていた修正(or リポジトリRSS フィード)で、何かのコマンドの終了コードに関する修正が流れていたのが、記憶に残っていたので、早速履歴を確認。

90f8b8dd0326 において、push/pull の実際の終了コードが、想定値/文書化されたものと違っていたものが修正されているのを発見。しかし、これは 0/1 の変更だから「-1 と 255」みたいになる筈は無い。

ついでに cygwin 版で、連携先に不正な URL を指定して push してみると、どちらの版も 255 終了。

これ以上は追加情報が必要なので、状況を確認する@メンションを飛ばしつつ、POSIX の exit(2) 仕様を確認。

記憶違いで無ければ、POSIX 仕様の場合、子プロセスの exit(2) 終了時のステータス値のうち、親プロセスに渡るのは、下位8ビットだけだった筈なので、"-1" ってのはちょっと変だよなぁ、という印象が。

手元の UNIX 環境の man ページを確認したところ、OpenSolaris 上で「下位8ビットだけが通知される」旨を確認。
(この時点では)現象が確認できるのが、hg 1.9.3@WinXP と hg 2.1.1@Win7 だったので、Windows 版による挙動違いを疑いつつ、一応 Mercurial の sys.exit 絡みのコードを確認。

……見つけた! 終了コードを8ビットマスクする修正が 2.1 で取り込まれていた。

Windows 上の Python の終了コードは、上位ビット切捨てをしてないのね。

とりあえず、Mercurial コマンドの終了コードの件は、これで終わったのだが、その過程で気になる挙動を発見。

Windows ネイティブの Python で:

import sys
exitcode = -1 # or 0xffffffff
sys.exit(exitcode)

上記の処理を実行した場合、-1 の場合は %ERRORLEVEL% が -1 なのに、0xffffffff の場合は 1 になる。

Pythonsys.exit() 仕様を確認してみると:

  • 引数が整数値の場合は、その値が終了コードに使用される
  • 引数が None の場合は、0 相当として扱う
  • 引数がそれ以外の場合は、標準エラー出力に書き出した上で、終了コード 1 で終了

っつーことで、0xffffffff は「それ以外」という扱いっぽい。

# 0xffffffff の符号無し整数値に相当する 4294967295 が画面に表示される

『32ビット符号付整数の範囲に収まらない』のが「それ以外」扱いの理由なのかな?

32ビット以上を使う値を指定した場合でも、標準エラー出力に書き出される値は、相応な整数値なので:

  • 32ビットを超える場合は、Python 内部で自動的にビット幅を拡張(= 整数型から変換)
  • sys.exit() に指定された場合は、「整数型」でないので、終了コードは 1

という扱いらしいと想像。

"type()" で確認すると、確かに "type(0xffffffff)" は long だけど、"type(-1)" は int だわ。

この辺、C 言語感覚が抜けない、前世紀の遺物的なオッサンの頭だと、注意しないと嵌りそうだ ……