Hatena::ブログ(Diary)

miauの避難所

2010-09-29

改行コード LF で日本語を含むバッチファイルの動作がおかしい件

ブログ書いている場合ではないんですけど、Hudson を触ってて結構ハマったので、さらっと現象&推測を書いておきます。


きっかけ

Hudson の「Windowsバッチコマンドの実行」で、

:(略)
set TL_BACKUP_DIR=E:\TracLightBackup\%date:~0,4%%date:~5,2%%date:~8,2%-%time:~0,2%%time:~3,2%%time:~6,5%
@echo "古いバックアップファイルを削除します。"
cd /d E:\TracLightBackup
:(略)

みたいなジョブを登録していたんだけど、なぜか実行に失敗する。

コンソール出力を見ると、

C:\TracLight\projects\hudson\.hudson\jobs\TracBackup\workspace>E:\TracLightBackup\20100929-175047.75
'E:\TracLightBackup\20100929-175047.75' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

C:\TracLight\projects\hudson\.hudson\jobs\TracBackup\workspace>ップファイルを削除します。"
'ップファイルを削除します。"' は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。

C:\TracLight\projects\hudson\.hudson\jobs\TracBackup\workspace>cd /d E:\TracLightBackup

のように、日本語が出てくる行まで、コマンドの先頭 18 bytes 程度が削られて実行されているみたい。

原因調査

コンソール出力の先頭に

[workspace] $ cmd /c call C:\Windows\TEMP\hudson7156645907269692053.bat

と書いてあるので、内部的にはテンポラリファイルを作成してそれを実行しているらしい。Hudson でこの .bat を出力するときの処理が何かおかしいとか?

とりあえずジョブの先頭に

type %0

と追加して、出力を見てみる。特に文字が欠けているということもないし、文字コードが間違っているというわけでもないらしい。

実際の .bat ファイルを見てみたいけど、ジョブ実行後にすぐに消されてしまうようなので、

copy %0 %0.bak

としてバックアップを取得して内容を確認してみた。どうも指定したコマンドが改行コード LF で出力されていて、それと日本語の絡みで問題が発生しているらしい。(改行コードが LF でも、日本語が含まれない場合は問題なく動作した。)

現象の確認

実験 1

テスト用にこんなバッチファイル(改行コードはすべて LF)を用意して、実行されたコマンドを確認してみる。

f:id:miau:20100929223839p:image

左側がバッチファイルで、右側が実行されたコマンドをまとめたもの。

この結果から、

  • 先頭行は期待通りに実行される
  • 2 行目以降は「それ以降に出現する 2 バイト文字の文字数」ぶんコマンドの先頭が削られて実行される

らしいことがわかる。

実験 2

次に、ところどころ CRLF を入れて、結果を確認してみる。

(※ここ で書いた自前ビルドサクラエディタを使っているので、LF = ピンク、CRLF = 青 で表示されています。)

f:id:miau:20100929223838p:image

この結果から、実験 1 の結果が CRLF で区切られた単位で適用されていることがわかる。具体的には、

  • 1〜2 行目のブロックは日本語を含まないので期待通りに実行される
  • 3〜5 行目のブロックは、先頭行(3 行目)は期待通りに実行されているし、4 行目はこのブロックに含まれる「あ」1 文字ぶんだけ先頭が削られている
  • 6〜12 行目のブロックは、ブロック内で「それ以降に出現する 2 バイト文字の文字数」ぶんコマンドの先頭が削られて実行されている

という具合。

内部動作の推測

現象に辻褄が合う内部動作を考えると、だいたいこんな感じ?

  • .bat は基本的に CRLF をコマンドの区切りとみなしているけど、CRLF での分割後にそれぞれのブロック内で「先頭から LF に出会うまでを 1 つのコマンドとして実行する」処理を繰り返している
  • LF に出会ったタイミングで次の行の先頭までポインタを移動しようとしているけど、この処理に問題があって「ブロック末尾のアドレス - (ブロック全体の文字数 + 実行したコマンドの文字数の合計)」に移動してしまっている。(本当は文字数でなくバイト数で処理されるべき)

今後の方針

  • とりあえず Hudson の「Windowsバッチコマンドの実行」でコマンドを実行してやるときは、2 バイト文字を書かないようにする形で回避する
  • 余裕があれば、Hudson のほうで LF じゃなくて CRLF で .bat を吐くように依頼する
  • もっと余裕があれば、コマンドプロンプトの日本語まわりの不具合も Microsoft に報告する(期待薄)

ということで。

もしユーザ入力値を引数にしてコマンド実行するプログラムがあったら、LF と日本語を混ぜてやることで任意のプログラムを実行させることもできたりするかも。脆弱性って扱いなら Microsoft でもちゃんと対応してくれるかなぁ・・・とか思ったりも。


追記:どうしても日本語を使いたい場合は

せっかく現象を調べたので、この動作を利用した回避策についても書いておきます。

先頭が削られる各コマンドの前に、削られる文字数以上の空白 or 改行を入れておけばいいですね。(べつに挿入するのは空白や改行以外でもいいんですが、Hudson 側で対応されたときのことも考えておくと、意味を持たない文字を入れておくのが無難かと)

echo 1
   echo 2
   echo 3
   echo "あ"
  echo 1
  echo 2
  echo 3
  echo "い"
 echo 1
 echo 2
 echo 3
 echo "う"
echo 1
echo 2
echo 3

この形であれば、正常に動作します。

kkawakkawa 2010/09/30 13:19 これはずばりHudsonがCR/LFに正規化して出力するようにするのが正解なのではないかと思うのですが。

miaumiau 2010/09/30 14:38 はい。バッチファイルの動作仕様がわからない(どこかで「LF でも動作する」とか規定されているかもしれない)ので Hudson に非があるとは言いきれませんが、Hudson 側でそのようにしていただけると助かります。

torutktorutk 2014/09/06 11:07 昨日この問題に陥りました。なかなか原因が分からずslaveをやめてみたり、javaのfile.encodingプロパティを変えてみたりと四苦八苦してました。この日記を見つけてやっと原因が分かりました。
Jenkinsのバグにないのか探してみたら次のチケットで2010年に登録されていました(現時点でUnreslved)。
https://issues.jenkins-ci.org/browse/JENKINS-7478
2014年8月に活動がありPull Requestが出ています。これは近々に解決しそうな雰囲気です。

copy %0 %0.bak はナイスなテクニックです。これも収穫です。

miaumiau 2014/09/07 01:16 お役に立ったようで何よりです。

トラックバックも拝見しましたが、
>バッチファイルに記述してJenkinsからはバッチファイルを呼び出す
この対策のほうが、「削られる文字数以上の空白を入れておく」なんて変な対策よりもいいですね・・・。もしチケットがクローズされないままで日本語が使いたくなったら、その方式にしようと思います。ありがとうございました。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

リンク元