Ruby 1.9.0 released?

12/25 23:33 の commit で 1.9.0 は freeze されてリリース……のかな?

Tue Dec 25 23:33:55 2007  Yukihiro Matsumoto  <matz@ruby-lang.org>

        * development version 1.9.0 released.

でも、svn リポジトリ上で tag が作られた様子は無いので、まだなのかも。
あと、Code Golf 用の goruby という実行ファイルがどさくさに紛れて追加されたようだ(ruby-dev:32766 )。
下のようなコードが予め組み込まれた ruby 処理系で、メソッド名を省略できたり h 一文字で "Hello, world!" と表示されたり、なかなかアヤシイ。

class Object
  def method_missing m, *a, &b
    r = /^#{m}/
    t = (methods + private_methods).sort.find{|e|r=~e}
    t ? __send__(t, *a, &b) : super
  end

  def h(a='H', b='w', c='!')
    puts "#{a}ello, #{b}orld#{c}"
  end
end

Ruby 1.9 の新機能を調べてみた

1.9.0 のリリースも近いということで、Changes in Ruby 1.9 を参考にしながら Ruby 1.9 trunk (r14828) で遊んでみた。
まあ、ほとんどは上のサイトに書いてあるとおりなんだけど、「おっ」と思った点やその他で気付いた点を以下に列挙。(既に書かれている内容はほぼ省略)

  • Changes in Ruby 1.9 のページに書いてあるけど現状に即していない点。
    • Object#__send__ は、けっきょく可視性に関わらず全てのメソッドを呼べるようになった。(__send__send! は廃止)
    • NameError は、けっきょく StandardError のサブクラスに戻った。
    • Hash#eachHash#each_pair は同じ動作になった。
    • 他にもあるかもしれないけど、とりあえず気付いたのは以上。
  • RubyGems が標準で組み込まれる
    • 機能縮小版が自動的に組み込まれる (gems_prelude.rb)
    • コマンドライン--disable-gems を指定すれば回避可能
  • StringEncoding
    • 文字列リテラル"\uHHHH" と書けるようになった。
    • $KCODE は意味をなさなくなった (使おうとすると warning が出る)
    • String#size で文字数, String#bytesize でバイト数
    • 文字列オブジェクトごとに encoding 情報を持っている。(String#encoding)
    • String#ord は、多バイト文字の場合は Unicode(UCS) が返る(ようだ)。 (追記: 勘違い)
    • String#force_encoding(encode_name) で encoding を変更. String#valid_encoding? でその文字列が正しい encoding なのか(文字列として正しいバイト表現なのか)をチェック。
a = "\xE3\x81\x82"  # UTF-8 で "あ"
p a.encoding 
#=> <Encoding:ASCII-8BIT>
a.force_encoding('UTF-8')
p a.encoding
#=> <Encoding:UTF-8>
    • ファイルの先頭付近に "# -*- encoding: UTF-8 -*-" と書いておくと、そのファイルのデフォルトエンコーディングを指定できる。
      • ファイルの途中に書いても無意味。2度目の指定も無意味。
      • デフォルトエンコーディングを指定した場合でも、8bit 文字が含まれていない場合は ASCII-8BIT になる?
# -*- encoding: UTF-8 -*-
p "a".encoding    #=> <Encoding:ASCII-8BIT>
p "".encoding   #=> <Encoding:UTF-8>
      • encoding: じゃなくて coding: でも同じ動作のようだけど、どちらが正しいんだろう?
    • コマンドラインから --encoding=UTF-8 といった指定も可能。
    • ファイルから読む場合は File.open(filename, 'r:UTF-8') とか。
    • File.open(filename, 'w:EUC-JP') とか書くと自動変換してくれるのかと思ったけど、してくれないようだ。 (追記: その後のバージョンでは変換してくれるようになった)
    • Encoding.default_external って、何の意味があるんだろう。どうやって変更するんだろう。
    • まだ全貌を掴めてない。
  • その他
    • p の返り値が nil ではなくなった。(引き数をそのまま返す。引き数なしの場合は nil)
  • 最近の話題
    • 単項演算子 ! がメソッド扱いになり、再定義できるようになった。
      • !!object で true または false に変換する、というイディオムが通用しなくなる(可能性がある)ので、ちょっとだけ気持ち悪い。
class A; def !@; "hoge"; end; end
p ! A.new  #=> "hoge"

! が再定義できると聞いて、とりあえず皆やるであろうことをやってみた。

class TrueClass
  def !@; true; end
end
p !true  #=> true

うお、再定義できた! すげえ。


後日追記。
m17n 関連について、もう少しきちんとまとめました。id:macks:20080102

Ruby で DNS サーバを自作する

プログラマブルDNS サーバが欲しくなったので、スクリプト系言語で DNS が実装できるかどうか調べてみた。
Perl であれば、CPANNet::DNS::Server というモジュールがあるので、これを使えば簡単に DNS サーバが実装できるようだ。
また、既存の実装では DNS Balance が「Ruby で実装された DNS サーバ」だということが分かったが、コードを見たところあまり流用したくなるような内容ではなかった。
そこで、RFC 1034, RFC 1035 を読みつつ*1Ruby で自作してみることにした。

*1:きちんと実装するなら RFC 1123 の Section 6.1、RFC 2181 あたりも読む必要がありそうだ。

続きを読む

Ruby で DNS Update (RFC 2136)

Ruby を使って RFC 2136 の DNS Update を実行する方法を調べてみた。
標準添付の Resolv::DNS クラスはルックアップの機能しか無いので、PerlNet::DNS のようなモジュールを探してみたところ、Perl の Net::DNS を移植したという 2 つのライブラリを見付けた。

両方比べてみたところ、Net::DNS の方は肝心の update 関連の機能が未実装だったので、今のところ選択肢は pNet::DNS だけのようだ。

pNet::DNS の使い方

まずはインストール。

$ sudo gem install pnet-dns

nsupdate コマンドでの操作を Ruby に置き換えるなら以下のようになる。

$ nsupdate
> server 192.168.1.1
> prereq nxdomain host1.example.com.
> update add host1.example.com. 3600 IN A 192.168.1.2
> send

↑上の操作は、以下↓のようになる。

require 'rubygems'
require 'Net/DNS'
resolver = Net::DNS::Resolver.new(:nameservers => %w(192.168.1.1))
packet = Net::DNS::Update.new_from_values('example.com')
packet.push('prereq', Net::DNS.nxdomain('host1.example.com'))
packet.push('update', Net::DNS.rr_add('host1.example.com 3600 IN A 192.168.1.2'))
resolver.send(packet)

また、TSIG (Transaction Signature) を使ってリクエストの認証を行なうには、Net::DNS::Resolver#send の直前に Net::DNS::Packet#sign_tsig を使用する。

packet.sign_tsig('キーの名前', 'Base64エンコードされた秘密鍵')
resolver.send(packet)

キーの名前や Base64 エンコードされた秘密鍵は、dnssec-keygen コマンドで作った鍵ファイルから取り出して使用する。
なお、pNet::DNS の現時点の最新リリース(0.0.4)には TSIG 関連の機能が動作しないバグがあるので、svn リポジトリから最新版を checkout してくるか、パッチ*1をあてて使用する必要がある。

感想

TSIG の動作修正パッチを作ったのは俺なんだけど、今まで誰も気付かず放置されてたのが不思議なくらいに初歩的なミスばかりで、こういう辺りは圧倒的なユーザー人口と厚みを誇る Perl とは比べものにならないなあ、と思った。

帰ってきた筋少ちゃん祭り!

恵比寿リキッドルーム筋肉少女帯を見に行ってきた。筋少のライヴは、渋谷公会堂の最終公演以来だ。
ステージはたいへん素晴らしく、筋少の素晴らしさと自分が今でも筋肉少女帯が大好きなんだということを再確認できた良いライヴだったんだが、そもそも体調があまり良くなかったため、途中(水木一郎が登場した辺り)でほぼ脱落。一生の不覚。
もっとも、新アルバムを作っているそうだし、アルバムが出るならツアーもやるだろうから、まだチャンスはあるさ、と自分に言い聞かせよう。

ThinkPad X60s に Debian GNU/Linux 4.0 (etch) をインストール

ThinkPad X60s に Debian GNU/Linux 4.0 (etch) をインストールしてみた。*1
とは言え、基本的なインストールは有線 LAN (e1000) 経由なら特に詰まることもなく、普通に終了。Xorg も i810 ドライバで普通に起動した。
少しだけひねりが必要だったのは、以下の 2 つ。

無線 LAN

私が購入したのは Intel 3945ABG (MIMO 非対応) が搭載されているモデルだったので、non-free/contrib セクションから ipw3945 および関連パッケージをインストール。

 $ sudo apt-get install ipw3945-modules-2.6-686 ipw3945d

ipw3945 モジュールをロードして /etc/init.d/ipw3945d を実行したところ、eth2 として認識された。
無線 LAN そのものの設定は、個人的事情により ESS-ID で使い分ける必要があったため、/etc/network/interfaces は以下のようになった。

(中略)
mapping eth2
        script /root/bin/scan-essid.sh
        map WLAN1xxxxxx wlan-station1
        map WLAN2yyyyyy wlan-station2

iface wlan-station1 inet dhcp
        wireless-essid WLAN1xxxxxx
        ...
(中略)

iface wlan-station2 inet dhcp
        wireless-essid WLAN2yyyyyy
        ...
(以下略)

また、/root/bin/scan-essid.sh はこんな感じ。

#!/bin/sh -e
iface="$1"
essid_list=$(iwlist $iface scanning 2>/dev/null | sed -n -e '/^ *ESSID:/ { s/.*:"\(.*\)"/\1/; p }')

while read id scheme; do
  for essid in $essid_list; do
    if [ "$id" = "$essid" ]; then
      echo $scheme
      exit 0
    fi
  done
done

exit 1

RTC

/sbin/hwclock を実行すると、時刻を読み出せずに `select() to /dev/rtc to wait for clock tick timed out' というエラーメッセージが出力されるという症状に見舞われた。これに加えて、ハードウェア時計を JST にしているため*2、起動時にシステム時計が 9 時間ずれる現象が発生した。
試行錯誤と Google 検索の後、`hwclock --directisa' と実行すれば時計が読めることが判明。ついでに、rtc モジュールを rmmod した場合も大丈夫なことが判明。
結局、/etc/modprobe.d/local に `blacklist rtc' と書いて解決。

*1:DtoD 移植の時にインストールした etch (id:macks:20070314)は、Windows を修復する時に消えてしまった。

*2:Windowsデュアルブートなので。

ThinkPad X60s DtoD(Disk to Disk) リカバリー領域の移植

ThinkPad X60s を買った。
安かったので HDD 40GB のモデルを購入したんだけど、もちろんそんな容量では話にならないので、さっそく HDD を換装した。
その際 DtoD(Disk to Disk)リカバリー領域を移植したので、その時の記録を残しておこうと思う。なお、本体と換装用ドライブ以外に私が使ったのは以下のもの。

  • USB 接続 HDD ケース (2.5" SATA 用)
  • Linux マシン1台
    • PXE ブート用の母艦。USB CD-ROM ドライブとかで Knoppix 等を起動できるなら不要。
  • Windows マシン1台
    • Lenovo からダウンロードするリカバリーディスクの FD イメージが自己展開式の .exe で配布されてるので必要。
  • USB FDD
    • リカバリーディスクを起動するのに必要。ネットワークブートが出来るなら memdisk (syslinux に同梱)を使えるので不要かも。
  • Debian(etch) の netboot インストーラ
    • 試行錯誤の度にネットワークブートするのが面倒だったので、etch をインストールしてしまった。

私がやった手順は以下のとおり。

  1. ドライブを換装する。元のドライブは USB 接続な HDD ケースに納める。
  2. 有線 LAN を使ってネットワークブート。適当なディスクレス Linux 環境を起動させる。また、USB に移植元のドライブを接続して認識したことを確認する(dmesg とか /proc/partitions とか)。
    • 以後、移植先のディスクを /dev/sda、移植元のディスクを /dev/sdb とする。
    • 私はこの時に HDD に Debian GNU/Linux をインストールした。
  3. " fdisk -l /dev/sdb " などで DtoD 領域のサイズを確認した後、移植先の HDD に適当にパーティションを確保して、dd でパーティション内容を丸ごとコピー。
    • パーティションID 0x12 なのが DtoD 領域。もちろん、移植先パーティションの ID も 0x12 にしておく。
    • 移植元が /dev/sdb2、移植先が /dev/sda2 なら、普通に dd if=/dev/sdb2 of=/dev/sda2
    • 移植先のパーティションはディスクのどこに確保しても大丈夫なようだが、とりあえずディスクの末尾に作ってみた。
    • dd が終われば移植元のディスクはもう使わないので、ケーブルを抜いておく。
  4. 移植先のディスクのジオメトリ情報を記録しておく。
    • sfdisk -l /dev/sda
    • fdisk -l /dev/sda
  5. 移植先パーティションのブートレコードを取り出す。(移植先パーティションは /dev/sda2)
    • dd if=/dev/sda2 of=/tmp/sda2.bin count=1
  6. file コマンドなどで、先ほど取り出した中身を確認する。ヘッダ数(heads)、開始セクタ位置(hidden sectors)、セクタ数(sectors)などが実際の値ではなく、移植元のドライブの値になっているはずなので、適当なバイナリエディタ等で修正する。
  7. ブートレコードを移植先パーティションに書き戻す。
    • dd if=/tmp/sda2.bin of=/dev/sda2
  8. Lenovo からリカバリー修復ディスケット]をダウンロードし、そこからブートする。
    • "replace the current mbr" を選ぶ。
  9. 後は、マシン起動時に ThinkVantage ボタンを押せば Rescue and Recovery メニューに入れるはずなので、システムのリカバリーを選択して Windows 環境を復旧させれば作業終了。

以上の方法は「私の場合、それで出来た」というだけであって、当然ながらあらゆる保証はしないので、参考にする場合は At your own risk で。それに、たぶんもっと簡単な方法がありそうな気がする。
その他、作業中に気づいた点などは以下のとおり。

  • DtoD 領域は、パーティション ID こそ特殊なものの、中身は FAT32
  • とは言え、ブートレコードが特殊らしく、普通に DOSWindows のブートが可能な FAT32 パーティションを作成し、ファイルをコピーする、という方法ではダメなようだ。

なお、今回の作業にあたって、X40 - ThinkPad X60s/X40/X31 メモ の記述がたいへん参考になったことを申し添えておく。

*1:結果はこんな感じ。> sda2.bin: x86 boot sector, code offset 0x58, OEM-ID "MSDOS5.0", sectors/cluster 8, Media descriptor 0xf8, heads 255, hidden sectors 225520470, sectors 8916074 (volumes > 32 MB) , FAT (32 bit), sectors/FAT 8680, reserved3 0x800000, serial number 0xccdee5ea, label: "SERVICEV001"