<?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>Wed, 01 Feb 2012 00:44:57 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1.2</generator>
		<item>
		<title>決してBusyにならない SIP 留守番電話機を Perl で作ってみた 〜 用件が録音されるとメールに添付して送信</title>
		<link>http://www.gcd.org/blog/2011/02/769/</link>
		<comments>http://www.gcd.org/blog/2011/02/769/#comments</comments>
		<pubDate>Thu, 24 Feb 2011 10:52:20 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=769</guid>
		<description><![CDATA[IP 電話でいろいろ遊ぼうとすると定番は Asterisk だが、 いかんせん牛刀すぎる。 個人的に VoIP を使う場合 PBX の必要はなく、 留守番電話や転送ができれば充分。 というわけでまずは留守番電話の機能をさ [...]]]></description>
			<content:encoded><![CDATA[<p>
IP 電話でいろいろ遊ぼうとすると定番は <a href="http://www.asterisk.org/">Asterisk</a> だが、
いかんせん牛刀すぎる。
個人的に VoIP を使う場合 PBX の必要はなく、
留守番電話や転送ができれば充分。
というわけでまずは留守番電話の機能をさくっと
<a href="http://www.gcd.org/sengoku/docs/answer_machine">Perl で書いてみた</a> 
(わずか 93行)。
</p>
<pre class="code">
sip_user: hiroaki_sengoku
sip_passwd: xxxxxxxx
sip_domain: ekiga.net
answer_rings: 30
answer_sound: /home/tam_ekiga/answer.raw
voicemail_time: 60
voicemail_dir: /home/tam_ekiga/LOCAL/voicemail
</pre>
<p>
設定ファイル answer_machine.conf を ↑ のような感じで書いておいて、
留守番電話スクリプト 
<a href="http://www.gcd.org/sengoku/docs/answer_machine">answer_machine</a> 
を実行する:
</p>
<pre class="code">
answer_machine --conf /home/tam_ekiga/answer_machine.conf
</pre>
<p>
sip:hiroaki_sengoku@ekiga.net に着信があると、
応答メッセージを流した後、
相手の音声を録音し、
/home/tam_ekiga/LOCAL/voicemail ディレクトリに
<a href="http://ja.wikipedia.org/wiki/%CE%9C-law">8kHz サンプリングの 8bit &mu;-law 形式</a>で保存する。
あとはこのディレクトリを監視するプログラムを走らせて、
新着録音をメールで送信するようにしておけばよい。
</p>
<p>
次に <a href="http://www.ipkall.com/">IPKall</a> 
を利用 (申込には米国の IP アドレスからアクセスする必要あり)
して電話番号をもらう。
米国ワシントン州<a href="http://goo.gl/maps/A7Ug">リンデン</a>の番号
+1-360-526-6699 が割当てられた。
上記 SIP 留守番電話が待受けている
sip:hiroaki_sengoku@<a href="https://www.ekiga.net/">ekiga.net</a> 
を、
この電話番号の転送先として IPKall に登録すれば、
Google Voice もどき (^^;) の出来上がり。
この番号に電話すると、
上記スクリプトが応答し
(上記設定ファイルに answer_rings: 30 と書いてある通り、
30秒間呼び出しを続けないと応答しない)、
何かメッセージを吹き込めば mp3 形式の音声データが添付されたメールが私宛に届く。
</p>
<p>
この仕掛けを作った晩の翌日未明 3:16 に、
さっそく着信があり録音データが添付されたメールが届いた。
発信者の電話番号は +1-877-347-3760 だった
(+1-877- は米国のフリーダイヤル)。
メールで送られて来た<a href="http://www.gcd.org/sengoku/docs/20110223-031640.800_Service.8773473760@66.54.140.46.mp3">録音データがコレ</a>。
英語が苦手な私にはちょっとつらい (^^;) が、
再生速度を遅めにして一生懸命聞き取ってみる:
</p>
<blockquote>
Hello, this is a message for Korety Martin.
If you are not Korety Martin, 
please hang up and call 877-347-3760 
to remove this phone number from our records.
If you continue to listen to this message,
you are acknowledging that you are Korety Martin. 
This message contains personal and private information. 
There will now be a three second pause.<br />
<br />
This is EOCCA, A Collection Agency.
This is an attempt to collect a debt.
Any information obtained will be used for that purpose.
Please contact us about this important business matter at 877-347-3760.
When calling please reference account number 9.
</blockquote>
<p>
真面目にディクテーションするのが馬鹿馬鹿しくなるような、
絵に描いたような詐欺電話 (*_*)。
「Collection Agency」
は債権回収会社のこと。
「EOCCA, A Collection Agency」 で検索してみると、
いっぱい出てくる。
借金の心当たりがある人 (のうち 1% くらい?) は、
つい騙されて折り返し電話をかけてしまうのか。
日本とかだと人海戦術で電話をかけまくるらしいが、
自動で電話をかけまくるのが米国流なのか?
電話版スパムメールといった感じ?
</p>
<p>
なお、
最後がしり切れとんぼのような感じがするが、
これは録音時間を 60秒間に制限しているため
(上記設定ファイルの voicemail_time: 60)。
</p>
<div align="center">- o -</div>
<p>
以下は、
Perl プログラミングに興味があるかた向け:
</p>
<span id="more-769"></span>
<p>
CPAN に登録されている
<a href="http://search.cpan.org/dist/Net-SIP/">Net::SIP</a> モジュールには、
サンプルプログラムとして留守番電話スクリプト 
answer_machine.pl が含まれている。
ところが、
このスクリプトだと着呼時にすぐオフフック (＝受話器を持ち上げて電話に出る) 
してしまう。
ふつー留守番電話といえば、
呼び出し時間 (つまりベル^H^H着メロを鳴らす時間) 
を設定できるものだと思っていたが、
残念ながらこのスクリプトにはそのような機能はない。
これでは留守じゃなくても留守番電話が先に出てしまう！
</p>
<p>
着呼時にすぐオフフックするのではなく、
一定秒数待ってからオフフックするようにスクリプトを修正するなんて、
sleep 命令を書き加えるだけだろうと思うかもしれないが、
さにあらず。
Net::SIP は非同期処理が中心なので、
全ての処理はイベントループ Net::SIP::Dispatcher::Eventloop 
から呼び出される仕組になっている。
つまり各処理の中で勝手に sleep とかすると、
他の処理が全部滞ってしまう。
逆に言うと、
非同期処理なので一つのプロセスで複数の通話を扱える。
つまり決してお話し中 (Busy) にならない。
</p>
<p>
answer_machine.pl から引用:
</p>
<pre class="code">
use Net::SIP;
use Net::SIP::Util ':all';
	...
$ua->listen(
	init_media =&gt; [ \&amp;play_welcome, $welcome,$hangup,$savedir ],
	...
	}
);
$ua-&gt;loop;
</pre>
<p>
着呼待ちのためのスクリプトはたったこれだけ。
$ua-&gt;listen で、
オフフックしたときに呼び出される Callback (init_media) として
\&amp;play_welcome (関数リファレンス) を登録し、
$ua-&gt;loop でイベントループに入る。
着呼すると即オフフックして関数 play_welcome を引数 
$welcome, $hangup, $savedir 付で呼び出す。
play_welcome は、
留守番電話の応答メッセージ 
(「ただいま留守にしております。
ご用件のあるかたはピーッという音の後にお話下さい」 など)
を送信し、
相手が話す 「用件」 を録音する関数。
</p>
<p>
answer_machine.pl では自前の関数 play_welcome を定義して 
init_media へ登録しているが、
Net::SIP::Simple::RTP モジュール内にも media_send_recv という
(応答メッセージを) 送信して、
(相手の用件を) 録音する関数が定義されている。
これを使えば、
</p>
<pre class="code">
$ua-&gt;listen(
    init_media =&gt; $ua-&gt;rtp('media_send_recv', 'answer.raw', 1, 'out.raw'),
    );
</pre>
<p>
などと書くこともできる。
ここで answer.raw は 
<a href="http://ja.wikipedia.org/wiki/%CE%9C-law">8kHz サンプリングの 8bit &mu;-law 形式</a>の音声データのファイル名で、
これが応答メッセージとして再生され、
ファイル out.raw に同形式で相手の用件が録音される。
</p>
<p>
したがって、
呼び出し時間が常に 0 秒で構わなければ、
</p>
<pre class="code">
#!/usr/bin/perl
use Net::SIP;
my $user = 'hiroaki_sengoku';
my $pass = 'xxxxxxxx';
my $domain = 'ekiga.net';
my $ua = Net::SIP::Simple-&gt;new(
    outgoing_proxy =&gt; $domain,
    registrar =&gt; $domain,
    domain =&gt; $domain,
    from =&gt; $user,
    auth =&gt; [ $user, $pass ],
    ) || die;
$ua-&gt;register;
$ua-&gt;listen(
    init_media =&gt; $ua-&gt;rtp('media_send_recv', 'answer.raw', 1, 'out.raw'),
    );
$ua-&gt;loop;
</pre>
<p>
これだけで留守番電話として機能する。
</p>
<p>
まったくもって簡潔すぎて、
どこをどう変更すれば呼び出し時間を確保できるのか、
これだけでは見当もつかないが、
関数 $ua-&gt;rtp (init_media に登録された Callback) 
が呼び出された段階で既にオフフックしてしまっているわけで、
時すでに遅しということだけは分かる。
どうやってオフフックする前に一定秒数待つ処理 
(以下、Ringing と略記) を割り込ませばよいだろうか?
</p>
<p>
Net::SIP のドキュメントにはオフフック前の挙動については一切言及がないので、
仕方なく Net::SIP::Simple の listen のソースを読んでみる:
</p>
<pre class="code">
sub listen {
	my Net::SIP::Simple $self = shift;
	my %args = @_;

	# handle new requests
	my $receive = sub {
		my ($self,$args,$endpoint,$ctx,$request,$leg,$from) = @_;
		...
		# new invite, create call
		my $call = Net::SIP::Simple::Call-&gt;new( $self,$ctx,{ %$args });
		my $cb = UNIVERSAL::can( $call,'receive' ) || die;
		...
		# setup callback on context and call it for this packet
		$ctx-&gt;set_callback([ $cb,$call ]);
		$cb-&gt;( $call,$endpoint,$ctx,undef,undef,$request,$leg,$from );
	};

	$self-&gt;{endpoint}-&gt;set_application([ $receive, $self,\%args ]);
}
</pre>
<p>
set_application は、
endpoint (Net::SIP::Endpoint, 通信端点) 
とユーザとをつなぐ application を endpoint に登録するための関数。
例えば着呼したら着信音を鳴らすとか、
ユーザが受話器を持ち上げたら着呼処理を行なうとか、
通信相手の音声をスピーカに出力してユーザに伝えるとか、
等々を行なう application (Callback) を登録する。
</p>
<p>
ここで登録される application である 
$receive (無名関数へのリファレンス) の処理内容を追っていくと、
実質的には Net::SIP::Simple::Call->receive を呼び出しているだけ。
で、この Net::SIP::Simple::Call->receive こそが実際の着呼処理、
すなわち通信相手からの INVITE リクエストに対して 
「SIP/2.0 200 OK」 を返す処理を行なっている。
</p>
<p>
ということはつまり、
Net::SIP::Simple::Call->receive 
の呼び出しが行なわれる前に Ringing 処理を割り込ませばよい。
いろいろ方法はあると思うが、
endpoint に登録されている application を、 
Ringing を行なう無名関数で置き換えてみた:
</p>
<pre class="code">
    my $sub = $ua-&gt;{endpoint}-&gt;{application}-&gt;[0];
    $ua-&gt;{endpoint}-&gt;{application}-&gt;[0] = sub {
	my ($self, $args, $endpoint, $ctx, $request, $leg, $from) = @_;
	my $meth = $request-&gt;method;
	if ($meth eq 'INVITE') {
	    my $res = $request-&gt;create_response('180', 'Ringing');
	    $endpoint-&gt;new_response($ctx, $res, $leg, $from);
	    $self-&gt;add_timer($rings, [$sub, $self, $args,
				      $endpoint, $ctx, $request, $leg, $from]);
	} else {
	    $endpoint-&gt;close_context($ctx);
	}
    };
</pre>
<p>
これで着呼時にオフフックせずに、
Ringing を行なうことになる。
つまり、
「180 Ringing」
レスポンスを返し、
一定秒数 ($rings 秒) 後にオフフックさせるために add_timer を呼ぶ。
add_timer は、
第一引数 ($rings) で示す秒数後に、
第二引数 ([$sub, ...]) を Callback として呼び出すタイマーを登録する関数。
第二引数に、
元々 endpoint に登録されていた application へのリファレンス $sub を指定すれば、
$rings 秒後にオフフックして留守番応答が行なわれる。
</p>]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2011/02/769/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
<enclosure url="http://www.gcd.org/sengoku/docs/20110223-031640.800_Service.8773473760@66.54.140.46.mp3" length="60480" type="audio/mpeg" />
		</item>
		<item>
		<title>Google Voice で電話する 「V字発信」 Perl スクリプトを書いてみた 〜 日本へは 2円/分、米国内は年内無料</title>
		<link>http://www.gcd.org/blog/2011/02/745/</link>
		<comments>http://www.gcd.org/blog/2011/02/745/#comments</comments>
		<pubDate>Mon, 14 Feb 2011 00:27:01 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=745</guid>
		<description><![CDATA[2009年に米国内でサービス開始した Google Voice は、 2010年6月から以前のような 「招待制」 ではなくなり、 米国内からであれば設定するだけですぐ利用できるようになった。 近いうちに米国外へも展開する [...]]]></description>
			<content:encoded><![CDATA[<p>
2009年に米国内でサービス開始した 
<a href="https://www.google.com/voice/">Google Voice</a> は、
2010年6月から以前のような 「招待制」 ではなくなり、
米国内からであれば設定するだけですぐ利用できるようになった。
近いうちに米国外へも展開すると思われるが、
日本は後回しになりそうな気がする 
(Android 版 Skype が使えるようになったのも世界中で一番最後だったし)。
米国内限定といっても、
米国内に IP アドレスと電話番号があれば利用できるようなので試しに使ってみた。
</p>
<p>
米国内 IP アドレスは
<a href="http://www.gcd.org/blog/2010/02/544/">Linode VPS</a>
(Virtual Private Server, 仮想専有サーバ) 
を契約しているので既に<a href="http://fremont.gcd.org/test">持っている</a>。
この VPS  上で 
<a href="http://ja.wikipedia.org/wiki/Point_to_Point_Tunneling_Protocol">PPTP</a> 
(Point to Point Tunneling Protocol) 
デーモンを立ち上げておけば、
Windows 標準機能の 「仮想プライベート ネットワーク」 (VPN) で接続して、
米国内からのアクセスを装うことができる。
WWW アクセスだけなら proxy server を立ち上げておくだけで充分かと思ったが、
proxy 経由のアクセスかどうか Google サーバ側で見ているらしく、
proxy 経由では Google Voice は利用できない。
</p>
<p>
なお、
いったん Google Voice の設定を済ませて Google Number を確保してしまえば、
米国内国外を問わず任意のマシンから Google Voice を利用可能。
つまり Google Number を確保するときのみ、
米国内の IP アドレスからアクセスする必要がある、
ということ。
</p>
<p>
米国内の電話番号は、
無料で電話番号を割当ててくれるサービスがあるのでそれを利用する。
私は <a href="http://www.ipkall.com/">IPKall</a> を利用した。
こちらも米国内の IP アドレスからアクセスする必要があるが、
Google Voice とは異なり米国内の proxy server を経由するだけで登録できる。
SIP フォン (VoIP 電話) のアドレスと、
メールアドレスを登録すると、
ワシントン州の電話番号を一つ割当て、
それをメールで教えてくれる。
割当てられた電話番号に着呼すると、
登録した SIPフォンへ転送してくれる仕掛け。
ただ、
米国は電話代が安すぎるためか<a href="http://twitter.com/gcd_org/status/33534681920049152">イタ電が多い</a>のがちょっと困りもの。
いたずら電話に困っている人がよほど多いのか、
<a href="http://whocallsme.com/">WhoCallsMe.com</a>, 
<a href="http://whocalled.us/">Who Called Us</a>, 
<a href="http://phoneowner.info/">PhoneOwner.info</a>,
<a href="http://www.whitepages.com/reverse_phone">white pages</a>,
<a href="http://www.melissadata.com/lookups/phonelocation.asp">Location Lookup</a> など、
かかってきた<a href="http://www.mycallbot.com/">電話番号を調べるサービス</a>がいくつもある。
</p>
<p>
維持費用無しで米国の電話番号を持てるのは大変ありがたいが、
30日間サービスを利用していないと登録が無効になってしまう (IPKall の場合)。
</p>
<blockquote>
IPKall Signup<br />
**ABSOLUTELY FREE**<br />
Washington state phone number to your IP phone.<br />
...(中略)...<br />
Accounts that are inactive for 30 days will be terminated for non-use.
<div align="right"><a href="http://phone.ipkall.com/">IPKall Signup</a> から引用</div>
</blockquote>
<p>
「inactive」
というのが、
(1)通話実績がないことを意味するのか、
(2)着呼だけでも 「active」 と見做されるのか、
あるいは (3)登録情報を更新するだけでもよいのかは不明。
</p>
<p>
仮に (1) だとすると、
毎月日本から国際電話をかけて通話実績を作らなければならず、
とても面倒だしお金もかかる。
そこで Google Voice を使って通話実績を作ることを考えた。
自動で発呼できれば手間もかからず番号を維持できる。
しかも 2011年中 Google Voice 
は米国内宛の通話が無料らしいので発呼の実験し放題。
</p>
<p>
<a href="http://code.google.com/p/google-voice-java/">Java</a> や
<a href="http://code.google.com/p/pygooglevoice/">Python</a> などから
Google Voice をアクセスする
非公式な Google Voice API が発表されているが、
公式ではないので Google が仕様変更すると使えなくなる恐れあり。
「非公式 API」 のコードを見てみると、
内容的にはとても単純であるように見える。
この程度なら自分で一から書いた方が Google の仕様変更への追随も即座にできるし、
Java や Python よりは Perl で書きたかったというのもあって、
さくっと<a href="http://www.gcd.org/sengoku/docs/gvcall.pl">書いてみた</a>。
わずか 58行、
しかも後述するように follow_meta_redirect のバグ対策で 21行ほど要してるので、
実質だとわずか 27行。
</p>
<span id="more-745"></span>
<img src="http://www.gcd.org/sengoku/picts/googlevoice.jpg" width="249" height="341" alt="Google Voice Call" class="pict-left" />
<p>
Google Voice で通話するには、
いわゆる 「V字発信」 を行なう。
つまり電話をかけようとする自分の側と通話先である相手側との両方に対して 
Google から電話をかけ、
両者を繋いで通話する。
自分の側の電話はあらかじめ登録してあるものから選ぶ。
</p>
<p>
Google Number を取得済の場合は、
<a href="https://www.google.com/voice/">Google Voice</a> 
ページにアクセスすると左上のロゴの下に 「Call」 ボタンがあって、
押すと<br />
← のようなウィンドウが現れる。
</p>
<p>
「Number to call」 に通話先の電話番号、
「Phone to call with」 に登録してある自分の電話のうちどれを選ぶかを指定し、
「Connect」 ボタンを押す。
自分の電話の電話番号の代わりに、
(登録済であれば) 
<a href="http://www.google.com/talk/">Google Talk</a> や 
<a href="http://www.google.com/gizmo5/">Gizmo5</a> のアカウントを指定することもできる。
</p>
<p>
すると、
まず自分の電話 (あるいは Google Talk や Gizmo5) に対して
Google から発呼が行なわれる。
電話に出ると、
次に Google は通話先の番号に対して発呼を行なう。
呼び出し中は呼び出し音が自分の電話から聞こえ、
相手が出ればそのまま通話できる。
</p>
<p>
以上を、
ブラウザで操作する代わりに、
Perl スクリプトから行なえばよい。<br />
まず Google Voice にログインする:
</p>
<pre class="code">
my $mech = WWW::Mechanize->new();
$mech->get("https://www.google.com/voice");
$mech->success || die "Fail to access\n";
my $res = $mech->submit_form(
    form_id => "gaia_loginform",
    fields => {
	Email => $user,
	Passwd => $pass,
    }
    );
$res->is_success || die "Fail to login\n";
</pre>
<p>
「WWW::Mechanize」
は、
ブラウザでの操作を簡単にスクリプト化できる便利な Perl モジュール。
「ページを開く」
「ボタンを押す」
「リンクをたどる」
等々、
ブラウザ操作をずらずら書くだけで自動化が実現できてしまう。
混雑時は何度もリロードしないとアクセスできないチケット販売サイト等で、
どうしてもチケットを購入したいときに威力を発揮する(うそ)。
</p>
<p>
最初に "https://www.google.com/voice" をアクセスすると、
Google アカウントのログインページが表示される。
HTML ソースを見ると、
</p>
<pre class="code">
&lt;form id="gaia_loginform"
      action="https://www.google.com/accounts/ServiceLoginAuth" method="post"
      onsubmit="return(gaia_onLoginSubmit());" &gt;
	...
&lt;input type="text" name="Email" id="Email"
       size="18" value="" class='gaia le val' /&gt;
	...
&lt;input type="password" name="Passwd" id="Passwd"
       size="18" class="gaia le val" /&gt;
	...
&lt;input type="submit" class="gaia le button" name="signIn" id="signIn"
       value="Sign in" /&gt;
	...
&lt;/form&gt;
</pre>
<p>
などとなっているので、
ID が "gaia_loginform" である form について、
「Email」 
フィールドに gmail アドレス、
「Passwd」 
フィールドにパスワードを入力して、
「Sign in」
ボタンを押せばよいことが分かる。
この操作をそのままスクリプトで記述して、
</p>
<pre class="code">
my $res = $mech->submit_form(
    form_id => "gaia_loginform",
    fields => {
	Email => $user,
	Passwd => $pass,
    }
    );
</pre>
<p>
などと書けばよい。
もちろん変数 
「$user」
「$pass」
には、
あらかじめそれぞれ
「gmail アドレス」
「パスワード」
を代入しておく。
</p>
<p>
大変便利な WWW::Mechanize モジュールだが、
画竜点睛を欠くことに meta refresh に対応していない。
すなわち、
「Sign in」
ボタンを押すと、
Google サーバから次のようなレスポンスが返ってくるが、
</p>
<pre class="code">
&lt;html&gt;&lt;head&gt;&lt;title&gt;Redirecting&lt;/title&gt;
	...
&lt;meta http-equiv="refresh" content="0; url=&amp;#39;http://www.google.co.jp/accounts/SetSID?ssdc=1&amp;amp;sidt=...(中略)...&amp;#39;"&gt;&lt;/head&gt;
&lt;body ... &gt;
	...
&lt;/body&gt;&lt;/html&gt;
</pre>
<p>
「&lt;meta http-equiv="refresh" content="0; url=...」
タグがあるので、
「url=」 
で示された URL へ遷移する必要がある。
ブラウザは自動でこのページ遷移を行なうが、
WWW::Mechanize は遷移してくれないので、
WWW::Mechanize::Plugin::FollowMetaRedirect モジュールの
follow_meta_redirect を呼び出す必要がある。
</p>
<p>
ところが残念なことに follow_meta_redirect にはバグ? があり、
点睛を入れても龍は空へ飛んでいかない。
上記のように URL が 「&amp;#39;」 (「'」 single quote) で囲われていると、
「'」 を含めて URL として扱ってしまう (つまり不正な URL になる) ために、
サーバ側に拒絶される。
</p>
<p>
仕方ないので、
follow_meta_redirect と同等のことを行なう関数 follow_refresh を、
follow_meta_redirect を真似して書いた:
</p>
<pre class="code">
sub follow_refresh {
    my ($mech) = @_;
    my $content = $mech-&gt;content;
    my $p = HTML::TokeParser-&gt;new(\$content) || return $content;
    while (my $token = $p-&gt;get_token) {
	if ($token-&gt;[0] eq 'S' &amp;&amp; $token-&gt;[1] eq 'meta') {
	    if (defined $token-&gt;[2] &amp;&amp; ref $token-&gt;[2] eq 'HASH') {
		if (defined $token-&gt;[2]-&gt;{'http-equiv'}
		    &amp;&amp; $token-&gt;[2]-&gt;{'http-equiv'} =~ /^refresh$/io) {
		    if (defined $token-&gt;[2]-&gt;{'content'}
			&amp;&amp; $token-&gt;[2]-&gt;{'content'}
			=~ m|^(([0-9]+);\s*)*url\=(\'?)(.+)\3$|io) {
			$mech-&gt;get($4);
			return $mech-&gt;content;
		    }
		}
	    }
	}
    }
    return $content;
}
</pre>
<p>
URL が 「'」 で囲われているときは、
その 「'」 を取り除いて $mech-&gt;get するだけ。
この関数を呼び出すことにより、
上記 meta タグで指定された URL へ遷移できる。
</p>
<p>
こうして遷移したページには、
以下のような部分がある:
</p>
<pre class="code">
&lt;form action="#" id="gc-search-form" method="post" onsubmit="return false;"&gt;
&lt;input name="_rnr_se" type="hidden" value="3W/C7p9ElNW6H+LdP0ndD9EIek4="/&gt;
</pre>
<p>
Google Voice では、
このように隠しフィールド (type="hidden") を使って
「_rnr_se」
の値を引き回しているようだ。
この値は Google Voice で電話をかける際にも必要なので、
保存しておく:
</p>
<pre class="code">
my $content = &amp;follow_refresh($mech);
$content =~ m/\&lt;input\s+name=\"_rnr_se\"[^\&gt;]*\svalue=\"(.*)\"/
    || die "Fail to get _rnr_se\n";
my $rnr_se = $1;
</pre>
<p>
以上で Google Voice へのログインが完了。
</p>
<p>
次に、
<a href="https://www.google.com/voice/">Google Voice</a> 
ページの 「Connect」 ボタン (前述) を押したとき、
どのようなリクエストが Google サーバへ送られるか調べる。
ブラウザの挙動を模倣する Perl スクリプトを書けば、
ブラウザで操作する代わりに Perl スクリプトで発呼を行なわせることができるはず。
</p>
<p>
どんなリクエストがサーバへ送られるか調べるには、
<a href="http://livehttpheaders.mozdev.org/">Live HTTP headers</a> 
アドオンが便利 (FireFox の場合)。
実際に調べてみるとこんな感じ:
</p>
<pre class="terminal-scroll">
POST /voice/call/connect/ HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded;charset=utf-8
Referer: https://www.google.com/voice?pli=1
Content-Length: 146
Cookie: (省略)
Pragma: no-cache
Cache-Control: no-cache

outgoingNumber=360469zzzz&amp;forwardingNumber=253802yyyy&amp;subscriberNumber=undefined&amp;phoneType=2&amp;remember=0&amp;_rnr_se=3W%2FC7p9ElNW6H%2BLdP0ndD9EIek4%3D
</pre>
<p>
POST リクエストの本文 (Content) に、
自分の電話番号 「253802yyyy」 (一部伏字、以下同様) と
通話先電話番号 「360469zzzz」 が指定されていることが分かる。
前述した 「_rnr_se」 の値も指定されている。
</p>
<p>
あとは同じリクエストを送信する Perl スクリプトを書くだけ:
</p>
<pre class="code">
$mech-&gt;post("https://www.google.com/voice/call/connect/",
	    Referer =&gt; "https://www.google.com/voice/",
	    Content =&gt; {
		outgoingNumber   =&gt; $call,
		forwardingNumber =&gt; $home,
		subscriberNumber =&gt; "undefined",
		phoneType        =&gt; 2,	# 7:gizmo, 9:gtalk
		remember         =&gt; 0,
		_rnr_se          =&gt; $rnr_se
	    });
$mech-&gt;success || die "Fail to call\n";
</pre>]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2011/02/745/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>地デジ MPEG-2 TS の PCR/PTS/DTS ラップアラウンド (PCR Wrap-around) 問題を回避して ffmpeg で PS 変換できるようにしてみた</title>
		<link>http://www.gcd.org/blog/2010/09/648/</link>
		<comments>http://www.gcd.org/blog/2010/09/648/#comments</comments>
		<pubDate>Mon, 20 Sep 2010 23:13:34 +0000</pubDate>
		<dc:creator>hiroaki_sengoku</dc:creator>
				<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=648</guid>
		<description><![CDATA[(地上)デジタルTV 放送を Linux サーバで予約録画しまくるようになった今日このごろ、 たまに失敗するのが気になっていた。 失敗といっても、 録画した MPEG-2 TS (Transport Stream) ファ [...]]]></description>
			<content:encoded><![CDATA[<p>
(地上)デジタルTV 放送を 
Linux サーバで予約録画しまくるようになった今日このごろ、
たまに失敗するのが気になっていた。
失敗といっても、
録画した MPEG-2 TS (Transport Stream) 
ファイルを 
<a href="http://www.videolan.org/">VLC</a> で再生する分には問題がないが、
<a href="http://ffmpeg.org/">ffmpeg</a> を使って MPEG-2 PS (Program Stream) 
フォーマットへ変換しようとすると途中で映像が止まってしまう
(TS 読み込みに失敗しているので他のフォーマットへの変換もおそらく無理)。
</p>
<p>
おそらく TS のデータに何らかの誤りがあって
ffmpeg が映像データを読むのをそこで止めてしまうためだろうと思っていた。
そりゃ放送なんだからノイズが混じることもあるだろうと思い込んでいたのだが、
よほど電波状況が悪くない限りエラー補正が効くだろうから、
ノイズが原因というのはありそうもない話。
</p>
<p>
なぜ PS へ変換したいかといえば、
TS のままだとファイルサイズが大きすぎる
(2時間の映画番組とかだと 12GB を超える) ので、
PS へ変換し (CM カットなどの編集を行なうときは PS の方が便利)、
さらに MPEG-4 などへ変換してサイズを小さくしたいから
(もちろん保存する必要がない番組は見たらすぐ消す)。
ところが PS への変換に失敗した番組は
TS のまま保存せざるを得ず、
そういうファイルが増えてきて、
2TB のハードディスクもだんだん手狭になってきた。
</p>
<p>
そこで、
何が原因で TS の読み込みに失敗しているのか、
まずは調べてみようと思ったのだが、
いかんせんファイルがデカすぎる。
12GB もあるとファイルの内容をダンプするのもままならない。
そこで変換が失敗する近辺のデータだけ抜き出して詳しく調べてみようと考えた。
<a href="http://en.wikipedia.org/wiki/MPEG_transport_stream">MPEG-2 TS</a> 
の仕様を調べてみると、
PCR (Program Clock Reference) なるタイムスタンプが 
100msec 以下の間隔で
MPEG-2 TS には埋め込まれているらしい。
問題の TS ファイルは、
2時間の番組中、
先頭から 1時間35分41秒ほど経過したあたりで読み込みに失敗するので、
PCR の値を手がかりにその近辺のデータを抜き出そうと考えた次第。
</p>
<p>
まずは PCR の値を表示する perl スクリプト tsdump.pl を作ってみた 
(後述する tsrenum.pl に -v オプションを与えることによって同じ出力が得られる)。
</p>
<pre class="terminal">
% ./tsdump.pl -v < 133000_GR23.ts
0008c4a PCR 24:55:00.570 8073051319+138
001a12d PTS 24:55:01.111 8073100071
001a132 DTS 24:55:01.011 8073091062
001d841 PTS 24:55:00.748 8073067367
0022556 PCR 24:55:00.628 8073056524+157
00226d5 PTS 24:55:01.045 8073094065
0026a65 PTS 24:55:00.769 8073069287
0029dcd PTS 24:55:01.078 8073097068
00300f1 PTS 24:55:00.791 8073071207
0033225 PTS 24:55:01.212 8073109080
003322a DTS 24:55:01.111 8073100071
0038f69 PTS 24:55:00.812 8073073127
003bcea PCR 24:55:00.685 8073061729+175
...
</pre>
<p>
「133000_GR23.ts」 
が問題の TS ファイル (23チャンネルなので TV東京 ^^;)。
次の
「0008c4a PCR 24:55:00.570 8073051319+138」
という行は、
ファイル先頭から 0008c4a バイト目に PCR があって、
そのタイムスタンプが 「24:55:00.570」 であることを示す。
</p>
<p>
行末尾の 「8073051319+138」 は生の PCR の値。
つまり、
PCR は 33bit の 「PCR base」 と 
9bit の 「PCR extension」 から構成されるが、
それぞれ 8073051319 と 138 であることを示す。
PCR base は 90kHz の解像度、
PCR extension は 27MHz の解像度。
とりあえずここでは後者は無視して、
前者 8073051319 を 90000 (= 90kHz) で割ると 
89700.570 秒で、
時分秒に直すと 24:55:00.570 になる。
</p>
<p>
PCR の他に、
PTS (Presentation Time Stamp) と DTS (Decode Time Stamp) 
というタイムスタンプも MPEG-2 TS には埋め込まれている。
これらは PCR と違って 90kHz の解像度の 33bit のデータのみ。
MPEG-2 TS では、
PCR の時刻を基準にして、
復号する時刻 (DTS) と再生する時刻 (PTS) を定めている
(音声と映像の同期のためにも使われる)。
前述したプログラムの出力において、
2カラム目に PTS, DTS と出力している行は、
それぞれ PTS, DTS の値であることを示す。
</p>
<p>
で、
PCR の値を表示させてみていきなり気付いてしまったのだが、
この問題の TS ファイルは PCR の値が 33bit の上限値ギリギリ。
33bit の上限は 0x1FFFFFFFF = 8589934591 だから、
PCR は 26:30:43.717 (26時間30分43秒余) までしかカウントできない。
このファイルは冒頭のタイムスタンプが 24:55:00.570 だから、
冒頭から 01:35:43.147 ほど経過したあたりで PCR の値が桁あふれ
(オーバーフロー) を起こして 
00:00:00.000 へ戻ってしまう (ラップアラウンド)。
</p>
<p>
これは冒頭から 1時間35分41秒ほど経過したあたりで 
ffmpeg が止まってしまう症状にピッタリ符合する
(2秒ほど差があるのは MPEG-2 復号に要する時間?)
ので、
もう原因は PCR のラップアラウンド (PCR Wrap-around) で間違いないと推測。
</p>
<blockquote>
「VLC で再生する分には問題がない」 と書いたが、
この問題の TS ファイルを VLC で再生して
1時間35分41秒あたり
(VLC の場合 TS 
再生時は時刻表示を行なわないのでシーンで探さざるを得ず大変)
をよく見てみると、
再生が一瞬 (1秒ほど?) 止まっていた。
26時間半に一度、
必ず起こるラップアラウンドなのに、
VLC でも完全な対策は (まだ) 行なわれていないようだ。
</blockquote>
<p>
原因調査の準備のために作ったスクリプトが、
いきなり原因究明の役に立つとは (^^;) と思いつつ、
tsdump.pl にタイムスタンプの値を付け替える
(すなわち 24:55:00.570 から始まるタイムスタンプを
00:00:00.000 始まりにリナンバーする)
処理を付け加えたスクリプト tsrenum.pl を書いてみた
(後述する pcr_write, ts_write 関数を追加しただけ)。
</p>
<pre class="terminal">
% ./tsrenum.pl < 133000_GR23.ts > 133000_GR23_fixed.ts
</pre>
<p>
tsrenum.pl を使ってタイムスタンプを付け替えた
133000_GR23_fixed.ts は、
ffmpeg を使って PS 変換ができるようになった。
原因調査の準備のために作ったスクリプトが、
ほとんどそのまま問題解決の役にも立ってしまった (^^;)^2。
</p>
<p>
tsrenum.pl は、
標準入力から読み込んだ MPEG-2 TS において最初に現れた 
PCR/PTS/DTS タイムスタンプを基準
(下記スクリプト中の $pcrOrg)
にして、
タイムスタンプを付け替えて標準出力へ書き出す:
</p>
<pre class="code">
#!/usr/bin/perl
use strict;
use warnings;
use POSIX;
use Getopt::Std;
our ($opt_v);
getopts('v') || &amp;help;
my $PacketSize = 188;
my $offset = 0;
my $pcrOrg;
for (;;) {
    my $buf;
    my $len = read(STDIN, $buf, $PacketSize);
    last unless $len &gt; 0;
    my @buf = unpack("C*", $buf);
    die if $buf[0] != 0x47;	# sync byte
    my $ind = (($buf[1] &amp; 0xE0) &gt;&gt; 5);
    my $adp = (($buf[3] &amp; 0x30) &gt;&gt; 4);
    my $pos = 4;
    if ($adp &amp; 0x2) {	# Adaptation field exist
	$pos++;
	my $af = $buf[$pos++];
	if ($af &amp; 0x10) {
	    my ($pcr33, $pcr9) = &amp;pcr(\@buf, $pos);
	    printf(STDERR "%07x PCR %s %d+%d\n",
		   $offset+$pos, &amp;stime($pcr33), $pcr33, $pcr9) if $opt_v;
	    &amp;pcr_write(\@buf, $pos, $pcr33);
	    $pos += 6;
	}
	if ($af &amp; 0x08) {
	    $pos += 6;
	}
	if ($af &amp; 0x04) {
	    $pos++;
	}
    }
    if ($ind &amp; 0x02) {
	if ($buf[$pos] == 0x00 &amp;&amp; $buf[$pos+1] == 0x00
	    &amp;&amp; $buf[$pos+2] == 0x01 &amp;&amp; ($buf[$pos+6] &amp; 0xC0) == 0x80) {
	    my $flag = $buf[$pos+7];
	    $pos += 9;
	    if ($flag &amp; 0x80) { # PTS
		my $ts = &amp;ts(\@buf, $pos);
		printf(STDERR "%07x PTS %s %d\n",
		       $offset+$pos, &amp;stime($ts), $ts) if $opt_v;
		&amp;ts_write(\@buf, $pos, $ts);
		$pos += 5;
		if ($flag &amp; 0x40) { # DTS
		    my $ts = &amp;ts(\@buf, $pos);
		    printf(STDERR "%07x DTS %s %d\n",
			   $offset+$pos, &amp;stime($ts), $ts) if $opt_v;
		    &amp;ts_write(\@buf, $pos, $ts);
		    $pos += 5;
		}
	    }
	}
    }
    syswrite(STDOUT, pack("C*", @buf), $len);
    $offset += $len;
}

sub stime {
    my ($ts) = @_;
    my $sec = floor($ts / 90000);	# 90kHz
    $ts -= $sec * 90000;
    my $min = floor($sec / 60);
    $sec -= $min * 60;
    my $hour = floor($min / 60);
    $min -= $hour * 60;
    return sprintf("%02d:%02d:%02d.%03d", $hour, $min, $sec, $ts/90);
}

sub help {
    print STDERR &lt;&lt;EOF;
Usage: tsrenum.pl &lt;opt&gt;
opt:   -v            ; verbose
EOF
    exit 1;
}
</pre>
<p>
MPEG-2 TS を標準入力から 1パケットずつ @buf に読み込んで、
PCR を見つけたら <br />
pcr(\@buf, $pos) 関数を呼び出して PCR base の値 
$pcr33 を取得し、<br />
pcr_write(\@buf, $pos, $pcr33) 関数で 
PCR base の値を変更して標準出力へ書き出す。
PTS, DTS についても同様に、
ts(\@buf, $pos) 関数を呼び出して値を取得し、
ts_write(\@buf, $pos, $ts) 関数で変更する。
</p>
<p>
<a href="http://neuron2.net/library/mpeg2/iso13818-1.pdf">ISO/IEC 13818-1</a>
の 2.4.3.4節 Adaptation field (20ページ) の
Table 2-6 - Transport Stream adaptation field を見ると、
adaptation field の 2バイト目から 33bit が PCR base の値、
続いて 6bit の予約ビット、
その次の 9bit が PCR extension の値であることが分かる。
したがって pcr(\@buf, $pos) 関数は以下のように書ける:
</p>
<pre class="code">
sub pcr {
    my ($br, $pos) = @_;
    my $pcr33 = (($br-&gt;[$pos] &lt;&lt; 25) | ($br-&gt;[$pos+1] &lt;&lt; 17)
		 | ($br-&gt;[$pos+2] &lt;&lt; 9) | ($br-&gt;[$pos+3] &lt;&lt; 1)
		 | (($br-&gt;[$pos+4] &amp; 0x80) != 0));
    my $pcr9 = ((($br-&gt;[$pos+4] &amp; 0x01) &lt;&lt; 8) | $br-&gt;[$pos+5]);
    return ($pcr33, $pcr9);
}
</pre>
<p>
pcr_write(\@buf, $pos, $pcr33) 関数は PCR base の値 $pcr33 から 
$pcrOrg を減算してから 
pcr(\@buf, $pos) 関数の逆を行なうだけ:
</p>
<pre class="code">
sub pcr_write {
    my ($br, $pos, $pcr33) = @_;
    $pcrOrg = $pcr33 unless defined $pcrOrg;
    $pcr33 -= $pcrOrg;
    $br-&gt;[$pos] =   (($pcr33 &gt;&gt; 25) &amp; 0xFF);
    $br-&gt;[$pos+1] = (($pcr33 &gt;&gt; 17) &amp; 0xFF);
    $br-&gt;[$pos+2] = (($pcr33 &gt;&gt;  9) &amp; 0xFF);
    $br-&gt;[$pos+3] = (($pcr33 &gt;&gt;  1) &amp; 0xFF);
    if ($pcr33 &amp; 1) {
	$br-&gt;[$pos+4] |= 0x80;
    } else {
	$br-&gt;[$pos+4] &amp;= 0x7F;
    }
}
</pre>
<p>
<a href="http://neuron2.net/library/mpeg2/iso13818-1.pdf">ISO/IEC 13818-1</a>
の 2.4.3.7節 Semantic definition of fields in PES packet (31ページ) の
Table 2-17 - PES packet を見ると、
<a href="http://en.wikipedia.org/wiki/Packetized_Elementary_Stream">PES</a> (Packetized Elementary Stream) の 9バイト目から
marker bit を挟みつつ 33bit の PTS と DTS が格納されていることが分かる。
</p>
<p>
したがって 
ts(\@buf, $pos) 関数と
ts_write(\@buf, $pos, $ts) 関数は以下のように書ける:
</p>
<pre class="code">
sub ts {
    my ($br, $pos) = @_;
    return ((($br-&gt;[$pos] &amp; 0x0E) &lt;&lt; 29)
	    | ($br-&gt;[$pos+1] &lt;&lt; 22) | (($br-&gt;[$pos+2] &amp; 0xFE) &lt;&lt; 14)
	    | ($br-&gt;[$pos+3] &lt;&lt; 7)  | (($br-&gt;[$pos+4] &amp; 0xFE) &gt;&gt; 1));
}

sub ts_write {
    my ($br, $pos, $ts) = @_;
    $pcrOrg = $ts unless defined $pcrOrg;
    $ts -= $pcrOrg;
    $br-&gt;[$pos] =   ((($ts &gt;&gt; 29) &amp; 0x0E) | ($br-&gt;[$pos] &amp; 0xF1));
    $br-&gt;[$pos+1] =  (($ts &gt;&gt; 22) &amp; 0xFF);
    $br-&gt;[$pos+2] = ((($ts &gt;&gt; 14) &amp; 0xFE) | ($br-&gt;[$pos+2] &amp; 0x01));
    $br-&gt;[$pos+3] =  (($ts &gt;&gt;  7) &amp; 0xFF);
    $br-&gt;[$pos+4] = ((($ts &lt;&lt;  1) &amp; 0xFE) | ($br-&gt;[$pos+4] &amp; 0x01));
}
</pre>
<p>
すべて解決した後で気付いたのだが (^^;)、
ラップアラウンドする TS ファイルを読み込んだときは 
ffmpeg が的確なエラーメッセージを出力していた:
</p>
<pre class="terminal">
...
frame= 1328 fps= 21 q=2.0 size=   31898kB time=44.00 bitrate=5938.8kbits/s dup=24 drop=0    
frame= 1341 fps= 21 q=2.0 size=   32002kB time=44.45 bitrate=5898.1kbits/s dup=24 drop=0    
[mpegts @ 0x6253c0]Invalid timestamps stream=0, pts=4078, dts=8589929661, size=53459
*** drop!
*** 1 dup!
*** drop!
*** drop!
*** drop!
*** drop!
*** drop!
adding -2147483648 audio samples of silence
adding -2147483648 audio samples of silence
*** drop!
...
</pre>
<p>
「Invalid timestamps ... pts=4078, dts=8589929661」
すなわち PTS が 00:00:00.045 なのに、
DTS が 26:30:43.663 なので無効なタイムスタンプである、
というエラー出力。
タイムスタンプがラップアラウンドしたときは、
「Invalid timestamps」
とは言えないと思うのだが...
</p>
<p>
念のため ffmpeg の最新版を 
「git clone git://git.ffmpeg.org/ffmpeg/」
で取得してコンパイルしてみたのだが、
「FFmpeg version git-735bbae」
でも同様に
「Invalid timestamps」
エラーが出力された。
</p>]]></content:encoded>
			<wfw:commentRss>http://www.gcd.org/blog/2010/09/648/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<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[Android]]></category>
		<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=613</guid>
		<description><![CDATA[Android は 2.1-update1 以降でマルチタッチ (Multi-touch) をサポートしている。 ところがマルチタッチといっても、 ピンチイン/ピンチアウトなどのジェスチャをサポートしているだけのアプリが [...]]]></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[Android]]></category>
		<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=609</guid>
		<description><![CDATA[Nexus One など最近のスマートフォンには、 加速度 (Accelerometer)、 環境光 (照度, Ambient Light)、 磁場 (磁界, Magnetic Field)、 方位 (電子コンパス, O [...]]]></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[Android]]></category>
		<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 とのデータ同期が基本 [...]]]></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/detail/nexus-one">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>
