<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>仙石浩明の日記 &#187; プログラミングと開発環境</title>
	<atom:link href="http://www.gcd.org/blog/category/programming/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.gcd.org/blog</link>
	<description>CTO兼プログラマ兼システム管理者の視点から</description>
	<lastBuildDate>Mon, 26 Jul 2010 14:25:33 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Nexus One で Android 2.2 froyo のマルチタッチを試してみる</title>
		<link>http://www.gcd.org/blog/2010/07/613/</link>
		<comments>http://www.gcd.org/blog/2010/07/613/#comments</comments>
		<pubDate>Sun, 25 Jul 2010 23:57:55 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=613</guid>
		<description><![CDATA[Android は 2.1-update1 以降でマルチタッチ (Multi-touch) をサポートしている。 ところがマルチタッチといっても、 ピンチイン/ピンチアウトなどのジェスチャをサポートしているだけのアプリが大半で、 複数のタッチを独立に扱えるアプリはいまだほとんどなく、 iPhone と比べるとその差が際立っている。 どうして Android にはマルチタッチを活用したアプリケーションが無いのだろう？ と思ったので、 マルチタッチを試すテストアプリ MultiTouch.java (apk) を書いてみた: タッチした位置にタッチの強さに応じた大きさの円を表示するだけの単純なアプリ。 指を移動すれば円も追随する。 Android ではタッチID が順に割り振られるので、 ID が 0 のタッチを赤色の円で、 ID が 1 のタッチを緑色の円で描いている。 プログラム上は ID が 2 のタッチを青色の円で描くことになっているが、 残念ながら現行の Android で同時に扱えるタッチは 2箇所のみ (追記: Samsung Galaxy S は 5箇所のマルチタッチが可能らしい) なので、 3箇所にタッチしても三つ目の円が描かれることはない。 だから例えば iPhone のアプリにあるような鍵盤楽器アプリを作ろうと思っても、 三つ以上の音を同時に鳴らすことはできない。 とはいえ、 2箇所のタッチを独立に扱えれば、 [...]]]></description>
			<content:encoded><![CDATA[<p>
Android は 2.1-update1 以降でマルチタッチ (Multi-touch) をサポートしている。
ところがマルチタッチといっても、
ピンチイン/ピンチアウトなどのジェスチャをサポートしているだけのアプリが大半で、
複数のタッチを独立に扱えるアプリはいまだほとんどなく、
iPhone と比べるとその差が際立っている。
</p>
<p>
どうして Android にはマルチタッチを活用したアプリケーションが無いのだろう？
と思ったので、
マルチタッチを試すテストアプリ
<a href="http://www.gcd.org/sengoku/android/MultiTouch.java">MultiTouch.java</a>
(<a href="http://www.gcd.org/sengoku/android/MultiTouch-debug.apk">apk</a>)
を書いてみた:
</p>
<img src="http://www.gcd.org/sengoku/android/MultiTouch.jpg" alt="MultiTouch" width="350" height="225" />
<p>
タッチした位置にタッチの強さに応じた大きさの円を表示するだけの単純なアプリ。
指を移動すれば円も追随する。
Android ではタッチID が順に割り振られるので、
ID が 0 のタッチを赤色の円で、
ID が 1 のタッチを緑色の円で描いている。
</p>
<p>
プログラム上は ID が 2 のタッチを青色の円で描くことになっているが、
残念ながら現行の Android で同時に扱えるタッチは 2箇所のみ
(追記: <a href="http://www.youtube.com/watch?v=KRCDRXYJBCY">Samsung Galaxy S は
5箇所のマルチタッチが可能</a>らしい)
なので、
3箇所にタッチしても三つ目の円が描かれることはない。
だから例えば iPhone のアプリにあるような鍵盤楽器アプリを作ろうと思っても、
三つ以上の音を同時に鳴らすことはできない。
</p>
<p>
とはいえ、
2箇所のタッチを独立に扱えれば、
いろいろ応用が効くだろうにと思いつつ、
このテストアプリをいじっていると...
</p>
<span id="more-613"></span>
<p>
ありゃ?
</p>
<p>
上の写真の状態から、
左指を上方へ、右指を下方へ動かしただけなのだが、
両方の指の X 座標 (画面では上下方向) が交わった時点で両方の円の 
X 座標が入れ替わってしまって、
緑円は右指の動きに合わせて下へ動き、
赤円は左指の動きに合わせて上に動いた。
その結果、
指の位置と円の位置がずれてしまった:
</p>
<img src="http://www.gcd.org/sengoku/android/MultiTouch2.jpg" alt="MultiTouch Error" width="350" height="225" />
<p>
つまり複数のタッチ 
(x<sub>i</sub>, y<sub>i</sub>),
(x<sub>j</sub>, y<sub>j</sub>)
を個別に扱えるのではなく、
X 座標の集合 {x<sub>i</sub>, x<sub>j</sub>} と
Y 座標の集合 {y<sub>i</sub>, y<sub>j</sub>} として検知している模様。
例えて言うならダイオード無しのスイッチ・マトリックスみたいな感じ。
だから (x<sub>i</sub>, y<sub>i</sub>), (x<sub>j</sub>, y<sub>j</sub>) の
2点をタッチしているのか、
(x<sub>i</sub>, y<sub>j</sub>), (x<sub>j</sub>, y<sub>i</sub>) の
2点をタッチしているのか、
の区別ができない。
</p>
<p>
Android のマルチタッチを扱うプログラムの書き方について<a href="http://www.zdnet.com/blog/burnette/how-to-use-multi-touch-in-android-2-part-3-understanding-touch-events/1775">解説している Web ページ</a>をほとんど見かけないので、
<a href="http://www.gcd.org/sengoku/android/MultiTouch.java">MultiTouch.java</a>
についても一応解説:
</p>
<p>
タッチイベントは、
<a href="http://developer.android.com/reference/android/view/MotionEvent.html">MotionEvent</a> 
インスタンスとして Android OS からアプリへ伝えられる。
MotionEvent#getAction() メソッドでイベント種別 
(ACTION_DOWN ならタッチ開始、
ACTION_MOVE ならドラッグ、
ACTION_UP ならタッチ終了といった具合) が取得できる。
</p>
<p>
マルチタッチの場合、
すなわちすでにタッチしている状況において追加で別の場所にタッチした場合、
ACTION_DOWN の代りに ACTION_POINTER_DOWN イベントが伝えられる。
パネルから指が離れてタッチが終わる場合、
まだ他の場所でタッチが継続しているのなら、
ACTION_UP の代りに ACTION_POINTER_UP イベントが伝えられる。
</p>
<p>
今回のテストプログラムの場合、
タッチの座標が分かればよいので、
ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_MOVE は区別せずに、
put_points メソッド (後述) でイベントの座標を記録している。
</p>
<pre class="code">
	public boolean onTouchEvent(MotionEvent ev) {
	    int action = ev.getAction();
	    switch (action &amp; MotionEvent.ACTION_MASK) {
	    case MotionEvent.ACTION_DOWN:
	    case MotionEvent.ACTION_POINTER_DOWN:
	    case MotionEvent.ACTION_MOVE:
		put_points(ev);
		break;
	    case MotionEvent.ACTION_UP:
		points.remove(ev.getPointerId(0));
		break;
	    case MotionEvent.ACTION_POINTER_UP:
		put_points(ev);
		int index = (action &amp; MotionEvent.ACTION_POINTER_INDEX_MASK)
		    >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
		points.remove(ev.getPointerId(index));
		break;
	    }
	    Canvas canvas = getHolder().lockCanvas();
	    if (canvas != null) {
		onDraw(canvas);
		getHolder().unlockCanvasAndPost(canvas);
	    }
	    return true;
	}
</pre>
<p>
MotionEvent インスタンスには
複数 (現状 2個までだが、
API 仕様的には 256個まで可能) のタッチの座標および強さ (pressure) 
が格納できて、
getX(i), getY(i), getPressure(i) の各メソッドを呼び出すことにより、
i番目 (i=0, 1,...) のタッチの X座標、Y座標、強さがそれぞれ取得できる。
タッチの個数は getPointerCount() メソッドで取得できる。
マルチタッチをサポートしない Android 2.0 eclair だと
getPointerCount() は常に 1 を返す
(ちなみに 1.5 cupcake や 1.6 donut だと getPointerCount() は存在しない)。
</p>
<p>
各タッチは、
指がパネルに触れてから離れるまで固有の ID が OS によって割り振られるので、
アプリは各タッチの動きをそれぞれ追うことができる。
getPointerId(i) メソッドを使えば i番目のタッチの ID が取得できる。
</p>
<p>
put_points(MotionEvent ev) メソッドでは、
id をキーとし、
TouchPoint (X座標、Y座標、強さの組) を値とするハッシュテーブル points に、
各タッチの現在値を登録している:
</p>
<pre class="code">
    class TouchPoint {
	public float x;
	public float y;
	public float p;
    }
    Hashtable&lt;Integer, TouchPoint> points;
	...
	points = new Hashtable&lt;Integer, TouchPoint>();
	...

	void put_points(MotionEvent ev) {
	    int count = ev.getPointerCount();
	    for (int i=0; i < count; i++) {
		int id = ev.getPointerId(i);
		TouchPoint p = new TouchPoint();
		p.x = ev.getX(i);
		p.y = ev.getY(i);
		p.p = ev.getPressure(i);
		points.put(id, p);
	    }
	}
</pre>
<p>
現状 ID は 0 または 1 の値しかとらないようなので、
ハッシュテーブル (java.util.Hashtable) は牛刀な感を否めないが、
API 仕様上 ID は int 型としか規定していない
(つまり最大値が 1ではなく int 型の上限となる可能性がある ^^;)
ので、
配列ではなくハッシュテーブルを使った次第。
</p>
<p>
あとは
SurfaceView#onDraw(Canvas canvas)
にて、
points ハッシュテーブルに登録された座標、強さのデータ通りに円を描くだけ。
</p>

<p>
追記:<br />
twitter で <a href="http://galaxys.samsungmobile.com/">Galaxy S</a> のマルチタッチはマトモと教えていただきました (_O_)<br />
以下、頂いたツイートを (時間順に) 引用:
</p>
<table class="tweet">
<tr><td><a href="http://twitter.com/gcd_org"><img alt="仙石浩明" height="48" src="http://a3.twimg.com/profile_images/488643843/sengoku_normal.jpg" width="48" /></a></td>
<td><strong><a href="http://twitter.com/gcd_org">gcd_org</a></strong>
Androidのマルチタッチについて書いてるページが見当たらなかったのでブログを書いてみました <a href="http://www.gcd.org/blog/2010/07/613/">http://www.gcd.org/blog/2010/07/613/</a> NexusOneだと座標を正しく取得できないケースがあるのですが他の機種だとどうですか？ <a href="http://twitter.com/search?q=%23androidjp">#androidjp</a><br />
<a class="entry-date" href="http://twitter.com/gcd_org/status/19536531076">2010年7月26日 10:41:34</a>
</td></tr>
<tr><td><a href="http://twitter.com/northeye"><img alt="Northeye" height="48" src="http://a1.twimg.com/profile_images/875256693/doya_normal.jpg" width="48" /></a></td>
<td><strong><a href="http://twitter.com/northeye">northeye</a></strong>
@<a href="http://twitter.com/gcd_org">gcd_org</a> Galaxy Sはわりとまとものようです <a href="http://www.youtube.com/watch?v=hVlsRCMltDg">http://www.youtube.com/watch?v=hVlsRCMltDg</a><br />
<a class="entry-date" href="http://twitter.com/northeye/status/19536763704">2010年7月26日 10:45:20</a>
</td></tr>
<tr><td><a href="http://twitter.com/gcd_org"><img alt="仙石浩明" height="48" src="http://a3.twimg.com/profile_images/488643843/sengoku_normal.jpg" width="48" /></a></td>
<td><strong><a href="http://twitter.com/gcd_org">gcd_org</a></strong>
@<a href="http://twitter.com/northeye">northeye</a> すごい＞Galaxy Sのマルチタッチ。これで3点以上のマルチタッチがサポートされたら言うことなしですね。 RT Galaxy Sはわりとまとものようです <a href="http://www.youtube.com/watch?v=hVlsRCMltDg">http://www.youtube.com/watch?v=hVlsRCMltDg</a><br />
<a class="entry-date" href="http://twitter.com/gcd_org/status/19537320366">2010年7月26日 10:54:20</a>
</td></tr>
<tr><td><a href="http://twitter.com/northeye"><img alt="Northeye" height="48" src="http://a1.twimg.com/profile_images/875256693/doya_normal.jpg" width="48" /></a></td>
<td><strong><a href="http://twitter.com/northeye">northeye</a></strong>
@<a href="http://twitter.com/gcd_org">gcd_org</a> 5点までいけるそうです <a href="http://www.youtube.com/watch?v=KRCDRXYJBCY">http://www.youtube.com/watch?v=KRCDRXYJBCY</a><br />
<a class="entry-date" href="http://twitter.com/northeye/status/19537433283">2010年7月26日 10:56:10</a>
</td></tr>
<tr><td><a href="http://twitter.com/nakamichito"><img alt="なかみちと" src="http://a1.twimg.com/profile_images/250689694/IAKlLwG2IZ__Clb1NZqDgxLvRu_uf8x4MrSp6N4ey1-AzfpyZ2d9efuh0R8ra-nj_normal.jpg" width="48" height="48" /></a></td>
<td><strong><a href="http://twitter.com/nakamichito">nakamichito</a></strong>
Galaxy Sではこんな感じです RT @<a href="http://twitter.com/gcd_org">gcd_org</a>: Androidのマルチタッチについて書いてるページが見当たらなかったのでブログを書いてみました <a href="http://www.gcd.org/blog/2010/07/613/">http://www.gcd.org/blog/2010/07/613/</a> <a href="http://twitter.com/search?q=%23androidjp">#androidjp</a><br />
<a class="entry-date" href="http://twitter.com/nakamichito/status/19538572777">2010年7月26日 11:13:40</a>
</td></tr>
<tr><td><a href="http://twitter.com/nakamichito"><img alt="なかみちと" src="http://a1.twimg.com/profile_images/250689694/IAKlLwG2IZ__Clb1NZqDgxLvRu_uf8x4MrSp6N4ey1-AzfpyZ2d9efuh0R8ra-nj_normal.jpg" width="48" height="48" /></a></td>
<td><strong><a href="http://twitter.com/nakamichito">nakamichito</a></strong>
URL忘れてた！ <a href="http://bit.ly/bFBicn">http://bit.ly/bFBicn</a> RT @<a href="http://twitter.com/nakamichito">nakamichito</a>: Galaxy Sではこんな感じです RT @<a href="http://twitter.com/gcd_org">gcd_org</a>: Androidのマルチタッチについて書いてるページが見当たらなかったのでブログを書いてみました <a href="http://twitter.com/search?q=%23androidjp">#androidjp</a><br />
<a class="entry-date" href="http://twitter.com/nakamichito/status/19543213654">2010年7月26日 12:25:39</a>
</td></tr>
</table>
<p>
すでに同種のテストアプリが Android Market で公開されていたのですね。<br />
車輪の再発明をしてしまった (後悔はしていない ;-)。
</p>]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2010/07/613/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Nexus One の近接センサ/環境光センサは、どこにあるのか？調べてみた</title>
		<link>http://www.gcd.org/blog/2010/07/609/</link>
		<comments>http://www.gcd.org/blog/2010/07/609/#comments</comments>
		<pubDate>Fri, 16 Jul 2010 23:55:21 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=609</guid>
		<description><![CDATA[Nexus One など最近のスマートフォンには、 加速度 (Accelerometer)、 環境光 (照度, Ambient Light)、 磁場 (磁界, Magnetic Field)、 方位 (電子コンパス, Orientation)、 近接 (Proximity) など、 様々なセンサがついている。 いろいろ応用できそうで夢がふくらむが、 携帯電話本来の使い方 (つまり通話すること) において、 使い勝手に直接影響する重要なセンサが近接センサ。 Nexus One や iPhone など全面タッチパネルの携帯電話だと、 (受話器として使うために) 耳に近づけたときタッチパネルが反応しては困る。 そこで近接センサを使って顔が接近してくることを感知し、 タッチパネルを無効にする (ついでにディスプレイをオフにして消費電力を抑える)。 私は Proximity なんて聞くと、 Proximity Warning System (接近警報システム) を思い浮かべてしまうくらいで、 携帯電話用の近接センサがどういうしくみか全く知らなかった。 今年1月の Nexus One の発表の時に近接センサのことを初めて知り、 その時はタッチパネル全体への接近を感知する (静電容量の変化を検知して?) のかと想像したが、 後述するように Nexus One の近接センサはタッチパネルの左上にしかなく、 [...]]]></description>
			<content:encoded><![CDATA[<p>
Nexus One など最近のスマートフォンには、
加速度 (Accelerometer)、
環境光 (照度, Ambient Light)、
磁場 (磁界, Magnetic Field)、
方位 (電子コンパス, Orientation)、
近接 (Proximity) など、
様々なセンサがついている。
いろいろ応用できそうで夢がふくらむが、
携帯電話本来の使い方 (つまり通話すること) において、
使い勝手に直接影響する重要なセンサが近接センサ。
</p>
<p>
Nexus One や iPhone など全面タッチパネルの携帯電話だと、
(受話器として使うために) 耳に近づけたときタッチパネルが反応しては困る。
そこで近接センサを使って顔が接近してくることを感知し、
タッチパネルを無効にする 
(ついでにディスプレイをオフにして消費電力を抑える)。
</p>
<blockquote>
私は Proximity なんて聞くと、
Proximity Warning System (接近警報システム) を思い浮かべてしまうくらいで、
携帯電話用の近接センサがどういうしくみか全く知らなかった。
今年1月の Nexus One の発表の時に近接センサのことを初めて知り、
その時はタッチパネル全体への接近を感知する (静電容量の変化を検知して?) 
のかと想像したが、
後述するように Nexus One の近接センサはタッチパネルの左上にしかなく、
タッチパネルの下方への接近は感知できないことが分かった。
</blockquote>
<p>
Nexus One のどこに近接センサが搭載されているか、
センサの感応範囲がどれくらいなのか、
私には見当もつかなかったし、
google で検索してもその手の情報は見つからなかったので、
近接センサが感知した値を表示するだけの<a href="http://www.gcd.org/sengoku/android/Proximity.java">簡単なプログラム</a>を書いてみた。
</p>
<p>
実は、
私にとって初めての android アプリ (^^;)。
しかも、
ここ数年 Java から遠ざかっていたので、
久々に書く Java プログラムだったりする。
</p>
<p>
お膳立ては Android SDK が全てやってくれるので、
わずか 74行のプログラム。
まず
SensorManager#getSensorList メソッドで、
PROXIMITY タイプのセンサを取得し (sensor)、
</p>
<pre class="code">
	sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
	List&lt;Sensor> sensors
	    = sensorManager.getSensorList(Sensor.TYPE_PROXIMITY);
	Sensor sensor = sensors.get(0);
</pre>
<p>
この sensor の値が変化したときなどにセンサの値を受け取るリスナ
(SensorEventListener) を、
SensorManager#registerListener メソッドで登録するだけ。
</p>
<pre class="code">
public class Proximity implements SensorEventListener {
	...
	sensorManager.registerListener(this, sensor,
				       SensorManager.SENSOR_DELAY_NORMAL);
	...
    @Override
    public void onSensorChanged(SensorEvent event) {
	view.update(event.values);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
    }
}
</pre>
<p>
近接センサの値が変化するとリスナの onSensorChanged メソッドが呼び出されるので、
新しいセンサ値を描画する (view.update):
</p>
<pre class="code">
	void update(float[] values) {
	    Canvas canvas = getHolder().lockCanvas();
	    if (canvas == null) return;
	    canvas.drawColor(Color.WHITE);
	    Paint paint = new Paint();
	    paint.setColor(Color.BLACK);
	    paint.setTextSize(40);
	    float height = paint.getTextSize();
	    for (int i = 0; i &lt; values.length; i++) {
		canvas.drawText(" "+values[i], 0, height * (i + 1), paint);
	    }
	    getHolder().unlockCanvasAndPost(canvas);
	}
</pre>
<p>
Nexus One のタッチパネル左上隅近く (黒枠部分) にセンサがあるらしく、
パネルまで 2cm ほどの距離に物体を近づけると反応する 
(センサの値が 9.0 から 0.0 へ変化する)。
また、
パネルと並行に物体を動かす場合、
センサの真上から 1cm ほど外れると反応しなくなる。
</p>
<table>
<tr>
<td>
<img src="http://www.gcd.org/sengoku/android/ProximitySensor.jpg" alt="Proximity Sensor" width="210" height="210" />
</td>
<td>　←　</td>
<td>
鈴を近づけたことにより、<br />
近接センサが反応して、<br />
値が 0.0 になっている<br />
</td>
</tr>
</table>
<p>
赤外線型の近接センサ 
(赤外線を照射し、近接する物体からの反射光を測定するセンサ) 
なので、
凸面の物体など赤外線があさっての方向へ反射してしまって受光素子に正しく届かない場合や、
あるいは黒色の物体などあまり反射しない場合などでは、
より近づけないと反応しない。
</p>
<p>
例えば、
黒く細い丸棒などだと 1cm 以下に近づけないと反応しない。
逆に白い紙 (凹面〜平面) など、
効果的に赤外線を反射し、かつ受光素子に反射光が効率的に届くケースだと、
2cm より遠くても (8cm くらいでも) 反応する。
</p>
<p>
ちなみに、
Nexus One に搭載されている近接センサは、
<a href="http://www.capellamicro.com.tw/">Capella Microsystems</a> の 
<a href="http://www.capellamicro.com.tw/EN/products_view.php?id=45">CM3602</a>
という、
環境光センサ付短距離近接センサ 
(Short Distance Proximity Sensor with Ambient Light Sensor) であるようだ。
名前の通り環境光も測定できる。
おそらく近接センサの受光素子をそのまま使って照度を測定しているのだろう。
</p>
<p>
<a href="http://www.gcd.org/sengoku/android/Proximity.java">前述したプログラム</a>において、
「Sensor.TYPE_PROXIMITY」 を
「Sensor.TYPE_LIGHT」 に置き換えれば、
環境光センサの値を読み取ることができる。
</p>
<span id="more-609"></span>
<p>
Android SDK において、
Eclipse を使わずにアプリを開発する方法を<a href="http://dsas.blog.klab.org/archives/51165740.html">説明しているページ</a>が少ないので、
蛇足ながらビルド〜インストール方法を書いてみる。
まず android コマンド (Android SDK の tools ディレクトリにある)
を使って Project ディレクトリを作る:
</p>
<pre class="terminal">
T:\src\android>android create project --target 1 --path Proximity --activity Proximity --package org.gcd.test
Created project directory: T:\src\android\Proximity
Created directory T:\src\android\Proximity\src\org\gcd\test
Added file T:\src\android\Proximity\src\org\gcd\test\Proximity.java
Created directory T:\src\android\Proximity\res
Created directory T:\src\android\Proximity\bin
Created directory T:\src\android\Proximity\libs
Created directory T:\src\android\Proximity\res\values
Added file T:\src\android\Proximity\res\values\strings.xml
Created directory T:\src\android\Proximity\res\layout
Added file T:\src\android\Proximity\res\layout\main.xml
Created directory T:\src\android\Proximity\res\drawable-hdpi
Created directory T:\src\android\Proximity\res\drawable-mdpi
Created directory T:\src\android\Proximity\res\drawable-ldpi
Added file T:\src\android\Proximity\AndroidManifest.xml
Added file T:\src\android\Proximity\build.xml

T:\src\android>
</pre>
<p>
「--target &lt;target_ID>」 オプションは、
利用する Android platform library を指定する。
&lt;target_ID> の一覧は、
「android list targets」 を実行することで得られる。
</p>
<p>
「--activity &lt;activity_name>」 オプションは、
アプリを起動したとき最初に表示される画面のクラス名を指定する。
</p>
<p>
作った Project ディレクトリ T:\src\android\Proximity の中に、
T:\src\android\Proximity\src\org\gcd\test\Proximity.java があるので、
これを<a href="http://www.gcd.org/sengoku/android/Proximity.java">書き換える</a>。
私の場合、
T: ドライブは Linux マシン (正確に言えば coLinux) のファイルシステムなので、
Linux 上の emacs を使って書き換えている。
</p>
<p>
あとは ant コマンドを使ってビルドするだけ。
Eclipse などの IDE (統合開発環境) を使う必要性を全く感じないのだが...
</p>
<pre class="terminal">
T:\src\android>cd Proximity

T:\src\android\Proximity>ant debug
Buildfile: T:\src\android\Proximity\build.xml
    [setup] Android SDK Tools Revision 6
    [setup] Project Target: Google APIs
    [setup] Vendor: Google Inc.
    [setup] Platform Version: 2.1-update1
    [setup] API level: 7
    [setup] WARNING: No minSdkVersion value set. Application will install on all Android versions.
    [setup] Importing rules file: platforms\android-7\ant\ant_rules_r2.xml

-compile-tested-if-test:

-dirs:
     [echo] Creating output directories if needed...
    [mkdir] Created dir: T:\src\android\Proximity\gen
    [mkdir] Created dir: T:\src\android\Proximity\bin\classes

-resource-src:
     [echo] Generating R.java / Manifest.java from the resources...

-aidl:
     [echo] Compiling aidl files into Java classes...

compile:
    [javac] C:\android-sdk-windows\platforms\android-7\ant\ant_rules_r2.xml:255: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
    [javac] Warning: org\gcd\test\R.java modified in the future.
    [javac] Compiling 2 source files to T:\src\android\Proximity\bin\classes

-dex:
     [echo] Converting compiled files and external libraries into T:\src\android\Proximity\bin\classes.dex...

-package-resources:
     [echo] Packaging resources
 [aaptexec] Creating full resource package...

-package-debug-sign:
[apkbuilder] Creating Proximity-debug-unaligned.apk and signing it with a debug key...
[apkbuilder] Using keystore: C:\Documents and Settings\sengoku\.android\debug.keystore

debug:
     [echo] Running zip align on final apk...
     [echo] Debug Package: T:\src\android\Proximity\bin\Proximity-debug.apk

BUILD SUCCESSFUL
Total time: 6 seconds
T:\src\android\Proximity>
</pre>
<p>
T:\src\android\Proximity\bin\Proximity-debug.apk
に apk ファイル (Android Package) ができるので、
adb コマンド
(これも Android SDK の tools ディレクトリにある)
を使って Nexus One へインストールする。
</p>
<pre class="terminal">
T:\src\android\Proximity>adb install bin\Proximity-debug.apk
296 KB/s (14596 bytes in 0.048s)
        pkg: /data/local/tmp/Proximity-debug.apk
Success

T:\src\android\Proximity>
</pre>
<p>
あるいは、
T:\src\android\Proximity\bin\Proximity-debug.apk を 
<a href="http://www.gcd.org/sengoku/android/Proximity-debug.apk">Web サーバ上に置いて</a>、
Internet 経由でダウンロード &amp; インストールしてもよい
(アプリケーションの設定にて、
「提供元不明のアプリ」 のインストールの許可が必要)。
</p>
<p>
以上、
Windows のコマンドプロンプトでの実行例を示したが、
もちろん Linux 上でも全く同様にビルド〜インストールが可能。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2010/07/609/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Google カレンダーの過去の予定を自動的に削除する方法</title>
		<link>http://www.gcd.org/blog/2010/07/606/</link>
		<comments>http://www.gcd.org/blog/2010/07/606/#comments</comments>
		<pubDate>Thu, 08 Jul 2010 00:44:19 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=606</guid>
		<description><![CDATA[ここ 2ヶ月ほど、 Willcom の HYBRID W-ZERO3 から Nexus One への移行を徐々に進めてきた。 Windows Mobile (HYBRID W-ZERO3) は PC とのデータ同期が基本なので、 PC にスケジュールや電話帳など丸ごと入れておけば済むが、 Android (Nexus One) の場合は 「クラウド」 との同期が基本なので一筋縄にはいかない。 つまり 「クラウド」 に全てのスケジュールを置いていいのか？ という問題。 私の場合、 「スケジュール」 といいつつ会議の議事メモから個人的な日記まで、 プライベートな情報を全て集積しているので問題がより深刻になる。 ちなみに私は、 1999年に WorkPad 30J を使い始めて以来、 プライベートなデータを PDA / スマートフォンに集積してきた。 今まで使ってきた PDA / スマートフォンをまとめてみる: 購入月機種OS 1999-04 WorkPad 30J PalmOS 3.1J 2000-03 TRGpro PalmOS [...]]]></description>
			<content:encoded><![CDATA[<p>
ここ 2ヶ月ほど、
Willcom の 
<a href="http://www.willcom-inc.com/ja/lineup/ws/027sh/">HYBRID W-ZERO3</a>
から 
<a href="http://www.google.com/phone/">Nexus One</a> 
への移行を徐々に進めてきた。
Windows Mobile (HYBRID W-ZERO3) は PC とのデータ同期が基本なので、
PC にスケジュールや電話帳など丸ごと入れておけば済むが、
Android (Nexus One) の場合は
「クラウド」
との同期が基本なので一筋縄にはいかない。
つまり
「クラウド」
に全てのスケジュールを置いていいのか？
という問題。
私の場合、
「スケジュール」
といいつつ会議の議事メモから個人的な日記まで、
プライベートな情報を全て集積しているので問題がより深刻になる。
</p>
<p>
ちなみに私は、
1999年に WorkPad 30J を使い始めて以来、
プライベートなデータを PDA / スマートフォンに集積してきた。
今まで使ってきた PDA / スマートフォンをまとめてみる:
</p>
<table class="ruler">
<tr><th>購入月</th><th>機種</th><th>OS</th></tr>
<tr><td>1999-04</td>
<td><a href="http://ja.wikipedia.org/wiki/WorkPad">WorkPad 30J</a></td>
<td>PalmOS 3.1J</td></tr>
<tr><td>2000-03</td>
<td><a href="http://www.handera.com/Products/TRGpro.aspx">TRGpro</a></td>
<td>PalmOS 3.5</td></tr>
<tr><td>2000-08</td>
<td><a href="http://en.wikipedia.org/wiki/Palm_m100_series">Palm m100</a></td>
<td>PalmOS 3.5.1</td></tr>
<tr><td>2002-09</td>
<td><a href="http://www.sharp.co.jp/products/mie1/">Zaurus MI-E1</a></td>
<td>ZaurusOS</td></tr>
<tr><td>2002-12</td>
<td><a href="http://ezaurus.com/lineup/sl/slc700/slc700_spec.html">Linux Zaurus SL-C700</a></td>
<td>Linux 2.4 Embedix</td></tr>
<tr><td>2003-06</td>
<td><a href="http://ezaurus.com/lineup/sl/slc750/slc750_spec.html">Linux Zaurus SL-C750</a></td>
<td>Linux 2.4 Embedix</td></tr>
<tr><td>2006-07</td>
<td><a href="http://www.sharp.co.jp/ws/007sh/">W-ZERO3[es]</a></td>
<td>Windows Mobile 5.0</td></tr>
<tr><td>2007-07</td>
<td><a href="http://www.sharp.co.jp/ws/011sh/">Advanced W-ZERO3[es]</a></td>
<td>Windows Mobile 6 Classic</td></tr>
<tr><td>2008-12</td>
<td><a href="http://www.htc.com/jp/product/p3600/specification.html">HTC P3600</a></td>
<td>Windows Mobile 5.0</td></tr>
<tr><td>2010-01</td>
<td><a href="http://www.sharp.co.jp/ws/027sh/">HYBRID W-ZERO3</a></td>
<td>Windows Mobile 6.5 Professional</td></tr>
<tr><td>2010-04</td>
<td><a href="http://www.google.com/phone/">Nexus One</a></td>
<td>Android 2.2 froyo</td></tr>
<tr><td>2010-06</td>
<td><a href="http://mb.softbank.jp/mb/iphone/">iPhone4</a></td>
<td>iOS 4</td></tr>
</table>
<p>
WorkPad 30J から HYBRID W-ZERO3 に至るまで、
全て PC とのデータ同期が基本だったし、
それぞれデータ移行ツールが用意されていたので移行は容易だった。
ところが Nexus One で同じような同期を行なうには、
データを PC ではなく 
<a href="http://www.google.com/calendar/render">Google Calendar</a> 
へ置かなければならない。
</p>
<p>
もちろん、 Google Calendar 
は共有設定さえ行なわなければ他人に読まれることはないだろうし、
「don't be evil」
と言ってるくらいだから、
Google が勝手にユーザのデータを活用する可能性も無い (と信じたい)。
</p>
<p>
だからといって、
個人的なデータや会社の超機密事項 (議事メモにはそういった情報も含まれる)
も洗いざらい Google に預けてしまう、
なんてことは小心な私にはとてもできない。
よく知られているように Google Calendar は
「<a href="http://www.google.com/support/calendar/bin/answer.py?hl=jp&amp;answer=37536">限定公開 URL</a>」
が漏れるだけで一巻の終わりであるわけで、
漏れることを前提でリスク評価すべき。
</p>
<p>
というわけで、
議事メモや日記を Google Calendar に置くことはハナからあきらめて、
Google Calendar には直近の予定だけを置くことにした。
万一漏れても、向こう一週間くらいの予定だけであれば、
致命的というほどでもない。
</p>
<p>
ところが驚いたことに Google Calendar には過去の予定を一括削除する機能がない。
手作業でいちいち消していかない限り、
データは残り続けるようだ。
当たり障りのない 「予定」 でも積み重なればいろいろ見えてくることがあるわけで、
長年にわたって溜った予定データは脅威となりうる。
</p>
<p>
どうしてこんな超基本機能が無いのだろうと思いつつ google で検索してみると、
見つかるのは
「どうやったら過去のデータを (一括) 消去できるのか？」
という質問のページばかり。
過去データを削除したい、
というニーズは確実にありそうなのに、
なぜ Google は実装しようとしないのか？
そういえば gmail も過去のメールを溜め続けるのが基本だし、
Google Calendar に自動的に削除する機能がないのは意図的なのかもしれない。
</p>
<p>
無い機能は作ってしまえと、
<a href="http://code.google.com/intl/ja/apis/calendar/data/2.0/developers_guide.html">Google Calendar API</a> 
のドキュメントを眺めてみる。
API を叩くためのクライアントライブラリが用意されているようだ。
が、.NET とか Java とか Python とか PHP とか、
あまり気の進まない (^^;) 言語ばかりが並んでいる。
Perl は無いのかっと思って 
<a href="http://www.cpan.org/">CPAN</a> を検索したら、
Net::Google::Calendar があっさり見つかった。
</p>
<p>
マニュアル片手にテストプログラムを書いてみる:
</p>
<pre class="code">
use Net::Google::Calendar;
use DateTime;
my $cal = Net::Google::Calendar->new;
$cal->login('sengoku@gmail.com', 'xxxxxxxx');
for my $event ($cal->get_events('start-max' => DateTime->now
				- DateTime::Duration->new(days => 7))) {
    $cal->delete_entry($event);
}
</pre>
<p>
たったこれだけ。
最初に login($user, $pass) して、
get_events(%opts) で「予定」データを取り出して、
delete_entry($event) で削除。
get_events の引数で、
開始時刻が一週間以上過去の予定のみ取り出すよう指定している。
</p>
<p>
ちょっと書き足して、
ユーザID やパスワードを設定ファイルで設定できるようにしたスクリプト
<a href="http://www.gcd.org/sengoku/docs/gcal_remove">gcal_remove</a>
を作ってみた。
</p>
<pre class="terminal">
% gcal_remove -c /path/to/config.yaml -d 7
</pre>
<p>
などと実行すると、
7日間以上前の予定を削除する。
</p>
<p>
設定ファイル config.yaml は以下のような感じ: 
</p>
<pre class="code">
google_user: sengoku@gmail.com
google_passwd: xxxxxxxx
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2010/07/606/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>サイボウズ オフィス8 のカレンダーを iCalendar 形式に変換するスクリプトを書いてみた</title>
		<link>http://www.gcd.org/blog/2010/05/585/</link>
		<comments>http://www.gcd.org/blog/2010/05/585/#comments</comments>
		<pubDate>Wed, 26 May 2010 07:01:33 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=585</guid>
		<description><![CDATA[私の職場ではグループウェアとしてサイボウズを利用している。 自分のスケジュールを iCalendar 形式で取得してみたくなった (Nexus One を買うとそういう気分になる ^^;) ので、 google で検索してみたところ次の二つのスクリプトが見つかった: サイボウズオフィス6のカレンダーをiCalendar形式に変換するスクリプト。 サイボウズofffice8と同期するツール暫定版 あいにく職場のサイボウズはオフィス8 なので、 オフィス8 に未対応の前者は使えない。 後者はオフィス8 用だが、 「暫定版」 と書いてある通りいろいろバグがある。 さくっと修正してみて一応それっぽく動かすことはできたのだが、 使い続けていくとなると一から書き直したほうがいいような気がしてきた (わたし的にはこの手のものを PHP スクリプトでは書きたくない) ので、 perl で書き直してみた。 この perl スクリプト cybozu8_ical を、 % cybozu8_ical --conf /path/to/config.yaml などと実行すると、 オフィス8 の 「月予定」 (月間スケジュール) ページをアクセスして、 iCalendar 形式に変換する。 向こう一週間以内の予定については 「予定の詳細」 ページもアクセスして、 「メモ」 および 「設備」 も取得する。 「月予定」 の全ての予定について [...]]]></description>
			<content:encoded><![CDATA[<p>
私の<a href="http://www.klab.jp/">職場</a>ではグループウェアとしてサイボウズを利用している。
自分のスケジュールを iCalendar 形式で取得してみたくなった
(Nexus One を買うとそういう気分になる ^^;) ので、
<a href="http://www.google.co.jp/search?q=iCalendar+cybozu">google で検索してみた</a>ところ次の二つのスクリプトが見つかった:
</p>
<ul>
<li><a href="http://code.google.com/p/ogawa/wiki/Cybozu2ICal?wl=ja">サイボウズオフィス6のカレンダーをiCalendar形式に変換するスクリプト。</a></li>
<li><a href="http://ameblo.jp/sitescope/entry-10260113074.html">サイボウズofffice8と同期するツール暫定版</a></li>
</ul>
<p>
あいにく職場のサイボウズはオフィス8 なので、
オフィス8 に未対応の前者は使えない。
後者はオフィス8 用だが、
「暫定版」
と書いてある通りいろいろバグがある。
さくっと修正してみて一応それっぽく動かすことはできたのだが、
使い続けていくとなると一から書き直したほうがいいような気がしてきた
(わたし的にはこの手のものを PHP スクリプトでは書きたくない) ので、
<a href="http://www.gcd.org/sengoku/docs/cybozu8_ical">perl で書き直してみた</a>。
</p>
<p>
この perl スクリプト <a href="http://www.gcd.org/sengoku/docs/cybozu8_ical">cybozu8_ical</a> を、
</p>
<pre class="terminal">
% cybozu8_ical --conf /path/to/config.yaml
</pre>
<p>
などと実行すると、
オフィス8 の 「月予定」 (月間スケジュール) ページをアクセスして、
iCalendar 形式に変換する。
向こう一週間以内の予定については 「予定の詳細」 ページもアクセスして、
「メモ」 および 「設備」 も取得する。
「月予定」 の全ての予定について 「予定の詳細」をアクセスすると、
オフィス8 サーバに負荷をかけすぎる懸念があったので、
このような仕様にしてみた。
</p>
<p>
設定ファイル config.yaml は以下のような感じ:
</p>
<pre class="code">
cybozu_url: http://intra.klab.org/cgi-bin/klcb/ag.cgi
calname: cybozu
userid: 73
password: xxxxxxxx
time_zone: Asia/Tokyo
input_encoding: shiftjis
output_file: /home/sengoku/tmp/cybozu.ics
</pre>
<p>
「cybozu_url」 はオフィス8 の URL を指定する
(もちろん intra.klab.org は KLab 社内LAN からしかアクセスできない)。
「userid」 と 「password」 
はスケジュールを取得したいユーザの ID とパスワード。
「output_file」 に指定したファイルへ 
iCalendar 形式のスケジュールを出力する。
「output_file」 を省略すると標準出力へ書き出す。
</p>
<p>
なお 
「--conf /path/to/config.yaml」 オプションを省略すると、
cybozu8_ical と同じディレクトリにある config.yaml が読み込まれる。
</p>
<p>
cybozu8_ical で生成した iCalendar 形式のファイルは、
とりあえず <a href="http://www.google.com/calendar/render?hl=ja">Google カレンダー</a> および
<a href="http://mozilla.jp/thunderbird/">Thunderbird</a> +
<a href="http://www.mozilla-japan.org/projects/calendar/lightning/">Lightning</a> で読み込めることは確認したが、
なにぶんまだ
<a href="http://tools.ietf.org/html/rfc2445">RFC 2445</a> 
を真面目に読んでいないので、
不具合などあったらご指摘頂けると有難い。
</p>
<p>
私はオフィス8 の API を知らないので、
「月予定」 「予定の詳細」 のページを取得してきて scrape しているだけ。
「繰り返し予定」 は個々の予定として扱っている。
iCalendar の UID (各予定固有のID) が同じままだと 
2個目以降の予定が無視されてしまうので、
UID の末尾に通し番号をつけて互いに区別できるようにしている。
</p>
<p>
サイボウズのスケジュールは 「場所」 を登録できない。
その代わり 「施設」 として会議室を登録する。
ところが私の職場の場合 
「施設」 には会議室だけでなく 
「ビデオ会議システム」 などもあったりするので、
「施設」 に登録されているデータをそのまま 
iCalendar の LOCATION として使うわけにもいかない。
そこで 「施設」 に会議室の名称が登録されている場合のみ、
その会議室名を LOCATION として出力するようにしている。
</p>
<p>
iCalendar の LOCATION として出力すべき会議室名の一覧を、
cybozu8_ical スクリプトの始めの方で、
</p>
<pre class="code">
my @facility = (
	'20F 大会議室1', '20F 大会議室2', '20F 大会議室3',
	'20F 中会議室1', '20F 中会議室2',
	'20F 小会議室1', '20F 小会議室2',
	'20F 和室', '22F 社長室前MTGｽﾍﾟ-ｽ'
);
</pre>
<p>
などと定義している。
ここに列挙されていない会議室等は  「施設」 に登録されていても単に無視される。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2010/05/585/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>tinydns のゾーンを MyDNS へ反映させるスクリプトを書いてみた</title>
		<link>http://www.gcd.org/blog/2010/03/573/</link>
		<comments>http://www.gcd.org/blog/2010/03/573/#comments</comments>
		<pubDate>Mon, 08 Mar 2010 00:25:06 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=573</guid>
		<description><![CDATA[MyDNS というのは MySQL (あるいは PostgreSQL) のレコードを、 そのままゾーンレコードとして扱えるネームサーバ。 私は MyDNS を使って実験的にダイナミックDNSサービスを (もちろん無償で) 提供している。 実験と言いつつ、 サービス開始以来 2年以上安定的に継続できているし、 先月から海外レンタルサーバを使って地域分散したので、 仮に私の自宅の回線が切れてもネームサーバが見えなくなることはない。 gcd.jp のマスタネームサーバである ns.gcd.jp と、 スレーヴネームサーバである ns2.gcd.jp (海外サーバ fremont.gcd.org の別名。 名前の通りカルフォルニア州フリーモントにある) とは、 MySQL のレプリケーションによってゾーンデータを同期させている。 したがってマスタ側での変更が即座にスレーヴ側に伝わる。 ちなみに、 ネームサーバ間の同期でよく利用されるゾーン転送 (AXFR) は、 ゾーンデータを丸ごと転送するので (ゾーンが大きくなってくると) 同期頻度を高くすることが難しく、 ダイナミックDNSサービスにはあまり向いていない。 MySQL のレプリケーションでネームサーバ間の同期が行なえてしまう MyDNS は、 ダイナミックDNSサービス向きと言える。 ところが gcd.org では (ダイナミックではない) 普通のネームサーバ ns1.gcd.org も運用していて、 これは tinydns を利用している。 せっかく海外にサーバ [...]]]></description>
			<content:encoded><![CDATA[<p>
<a href="http://mydns.bboy.net/">MyDNS</a> というのは MySQL 
(あるいは PostgreSQL) のレコードを、
そのままゾーンレコードとして扱えるネームサーバ。
私は MyDNS 
を使って実験的に<a href="http://www.gcd.org/blog/2007/08/124/">ダイナミックDNSサービス</a>を
(もちろん無償で) 提供している。
実験と言いつつ、
サービス開始以来 2年以上安定的に継続できているし、
先月から<a href="http://www.gcd.org/blog/2010/02/544/">海外レンタルサーバ</a>を使って地域分散したので、
仮に私の自宅の回線が切れてもネームサーバが見えなくなることはない。
</p>
<p>
gcd.jp のマスタネームサーバである ns.gcd.jp と、
スレーヴネームサーバである ns2.gcd.jp 
(海外サーバ fremont.gcd.org の別名。
名前の通りカルフォルニア州フリーモントにある) とは、
MySQL のレプリケーションによってゾーンデータを同期させている。
したがってマスタ側での変更が即座にスレーヴ側に伝わる。
</p>
<p>
ちなみに、
ネームサーバ間の同期でよく利用されるゾーン転送 
(AXFR) は、
ゾーンデータを丸ごと転送するので (ゾーンが大きくなってくると) 
同期頻度を高くすることが難しく、
ダイナミックDNSサービスにはあまり向いていない。
MySQL のレプリケーションでネームサーバ間の同期が行なえてしまう MyDNS は、
ダイナミックDNSサービス向きと言える。
</p>
<p>
ところが gcd.org では (ダイナミックではない) 
普通のネームサーバ ns1.gcd.org も運用していて、
これは
<a href="http://www.gcd.org/blog/2006/07/85/">tinydns</a> を利用している。
せっかく海外にサーバ fremont.gcd.org を借りたのだから、
tinydns で管理しているゾーンも fremont.gcd.org で引けるようにしたいが、
fremont に複数 IP アドレスを付与するのはお金がかかる (月額 $1 追加) ので、
fremont で MyDNS と tinydns の両方を走らせるわけにもいかない。
</p>
<blockquote>
私が借りてるレンタルサーバ (正確に言うと VPS) 
<a href="http://www.linode.com/?r=577c3721c763acfc298a4e4cc4acaf654b4dbdc8">Linode</a> は、
もともと DNS サービス (マスタ/スレーヴどちらでも、ドメインいくつでも) 
を無料で利用できるので、
DNS のためだけに 1IP 追加する気にはちょっとなれない。
</blockquote>
<p>
もちろん、
tinydns を止めてしまって全てのゾーンを MyDNS 
へ移行してしまうという解決策も無くはないのだが、
MyDNS に全面的に依存してしまうのは恐い気もする。
できれば tinydns 側はいじらずに、
tinydns のゾーンデータを MyDNS 側へ反映させる仕掛けを作りたい。
</p>
<p>
というわけで、
tinydns のゾーンデータを読み込み、
MyDNS のゾーンデータの形式で MySQL へレコードを書込む
<a href="http://sourceforge.jp/cvs/view/stone/dns/tinydns2mydns?revision=1.1">perl スクリプト tinydns2mydns</a>
を書いてみた
(<a href="http://sourceforge.jp/cvs/view/stone/dns/tinydns2mydns?view=log">CVSリポジトリ</a>)。
</p>
<p>
なお MyDNS には、
<a href="http://mydns.bboy.net/doc/html/mydns_26.html#SEC26">mydnsimport</a> という外部からゾーンデータを取り込むツールが付属していて、
tinydns のゾーンデータもインポートできるのだが、
以下の欠陥があってゾーン転送目的には使えない:
</p>
<ol>
<li>
ゾーンの全レコードをいったん削除した上でインポートを行なう実装になっている。
</li>
<li>
SRV レコードをインポートできない。
</li>
</ol>
<span id="more-573"></span>
<p>
1. tinydns 側で新しいレコードを追加したとき、
対応するレコードのみを DB に INSERT できる実装になっているかと思いきや、
ソースを確認してみると、
いったん全部 DELETE した後
tinydns のゾーンデータを 1レコードずつ INSERT する実装になっていた。
</p>
<p>
つまり、
短い時間とはいえ、
ゾーンにレコードが存在しないタイミングが存在する。
したがって、
その瞬間に届いたクエリに対して非存在 (NXDOMAIN) を回答してしまう。
mydnsimport のソースをいじって、
DELETE してから INSERT 完了まで読み込みをロックするようにすれば、
NXDOMAIN を返すこと自体は回避可能だが、
ネームサーバとしての性能を大いに損ねてしまう。
</p>
<p>
2. tinydns には 
A, AAAA, NS, CNAME, PTR, MX, TXT
以外の任意のタイプのレコードを定義する方法として、
タイプを数値で指定する汎用記法がある:
</p>
<blockquote>
:<i>fqdn</i>:<i>n</i>:<i>rdata</i>:<i>ttl</i>:<i>timestamp</i>:<i>lo</i><br />
<br />
Generic record for <tt><i>fqdn</i></tt>.
<tt>tinydns-data</tt> creates a record of type <tt><i>n</i></tt>
for <tt><i>fqdn</i></tt>
showing <tt><i>rdata</i></tt>.
<tt><i>n</i></tt> must be an integer between 1 and 65535;
it must not be
2 (NS), 5 (CNAME), 6 (SOA), 12 (PTR), 15 (MX), or 252 (AXFR).
The proper format of <tt><i>rdata</i></tt> depends on <tt><i>n</i></tt>.
You may use octal <tt>\<i>nnn</i></tt> codes
to include arbitrary bytes inside <tt><i>rdata</i></tt>.
<div align="right"><a href="http://cr.yp.to/djbdns/tinydns-data.html">The tinydns-data program</a> から引用</div>
</blockquote>
<p>
この記法を利用して、
例えば以下のように
<a href="http://www.ietf.org/rfc/rfc2782.txt">SRV レコード</a>を定義できる。
</p>
<pre class="code">
:_jabber._tcp.jabber.jp:33:\000\012\000d\024\225\006jabber\002jp\000:300
</pre>
<p>
つまり
<tt><i>fqdn</i></tt> が 「_jabber._tcp.jabber.jp」、
<tt><i>n</i></tt> が 「33」 で SRV の意味。
<tt><i>rdata</i></tt> はタイプによってデータの意味が変わってくるが、
SRV の場合は次のような構造になる:
</p>
<pre class="fig">
┌─┬─┬─┬─┬─┬─┬─┬─≫┬─┬─┬─┬─≫─┬─┬─┐
│優先度│ 重み │ポート│長│ホスト名│長│ドメイン名……│０│
└─┴─┴─┴─┴─┴─┴─┴─≪┴─┴─┴─┴─≪─┴─┴─┘
</pre>
<p>
つまり先頭から 2バイトずつ 3つの数値 (ネットワークバイトオーダ) を表わし、
複数個の文字列 (先頭に 1バイトの文字列長) が続いて最後に 0。
前述の SRV レコードの場合であれば、
優先度は 「\000\012」 つまり 8進数で 000 012 だから 10進数だと 10 になる。
以下同様に重みが 100 で、
ポート番号が 5269 で、
続く文字列が 「jabber」 と 「jp」 でホスト名 jabber.jp を意味する。
</p>
<pre class="fig">
　優先度　 重み 　ポート　ホスト名
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│00　0A│00　64│14　95│06│ｊ│ａ│ｂ│ｂ│ｅ│ｒ│02│ｊ│ｐ│00│
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
　　10　　 100　　 5269 　　　　　6 文字　　　　　　　　　2 文字
</pre>
<p>
ゾーンファイルの書式で書けば次のようなレコードになる:
</p>
<pre class="code">
_jabber._tcp.jabber.jp.	300	IN	SRV	10 100 5269 jabber.jp.
</pre>
<p>
つまり、
ドメイン <a href="http://www.jabber.jp/">jabber.jp</a> における 
TCP/IP の jabber サービスは、
ホスト jabber.jp の 5269 番ポートにアクセスすればよい、
ということを表わす。
同じサービスを提供するホスト・ポートが複数ある場合、
優先度の数値が (アクセス可能なものの中で) 
最小のものに接続することが求められる (MUST)。
同じ優先度のホスト・ポートが複数ある場合は、
重みの比率に応じてアクセスを振り分けるべき (SHOULD)。
</p>
<p>
tinydns2mydns スクリプトにおいて 
SRV レコードを変換する部分は次のようになっている:
</p>
<pre class="code">
    # :fqdn:n:rdata:ttl:timestamp:lo
    elsif ($top eq ':') {
	my ($fqdn, $n, $rdata, $ttl, $timestamp, $lo) = @_;
	my $domain = &amp;get_domain($fqdn);
	next unless defined $domain;
	my $name = &amp;get_host($fqdn, $domain);
	if ($n == 33) {
	    $rdata =~ s/\\(\d\d\d)/sprintf("%c", oct($1))/ge;
	    my ($priority, $weight, $port, @target)
		= unpack("nnn(C/a)*", $rdata);
	    &amp;record($sth_insert, $sth_update,
		    $Zones{$domain}, $name,
		    'SRV', "$weight $port " . join(".", @target),
		    $priority, $ttl, $lo);
	}
    }
</pre>
<p>
眺めるだけで処理が見えてくる見通しのよいスクリプトだと思う (自画自賛 ;)。
$<tt><i>n</i></tt> が 33 のとき SRV レコードだから
続く $<tt><i>rdata</i></tt> を読み込んで、
8進数表記 <tt>\<i>nnn</i></tt> をバイトデータへ変換し、
「優先度」 「重み」 「ポート」 および複数個の文字列に分解する:
</p>
<pre class="code">
$rdata =~ s/\\(\d\d\d)/sprintf("%c", oct($1))/ge;
my ($priority, $weight, $port, @target) = unpack("nnn(C/a)*", $rdata);
</pre>
<p>
C で書けば何十行にもおよぶであろうこの処理をわずか 
2行で書けてしまう perl はスゴイと改めて思う。
あとは分解したデータを MyDNS のレコードの形式に組み立てるだけ:
</p>
<pre class="code">
$Zones{$domain}, $name, "$weight $port " . join(".", @target), $priority, $ttl
</pre>
<p>
mydnsimport のソースが C で 2000行近くもある 
(MyDNS ライブラリ群のソースを除く) のに対し、
tinydns2mydns はわずか 353行で書けてしまい、
しかも上記 mydnsimport にある欠陥を解決できている。
現状では tinydns の汎用記法のうち SRV しか対応していないが、
スクリプトの見通しがよいため他タイプに対応させるのも容易だろう。
</p>
<p>
tinydns2mydns スクリプトが DB に書込んだレコードは以下のようになる:
</p>
<pre class="terminal">
mysql> select * from rr where name='_jabber._tcp';
+-----+------+--------------+------+---------------------+-----+-----+
| id  | zone | name         | type | data                | aux | ttl |
+-----+------+--------------+------+---------------------+-----+-----+
| 817 |   13 | _jabber._tcp | SRV  | 100 5269 jabber.jp. |  10 | 300 |
+-----+------+--------------+------+---------------------+-----+-----+
1 row in set (0.00 sec)

</pre>
<p>
外部のサイトから jabber.jp の SRV レコードを問合わせてみると、
fremont も正しいレコードを返すことが確認できる:
</p>
<pre class="terminal">
% host -t srv _jabber._tcp.jabber.jp fremont.gcd.org
Using domain server:
Name: fremont.gcd.org
Address: 74.207.241.21#53
Aliases: 

_jabber._tcp.jabber.jp has SRV record 10 100 5269 jabber.jp.

% host -t srv _jabber._tcp.jabber.jp
_jabber._tcp.jabber.jp has SRV record 10 100 5269 jabber.jp.
</pre>]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2010/03/573/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>x86_64 Linux などの 64bit 環境で MD5 を使うときの注意点</title>
		<link>http://www.gcd.org/blog/2010/03/556/</link>
		<comments>http://www.gcd.org/blog/2010/03/556/#comments</comments>
		<pubDate>Mon, 01 Mar 2010 00:34:46 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=556</guid>
		<description><![CDATA[MD5 (Message Digest Algorithm 5) は、 RFC 1321 でアルゴリズムが紹介されていて、 Appendix (付録) として C によるリファレンス実装が付属しているが、 その global.h に /* UINT4 defines a four byte word */ typedef unsigned long int UINT4; と書いてある。 すなわち 32bit 整数として UINT4 型を定義している。 x86_64 Linux を始め多くの 64bit Unix は LP64 すなわち long int (とポインタ) が 64bit な整数データモデルを採用している。 したがって UINT4 型の定義が 「unsigned long [...]]]></description>
			<content:encoded><![CDATA[<p>
MD5 (Message Digest Algorithm 5) は、
<a href="http://www.ietf.org/rfc/rfc1321.txt">RFC 1321</a>
でアルゴリズムが紹介されていて、
Appendix (付録) として C によるリファレンス実装が付属しているが、
その global.h に
</p>
<pre class="code">
/* UINT4 defines a four byte word */
typedef unsigned long int UINT4;
</pre>
<p>
と書いてある。
すなわち 32bit 整数として UINT4 型を定義している。
x86_64 Linux を始め多くの 64bit Unix は
<a href="http://www.unix.org/version2/whatsnew/lp64_wp.html">LP64</a> 
すなわち long int (とポインタ) が 64bit な整数データモデルを採用している。
したがって UINT4 型の定義が 「unsigned long int」 のままで、
この MD5 リファレンス実装を使ってしまうと、
32bit であるべき UINT4 型が 64bit になってしまい、
間違ったハッシュ値を算出してしまう。
</p>
<blockquote>
16bit CPU が主流だった大昔なら
「int が 16bit なデータモデルを採用している環境」 
が多かったのかもしれないが、
RFC 1321 が出た 1992年ごろは既に 32bit CPU が主流だったわけで、
UINT4 型を 「int」 と定義しておいてくれてもよかったのにと思う。
そうすれば、
「long が 64bit なデータモデルを採用している環境」
が多くなる昨今でも (int は 32bit のままなので) 
問題を起こさずに済んだだろうに。
</blockquote>
<p>
試しにテストプログラムを書いてみる:
</p>
<pre class="code">
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
#include &lt;string.h&gt;
#include "global.h"
#include "md5.h"
#define DIGEST_LEN 16
#define BUFFER_LEN 256

int main(int argc, char *argv[]) {
    MD5_CTX context;
    unsigned char digest[DIGEST_LEN];
    unsigned char buf[BUFFER_LEN];
    int i;
    MD5Init(&amp;context);
    while ((i=read(0, buf, BUFFER_LEN)) &gt; 0) MD5Update(&amp;context, buf, i);
    MD5Final(digest, &amp;context);
    for (i=0; i &lt; DIGEST_LEN; i++) printf("%02x", digest[i]);
    printf("\n");
    return 0;
}
</pre>
<p>
32bit 環境 (i686 Linux) では正しく動く:
</p>
<pre class="terminal">
senri:/home/sengoku/src/md5 % uname -m
i686
senri:/home/sengoku/src/md5 % ls
global.h  main.c  md5.h  md5c.c
senri:/home/sengoku/src/md5 % cc -Wall main.c md5c.c
senri:/home/sengoku/src/md5 % file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.0.0, dynamically linked (uses shared libs), not stripped
senri:/home/sengoku/src/md5 % echo "Hello, world" | ./a.out
a7966bf58e23583c9a5a4059383ff850
senri:/home/sengoku/src/md5 % echo "Hello, world" | openssl md5
a7966bf58e23583c9a5a4059383ff850
</pre>
<p>
ところが、
64bit 環境 (x86_64 Linux) だと:
</p>
<pre class="terminal">
senri:/home/sengoku/src/md5 % uname -m
x86_64
senri:/home/sengoku/src/md5 % cc -Wall main.c md5c.c
senri:/home/sengoku/src/md5 % file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), for GNU/Linux 2.4.0, dynamically linked (uses shared libs), not stripped
senri:/home/sengoku/src/md5 % echo "Hello, world" | ./a.out
fd578222c6a471623ea1e3eb2b6e6f6b
</pre>
<p>
などと、
誤った MD5 の値が出力されてしまう。
</p>
<p>
MD5 の値を求めること自体が目的であれば、
誤ったハッシュ値が出力されればすぐ気付くのでいいのだが、
値そのものが目的であることは (当然ながら) あまりなくて、
普通はアプリケーションの中で MD5 を利用するので、
32bit 環境で使っていたアプリケーションを
64bit 環境でコンパイルし直して使おうとするとハマる。
</p>
<span id="more-556"></span>
<p>
私は qmapop-0.3 (Bert Gijsbers 氏が開発した、qmail 用 APOP 認証パッケージ。
とうの昔に obsolete になってる ^^;)
を x86_64 Linux でコンパイルし直したら
APOP 認証が通らなくなってしまって焦った。
</p>
<p>
なにぶん古いプログラム (1995年5月リリース) に
いろいろ手を加えて使っていたので、
APOP 認証が失敗するのは必要なパッチをあてていないためだろうと思ってしまった。
しかし最後にコンパイルしたのは何年も前 (2002年4月) なので、
どんな修正を行ったか思い出せない。
</p>
<p>
仕方ないのでソースをながめて原因を探る羽目になり、
動作させつつ処理を追っていたら、
何のことはない単に算出した MD5 の値が誤ってるだけということに気付き、
(qmapop-0.3 が使ってた) 
MD5 リファレンス実装がデータモデルに依存していることを見つけた次第。
</p>
<p>
<a href="http://en.wikipedia.org/wiki/C99">C99</a> で 
<a href="http://en.wikipedia.org/wiki/Stdint.h">stdint.h</a>
が定義されて、
整数データモデルに依存しない、
移植性の高い書き方が可能になった。
以下のように global.h を修正すればよい:
</p>
<pre class="code">
senri:/home/sengoku/src/md5 % diff -ub global.h~ global.h
--- global.h~	1993-05-14 02:15:17.000000000 +0900
+++ global.h	2010-02-27 23:49:36.553633806 +0900
@@ -13,11 +13,13 @@
 /* POINTER defines a generic pointer type */
 typedef unsigned char *POINTER;
 
+#include &lt;stdint.h&gt;
+
 /* UINT2 defines a two byte word */
-typedef unsigned short int UINT2;
+typedef uint16_t UINT2;
 
 /* UINT4 defines a four byte word */
-typedef unsigned long int UINT4;
+typedef uint32_t UINT4;
 
 /* PROTO_LIST is defined depending on how PROTOTYPES is defined above.
 If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it
</pre>]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2010/03/556/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CTO日記も livedoorブログから WordPress へ引越しました  (URL は変更なし)</title>
		<link>http://www.gcd.org/blog/2010/02/540/</link>
		<comments>http://www.gcd.org/blog/2010/02/540/#comments</comments>
		<pubDate>Tue, 09 Feb 2010 22:46:28 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[システム構築・運用]]></category>
		<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=540</guid>
		<description><![CDATA[「仙石浩明の日記」 に続いて、 「仙石浩明CTO の日記」 も先週末に livedoorブログから自宅サーバへ引っ越した (つまりネームサーバの設定を変更して切替。有料プランの解約はこれから)。 もともと両ブログは相互にリンクを張って密接に連係していたので、 引越を機会に両者を統合した。 統合といっても両ブログは微妙(?)に読者層が異なると思われるし、 何よりページの体裁が大きく変わってしまっては読者の方々を戸惑わせてしまうので、 CTO日記を 「仙石浩明の日記」 の一カテゴリという位置付けにして、 かつページの体裁は WordPress のテーマを切り替えることによって、 どちらのブログもあまり大きな変化がないようにしている。 「仙石浩明CTO の日記」 http://sengoku.blog.klab.org/ をアクセスすると、 次のような PHP スクリプトを走らせた上で、 WordPress を呼び出す (末尾の require 文): &#60;?php $new = NULL; if ($_SERVER['REQUEST_URI'] == "/") { $new = "/blog/category/cto/"; } elseif ($_SERVER['REQUEST_URI'] == "/feed/") { $new = "/blog/category/cto/feed/"; } elseif (preg_match('@^/\d+/\d+/\d+/@', $_SERVER['REQUEST_URI'], [...]]]></description>
			<content:encoded><![CDATA[<p>
「仙石浩明の日記」 に続いて、
「仙石浩明CTO の日記」 も先週末に 
livedoorブログから<a href="http://www.gcd.org/">自宅サーバ</a>へ引っ越した 
(つまりネームサーバの設定を変更して切替。有料プランの解約はこれから)。
もともと両ブログは相互にリンクを張って密接に連係していたので、
引越を機会に両者を統合した。
</p>
<p>
統合といっても両ブログは微妙(?)に読者層が異なると思われるし、
何よりページの体裁が大きく変わってしまっては読者の方々を戸惑わせてしまうので、
CTO日記を 
「<a href="http://www.gcd.org/blog/">仙石浩明の日記</a>」 
の<a href="http://www.gcd.org/blog/category/cto/">一カテゴリ</a>という位置付けにして、
かつページの体裁は 
<a href="http://ja.wordpress.org/">WordPress</a> 
のテーマを切り替えることによって、
どちらのブログもあまり大きな変化がないようにしている。
</p>
<p>
「仙石浩明CTO の日記」 
<a href="http://sengoku.blog.klab.org/">http://sengoku.blog.klab.org/</a>
をアクセスすると、
次のような PHP スクリプトを走らせた上で、
WordPress を呼び出す (末尾の require 文):
</p>
<pre class="code">
&lt;?php
$new = NULL;
if ($_SERVER['REQUEST_URI'] == "/") {
    $new = "/blog/category/cto/";
} elseif ($_SERVER['REQUEST_URI'] == "/feed/") {
    $new = "/blog/category/cto/feed/";
} elseif (preg_match('@^/\d+/\d+/\d+/@',
		     $_SERVER['REQUEST_URI'], $matches)) {
    $new = $_SERVER['REQUEST_URI'];
...(中略)...
}
if ($new) {
    ...(中略)...
    $ORIG_SERVER_NAME = $_SERVER['SERVER_NAME'];
    $host = "www.gcd.org";
    $_SERVER['SERVER_NAME'] = $host;
    $_SERVER['REQUEST_URI'] = $new;
    $_SERVER['SCRIPT_NAME'] = $new;
    $_SERVER['PHP_SELF'] = $new;
    $abspath = "/usr/local/www/wordpress/";
    $themepath = "${abspath}wp-content/themes/sengoku_cto/";
    define('WP_USE_THEMES', true);
    define('TEMPLATEPATH', $themepath);
    define('STYLESHEETPATH', $themepath);
    require("${abspath}wp-blog-header.php");
...(中略)...
}
?&gt;
</pre>
<p>
つまり
http://sengoku.blog.klab.org/ へのアクセスは、
パス名に 「/category/cto/」 を追加することによって、
CTO日記カテゴリへのアクセスに変換する。
</p>
<p>
ページの体裁については、
「wp-content/themes/sengoku_cto/」 ディレクトリが、
CTO日記のテーマフォルダで、
二つの PHP 定数 TEMPLATEPATH と STYLESHEETPATH 
をこのディレクトリへ設定することによって、
テーマの切り替えを行なっている。
</p>
<p>
テーマフォルダの中にあるテーマ関数ファイル 「functions.php」 は、
WordPress の初期化中に読み込まれるので、
ここに PHP スクリプトを書いておくことによって
WordPress の挙動を変更することができる。
例えばブログのタイトルを 「仙石浩明CTO の日記」 に変更するには、
以下のスクリプトを functions.php に追加しておけばよい:
</p>
<pre class="code">
function option_blogname_cto() {
    return '仙石浩明CTO の日記';
}
add_filter('pre_option_blogname', 'option_blogname_cto');
</pre>
<p>
つまり、
pre_option_blogname フックに、
option_blogname_cto フィルタを登録する。
</p>
<p>
WordPress では、
ブログのタイトルなど各種オプションの設定値 (DB に格納している) を、
get_option($setting) 関数を呼び出すことで参照している。
例えばタイトルは get_option('blogname') を呼び出すことで得られ、
URL は get_option('home') で得られる。
</p>
<p>
get_option($setting) 関数は wp-includes/functions.php で定義されていて、
以下のようにフィルタフック pre_option_* が定義されている:
</p>
<pre class="code">
function get_option( $setting, $default = false ) {
    global $wpdb;

    // Allow plugins to short-circuit options.
    $pre = apply_filters('pre_option_' . $setting, false);
    if ( false !== $pre )
	return $pre;
    ...(中略)...
}
</pre>
<p>
つまり、
「pre_option_設定名」 というフックに登録されたフィルタが値を返すなら、
get_option はオプションの設定値ではなくフィルタが返した値を返すようになる。
前述の例なら、
「pre_option_blogname」 フックに登録された
「option_blogname_cto」 フィルタが 「仙石浩明CTO の日記」 という値を返すので、
get_option('blogname') も 「仙石浩明CTO の日記」 という値を返すようになり、
結果としてブログのタイトルを変更できる、というわけ。
</p>
<p>
ただし、
前述したように CTO日記は 「仙石浩明の日記」 の一カテゴリという位置付けなので、
ブログのタイトルを変更しただけだと、
ブログ 「仙石浩明CTO の日記」 の 「仙石浩明CTO の日記」 カテゴリということで、
ページのタイトル等が
「仙石浩明CTO の日記 &raquo; 仙石浩明CTO の日記」
という冗長なものになってしまう。
そこで、
以下のようなスクリプトを 「functions.php」 に追加して、
タイトルとカテゴリ名が同じときはカテゴリ名が表示されないようにする:
</p>
<pre class="code">
function single_cat_title_cto($category_name) {
    $name = get_option('blogname');
    if ($category_name == $name) return "";
    return $category_name;
}
add_filter('single_cat_title', 'single_cat_title_cto');
</pre>
<p>
single_cat_title フックは、
wp-includes/general-template.php で定義されていて、
ページのタイトルなどに表示されるカテゴリ名を変更することができる。
</p>
<p>
以上で、
「仙石浩明の日記」 の一カテゴリを 
「仙石浩明CTO の日記」 の体裁で見せることができるようになる。
しかし、
元が 「仙石浩明の日記」 であるだけに、
リンク先が全て 「仙石浩明の日記」 のページになってしまう。
例えば、
<a href="http://sengoku.blog.klab.org/">「仙石浩明CTO の日記」 のトップページ</a>の一番下に、
「<a href="http://sengoku.blog.klab.org/page/2/">古い投稿 &raquo;</a>」 
というリンクがあるが、
このリンク先が http://www.gcd.org/blog/page/2/ になってしまい、
たどると <a href="http://www.gcd.org/blog/page/2/">「仙石浩明の日記」 のトップページの 2ページ目</a>へ遷移してしまう。
</p>
<p>
また、
本文中 (あるいはサイドバー) に現れるリンクも、
DB のデータは
「仙石浩明の日記」 のパーマリンクを用いているので、
たとえそれが 「仙石浩明CTO の日記」 カテゴリに含まれていても、
そのリンクをたどると
「仙石浩明の日記」 の記事として表示されてしまう。
</p>
<p>
そこで、
遷移先も 「仙石浩明CTO の日記」 として表示したいリンクを、
フィルタで書き換えることにした。
つまり DB のデータは 「仙石浩明の日記」 へのリンクのままで、
ブラウザに送信する前に都度書き換える。
</p>
<p>
対象となるリンクは、
記事本文中だけでなく、
前述したページナビ 「古い投稿」 「新しい投稿」 や、
サイドバー (「人気記事」 や 「最近の投稿」) にも現れる。
ページ丸ごと (つまり HTTP レスポンス丸ごと) 
HTML を書き換えられるフックがあるとよかったのだが、
残念ながらそういうフックは定義されていないようだ。
以下のフックそれぞれについてリンクを書き換えればよさげ:
</p>
<table class="ruler">
<tr><th>フィルタフック</th>
<th>フィルタが変更できる対象, 第2引数, ...</th></tr>
<tr><td>the_content</td>
<td>記事本文 HTML</td></tr>
<tr><td>the_category</td>
<td>記事の末尾に表示されるカテゴリーリストの HTML,<br />
$separator, $parents</td></tr>
<tr><td>get_pagenum_link</td>
<td>ページ末尾に表示されるページナビ 「古い投稿」 「新しい投稿」 の URL</td></tr>
<tr><td>post_link</td>
<td>記事の URL (パーマリンク), $post, $leavename</td></tr>
<tr><td>widget_text</td>
<td>サイドバーに表示されるテキストウィジェットの HTML, $instance</td></tr>
<tr><td>wp_list_categories</td>
<td>サイドバーに表示されるカテゴリーのリストの HTML</td></tr>
<tr><td>category_feed_link</td>
<td>カテゴリーの RSSフィードの URL, $feed</td></tr>
</table>
<p>
書き換え対象のリンクを決めるために、
まず CTO日記カテゴリに属す記事の ID を取得する:
</p>
<pre class="code">
function setup_cto_id() {
    global $wpdb;
    global $is_cto_id;
    $result = $wpdb-&gt;get_results("SELECT object_id FROM wp_term_relationships WHERE term_taxonomy_id=17", ARRAY_N);
    foreach ($result as $row) {
	$is_cto_id[$row[0]] = 1;
    }
}
</pre>
<p>
あるカテゴリに属す記事ID のデータを取得する関数など、
WordPress に含まれているんじゃないかと探してみたのだが、
見つからなかったので DB に問合わせて取得するようにしてみた。
毎回 DB アクセスが発生してしまうが、
キャッシュとかはアクセス数が増えてから考える (^^;)。
「term_taxonomy_id=17」 が CTO日記のカテゴリ 
(決め打ち ^^;)。
CTO日記カテゴリに属す記事は、
配列 $is_cto_id[記事ID] に 1 を代入しておく。
</p>
<p>
次に URL を書き換えるスクリプト:
</p>
<pre class="code">
function replace_URL_cto($matches) {
    global $is_cto_id;
    global $ORIG_SERVER_NAME;
    if (!is_array($is_cto_id)) {
	setup_cto_id();
    }
    if (is_null($matches[2])) {
	if ($matches[1] == "category/cto/") return "http://$ORIG_SERVER_NAME/";
	return "http://$ORIG_SERVER_NAME/$matches[1]";
    } elseif ($is_cto_id[$matches[2]]) {
	return "http://$ORIG_SERVER_NAME/$matches[1]";
    }
    return $matches[0];
}

function convert_URLs_cto($text) {
    $textarr = preg_split("/(<.*>)/U", $text, -1, PREG_SPLIT_DELIM_CAPTURE);
    $stop = count($textarr);
    for ($i = 0; $i < $stop; $i++) {
	$content = $textarr[$i];
	if (strlen($content) > 0) {
	    $content = preg_replace_callback(
		    '@http://www.gcd.org/blog/(\d+/\d+/(\d+)|category/cto/)@',
		    'replace_URL_cto', $content);
	}
	$output .= $content;
    }
    return $output;
}
</pre>
<p>
顔文字を画像に変換して表示するフィルタ wptexturize 
(wp-includes/formatting.php で定義されている) を参考にさせてもらった。
preg_replace_callback() を使って書き換え対象リンクを探し、
replace_URL_cto で記事ID がCTO日記カテゴリに属す
($is_cto_id[$matches[2]] が TRUE)
場合のみ書き換える。
</p>
<p>
最後に、
この書き換えフィルタ replace_URL_cto を前述したフィルタフックに追加:
</p>
<pre class="code">
add_filter('the_content', 'convert_URLs_cto');
add_filter('the_category', 'convert_URLs_cto');
add_filter('get_pagenum_link', 'convert_URLs_cto');
add_filter('post_link', 'convert_URLs_cto');
add_filter('widget_text', 'convert_URLs_cto');
add_filter('wp_list_categories', 'convert_URLs_cto', 12000);
add_filter('category_feed_link', 'convert_URLs_cto');
</pre>
<p>
wp_list_categories にフィルタを追加すると
<a href="http://www.coppit.org/code/">Category Order プラグイン</a>と衝突するので、
優先順位を下げて Category Order プラグインの後で実行されるようにしている。
</p>
<p>
また、
the_category, post_link, widget_text, category_feed_link 各フックは、
2つ以上の引数を持つが、
第1引数 (書き換え対象の HTML あるいは URL) 
のみ使用するので引数の数 (add_filter の第3引数) を省略している。
</p>]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2010/02/540/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>apache httpd が 「(22)Invalid argument: alloc_listener: failed to get a socket for (null)」 エラーを出して立ち上がらない理由</title>
		<link>http://www.gcd.org/blog/2009/12/181/</link>
		<comments>http://www.gcd.org/blog/2009/12/181/#comments</comments>
		<pubDate>Thu, 03 Dec 2009 22:24:16 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2009/12/181/</guid>
		<description><![CDATA[私は普段持ち歩いているノートPC (レッツノート CF-R7) で coLinux を常用している。 Windows マシンで Linux を使いたい場合、 QEMU や VMware などの PC エミュレータ (完全仮想化) を用いる方法もあるが、 coLinux などの準仮想化の方がパフォーマンス的に有利なので、 日常的に使用する (私の場合、Windows マシンを使っていながらほとんどの作業は Linux の中で完結させている) なら coLinux の方が便利だと思う。 もちろん、 準仮想化であるから標準的なカーネルをそのまま走らせることはできず、 パッチをあてる必要がある。 残念ながら最新カーネル用のパッチはまだ作られていないようで、 現時点では Linux kernel 2.6.26.8 用のパッチが最新。 私は自分で管理している Linux マシン (自宅と職場合わせて 10台以上ある) は、 この coLinux なマシンも含めてハードディスク (正確に言えば Linux のパーティション) の内容を同一にしている。 すなわち、 マスタマシン (senri.gcd.org) の内容を定期/不定期的に rsync [...]]]></description>
			<content:encoded><![CDATA[<p>
私は普段持ち歩いているノートPC (レッツノート CF-R7) で
<a href="http://www.colinux.org/">coLinux</a> を常用している。
Windows マシンで Linux を使いたい場合、
QEMU や VMware などの PC エミュレータ (完全仮想化) を用いる方法もあるが、
coLinux などの準仮想化の方がパフォーマンス的に有利なので、
日常的に使用する
(私の場合、Windows マシンを使っていながらほとんどの作業は
Linux の中で完結させている)
なら coLinux の方が便利だと思う。
</p>
<p>
もちろん、
準仮想化であるから標準的なカーネルをそのまま走らせることはできず、
<a href="http://colinux.svn.sourceforge.net/viewvc/colinux/branches/devel/patch/">パッチ</a>をあてる必要がある。
残念ながら最新カーネル用のパッチはまだ作られていないようで、
現時点では Linux kernel
<a href="http://www.henrynestler.com/">2.6.26.8 用のパッチ</a>が最新。
</p>
<p>
私は<a href="http://www.gcd.org/sengoku/machine.ja.html">自分で管理している Linux マシン</a>
(自宅と職場合わせて 10台以上ある) は、
この coLinux なマシンも含めてハードディスク
(正確に言えば Linux のパーティション) の内容を同一にしている。
すなわち、
マスタマシン (senri.gcd.org) の内容を定期/不定期的に
rsync を使って各マシンへ同期させている。
マスタマシンのカーネルは Linux 2.6.31.6 なので、
マスタでビルドしたソフトウェアの中には、
Linux 2.6.26.8 ベースである coLinux 環境で動かないものも当然でてくる。
</p>
<p>
先日 <a href="http://httpd.apache.org/">apache httpd</a> 2.2.14 を
(マスタマシンで) ビルドしたら、
coLinux 環境で動かなかった:
</p>
<pre class="terminal">
# uname -a
Linux ikeda.gcd.org 2.6.26.8-co-0.8.0 #1 PREEMPT Sat Nov 14 19:23:55 JST 2009 i686 GNU/Linux
# /usr/apache2/bin/httpd -t
[Sun Nov 29 17:13:41 2009] [crit] (22)Invalid argument: alloc_listener: failed to get a socket for (null)
Syntax error on line 32 of /usr/apache2/conf/httpd.conf:
Listen setup failed
</pre>
<p>
「Syntax error on line 32」 ということだが
httpd.conf の 32行目は、
</p>
<pre class="code">
Listen 80
</pre>
<p>
となっているので、
少なくとも 「Syntax error」 ではないことは明らか。
「Listen 80」 の代りに 「Listen localhost:80」 などと書けば、
「failed to get a socket for (null)」 というエラーメッセージが
「failed to get a socket for localhost」 に変わる。
</p>
<p>
このエラーメッセージを手がかりに apache httpd 2.2.14 のソースを探すと、
エラーを出しているのは server/listen.c の以下の部分:
</p>

<span id="more-181"></span>
<pre class="code">
        status = apr_socket_create(&amp;new->sd, new->bind_addr->family,
                                    SOCK_STREAM, 0, process->pool);
	...
        if (status != APR_SUCCESS) {
            ap_log_perror(APLOG_MARK, APLOG_CRIT, status, process->pool,
                          "alloc_listener: failed to get a socket for %s",
                          addr);
            return "Listen setup failed";
        }
</pre>
<p>
つまり <a href="http://apr.apache.org/docs/apr/1.3/group__apr__network__io.html#g46b81e267ef80e64510d4e83880a91bd">apr_socket_create</a> 関数でソケットを生成しようとしてエラーが起きている。
APR つまり <a href="http://apr.apache.org/">Apache Portable Runtime</a>
の中でのエラー。
真面目に APR のソースを追ってもいいのだが、
エラーメッセージに 22 (= EINVAL) と出ていることから
<a href="http://www.linux.or.jp/JM/html/LDP_man-pages/man2/socket.2.html">socket</a>(2)
システムコールが EINVAL
(Unknown protocol, or protocol family not available)
エラーを返しているのだろうとあたりをつけて、
strace コマンドを使って手っ取り早く確認してみた:
</p>
<pre class="terminal">
# strace /usr/apache2/bin/httpd -t
execve("/usr/apache2/bin/httpd", ["/usr/apache2/bin/httpd", "-t"], [/* 28 vars */]) = 0
brk(0)                                  = 0x80a6000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/usr/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=66428, ...}) = 0
old_mmap(NULL, 66428, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7ffe000
close(3)                                = 0
open("/usr/lib/libm.so.6", O_RDONLY)  = 3
	...
socket(PF_INET, 0x80001 /* SOCK_??? */, IPPROTO_IP) = -1 EINVAL (Invalid argument)
	...
</pre>
<p>
確かに socket(2) が EINVAL を返している。
socket の第1, 3 引数はエラーになりようがないので、
第2 引数 「0x80001」 が原因だろう。
この値は、
「SOCK_STREAM | SOCK_CLOEXEC」 という意味なので、
Linux 2.6.26.8 の socket(2) が 「SOCK_CLOEXEC」
フラグをサポートしていないのだろうと推測できる。
実際、
カーネルのソースを確認してみると
SOCK_CLOEXEC をサポートしているのは Linux 2.6.27 以降だった。
</p>
<p>
CLOEXEC というのは close-on-exec の意味。
ファイルディスクリプタには FD_CLOEXEC フラグを設定できて、
このフラグが立っているファイルディスクリプタは、
exec を行なうと自動的にクローズされる。
fcntl(2) でこのフラグの設定/解除ができるが、
SOCK_CLOEXEC を付けて socket(2) を呼び出すと、
ソケットをオープンすると同時に
FD_CLOEXEC フラグを設定できる。
<a href="http://www.linux.or.jp/JM/html/LDP_man-pages/man2/open.2.html">open</a>(2)
でも同様に O_CLOEXEC を付けることができる
(Linux 2.6.23 以降)。
</p>
<blockquote>
open() システムコールと fcntl(FD_CLOEXEC) とが、
2つの手順に分離されていることが問題だといいます。
もし 「open」 と 「fcntl」 という 2つの処理のすき間で、
別スレッド、またはシグナルハンドラから fork が呼ばれると、
ファイルディスクリプタが閉じられずにリークしてしまいます。
もし、 exec したプログラムに脆弱性が含まれていた場合、
親プロセスがオープンしていたファイルまでもが危険にさらされることになります。
<br />
...(中略)...<br />
Linux においては、
open 以外にも新規ファイルディスクリプタを作成して返却するシステムコールは大量にあります。
そのすべてにおいて、
フラグ引数に 「XX_CLOEXEC フラグ」 を追加する、
またはフラグ引数を追加した新システムコールを新設するという修正が行われました。
<div align="right"><a href="http://www.atmarkit.co.jp/flinux/rensai/watch2008/watch08a.html">ファイルディスクリプタリークを防げ！</a> から引用</div>
</blockquote>
<p>
というわけでエラーの原因は分かったが、
どうやって対策したものか。
coLinux が 2.6.27 以降をサポートするのはまだ先のことになりそうだし、
かといって SOCK_CLOEXEC 付で socket(2)
がエラーを返さないようにする小手先パッチを作るのは、
他の全てのプログラムに影響するのであまりよろしくない
(実は SOCK_CLOEXEC が付いていてもエラーにしないパッチも作ってみたのだが、
APR は他にも Linux 2.6.31.6 ではサポートされているが
Linux 2.6.26.8 ではサポートされていない機能を使ってるらしく、
httpd の子プロセスがゾンビになってしまった)。
</p>
<p>
というわけで、
Linux 2.6.26.8 (つまり coLinux 環境) 下で APR ライブラリをビルドし直し、
得られた libapr-1.so を /usr/lib/colinux ディレクトリに入れておいて、
coLinux 環境ではこのディレクトリを
LD_LIBRARY_PATH を指定して apache httpd を実行することにした。
</p>
<pre class="terminal">
ikeda:/usr/local/src/apr-1.3.9 % ./configure
	...
checking for epoll_create1 support... no
checking for dup3 support... no
checking for accept4 support... no
checking for SOCK_CLOEXEC support... no
	...
ikeda:/usr/local/src/apr-1.3.9 % make
ikeda:/usr/local/src/apr-1.3.9 % fg
# cp -d /usr/local/src/apr-1.3.9/.libs/*.so* /usr/lib/colinux/.
# ls -l /usr/lib/colinux
total 572
lrwxrwxrwx 1 root root     17 Dec  3 08:46 libapr-1.so -> libapr-1.so.0.3.9
lrwxrwxrwx 1 root root     17 Dec  3 08:46 libapr-1.so.0 -> libapr-1.so.0.3.9
-rwxr-xr-x 1 root root 578999 Dec  3 08:46 libapr-1.so.0.3.9
# LD_LIBRARY_PATH=/usr/lib/colinux /usr/apache2/bin/httpd
</pre>
<p>
無事 coLinux 環境においても httpd サーバを実行することができた。
</p>
<p>
なお、
<a href="http://www.linux.or.jp/JM/html/LDP_man-pages/man2/epoll_create.2.html">epoll_create1</a>(2),
<a href="http://www.linux.or.jp/JM/html/LDP_man-pages/man2/dup.2.html">dup3</a>(2),
<a href="http://www.linux.or.jp/JM/html/LDP_man-pages/man2/accept.2.html">accept4</a>(2)
は、
それぞれ
epoll_create(2),
dup2(2),
accept(2)
に int flags 引数を追加し、
close-on-exec フラグを設定できるようにした Linux 独自の拡張。
いずれも Linux 2.6.26 以前ではサポートされない。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2009/12/181/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>__sync_bool_compare_and_swap_4 とは何か？ ～ glibc をビルドする場合は、 gcc の &#8211;with-arch=i686 configure オプションを使ってはいけない</title>
		<link>http://www.gcd.org/blog/2009/10/179/</link>
		<comments>http://www.gcd.org/blog/2009/10/179/#comments</comments>
		<pubDate>Thu, 08 Oct 2009 00:39:55 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[システム構築・運用]]></category>
		<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2009/10/179/</guid>
		<description><![CDATA[glibc-2.10.1 をビルドしようとしたら、 「__sync_bool_compare_and_swap_4 が定義されていない」 というエラーが出た: senri:/usr/local/src/glibc-2.10.1.i386 % ../glibc-2.10.1/configure ... senri:/usr/local/src/glibc-2.10.1.i386 % make ... /usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `__libc_fork': /usr/local/src/glibc-2.10.1/posix/../nptl/sysdeps/unix/sysv/linux/i386/../fork.c:79: undefined reference to `__sync_bool_compare_and_swap_4' /usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `__nscd_drop_map_ref': /usr/local/src/glibc-2.10.1/nscd/nscd-client.h:320: undefined reference to `__sync_fetch_and_add_4' ... /usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `*__GI___libc_freeres': /usr/local/src/glibc-2.10.1/malloc/set-freeres.c:39: undefined reference to `__sync_bool_compare_and_swap_4' collect2: ld returned 1 exit status make[1]: *** [/usr/local/src/glibc-2.10.1.i386/libc.so] Error 1 make[1]: Leaving directory [...]]]></description>
			<content:encoded><![CDATA[<p>
glibc-2.10.1 をビルドしようとしたら、
「__sync_bool_compare_and_swap_4 が定義されていない」
というエラーが出た:
</p>
<pre class="terminal">
senri:/usr/local/src/glibc-2.10.1.i386 % ../glibc-2.10.1/configure
	...
senri:/usr/local/src/glibc-2.10.1.i386 % make
	...
/usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `__libc_fork':
/usr/local/src/glibc-2.10.1/posix/../nptl/sysdeps/unix/sysv/linux/i386/../fork.c:79: undefined reference to `__sync_bool_compare_and_swap_4'
/usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `__nscd_drop_map_ref':
/usr/local/src/glibc-2.10.1/nscd/nscd-client.h:320: undefined reference to `__sync_fetch_and_add_4'
	...
/usr/local/src/glibc-2.10.1.i386/libc_pic.os: In function `*__GI___libc_freeres':
/usr/local/src/glibc-2.10.1/malloc/set-freeres.c:39: undefined reference to `__sync_bool_compare_and_swap_4'
collect2: ld returned 1 exit status
make[1]: *** [/usr/local/src/glibc-2.10.1.i386/libc.so] Error 1
make[1]: Leaving directory `/usr/local/src/glibc-2.10.1'
make: *** [all] Error 2
</pre>
<p>
__sync_bool_compare_and_swap_4 は gcc の組み込み関数なので、
関数が未定義であることを示す
「undefined reference to ...」 というエラーメッセージは、
誤解を招く不親切なメッセージだと思う。
</p>
<p>
__sync_bool_compare_and_swap_4(mem, oldval, newval) は、
mem が指し示すメモリの値 (4バイト分) が oldval であれば newval に変更する、
という操作をアトミックに行なう組み込み関数。
<a href="http://ja.wikipedia.org/wiki/%E4%B8%8D%E5%8F%AF%E5%88%86%E6%93%8D%E4%BD%9C">アトミック (不可分) 操作</a>とは、
操作の途中が存在してはいけない操作のことで、
この例なら比較 (メモリの値が oldval か?)
と代入 (newval に変更) が必ず 「いっぺん」 に行なわれ、
「比較だけ行なったけどまだ代入は行なわれていない」
という状態が存在しないことを意味する。
</p>
<p>
アトミックに行なうためには、
当然ながら CPU でその操作をサポートしている必要がある
(複数個の命令の列で実現しようとすると、
命令列の半ばを実行中の状態が必ず存在してしまう)
わけだが、
残念ながら Intel 386 プロセッサでは、
この compare_and_swap
(<a href="http://faydoc.tripod.com/cpu/cmpxchg.htm">CMPXCHG 命令</a>)
をサポートしておらず、
サポートするのは Intel 486 以降の CPU である。
テストプログラムを書いて試してみる:
</p>
<pre class="code">
#include &lt;stdio.h&gt;

int main() {
    int mem[1], oldval, newval;
    oldval=0;
    newval=1;
    mem[0] = 0;
    __sync_bool_compare_and_swap(mem, oldval, newval);
    printf("mem[0]=%d\n", mem[0]);
    return 0;
}
</pre>
<p>
見ての通り、
mem[0] の値を oldval の値 (0) と比較し、
一致していたら newval の値 (1) を代入し、
mem[0] の値を表示するだけのプログラムである。
</p>
<p>
関数名が 「__sync_bool_compare_and_swap」 であって、
後ろに 「_4」 がついていないことに注意。
gcc が引数の型 (この例では int) を見て、
その型のビット長を後ろにつけてくれる
(この例では int 型は 4 バイトなので 「_4」 をつけてくれる)。
</p>
<p>
gcc では 「-march=タイプ」 オプションを指定することによって
CPU タイプを指定できる。
-march オプションを指定しなかったり
(この場合は全 CPU でサポートされる組み込み関数のみ利用できる)、
あるいは -march=i386 を指定したりすると、
コンパイル時にエラーになる:
</p>
<pre class="terminal">
% gcc -Wall test.c
/tmp/cc4eNX6L.o: In function `main':
test.c:(.text+0x3b): undefined reference to `__sync_bool_compare_and_swap_4'
collect2: ld returned 1 exit status
% gcc -Wall -march=i386 test.c
/tmp/cc6chtFj.o: In function `main':
test.c:(.text+0x36): undefined reference to `__sync_bool_compare_and_swap_4'
collect2: ld returned 1 exit status
% gcc -Wall -march=i486 test.c
% ./a.out
mem[0]=1
</pre>
<p>
いまさら i486 というのもアレなので、
今なら i686 を指定するのがよさげ。
私の手元にはいまだ PentiumIII マシンがあるものの、
PentiumIII より古いマシンはない
(昨年 <a href="http://www.gcd.org/blog/2008/03/151/">ML115 と SC440 を買った</a>とき
<a href="http://www.gcd.org/sengoku/machine.ja.html#ASAO1">PentiumII マシン</a>を引退させた)
ので、
pentium3 を指定すれば
SSE (Streaming SIMD Extensions) が利用できるようになるが、
glibc をビルドするときに必要かというと、
たぶん必要ない。
</p>
<p>
というわけでエラーの原因は分かったが、
では glibc をビルドするときは、
どうすればいいだろうか？
</p>
<p>
とりあえず google で検索してみたら、
gcc の configure オプションに
「--with-arch=i686」
を指定して gcc をビルドする必要がある、
と書いてあるページが見つかった。
</p>
<p>
--with-arch オプションは、
-march のデフォルトを設定するための configure オプションである。
つまり 「--with-arch=i686」 を指定して gcc を再インストールすると、
gcc に -march オプションをつけなくてもデフォルトが i686 になる。
なるほど確かにそうすれば、
glibc 側で何も変更せずに
 __sync_bool_compare_and_swap_4 関数が使えるようになりそうである。
</p>
<p>
いまどき i686 以前の
CPU 用のコードが必要になりそうなケースは滅多にないだろうから、
-march オプションのデフォルトを i686 にするのも悪い選択ではないように思えた。
gcc をビルドし直すのは面倒だなーと思いつつも、
ついでに gcc のバージョンを上げておこうと
gcc-4.3.4 をダウンロードしてきて  「--with-arch=i686」 付でビルドしてみた。
</p>
<p>
ところが！
</p>

<span id="more-179"></span>
<p>
「--with-arch=i686」付でビルドした gcc を使って
glibc をビルドしようとすると、
先ほどコンパイラエラーが出た場所より前の段階で、
アセンブラがエラーを出力して make が止まってしまった:
</p>

<pre class="terminal">
senri:/usr/local/src/glibc-2.10.1.i386 % make
	...
../sysdeps/i386/fpu/s_frexp.S: Assembler messages:
../sysdeps/i386/fpu/s_frexp.S:66: Error: invalid identifier for ".ifdef"
../sysdeps/i386/fpu/s_frexp.S:66: Error: junk at end of line, first unrecognized character is `1'
	...
../sysdeps/i386/fpu/s_frexp.S:66: Error: ".endif" without ".if"
../sysdeps/i386/fpu/s_frexp.S:66: Error: junk `.get_pc_thunk.dx' after expression
make[2]: *** [/usr/local/src/glibc-2.10.1.i386/math/s_frexp.os] Error 1
make[2]: Leaving directory `/usr/local/src/glibc-2.10.1/math'
make[1]: *** [math/subdir_lib] Error 2
make[1]: Leaving directory `/usr/local/src/glibc-2.10.1'
make: *** [all] Error 2
</pre>
<p>
こちら (コンパイラ側) を立てれば、あちら (アセンブラ側) が立たず、
な二律背反状態。
</p>
<p>
「invalid identifier for ".ifdef"」
というエラーメッセージが (ぱっと見には) 意味不明である。
続いて
「junk at end of line, first unrecognized character is `1'」
と言っているから、
「.ifdef 1」 みたいなコードなのだろうか？
エラーが起きた
sysdeps/i386/fpu/s_frexp.S の 66行目あたりは以下のようになっている。
LOAD_PIC_REG の行が問題の 66行目:
</p>
<pre class="code">
	cmpl	$0x00100000, %eax
	jae	2f

	fldl	VAL0(%esp)
#ifdef	PIC
	LOAD_PIC_REG (dx)
#endif
	fmull	MO(two54)
	movl	$-54, %ecx
	fstpl	VAL0(%esp)
	fwait
	movl	VAL1(%esp), %eax
	movl	%eax, %edx
	andl	$0x7fffffff, %eax
</pre>
<p>
おそらく LOAD_PIC_REG マクロを展開した結果がおかしいのだろうと、
LOAD_PIC_REG マクロの定義 (sysdeps/i386/sysdep.h) を調べてみる:
</p>
<pre class="code">
# define SETUP_PIC_REG(reg) \
  .ifndef __i686.get_pc_thunk.reg;					      \
  .section .gnu.linkonce.t.__i686.get_pc_thunk.reg,"ax",@progbits;	      \
  .globl __i686.get_pc_thunk.reg;					      \
  .hidden __i686.get_pc_thunk.reg;					      \
  .type __i686.get_pc_thunk.reg,@function;				      \
__i686.get_pc_thunk.reg:						      \
  movl (%esp), %e##reg;							      \
  ret;									      \
  .size __i686.get_pc_thunk.reg, . - __i686.get_pc_thunk.reg;		      \
  .previous;								      \
  .endif;								      \
  call __i686.get_pc_thunk.reg

# define LOAD_PIC_REG(reg) \
  SETUP_PIC_REG(reg); addl $_GLOBAL_OFFSET_TABLE_, %e##reg
</pre>
<p>
プリプロセッサの出力を確認してみると、
66行目は次のようにマクロ展開されている:
</p>
<pre class="code">
 .ifndef 1 .get_pc_thunk.dx; .section .gnu.linkonce.t. 1 .get_pc_thunk.dx,"ax",@progbits; .globl 1 .get_pc_thunk.dx; .hidden 1 .get_pc_thunk.dx; .type 1 .get_pc_thunk.dx,@function; 1 .get_pc_thunk.dx: movl (%esp), %edx; ret; .size 1 .get_pc_thunk.dx, . - 1 .get_pc_thunk.dx; .previous; .endif; call 1 .get_pc_thunk.dx; addl $_GLOBAL_OFFSET_TABLE_, %edx
</pre>
<p>
確かに、いきなり
「.ifndef 1」 となっているので、
エラーになるのは明らか。
上記 SETUP_PIC_REG マクロ定義内の
「__i686」 がことごとく 「1」 に展開されていて、
意味不明なコードになってしまっている。
</p>
<p>
というわけで、
原因に思い当たった。
プリプロセッサで自動的に定義されるマクロを表示させてみる:
</p>
<pre class="terminal">
% gcc -E -dM -x c /dev/null
#define __DBL_MIN_EXP__ (-1021)
#define __pentiumpro__ 1
#define __FLT_MIN__ 1.17549435e-38F
#define __DEC64_DEN__ 0.000000000000001E-383DD
#define __CHAR_BIT__ 8
	...
#define __i686 1
	...
#define __i686__ 1
	...
</pre>
<p>
「#define __i686 1」 だから当然そういうマクロ展開が行なわれる、
というわけ。
gcc と glibc では違うソフトウェアであるとはいえ、
gcc のプリプロセッサで自動的に定義される
「#define __i686 1」 とバッティングするようなラベル
「__i686.get_pc_thunk.reg」
をマクロ中で使うというのは、
いかがなものか。＞ glibc
</p>
<p>
「--with-arch=i686」付でビルドした gcc,
あるいは 「-march=i686」 オプション付で実行した gcc は、
-march を指定しない gcc と比べて、
プリプロセッサにおいて以下の定義が追加されるようだ:
</p>
<pre class="terminal">
#define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_1 1
#define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_2 1
#define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_4 1
#define __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 1
#define __i686 1
#define __i686__ 1
#define __pentiumpro 1
#define __pentiumpro__ 1
</pre>
<p>
ちなみに、
SETUP_PIC_REG マクロ定義内で定義されている __i686.get_pc_thunk.reg は、
eip の値をレジスタ reg へ代入するための関数 (というかサブルーチン)。
サブルーチン名の末尾の 「reg」 はマクロ引数で置換される。
この例では dx。
つまり SETUP_PIC_REG マクロを展開することにより
__i686.get_pc_thunk.dx というサブルーチンが定義される。
</p>
<p>
そして、
このサブルーチンを呼び出す (call __i686.get_pc_thunk.dx) と、
スタックトップをレジスタへ代入 (movl (%esp), %edx) して復帰 (ret) する。
つまりプログラムカウンタ (スタックトップに積まれた戻りアドレス)
の値がレジスタ edx へ代入される、というわけ。
</p>
<p>
続いて、
edx に $_GLOBAL_OFFSET_TABLE_ を加算することにより
edx にグローバル変数領域へのポインタが代入される
(addl $_GLOBAL_OFFSET_TABLE_, %edx)。
</p>
<blockquote>
__i686.get_pc_thunk.bx は、
gccが用意してくれる関数で、
PC (プログラムカウンタ、EIP レジスタの値) を ebx にコピーします。<br />
　(中略)<br />
なんでここで PC が必要かというと、
グローバル変数にアクセスしたいからです。
PIC/PIE の場合、
実行時に自分自身 (ELF バイナリ)
がどの仮想アドレスに mmap されるかはわかりませんので、
アドレス決め打ちで変数にアクセスはできません。
でも、
いま実行している命令 (61d:) から変数までのオフセットは
*.o をリンクして so にした時点で判明します
(so ファイルのナカミがほぼそのままメモリに貼られるわけなんで)。<br />
　(中略)<br />
mov 0xほげ(%eip) ふが; とか出来れば一番いいんですが(x86_64はできる)、
x86はそういうPC相対のアドレス指定はできません。
というか、eipを直接的に得ることすら出来ません。
なんで、仕方なく一度call命令を発行して、
リターンアドレス（callを発行した命令の次の命令のアドレス）
をスタック上に自動push させて、
__i686.get_pc_thunk.bx内でebxレジスタにpopしてます。
x86だと、PIC/PIEなコードでグローバル変数を触るだけで、
関数呼び出しと同じようなコストがかかるんざますよ！奥様。
<div align="right"><a href="http://d.hatena.ne.jp/yupo5656/20060907">memologue 2006-09-07</a> から引用</div>
</blockquote>
<p>
glibc でどんなグローバル変数にアクセスしているのかと思って、
ちょっと調べてみた:
</p>
<p>
この sysdeps/i386/fpu/s_frexp.S は、
そのファイル名の通り
<a href="http://www.linux.or.jp/JM/html/LDP_man-pages/man3/frexp.3.html">frexp(3)</a>
のアセンブリ言語による実装。
C による実装は sysdeps/ieee754/dbl-64/s_frexp.c にある。
ディレクトリ名から分かるように、
<a href="http://ja.wikipedia.org/wiki/IEEE_754">IEEE 754</a>
(IEEE 浮動小数点数演算標準) に基づいている。
</p>
<p>
double frexp(double x, int *exp) は、
浮動小数点実数 x を正規化小数と指数に分解し、
指数を *exp に格納した上で正規化小数を返す。
浮動小数点実数 x が<a href="http://ja.wikipedia.org/wiki/%E9%9D%9E%E6%AD%A3%E8%A6%8F%E5%8C%96%E6%95%B0">非正規化数</a>であるとき
frexp は x に 2^54 を乗じた上で *exp に -54 を返すのだが、
この 2^54 = 18014398509481984 という定数を double 型で保持するために、
グローバル変数 two54 を使用している。
</p>

<p>
話が脱線したので元に戻すと、
要は
「--with-arch=i686」 付でビルドした gcc だと、
C のソースだろうとアセンブリ言語で書かれたソースだろうと見境なく
「#define __i686 1」 を定義してしまうので、
相手が C のソースの時のみ 「-march=i686」 を付けて gcc を実行すればよい。
つまり、
gcc は 「--with-arch=i686」 付でビルドしてはいけない。
</p>
<p>
ではどうすればいいか？
いろいろ方法はあると思うが、
glibc をビルドするときに CFLAGS を
「CFLAGS="-g -O2 -march=i686"」 などと指定して
configure を実行するのも一つの解決策。
CFLAGS は C のソースをコンパイルするときだけ使われて、
アセンブルするときには使われないので、
前述したようなコンパイラ/アセンブラ二律背反問題を回避できる。
というわけで、
無事 glibc 2.10.1 をビルドすることができた:
</p>
<pre class="terminal">
senri:/usr/local/src/glibc-2.10.1.i386 % ../glibc-2.10.1/configure CFLAGS="-g -O2 -march=i686"
	...
senri:/usr/local/src/glibc-2.10.1.i386 % make
	...
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2009/10/179/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>freeRADIUS 2.1.3 のバグ: ログを stdout/stderr へ出力できない</title>
		<link>http://www.gcd.org/blog/2008/12/165/</link>
		<comments>http://www.gcd.org/blog/2008/12/165/#comments</comments>
		<pubDate>Mon, 08 Dec 2008 23:02:28 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2008/12/165/</guid>
		<description><![CDATA[無線LAN の脆弱性について警告が飛び交う昨今、 WPA2 (Wi-Fi Protected Access) といえど、 パーソナル (PSK, Pre-Shared Key) モードだとパスワード破りの可能性が 無いわけでも無いので、 エンタープライズ (EAP, Extensible Authentication Protocol) モードに乗り換えてみた。 EAP (社員支援プログラムではなくて、 拡張認証プロトコル) の認証方式には EAP-MD5, EAP-FAST, EAP-SKE, EAP-SRP, MS-CHAP, EAP-GTC, EAP-GTC, Cisco LEAP, EAP-TLS, EAP-TTLS, PEAP, EAP-MAKE, EAP-SIM などがあるが、 対応機器/ソフトウェアが多そうな EAP-TLS を使ってみることにした。 EAP-TLS とは、 TLS (Transport Layer Security) すなわち SSL (Secure Sockets Layer) のサーバ認証とクライアント認証を行なって、 RADIUS サーバと無線LAN [...]]]></description>
			<content:encoded><![CDATA[<p>
無線LAN の脆弱性について警告が飛び交う昨今、
WPA2 (Wi-Fi Protected Access) といえど、
パーソナル (PSK, Pre-Shared Key) モードだとパスワード破りの可能性が
無いわけでも無いので、
エンタープライズ (EAP, Extensible Authentication Protocol)
モードに乗り換えてみた。
</p>
<p>
EAP (<a href="http://eapaj.umin.ac.jp/">社員支援プログラム</a>ではなくて、
拡張認証プロトコル)
の認証方式には
EAP-MD5, EAP-FAST, EAP-SKE, EAP-SRP, MS-CHAP, EAP-GTC, EAP-GTC, Cisco LEAP,
EAP-TLS, EAP-TTLS, PEAP, EAP-MAKE, EAP-SIM
などがあるが、
対応機器/ソフトウェアが多そうな EAP-TLS を使ってみることにした。
EAP-TLS とは、
TLS (Transport Layer Security) すなわち
SSL (Secure Sockets Layer) のサーバ認証とクライアント認証を行なって、
RADIUS サーバと無線LAN 端末が相互に認証を行なう仕掛けである。
</p>
<p>
RADIUS サーバとしては、
<a href="http://freeradius.org/">free RADIUS</a> 2.1.3 を使用した。
<a href="http://www.gcd.org/sengoku/docs/NikkeiLinux00-09/SSL.ja.html">自前の認証局</a>
でサーバ証明書とクライアント証明書を発行し、
それぞれ RADIUS サーバと無線LAN 端末 (Windows マシン) へインストールする
(自分で発行する証明書だが、
認証する側が自分の管理下なので、
いわゆる「オレオレ証明書」ではない)。
</p>
<p>
まず radiusd をデバッグ モードで走らせてみる:
</p>
<pre class="terminal">
# radiusd -X
FreeRADIUS Version 2.1.3, for host i686-pc-linux-gnu, built on Dec  6 2008 at 17:50:58
Copyright (C) 1999-2008 The FreeRADIUS server project and contributors. 
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A 
PARTICULAR PURPOSE. 
You may redistribute copies of FreeRADIUS under the terms of the 
GNU General Public License v2. 
Starting - reading configuration files ...
including configuration file /usr/local/etc/raddb/radiusd.conf
including configuration file /usr/local/etc/raddb/clients.conf
including configuration file /usr/local/etc/raddb/eap.conf
group = radius
user = radius
including dictionary file /usr/local/etc/raddb/dictionary
	...(中略)...
Listening on authentication address * port 1812
Ready to process requests.
</pre>
<p>
とりあえず動いているようだ。
Windows マシンからアクセスポイントへ接続してみると、
アクセスポイントを介して Windows マシンと RADIUS サーバ間で、
TLS サーバ/クライアント認証が行なわれ、
無事 WPA2 エンタープライズ モードで接続が完了した。
</p>
<p>
では、radiusd を
<a href="http://cr.yp.to/daemontools.html">daemontools</a> 配下で動かそうと、
次のような /service/radius/run スクリプトを書いて動かしてみる:
</p>
<pre class="code">
#!/bin/sh
export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin
exec 2>&amp;1
exec radiusd -fxxx
</pre>
<p>
-x を指定して詳細なデバッグ情報を出力させるようにする。
daemontools 配下で動かす場合、
multilog プログラムがログをどんどんローテートしてくれるので、
通常運用でもデバッグ情報を出力させておける。
ログを標準出力 (stdout) へ出力させるため、
設定ファイル radiusd.conf において、
次のように指定しておく。
</p>
<pre class="code">
log {
	destination = stdout
}
</pre>
<p>
これでログが /service/radius/log/main/current に書き出されるはず、
と思ったら何も出力されない... 何故に...?
</p>
<p>
radiusd(8) によれば、-X オプションは 「-sfxx -l stdout」 と等価らしい。
-s オプションは、
RADIUS サーバを単一スレッド/プロセスで走らせるための指定。
個人で使う分には単一スレッドでも構わないといえば構わないので、
ログを stdout に出力する目的で -X オプションを使ってしまっても構わないのだが、
せっかくだからともうちょっと追ってみることにした。
</p>
<p>
まず上記 run スクリプトにおいて 「-l stdout」 を指定してみる。
すると、/service/radius ディレクトリに stdout というファイルができて、
そこにログが出力された。
ダメだこりゃ。
-l オプションはマニュアルには記載されていないので、
-l に続く
「stdout」 をファイル名と見なすのも一つの「仕様」と言えなくもないが...
</p>
<p>
ソース radiusd.c を見てみると、
確かに -l オプションの処理では続く引数をファイル名としてしか扱っていない。
では設定ファイル radiusd.conf に指定した場合はどうかと、
mainconfig.c を見てみる。
「log { ... }」 の中で 「destination = stdout」 を指定すると、
mainconfig.radlog_dest に RADLOG_STDOUT が代入されるようだ。
ところが、mainconfig.radlog_fd を設定するコードがない。
これでは stdout にログが出力されるはずがない。
</p>
<p>
「-l stdout」 の件は百万歩譲って「仕様」でも構わないが、
mainconfig.radlog_dest に RADLOG_STDOUT を代入しておきながら
mainconfig.radlog_fd に代入し忘れるのは、
仕様うんぬん以前にソースとして首尾一貫していないので、
明らかにバグである。
</p>
<p>
そこで以下のようなパッチをあてて、
mainconfig.radlog_dest が RADLOG_STDOUT あるいは RADLOG_STDERR のときは、
mainconfig.radlog_dest に STDOUT_FILENO あるいは STDERR_FILENO を
それぞれ代入するようにしてみた。
</p>
<pre class="code">
--- src/main/mainconfig.c~	2008-12-06 01:37:56.000000000 +0900
+++ src/main/mainconfig.c	2008-12-06 16:16:27.455277946 +0900
@@ -738,6 +738,10 @@
 				cf_section_free(&amp;cs);
 				return -1;
 			}
+		} else if (mainconfig.radlog_dest == RADLOG_STDOUT) {
+			mainconfig.radlog_fd = STDOUT_FILENO;
+		} else if (mainconfig.radlog_dest == RADLOG_STDERR) {
+			mainconfig.radlog_fd = STDERR_FILENO;
 		}
 	}
 
</pre>
<p>
これで無事、
ログが stdout に出力され、
/service/radius/log/main/current に書き出されるようになった。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2008/12/165/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Western Digital RMA チームから届いた文字化けメールを解読してみた</title>
		<link>http://www.gcd.org/blog/2008/07/159/</link>
		<comments>http://www.gcd.org/blog/2008/07/159/#comments</comments>
		<pubDate>Wed, 02 Jul 2008 22:36:11 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[ハードウェアの認識と制御]]></category>
		<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2008/07/159/</guid>
		<description><![CDATA[「故障した HDD WD10EACS を RMA (Return Merchandise Authorization, 返却承認) 手続きで交換してみた」で書いたように、 RMA 手続きを行なった上で Western Digital へ故障したハードディスク ドライブ (以下 HDD と略記) を送ったら、 激しく文字化けしたメールが送られてきた。 あとは HDD が送られてくるのを のんびり待つだけと思っていたら、 わずか一日後 6/26 18:44 に Western Digital からメールが来た。 しかし文字化けがひどくて読めない。 最初は何語で書いてあるかすら判然としなかったのだが、 どうやら Shift JIS で書かれた文面を quoted-printable エンコードする際に なにか問題があったようだ。 例えば 0x82 が「,」に、0x95 が「.」に置き換わってしまっている。 置換が規則的でないので、 暗号解読よろしく一文字一文字置き換え規則を推測していくしかない。 故障した HDD WD10EACS を RMA 手続きで交換してみた から引用 文面を再現するのに時間がかかりそうだなぁ～と思っている間に、 [...]]]></description>
			<content:encoded><![CDATA[<p>
「<a href="http://www.gcd.org/blog/2008/06/158/">故障した HDD WD10EACS を RMA (Return Merchandise Authorization, 返却承認) 手続きで交換してみた</a>」で書いたように、
RMA 手続きを行なった上で Western Digital へ故障したハードディスク ドライブ
(以下 HDD と略記) を送ったら、
激しく文字化けしたメールが送られてきた。
</p>
<blockquote>
あとは HDD が送られてくるのを
のんびり待つだけと思っていたら、
わずか一日後 6/26 18:44 に Western Digital からメールが来た。
しかし文字化けがひどくて読めない。
最初は何語で書いてあるかすら判然としなかったのだが、
どうやら Shift JIS で書かれた文面を quoted-printable エンコードする際に
なにか問題があったようだ。
例えば 0x82 が「,」に、0x95 が「.」に置き換わってしまっている。
置換が規則的でないので、
暗号解読よろしく一文字一文字置き換え規則を推測していくしかない。
<div align="right"><a href="http://www.gcd.org/blog/2008/06/158/">故障した HDD WD10EACS を RMA 手続きで交換してみた</a> から引用</div>
</blockquote>
<p>
文面を再現するのに時間がかかりそうだなぁ～と思っている間に、
交換品の HDD が届いてしまったので、
「暗号」解読するモチベーションを失ってしまっていたのだが、
</p>
<blockquote>
Posted by 通りすがり    2008年07月02日 00:36<br />
結局、メールにはなんて書いてあったのでしょうか？
<div align="right"><a href="http://www.gcd.org/blog/2008/06/158/#comments">同ページ コメント欄</a> から引用</div>
</blockquote>
<p>
というコメントを頂いてしまったので、
暗号解読してみることにした。
</p>
<p>
以下、Western Digital からの文字化けメールを全文引用 (一部伏字) する:
</p>
<pre class="code">
From: "Western Digital RMA" &lt;noreply@wdc.com>
To: &lt;sengoku@gcd.org>
Date: Thu, 26 Jun 2008 02:44:25 -0700
MIME-Version: 1.0
Content-Type: text/plain;
	charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
X-Mailer: Microsoft CDO for Windows 2000
Content-Class: urn:content-classes:message
X-MimeOLE: Produced By Microsoft MimeOLE V6.00.2800.1896
X-OriginalArrivalTime: 26 Jun 2008 09:44:25.0728 (UTC) FILETIME=[3861F800:01C8D771]

HIROAKI SENGOKU -l,=D6=81A

^=C8?=BA,=C9.\Z=A6,=B3,=EA,=BD RMA =
,=CCfXfe=81[f^fX,=F0Sm"F,=B5,=C4,=AD,=BE,=B3,=A2=81B  RMA
,=C9S=D6,=B7,=E9,=A8-=E2,=A2=8D?,=ED,=B9,=CD,=B1,=CCf=81=81[f&lt;,=C9.=D4=90=
M,=B5,=C4,=AD,=BE,=B3,=A2=81B
=8F=EE.=F1,=AA=90=B3,=B5,=A2=8F=EA=8D?=81A,=B1,=CC"dZqf=81=81[f&lt;,=C9,=CD.=
=D4=90M,=B5,=C8,=A2,=C5,=AD,=BE,=B3,=A2=81B


RMA "=D4=8D?=81F 8083XXXX

--------------------------------------------------------------

O=F0S=B7fhf?fCfu,=F0 5=81`7 ?c&lt;=C6"=FA'?,=C9"=AD'-,=B5,=DC,=B7=81B

^=C8?=BA,=CCfhf?fCfu,=F0 Western Digital =
,=CDZ=F3-=CC,=B5,=DC,=B5,=BD=81F

     fVfSfAf&lt;"=D4=8D?     =90=BB.i"=D4=8D?             =
Z=F3-=CC"=FA=81iGMT=81j
     ------------     ---------------      -------------
     WCASJxxxxxxx     WD10EACS-00ZJB0      6/25/2008

--------------------------------------------------------------

^=C8?=BA,=C9.\Z=A6,=B3,=EA,=BD RMA =
"=AD'-=8F=F3&lt;=B5,=F0Sm"F,=B5,=C4,=AD,=BE,=B3,=A2=81B

-A'-&lt;=C6Z=D2,=CCfVfXfef?,=CC=8DX=90V,=C91?c&lt;=C6"=FA,=AA,=A9,=A9,=E8=81A,=BB=
,=CCO=E3"=AD'-'=C7=90=D5"=D4=8D?,=AA-LO=F8,=C9,=C8,=E8,=DC,=B7,=CC,=C5=81=
A,=B2-=B9=8F=B3,=AD,=BE,=B3,=A2=81B

O=F0S=B7fhf?fCfu,=CC'-.t=90=E6=81F

     HIROAKI SENGOKU
     XXXXXXXXXXXXXXXXXXXXXXXXX TAKATSU
     KAWASAKI, Japan 213-XXXX
     JAPAN

"z'-&lt;=C6Z=D2=81F     Fedex
"z'-'=C7=90=D5"=D4=8D?=81F XXXXXXXXXXXX

     fVfSfAf&lt;"=D4=8D?     =90=BB.i"=D4=8D?             =
"=AD'-"=FA=81iGMT=81j
     ------------     ---------------      -------------
     WCASJXXXXXXX     WD10EACS-32ZJB0      6/26/2008

--------------------------------------------------------------

S=D6~AfSf"fN=81F
RMAZ=E8=8F?ZwZ=A6=8F=EE.=F1,=CC?{--/^=F3=8D=FC
  - =

http://websupport.wdc.com/rd.asp?t=3D102&amp;l=3Djp&amp;p=3Dm&amp;r=3D8083XXXX&amp;f=3De

"=AD'-,=C6=8D=AB.=EF,=CC=8F=EE.=F1
  - http://websupport.wdc.com/rd.asp?t=3D103&amp;l=3Djp&amp;p=3Drp

RMAfXfe=81[f^fX,=CC?{--
  - =

http://websupport.wdc.com/rd.asp?t=3D104&amp;l=3Djp&amp;p=3Dv&amp;r=3D8083XXXX&amp;z=3D21=

3-XXXX

Western Digital fTf|=81[fgfz=81[f?fy=81[fW
  - http://websupport.wdc.com/rd.asp?t=3D105&amp;l=3Djp&amp;p=3Dh

^=C8=8F=E3=81A
WD RMA f`=81[f?

http://websupport.wdc.com/rd.asp?t=3D105&amp;l=3Djp&amp;p=3Dh

</pre>
<p>
ヘッダに「quoted-printable」と書いてあるとおり、
quoted-printable エンコーディングを行なったのだろうが、
のっけから「<tt>^=C8?=BA,=C9.\Z=A6,=B3,=EA,=BD</tt>」となっていて、
一体何語なんだ？と思わせる始まり方である。
</p>
<blockquote>
ちなみに quoted-printable というのは 8bit データを、
「印字可能 (printable)」つまり 7bit の英数字・記号だけで表現するための方法
(エンコーディング) で、
印字可能でない 8bit データは 16進数で表わして前に「=」をつける
(「=」自身は「=3D」で表現する)。
例えば「<tt>^=C8?=BA,=C9.\Z=A6,=B3,=EA,=BD</tt>」は、
16進数で書くと
「<tt>5E C8 3F BA 2C C9 2E 5C 5A A6 2C B3 2C EA 2C BD</tt>」
という 8bit データ列を意味する。
</blockquote>
<p>
腕に覚えのあるかたは、解答を見ずに解読を試みてはいかがだろうか？
</p>

<span id="more-159"></span>
<p>
結論から言うと元のテキストは Shift JIS で書かれた日本語である。
海外からのメールなので、日本語以外の可能性を考えてしまったのが敗因だった。
最初から日本語であると決めつけていれば、
もうちょっと早く解読できていたかもしれない。
</p>
<p>
Shift JIS の場合 1byte 目は 0x80 (16進数で 80) 以上であるはずなのだが、
なぜ冒頭が「5E C8 3F BA ...」になってしまっているかというと、
元テキストにおいて 0x80 ～ 0x98 のデータが、
メール中で以下のように化けてしまっているため。
</p>
<div style="a:visited{}">
<table><tr><td>
<table class="ruler">
<tr><th>元データ</th><th>メール中の文字</th></tr>
<tr><td>80</td><td>?</td></tr>
<tr><td>81</td><td>=81 (正常)</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=201A">82</a>　&#8218;</td><td>,</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=0192">83</a>　&#402;</td><td>f</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=2020">86</a>　&#8224;</td><td>?</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=2021">87</a>　&#8225;</td><td>?</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=02C6">88</a>　&#710;</td><td>^</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=2030">89</a>　&#8240;</td><td>?</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=0160">8A</a>　&#352;</td><td>S</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=2039">8B</a>　&#8249;</td><td>&lt;</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=0152">8C</a>　&#338;</td><td>O</td></tr>
<tr><td>8D</td><td>=8D (正常)</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=017D">8E</a>　&#381;</td><td>Z</td></tr>
<tr><td>8F</td><td>=8F (正常)</td></tr>
</table></td><td>　</td><td valign="top">
<table class="ruler">
<tr><th>元データ</th><th>メール中の文字</th></tr>
<tr><td>90</td><td>=90 (正常)</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=2018">91</a>　&#8216;</td><td>'</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=2019">92</a>　&#8217;</td><td>'</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=201C">93</a>　&#8220;</td><td>"</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=201D">94</a>　&#8221;</td><td>"</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=2022">95</a>　&#8226;</td><td>.</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=2013">96</a>　&#8211;</td><td>-</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=2014">97</a>　&#8212;</td><td>-</td></tr>
<tr><td><a href="http://www.eki.ee/letter/chardata.cgi?ucode=02DC">98</a>　&#732;</td><td>~</td></tr>
</table></td></tr>
</table>
</div>
<p>
表中、「元データ」の欄に、Windows の欧文文字コード CP1252 の字形を入れてみた。
それぞれの元データがメール中でどのように化けるかを見ていると、
quoted-printable エンコーディングにおいて、
CP1252 の字形を印字可能文字
(7bit の英字・記号) で無理矢理表現しようとした結果、
このような「文字化け」変換が行なわれてしまったように思えてくる。
</p>
<p>
注目すべきはこの変換が多対一対応である点、
すなわち異なるデータがメール中で同じ文字に変換されてしまっている点だろう。
例えば、
0x80, 0x86, 0x87, 0x89 のいずれのデータも、
メール中では「?」という文字に置き換えられてしまっているし、
0x93 も 0x94 も区別無く「"」という文字に置き換えられてしまっている。
すなわちメール中に「"」が現れても、それが 0x93 なのか 0x94 なのか、
はたまた「"」自身なのか、区別がつかない、ということである。
</p>
<p>
元のテキストでは、
冒頭は『以下に表示された』という文字列だったと推測される。
『以下に表示された』という文字列を
quoted-printable エンコーディングで変換すると、
「<tt>=88=C8=89=BA=82=C9=95\=8E=A6=82=B3=82=EA=82=BD</tt>」になるが、
0x88 は上表にあるように「^」に化けてしまう。
同様に、0x80～0x98 の各データについて上表の「文字化け」変換を行なうと、
「<tt>^=C8?=BA,=C9.\Z=A6,=B3,=EA,=BD</tt>」となって、
前掲したメールの冒頭に一致する。
</p>
<p>
このように元のテキストから「文字化け」したメールを生成するのは、
上表を用いれば簡単に行なえるが、
その逆変換 (つまり文字化けの修復) は複数の可能性
(例えば「^」は 0x88 と「^」自身の二通りの可能性がある)
の中から最も日本語として自然なものを選ばなければならない。
</p>
<p>
というわけで、
化けている文字それぞれについて、
一番自然に見えるものを選ぶ、
という操作を繰り返すことによって「暗号解読」すると、
元のテキストと思しき次の文面が得られる:
</p>
<pre class="code">
HIROAKI SENGOKU 様へ、

以下に表示された RMA のステータスを確認してください。  RMA
に関するお問い合わせはこのメールに返信してください。
情報が正しい場合、この電子メールには返信しないでください。


RMA 番号： 8083XXXX

--------------------------------------------------------------

交換ドライブを 5～7 営業日中に発送します。

以下のドライブを Western Digital は受領しました：

     シリアル番号     製品番号             受領日（GMT）
     ------------     ---------------      -------------
     WCASJxxxxxxx     WD10EACS-00ZJB0      6/25/2008

--------------------------------------------------------------

以下に表示された RMA 発送状況を確認してください。

輸送業者のシステムの更新に1営業日がかかり、その後発送追跡番号が有効になりますので、ご了承ください。

交換ドライブの送付先：

     HIROAKI SENGOKU
     XXXXXXXXXXXXXXXXXXXXXXXXX TAKATSU
     KAWASAKI, Japan 213-XXXX
     JAPAN

配送業者：     Fedex
配送追跡番号： XXXXXXXXXXXX

     シリアル番号     製品番号             発送日（GMT）
     ------------     ---------------      -------------
     WCASJXXXXXXX     WD10EACS-32ZJB0      6/26/2008

--------------------------------------------------------------

関連リンク：
RMA手順指示情報の閲覧/印刷
  - http://websupport.wdc.com/rd.asp?t=102&amp;l=jp&amp;p=m&amp;r=8083XXXX&amp;f=e

発送と梱包の情報
  - http://websupport.wdc.com/rd.asp?t=103&amp;l=jp&amp;p=rp

RMAステータスの閲覧
  - http://websupport.wdc.com/rd.asp?t=104&amp;l=jp&amp;p=v&amp;r=8083XXXX&amp;z=213-XXXX

Western Digital サポートホームページ
  - http://websupport.wdc.com/rd.asp?t=105&amp;l=jp&amp;p=h

以上、
WD RMA チーム

http://websupport.wdc.com/rd.asp?t=105&amp;l=jp&amp;p=h

</pre>
<p>
「交換ドライブを 5～7 営業日中に発送します」と書いてあるが、
実際にはこのメールが送信された時 (日本時間で 6/26 18:44)
には既に発送が完了 (18:02) していて輸送中である。
このメールを 6/27 の時点で解読できていれば、
HDD が届く前に <a href="http://www.fedex.com/Tracking?cntry_code=jp">FedEx の追跡ページ</a> で HDD が今どこを輸送中であるか調べることができたはずであるが、
あいにく 6/27 は一日中外出していたので、
解読を始めたのは 6/28 土曜日のお昼過ぎになってからである。
ちょうど解読の糸口がつかめたあたり
(元テキストが Shift JIS であることが判明した時分) で
HDD が届いてしまった (6/28 15:34)。
</p>
<p>
参考までに、復号するための perl スクリプトを以下に添付する。
解読はこのような復号スクリプトを徐々に形作りつつ、
前述した表を推測していく形で行なった。
</p>
<pre class="code">
#!/usr/bin/perl
use strict;
use warnings;
while (&lt;>) {
    while (/=\n/) {
	$_ = $` . &lt;>;
    }

    s/\"=D4=8D\?/*94*D4*8D*86/g;	# 番号
    s/=(8[DF])\?/*$1*87/g;	# 合
    s/\?\{\-\-/*89*7B*97*97/g;	# 閲覧
    s/fVfXfef\?/*83V*83X*83e*83*80/g;	# システム
    s/fTf\|=81\[fgfz=81\[f\?fy=81\[fW/*83T*83|=81[*83g*83z=81[*83*80*83y=81[*83W/g;	# サポートホームページ
    s/f\`=81\[f\?/*83`*81[*83*80/g;	# チーム
    s/f\?/*83*89/g;	# ラ
    s/fS/*83*8A/g;	# リ
    s/f\&lt;/*83*8B/g;	# ル
    s/f\"/*83*93/g;	# ン
    s/\'-/*91*97/g;	# 送
    s/\'\?/*92*86/g;	# 中

    s/\,(=[0-9A-F][0-9A-F])/*82$1/g;
    s/f(=[89A-F][0-9A-F]|[\^\|ACNSTVXehu])/*83$1/g;
    s/\^(=[0-9A-F][0-9A-F])/*88$1/g;
    s/\?(=[0-9A-F][0-9A-F]|c)/*89$1/g;
    s/S(=[0-9A-F][0-9A-F]|[cm])/*8A$1/g;
    s/\&lt;(=[0-9A-F][0-9A-F])/*8B$1/g;
    s/O(=[0-9A-F][0-9A-F])/*8C$1/g;
    s/Z(=[0-9A-F][0-9A-F]|[qw])/*8E$1/g;
    s/\'(=[0-9A-F][0-9A-F])/*92$1/g;
    s/\"(=FA|d)/*93$1/g;
    s/\"(=[0-9A-F][0-9A-F]|[Fz])/*94$1/g;
    s/\.(=[0-9A-F][0-9A-F]|[it\\])/*95$1/g;
    s/\-(=E2)/*96$1/g;
    s/\-(=[0-9A-F][0-9A-F]|[ALl])/*97$1/g;
    s/\~(=[0-9A-F][0-9A-F]|[ALl])/*98$1/g;

    s/[=\*]([0-9A-F][0-9A-F])/pack("H*", "$1")/eg;
    print;
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2008/07/159/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>x86_64 な Linux カーネルで i386 プログラムを実行するときの注意点 ── ivtv ドライバの ioctl インタフェース</title>
		<link>http://www.gcd.org/blog/2008/05/156/</link>
		<comments>http://www.gcd.org/blog/2008/05/156/#comments</comments>
		<pubDate>Mon, 12 May 2008 00:12:42 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[ハードウェアの認識と制御]]></category>
		<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2008/05/156/</guid>
		<description><![CDATA[64bit Linux (x86_64 別名 amd64) は、 CONFIG_IA32_EMULATION を有効にしておくことにより、 32bit プログラム (i386 別名 ia32) を走らせることができる。 したがって 64bit へ移行する際は、 全プログラムを一度に 64bit 化する必要はなく、 まずカーネルだけ 64bit 化しておいて、 各プログラムは (バージョンアップの機会などに) 徐々に 64bit 化していけばよい。 ただし 32bit プログラムがカーネルの機能を呼び出す場合は、 各機能それぞれが 32bit プログラムからの呼び出しに対応していることが前提となる。 32bit プログラムからの呼び出しに対応するといっても、 基本的には引数の型を変換するだけである。 x86_64 の整数データモデルは LP64、 つまり long int 型とポインタ型が 64bit で (引数の型として多用される) int型は 32bit のままなので、 変換が不要なケースも多い。 例えば ioctl システムコールはファイル・ディスクリプタ (file [...]]]></description>
			<content:encoded><![CDATA[<p>
64bit Linux (x86_64 別名 amd64) は、
CONFIG_IA32_EMULATION を有効にしておくことにより、
32bit プログラム (i386 別名 ia32) を走らせることができる。
したがって 64bit へ移行する際は、
全プログラムを一度に 64bit 化する必要はなく、
まずカーネルだけ 64bit 化しておいて、
各プログラムは (バージョンアップの機会などに) 徐々に 64bit 化していけばよい。
ただし 32bit プログラムがカーネルの機能を呼び出す場合は、
各機能それぞれが 32bit プログラムからの呼び出しに対応していることが前提となる。
</p>
<blockquote>
32bit プログラムからの呼び出しに対応するといっても、
基本的には引数の型を変換するだけである。
x86_64 の整数データモデルは LP64、
つまり long int 型とポインタ型が 64bit で
(引数の型として多用される) int型は 32bit のままなので、
変換が不要なケースも多い。
</blockquote>
<p>
例えば ioctl システムコールはファイル・ディスクリプタ
(file descriptor, 以下 fd と略記) ごとに
カーネルが実行すべき機能は変わってくるわけで、
その実装は各デバイス・ドライバに委ねられることが多い。
したがって 32bit プログラムからの ioctl 呼び出しに応えられるか否かは、
各ドライバが 32bit 対応しているか否かに依存する。
不幸にしてドライバが対応していない場合は、
</p>
<pre class="code">
ioctl32(tv:11028): Unknown cmd fd(5) cmd(40045613){t:'V';sz:4} arg(081ec8b4) on /dev/video0
</pre>
<p>
などといったカーネル・メッセージ (dmesg) が出力される。
このメッセージは、
カーネル・ソース中 fs/compat_ioctl.c の compat_ioctl_error が出力している:
</p>
<pre class="code">
static void compat_ioctl_error(struct file *filp, unsigned int fd,
    unsigned int cmd, unsigned long arg)
{
    ...
    compat_printk("ioctl32(%s:%d): Unknown cmd fd(%d) "
            "cmd(%08x){t:%s;sz:%u} arg(%08x) on %s\n",
            current->comm, current->pid,
            (int)fd, (unsigned int)cmd, buf,
            (cmd >> _IOC_SIZESHIFT) &amp; _IOC_SIZEMASK,
            (unsigned int)arg, fn);
    ...
}
</pre>
<p>
fs/compat_ioctl.c は 32bit 版 ioctl システムコールを実装していて、
32bit プログラムが ioctl システムコールを呼び出すと、
この中の compat_sys_ioctl ルーチンが呼ばれる:
</p>
<pre class="code">
asmlinkage long compat_sys_ioctl(unsigned int fd, unsigned int cmd,
                unsigned long arg)
{
    ...
        if (filp->f_op &amp;&amp; filp->f_op->compat_ioctl) {
            error = filp->f_op->compat_ioctl(filp, cmd, arg);
            if (error != -ENOIOCTLCMD)
                goto out_fput;
        }
    ...
            compat_ioctl_error(filp, fd, cmd, arg);
    ...
 out_fput:
    fput_light(filp, fput_needed);
 out:
    return error;
}
</pre>
<p>
つまりドライバ側で file 構造体の compat_ioctl 関数ポインタ
(filp->f_op->compat_ioctl) が定義されていればそれが呼ばれ、
未定義ならば上記のような「Unknown cmd」エラーが出力される。
</p>
<p>
ちなみにこのエラーメッセージの「tv:11028」は、
ioctl を呼び出した 32bit プロセスの名前 (コマンド名) とプロセスID であり、
fd(5), cmd(40045613), arg(081ec8b4) は、
それぞれ
ioctl システムコールの第一 (つまり fd 番号)、
第二 (ioctl リクエスト番号)、第三引数 (ioctl リクエストの引数) であり、
最後の on /dev/video0 は
(第一引数の) fd 番号に対応するファイルのパス名である。
</p>
<p>
そして、この tv コマンドは
「<a href="http://www.gcd.org/blog/2008/04/155/">ビデオキャプチャ・カード GV-MVP/RX2W を使って Linux 2.6.24.4 でテレビ録画</a>」で紹介した
<a href="http://www.gcd.org/sengoku/docs/tv">perl スクリプト</a> であり、
その名称から推測できるとおりテレビ録画を行なうためのスクリプトである。
</p>
<p>
このスクリプトでは
<a href="http://sourceforge.net/project/showfiles.php?group_id=73219">Video::ivtv モジュール</a>を利用していて、
このモジュールが /dev/video0 つまり TV キャプチャ・デバイスに対して、
ioctl システムコールを呼び出している。
上記エラーはスクリプト中 $IvTV->stopEncoding($TunerFD);
を実行したときに発生した。
</p>
<p>
その名称から推測できる通り、
stopEncoding メソッドはキャプチャ・デバイスに対して
エンコーディングの停止を指示するためのもので、
内部で ioctl(fd, VIDIOC_STREAMOFF) などと
ioctl 呼び出しを行なっている。
VIDIOC_STREAMOFF は videodev2.h にて、
</p>
<pre class="code">
#define VIDIOC_STREAMOFF    _IOW  ('V', 19, int)
</pre>
<p>
と定義されていて、このマクロを展開すると 40045613 (16進) となり、
上記カーネル・メッセージ「cmd(40045613)」と一致する。
</p>
<p>
というわけで、(少なくとも Linux 2.6.24.7 に含まれる) ivtv ドライバは、
残念ながら 32bit 対応していないことが分かった。
もちろん x86_64 なカーネルではなく、
i386 カーネルを使えば 32bit プログラムから ivtv ドライバを使うことができるが、
x86_64 なカーネルでは、32bit プログラムからの ioctl システムコールを
64bit カーネルが受付けられる形に変換できないということだ。
</p>
<p>
とはいうものの、
32bit だろうが 64bit だろうが
ioctl のインタフェースに大した変わりはないはずだ。
どうして ivtv ドライバは 32bit 呼び出しをサポートしていないのだろう？ と
思いながらドライバのソースを眺めていると...
drivers/media/video/compat_ioctl32.c を見つけた。
名前からしていかにも 32bit 版 ioctl のように見える。
</p>
<p>
compat_ioctl32.c の中の v4l_compat_ioctl32 ルーチンは、
32bit な ioctl 呼び出しを受付けて
引数を 64bit へ変換し (といっても int 型はどちらも 32bit だが)、
本来の (64bit な) ioctl を呼び出し直す仕組みになっている。
なぜ ivtv ドライバは、このルーチンを利用していないのだろうか。
</p>
<pre class="code">
static int do_video_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    ...
    /* First, convert the command. */
    switch(cmd) {
        ...
    case VIDIOC_STREAMOFF32: realcmd = cmd = VIDIOC_STREAMOFF; break;
    };

    switch(cmd) {
        ...
    case VIDIOC_STREAMOFF:
        err = get_user(karg.vx, (u32 __user *)up);
        compatible_arg = 1;
        break;
        ...
    };
    if(err)
        goto out;

    if(compatible_arg)
        err = native_ioctl(file, realcmd, (unsigned long)up);
    else {
        mm_segment_t old_fs = get_fs();

        set_fs(KERNEL_DS);
        err = native_ioctl(file, realcmd, (unsigned long) &amp;karg);
        set_fs(old_fs);
    }
    ...
    return err;
}

long v4l_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg)
{
    ...
        ret = do_video_ioctl(file, cmd, arg);
        break;
    ...
    return ret;
}
</pre>
<p>
ざっと見た感じ、
ivtv ドライバからこの v4l_compat_ioctl32 ルーチンを呼んでも
特に問題は無いように思われる。
</p>
<p>
そこで、ivtv ドライバの file 構造体 (の中の file_operations 構造体) の
compat_ioctl 関数ポインタに、
v4l_compat_ioctl32 を設定してみた。
</p>
<pre class="code">
--- linux-2.6.24.5.org/drivers/media/video/ivtv/ivtv-streams.c	2008-01-25 07:58:37.000000000 +0900
+++ linux-2.6.24.5/drivers/media/video/ivtv/ivtv-streams.c	2008-05-04 09:10:07.581416212 +0900
@@ -49,6 +49,7 @@
       .write = ivtv_v4l2_write,
       .open = ivtv_v4l2_open,
       .ioctl = ivtv_v4l2_ioctl,
+      .compat_ioctl = v4l_compat_ioctl32,
       .release = ivtv_v4l2_close,
       .poll = ivtv_v4l2_enc_poll,
 };
@@ -59,6 +60,7 @@
       .write = ivtv_v4l2_write,
       .open = ivtv_v4l2_open,
       .ioctl = ivtv_v4l2_ioctl,
+      .compat_ioctl = v4l_compat_ioctl32,
       .release = ivtv_v4l2_close,
       .poll = ivtv_v4l2_dec_poll,
 };
</pre>
<p>
このパッチをあてることにより、
x86_64 なカーネル上で i386 な Video::ivtv モジュールを使って、
ビデオキャプチャ・カード GV-MVP/RX2W を
コントロールすることができるようになった。
一週間ほど使ってみた (多数の TV 番組を予約録画した) が、
今のところ問題は起きていない。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2008/05/156/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>2行でできる高精度ハードウェア自動認識 (initramfs の init を busybox だけで書く)</title>
		<link>http://www.gcd.org/blog/2007/12/144/</link>
		<comments>http://www.gcd.org/blog/2007/12/144/#comments</comments>
		<pubDate>Mon, 24 Dec 2007 23:04:43 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2007/12/144/</guid>
		<description><![CDATA[「10行でできる高精度ハードウェア自動認識」にコメントを頂いた: 最近の modprobe は、 自分で勝手に modules.alias を探してくれるようになっているようです。 この機能を使うと、 より簡単かつ高速に自動認識が可能になります。 そうだったのか... orz いままで、 modules.alias から modporbe すべきモジュールを検索するために、 以下のような感じで sh スクリプト (/tmp/dev2mod) を生成し、 それを読み込んで (. $tmp) いたのだが、 tmp=/tmp/dev2mod echo 'dev2mod(){ while read dev; do case $dev in' > $tmp sort -r /lib/modules/`uname -r`/modules.alias \ &#124; sed -n 's/^alias *\([^ ]*\) *\(.*\)/\1)modprobe \2;;/p' >> $tmp echo 'esac; done; [...]]]></description>
			<content:encoded><![CDATA[<p>
「<a href="http://www.gcd.org/blog/2007/09/133/">10行でできる高精度ハードウェア自動認識</a>」にコメントを頂いた:
</p>
<blockquote>
最近の modprobe は、
自分で勝手に modules.alias を探してくれるようになっているようです。
この機能を使うと、
より簡単かつ高速に自動認識が可能になります。
</blockquote>
<p>
そうだったのか... orz
</p>
<p>
いままで、
modules.alias から modporbe すべきモジュールを検索するために、
以下のような感じで sh スクリプト (/tmp/dev2mod) を生成し、
それを読み込んで (. $tmp) いたのだが、
</p>
<pre class="code">
tmp=/tmp/dev2mod
echo 'dev2mod(){ while read dev; do case $dev in' > $tmp
sort -r /lib/modules/`uname -r`/modules.alias \
| sed -n 's/^alias  *\([^ ]*\)  *\(.*\)/\1)modprobe \2;;/p' >> $tmp
echo 'esac; done; }' >> $tmp
. $tmp
rm $tmp
cat /sys/bus/*/devices/*/modalias | dev2mod
</pre>
<p>
modprobe が自分で modules.alias を探してくれるとなると、
sh スクリプトを動的生成する必要が無くなってしまい、
上記コードは次のように書けてしまう:
</p>
<pre class="code">
dev2mod(){ while read dev; do modprobe $dev; done }
cat /sys/bus/*/devices/*/modalias | dev2mod
</pre>
<p>
わずかに 2行 (^^;)
</p>
<p>
/sys/bus/*/devices/*/modalias の内容を手当たり次第 modprobe するので、
modprobe が「failed to load module」というエラー・メッセージを出してしまうが、
特に問題は無さげである。
</p>
<p>
PCMCIA や USB につないだデバイスも、
以下のように dev2mod を二度呼び出すだけで、
自動認識してしまう。
</p>
<pre class="code">
dev2mod(){ while read dev; do modprobe $dev; done }
cat /sys/bus/*/devices/*/modalias | dev2mod
modprobe pcmcia
cat /sys/bus/*/devices/*/modalias | dev2mod
</pre>
<p>
う～んすごい。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2007/12/144/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>x86_64 Linux でメモリ・デバッグ・ツール Valgrind を使う場合の注意点</title>
		<link>http://www.gcd.org/blog/2007/11/138/</link>
		<comments>http://www.gcd.org/blog/2007/11/138/#comments</comments>
		<pubDate>Thu, 22 Nov 2007 11:36:13 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[stone 開発日記]]></category>
		<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2007/11/138/</guid>
		<description><![CDATA[次のようなプログラム test.c について考える: #include &#60;stdio.h> #include &#60;stdlib.h> #include &#60;sys/types.h> #include &#60;string.h> struct test { int32_t len; int8_t buf[16]; }; int main(int argc, char *argv[]) { struct test *p = malloc(sizeof(struct test)); int8_t buf[16]; p->len = sizeof(p->buf); bzero(p->buf, p->len); printf("0x%lX-0x%lX => 0x%lX\n", (long)p->buf, (long)p->buf+p->len-1, (long)buf); bcopy(p->buf, buf, p->len); free(p); return 0; } malloc(3) で確保した領域のうち、 16 byte [...]]]></description>
			<content:encoded><![CDATA[<p>
次のようなプログラム test.c について考える:
</p>
<pre class="code">
#include &lt;stdio.h>
#include &lt;stdlib.h>
#include &lt;sys/types.h>
#include &lt;string.h>

struct test {
    int32_t len;
    int8_t buf[16];
};

int main(int argc, char *argv[]) {
    struct test *p = malloc(sizeof(struct test));
    int8_t buf[16];
    p->len = sizeof(p->buf);
    bzero(p->buf, p->len);
    printf("0x%lX-0x%lX => 0x%lX\n",
	   (long)p->buf, (long)p->buf+p->len-1, (long)buf);
    bcopy(p->buf, buf, p->len);
    free(p);
    return 0;
}
</pre>
<p>
malloc(3) で確保した領域のうち、
16 byte を bcopy(3) でコピーするだけの極めて単純なプログラムであり、
特に問題はないように見える。
</p>
<p>
ところが memory debugging tool <a href="http://valgrind.org/">Valgrind</a> を使って検証してみると、
x86_64 Linux だと次のようなエラーが出てしまう。
</p>
<pre class="terminal">
sag16:/home/sengoku/tmp % cc -O -Wall test.c
sag16:/home/sengoku/tmp % valgrind ./a.out
==19008== Memcheck, a memory error detector.
==19008== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
==19008== Using LibVEX rev 1658, a library for dynamic binary translation.
==19008== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
==19008== Using valgrind-3.2.1-Debian, a dynamic binary instrumentation framework.
==19008== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
==19008== For more details, rerun with: -v
==19008==
0x4D5C034-0x4D5C043 => 0x7FF000750
==19008== Invalid read of size 8
==19008==    at 0x4B9326B: (within /lib/libc-2.3.6.so)
==19008==    by 0x4B92C06: bcopy (in /lib/libc-2.3.6.so)
==19008==    by 0x4005BD: main (in /home/sengoku/tmp/a.out)
==19008==  Address 0x4D5C040 is 16 bytes inside a block of size 20 alloc'd
==19008==    at 0x4A1B858: malloc (vg_replace_malloc.c:149)
==19008==    by 0x400574: main (in /home/sengoku/tmp/a.out)
==19008==
==19008== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 8 from 1)
==19008== malloc/free: in use at exit: 0 bytes in 0 blocks.
==19008== malloc/free: 1 allocs, 1 frees, 20 bytes allocated.
==19008== For counts of detected errors, rerun with: -v
==19008== All heap blocks were freed -- no leaks are possible.
</pre>
<p>
「Invalid read of size 8」、
すなわちアクセスすべきではないメモリを、
64bit (8 byte) 読み込み命令で読んだというエラー。
</p>
<p>
test.c で読み込みを行なう可能性があるところと言えば、
「<tt>bcopy(p->buf, buf, p->len);</tt>」の部分だけであり、
その範囲は printf で表示しているように、
0x4D5C034 番地から 0x4D5C043 番地までの 16 byte である。
</p>
<p>
ところが、Valgrind 曰く:
</p>
<pre class="code">
Address 0x4D5C040 is 16 bytes inside a block of size 20 alloc'd
</pre>
<p>
ちょっと英語の意味が取りにくい (私の英語力が低いだけ? ^^;) が、
つまり「malloc で確保した 20 byte の領域のうち、
先頭から数えて 16 byte 目 (先頭は 0 byte 目と数える) が
0x4D5C040 番地であり、
この番地に対してメモリ読み込みが行なわれた」
という意味である (「16 byte 目」なら
「16 bytes」でなくて「16th byte」のような...?)。
</p>
<p>
すなわち、
「20 byte の領域のうち 16 byte 目」というのは残り 4 byte であり、
あと 4 byte コピーすればいいのにもかかわらず、
64bit 読み込み命令を使って 8 byte いっぺんに読んでしまっているから、
malloc で確保した領域の外をアクセスしてしまう、というわけ。
</p>
<p>
結果として 4 byte 無駄に読んでしまっている
(実はコピー開始位置も 4 byte 前から行なうので、計 8 byte 余計に読み込んでいる)
わけだが、
CPU にとって一番高速にコピーできる単位が
(64bit 境界に合わせた) 64bit 読み書きだから、
bcopy の実装がこのようになっているのだろう。
</p>
<blockquote>
より正確に言えば、
bcopy は 16 byte 以上のコピーを行なう場合は
コピー開始位置手前の 64bit 境界 (alignment) の番地から 64bit ずつコピーし、
16 byte 未満の場合は byte 単位でコピーする。
test.c では、
コピー開始位置 p->buf が
(直前のメンバが int32_t なので) 64bit 境界に一致しておらず、
しかもコピーする byte 数 p->len が 16 byte (= 64bit の倍数) なので、
16 byte 以上のコピーかつコピー終了位置も 64bit 境界に一致していない、
というのがミソである。
</blockquote>
<p>
したがって 32bit な x86 Linux の場合であれば 32bit 単位でコピーを行なうので、
test.c ではこのようなエラーは起きない。
もちろん、64bit な x86_64 Linux で Valgrind がエラーを出すからといって、
bcopy の x86_64 における実装に問題がある、というわけではない。
Valgrind は、
あくまでバグの「可能性」を指摘するだけであって、
malloc で確保した領域の外へのアクセスでも、
それが意図的なものであれば (メモリ保護違反などでない限り) 何の問題もない。
</p>
<p>
分かってみれば単純な話なのであるが、
Valgrind のメッセージ「16 bytes inside a block」の意味が把握できなかった私は、
glibc の bcopy のソースを読んで 64bit 単位でコピーを行なっていることを知り、
4 byte の領域外読み込みが行なわれることを理解して初めて、
Valgrind のメッセージの意味が分かったという、
本末転倒な体験をした (^^;)。
</p>
<p>
ちなみに、
もちろん最初から上記のようなテストプログラムを Valgrind で
チェックしようと思ったわけではなく、
「struct test」構造体は実際には次のような SockAddr 構造体であり、
saDup 関数にて malloc した SockAddr 構造体を
doconnect 関数で bcopy する処理になっていて、
元ネタは拙作 <a href="http://www.gcd.org/sengoku/stone/Welcome.ja.html">stone</a> である。
</p>
<pre class="code">
typedef struct {
    socklen_t len;
    struct sockaddr addr;
} SockAddr;
#define SockAddrBaseSize	((int)&amp;((SockAddr*)NULL)->addr)
...

SockAddr *saDup(struct sockaddr *sa, socklen_t salen) {
    SockAddr *ret = malloc(SockAddrBaseSize + salen);
...

int doconnect(Pair *p1, struct sockaddr *sa, socklen_t salen) {
    struct sockaddr_storage ss;
    struct sockaddr *dst = (struct sockaddr*)&amp;ss;	/* destination */
...
    bcopy(sa, dst, salen);
...
</pre>
<p>
stone ML にて、
Valgrind で検証したらエラーが出た、という報告を頂いて (_O_)
以上のような調査を行なった次第。
bcopy に与えた引数に問題はなく、
どうしてこれが 「Invalid read of size 8」 エラーを引き起こすのか謎だった。
結果的には stone には問題はなく、
修正の必要もないことが判明したわけであるが、
今まで使っていなかった Valgrind を使ってみるいいきっかけになった。
実を言うと 64bit Linux を (プログラミングのレベルで) 使ったのも、
今回が初めてだったりする (^^;)。
</p>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2007/11/138/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>10行でできる高精度ハードウェア自動認識 (initramfs の init を busybox だけで書く)</title>
		<link>http://www.gcd.org/blog/2007/09/133/</link>
		<comments>http://www.gcd.org/blog/2007/09/133/#comments</comments>
		<pubDate>Sat, 29 Sep 2007 13:47:18 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[ハードウェアの認識と制御]]></category>
		<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/2007/09/133/</guid>
		<description><![CDATA[これまでLinuxのハードウェア自動認識と言えば、 /sys/bus/pci/devices 以下と、 /lib/modules/`uname -r`/modules.pcimap を照らし合わせて 解析していくのが定石でした。 USBにも対応しようとすると、もう一つ大変です。 しかしこれからの常識は、 /sys/bus/*/devices/*/modalias と /lib/modules/`uname -r`/modules.alias です。 古橋貞之の日記「20行できる高精度ハードウェア自動認識」から引用 すばらしい。 確かに modules.alias を使う方が、 簡単かつ確実に必要なモジュールを読み込むことができそう。 さっそくこの方法を使って initramfs の init スクリプトを書き直してみた。 tmp=/tmp/dev2mod echo 'dev2mod(){ while read dev; do case $dev in' > $tmp sort -r /lib/modules/`uname -r`/modules.alias \ &#124; sed -n 's/^alias *\([^ ]*\) *\(.*\)/\1)modprobe \2;;/p' >> $tmp echo 'esac; done; [...]]]></description>
			<content:encoded><![CDATA[<blockquote>
これまでLinuxのハードウェア自動認識と言えば、
/sys/bus/pci/devices 以下と、
/lib/modules/`uname -r`/modules.pcimap を照らし合わせて
解析していくのが定石でした。
USBにも対応しようとすると、もう一つ大変です。<br />
しかしこれからの常識は、
/sys/bus/*/devices/*/modalias と<br />
/lib/modules/`uname -r`/modules.alias です。
<div align="right">古橋貞之の日記「<a href="http://d.hatena.ne.jp/viver/20070929/p1">20行できる高精度ハードウェア自動認識</a>」から引用</div>
</blockquote>
<p>
すばらしい。
確かに modules.alias を使う方が、
簡単かつ確実に必要なモジュールを読み込むことができそう。
さっそくこの方法を使って
<a href="http://www.gcd.org/blog/2007/09/129/">initramfs の init スクリプト</a>を書き直してみた。
</p>
<pre class="code">
tmp=/tmp/dev2mod
echo 'dev2mod(){ while read dev; do case $dev in' > $tmp
sort -r /lib/modules/`uname -r`/modules.alias \
| sed -n 's/^alias  *\([^ ]*\)  *\(.*\)/\1)modprobe \2;;/p' >> $tmp
echo 'esac; done; }' >> $tmp
. $tmp
rm $tmp
cat /sys/bus/*/devices/*/modalias | dev2mod
</pre>
<p>
わずかに 8行 (^^)<br />
(9/30追記: modules.alias を逆順ソートしておく必要があることが判明、sort -r を追加)。
</p>
<blockquote>
シェルスクリプト版はRuby版と比べて40倍くらい遅いので注意。
<div align="right">同ページ(古橋貞之の日記)から続けて引用</div>
</blockquote>
<p>
sh スクリプトの名誉のために言っておくと、
私が書いた上記 sh スクリプトだと、
古橋さんの Ruby 版と比べて 4倍くらいの遅さで済んでいる。
<pre class="terminal">
% time ./dev2mod
ide_cd
intel_agp
intelfb
uhci_hcd
...(中略)...
libusual
usbcore
0.252u 0.012s 0:00.77 33.7%	0+0k 0+0io 0pf+0w
% time ./detect_kmod.rb
["ivtv", "snd_intel8x0", "intelfb", "libusual", "ftdi_sio", "usbhid", "uhci_hcd", "ehci_hcd", "usbcore", "via_velocity", "eepro100", "e100", "3c59x", "psmouse", "ide_cd", "i2c_i801", "hw_random", "intel_agp"]
0.072u 0.008s 0:00.18 38.8%	0+0k 0+0io 0pf+0w
</pre>
<p>
ちなみに古橋さんのスクリプトは、
modules.alias の各行それぞれに対し、
マッチするデバイスが /sys/bus/*/devices/*/modalias に存在すれば、
そのモジュールを読み込む処理になっている。<br />
しかしながら、これだと一つのデバイスに対し、
複数のモジュールが読み込まれてしまうことになるのではないだろうか？
</p>
<blockquote>
古橋さんが同日追記されているように、
複数のモジュールが読み込まれること自体は簡単に修正可能で、
むしろモジュールの読み込み順が
modules.alias に載っている順になることのほうが問題。
この問題点を解決するため、
/sys/bus/*/devices/*/modalias の各行それぞれに対し、
マッチするモジュールを modules.alias から見つける修正版が追記された。
さすが古橋さん、すばやい。
<div align="right">9/30追記</div>
</blockquote>
<p>
例えば古橋さんのスクリプトだと、
私の手元のマシンでは e100 と eepro100 の両方のモジュールが読み込まれてしまう。
つまり、
</p>
<pre class="terminal">
% cat /sys/bus/pci/devices/0000:01:08.0/modalias
pci:v00008086d00001050sv0000107Bsd00004043bc02sc00i00
</pre>
<p>
が、modules.alias の次の二つの行にマッチするため、
このようなことが起こる。
</p>
<pre class="code">
alias pci:v00008086d00001050sv*sd*bc*sc*i* eepro100
alias pci:v00008086d00001050sv*sd*bc02sc00i* e100
</pre>
<p>
modules.alias を検索する際は、
マッチする行が見つかった時点で以降の行はスキップしないと、
この例のように複数のモジュール読み込みが起きる恐れがある。
マッチした以降の行を読み飛ばすには、
私が書いた上記 sh スクリプトのように、
/sys/bus/*/devices/*/modalias の各行それぞれに対し、
マッチするモジュールを一つだけ modules.alias から見つけて読み込む処理のほうが、
簡単に書けるのではないかと思うがどうだろうか。
</p>
<blockquote>
とはいえ、実際の NIC は Intel Pro 10/100 だったりする (^^;) ので、
読み込むべきモジュールは e100 であるような気もする。
もし e100 が正しいモジュールであるのなら、
modules.alias における eepro100 のパターンが適切ではないということになるのかも。
<div align="right">9/30追記</div>
「*」を多く含むパターンは「後で」マッチさせたほうが、
より適切なモジュールを選択できると考えられるため、
modules.alias を逆順ソートしておくことにした。
これにより、eepro100 ではなく、e100 を読み込むようになった。
<div align="right">9/30さらに追記</div>
</blockquote>
<p>
参考までに initramfs の /init スクリプト全体を添付しておく:
</p>

<span id="more-133"></span>
<pre class="code">
#!/bin/ash
export PATH="/bin:/sbin:/usr/bin:/usr/sbin"
KERNVER="`uname -r`"
FILES=`echo /* | sed 's@/mnt @ @'`
if [ -n "$AUFS" ]; then
    if [ -z "$INIT_TMPFS" ]; then
	mount -t tmpfs none /mnt
	cd /mnt
	cp -a $FILES .
	mkdir mnt
	export INIT_TMPFS=1
	exec switch_root . /init
    fi
fi

mount -t proc none /proc
ROOTFLAG="ro"
INIT="/sbin/init"
for p in `cat /proc/cmdline`; do
    v=`echo $p | sed 's/[^=]*=//'`
    case $p in
	root=*)
	    ROOT=$v
	    ;;
	rootfstype=*)
	    ROOTPARM="-t $v"
	    ;;
	rootflags=*)
	    ROOTFLAG=$v
	    ;;
	nfsroot=*)
	    NFSROOT=`echo $v | sed 's/,.*//'`
	    NFSOPTS=`echo $v | sed 's/[^,]*,*//'`
	    NFSPARM='-t nfs'
	    if [ -n "$NFSOPTS" ]; then
		NFSPARM="$NFSPARM -o $NFSOPTS"
		unset NFSOPTS
	    fi
	    ;;
	init=*)
	    INIT=$v
	    ;;
	[0-6S])
	    RUNLEVEL=$p
	    ;;
    esac
done
unset p v
ROOTPARM="$ROOTPARM -o $ROOTFLAG"
unset ROOTFLAG
if [ -n "$RUNLEVEL" ]; then
    INIT="$INIT $RUNLEVEL"
fi
hwclock --hctosys

mount -t sysfs none /sys
tmp=/tmp/dev2mod
echo 'dev2mod(){ while read dev; do case $dev in' > $tmp
sort -r /lib/modules/$KERNVER/modules.alias \
| sed -n 's/^alias  *\([^ ]*\)  *\(.*\)/\1)modprobe \2;;/p' >> $tmp
echo 'esac; done; }' >> $tmp
. $tmp
rm $tmp
unset tmp
cat /sys/bus/*/devices/*/modalias | dev2mod
modprobe pcmcia
cat /sys/bus/*/devices/*/modalias | dev2mod
umount /sys
modprobe af_packet
modprobe unix
modprobe ext3
modprobe xfs

if [ -z "$NIC_DEV" ]; then
    NIC_DEV=eth0
fi
if [ -n "$NFSROOT" ]; then
    ifconfig lo 127.0.0.1
    if ifconfig $NIC_DEV; then
	ifconfig $NIC_DEV up
	sleep 3
	udhcpc --retries=2 -i $NIC_DEV
	mount -t nfs $NFSPARM $NFSROOT /mnt
    fi
elif [ -n "$ROOT" ]; then
    mount $ROOTPARM $ROOT /mnt
fi

if [ -n "$INIT_DEBUG" ]; then
    set
    exec /bin/sh
fi

umount /proc
if [ -n "$AUFS" ]; then
    case $AUFS in
	/*)
	    mnt="/mnt$AUFS"
	    ;;
	*)
	    mnt="/mnt"
	    ;;
    esac
    mkdir /.rw /.root
    mount -t aufs -o br:/.rw:${mnt}=ro none /.root
    unset mnt
    cd /.root
    if [ -n "$INIT_TMPFS" ]; then
	test -d initrd || mkdir initrd
	pivot_root . initrd
	initrd/bin/busybox rm -rf `echo $FILES /.root | sed 's@/@initrd/@g'`
	exec $INIT
    else
	cp -a /bin/busybox .
	rm -rf $FILES
	./busybox mount --move . /
	exec ./busybox chroot . /bin/sh -c "rm ./busybox; exec $INIT"
    fi
fi
exec switch_root /mnt $INIT
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2007/09/133/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