<tr><td>2010-11</td>
<td><a href="http://www.sharp.co.jp/products/is01/">IS01</a></td>
<td>Android 1.6 donut</td></tr>
<tr><td>2010-12</td>
<td><a href="http://www.samsungmobile.com.hk/galaxys/ENG/">Galaxy S</a></td>
<td>Android 2.2 froyo</td></tr>
<tr><td>2010-12</td>
<td><a href="http://www.huaweidevice.com/worldwide/productFeatures.do?pinfoId=2831&amp;directoryId=2037&amp;treeId=37">IDEOS U8150-B</a></td>
<td>Android 2.2 froyo</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[Android]]></category>
		<category><![CDATA[プログラミングと開発環境]]></category>

		<guid isPermaLink="false">http://www.gcd.org/blog/?p=585</guid>
		<description><![CDATA[私の職場ではグループウェアとしてサイボウズを利用している。 自分のスケジュールを iCalendar 形式で取得してみたくなった (Nexus One を買うとそういう気分になる ^^;) ので、 google で検索し [...]]]></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サービスを (もちろん無償で) [...]]]></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 によるリファレンス実装が付属しているが、 その glob [...]]]></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>1</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ブログから自宅サーバへ引っ越した (つまりネームサーバの設定を変更して切替。有料プランの解約はこれから)。 もともと両ブログは相互に [...]]]></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 エミュレータ (完全 [...]]]></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.1 [...]]]></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) モードだとパスワード破りの可能性が 無いわ [...]]]></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 D [...]]]></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&l=3Djp&p=3Dm&r=3D8083XXXX&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&l=3Djp&p=3Dv&r=3D8083XXXX&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&l=3Djp&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&l=jp&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) を走らせることができる。  [...]]]></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>
	</channel>
</rss>

