雑記

2000|01|
2003|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|
2007|01|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|09|11|
2009|02|03|05|06|07|08|10|11|12|
2010|01|03|04|05|06|07|08|09|10|
2011|05|06|09|10|
2012|03|07|09|12|
2013|01|02|04|05|07|08|10|11|
2014|04|05|08|10|12|
2015|01|05|
2016|09|
2024|06|
2025|01|

2009-07-08 [長年日記]

[Ruby]「〜」のUTF-8からISO-2022-JPへの変換で文字化け

Ruby on Rails 1.2.6 + GetText でActionMailerを使ってフォームから入力された内容をメールで送信するような実装をしていたところ、「〜」を入力すると文字化けするとの連絡が。

調べたところ、Windowsで「〜」を入力すると、fullwidth tilde(U+FF5E, EFBD9E)のコードが渡されるんですが、GetTextから呼ばれているrubyのNKFモジュールはこれをJIS補助漢字の(2237)に変換するため、補助漢字を表示できないメールクライアントでは文字化けするようでした。より文字化けしにくいと思われるJIS基本漢字の(2141)の方はwave dash(U+301C, E3809C)に割り当てられているようです。 さらに探るとどろどろした歴史が垣間見えてきたのでこれはこういうもんだと納得することに。

かといって、全Windowsユーザーがらみで発生する問題を無視するわけにもいかないので、fullwidth tildeをwave dashに変換してしまうことに。

具体的には、

1. 以下の内容のファイルをRAILS_ROOT/lib/gettext_ext.rbという名前で保存

if defined? ActionMailer
  module ActionMailer #:nodoc:
    class Base #:nodoc:
      def create!(*arg) #:nodoc:
        create_without_gettext!(*arg)
        if Locale.get.language == "ja"
          require 'nkf'
          @mail.subject = base64(@mail.subject)
          part = @mail.parts.empty? ? @mail : @mail.parts.first
          if part.content_type == 'text/plain'
            part.charset = 'iso-2022-jp'
            if NKF.guess(part.body) == NKF::UTF8
               part.body = part.body.gsub("\xef\xbd\x9e", "\xe3\x80\x9c")
            end
            part.body = NKF.nkf('-j', part.body)
          end
        end
        @mail
      end
    end
  end
end

2. environment.rbに次の1行を追加

require 'lib/gettext_ext.rb'

3. Rails再起動で完了

ところで、

part.body = part.body.gsub("\xef\xbd\x9e", "\xe3\x80\x9c")

の行が

part.body.gsub!("\xef\xbd\x9e", "\xe3\x80\x9c")

では駄目だったんですけど、なぜ??


2009-07-10 [長年日記]

[Ruby][Rails] TMailでSubjectにJIS補助漢字を使用すると文字化け

前回の続き。

前回の対処後、他に文字化けしないか検証していたところ、SubjectにJIS補助漢字が入ると文字化けすることを確認。

単純に、前回のソースの

 @mail.subject = base64(@mail.subject)

の部分を

 if NKF.guess(@mail.subject) == NKF::UTF8
    @mail.subject = @mail.subject.gsub("\xef\xbd\x9e", "\xe3\x80\x9c")
 end
 @mail.subject = base64(@mail.subject)

のように書き換えると、「〜」に関してはうまくいくようになるが、コピーライトマークのような他のJIS補助漢字を試すと、Thunderbirdなどの、本文の時には文字化けが発生していなかったクライアントでも化けてしまう。

具体例を挙げると、コピーライトマーク一文字のSubjectは、「=?iso-2022-jp?B?GyRCRCJtGyhC?=」となり、これをデコードすると 「ESC $ B D " ( B ESC ( B \n」という完全に壊れた文字列になる。

で、ソース探索の旅に出て、ようやくたどり着いたのがTMailのEncoderクラスの中のscanaddとextract_Jの両メソッド。これらがJIS補助漢字のことを考慮に入れていないため、文字列を完全に壊してしまうらしい。

ということで、サブジェクトの文字化けにも対応した版はこちら。前回のgettext_ext.rbを置き換えて下さい。

if defined? ActionMailer
  module ActionMailer #:nodoc:
    class Base #:nodoc:
      def create!(*arg) #:nodoc:
        create_without_gettext!(*arg)
        if Locale.get.language == "ja"
          require 'nkf'
          if NKF.guess(@mail.subject) == NKF::UTF8
            @mail.subject = @mail.subject.gsub("\xef\xbd\x9e", "\xe3\x80\x9c")
          end
          @mail.subject = NKF.nkf('-j -m0', @mail.subject)
          part = @mail.parts.empty? ? @mail : @mail.parts.first
          if part.content_type == 'text/plain'
            part.charset = 'iso-2022-jp'
            if NKF.guess(part.body) == NKF::UTF8
               part.body = part.body.gsub("\xef\xbd\x9e", "\xe3\x80\x9c")
            end
            part.body = NKF.nkf('-j', part.body)
          end
        end
        @mail
      end
    end
  end

  module TMail
    class Encoder
      def scanadd( str, force = false )
        types = ''
        strs = []
        ki = false
        if str.respond_to?(:encoding)
          enc = str.encoding
          str.force_encoding(Encoding::ASCII_8BIT)
        end
        until str.empty?
          if m = /\A[^\e\t\r\n ]+/.match(str)
            types << (force ? 'j' : 'a')
            if str.respond_to?(:encoding)
              strs.push m[0].force_encoding(enc)
            else
              strs.push m[0]
            end
          elsif m = /\A[\t\r\n ]+/.match(str)
            types << 's'
            if str.respond_to?(:encoding)
              strs.push m[0].force_encoding(enc)
            else
              strs.push m[0]
            end
          elsif m = /\A\e(\x24(?:[B@]|\([BD@])[^\e]+)/.match(str)
            types << 'j'
            ki = true
            str = m.post_match
            if str.respond_to?(:encoding)
              strs.push m[1].force_encoding(enc)
            else
              strs.push m[1]
            end
          elsif m = /\A\e\(B/.match(str)
            str = m.post_match
            if ki
              ki = false
            else
              types << 'a'
              if str.respond_to?(:encoding)
                strs.push m[0].force_encoding(enc)
              else
                strs.push m[0]
              end
            end
          else
            raise 'TMail FATAL: encoder scan fail'
          end
          (str = m.post_match) unless m.nil?
        end

        do_encode types, strs
      end

      def extract_J( chunksize, str )
        size = max_bytes(chunksize, str.size) - 6
        size = (size % 2 == 0) ? (size) : (size - 1)
        return nil if size <= 0
        "\e#{str.slice!(0, size)}\e(B"
      end
    end
  end

end

ちなみに、前回

 @mail.subject = base64(@mail.subject)

だったところが、

 @mail.subject = NKF.nkf('-j -m0', @mail.subject)

に書き換わってますが、これは追跡の途中、ここでbase64エンコードしておいてもまったく意味が無いことが分かったので、JISに変換するだけにしました。

余談ですが、今回、grep頼りのソース探査の限界をひしひしと感じました。IDEとか使うとこういう作業は楽になるんですかね?


2009-07-11 [長年日記]

[FreeBSD] NEC Express5800/110GeでFreeBSD

Express5800で今度はFreeBSDのインストールに挑戦。SATAのHDDや9650SEを使ったRAIDディスクへのインストールは普通にできるので、オンボードRAIDについてのみ書きます。

準備

  1. マニュアルに従い、マザーボードのジャンパを切り替えてRAIDを有効にする
  2. BIOS画面でRAIDコントローラのメッセージが出たらCtrl+Mで設定画面に入る
  3. RAIDを構築する

インストール

バージョンごとの状況は以下のようになりました

6.3-RELEASE
HDDもRAIDも認識せず。
6.4-RELEASE
HDDは認識するが、RAIDとしては認識せず。
7.2-RELEASE
RAIDをarデバイスとして認識する。

というわけで、7.2をインストール。 インストーラを起動してディスク選択に進むと、一覧ではHDDがadとして、RAIDがarとして見えます。^J .orangered.arで使用しているディスクがadとしても見える状態になりますが、RAIDとして使う場合はad側を使ってはいけません。^J あとはarを選択して通常のディスクと同じように使用領域の設定をし、インストールするだけです。

監視

root権限でatacontrolコマンドを実行すると状態を確認できます。

■ RAID0の場合
# atacontrol status ar0
ar0: ATA RAID0 stripesize=128 status: READY
 subdisks:
   0 ad6  ONLINE
   1 ad8  ONLINE

■ RAID1の場合
# atacontrol status ar0
ar0: ATA RAID1 status: READY
 subdisks:
   0 ad6  ONLINE
   1 ad8  ONLINE

root権限が必要なので、Nagiosなどで監視する場合sudoの類と組み合わせる必要があります。

1. check_raid_arを/usr/local/libexec/nagios/の下にコピー
2. /usr/local/etc/sudoersに以下の行を追加
 nagios  ALL=NOPASSWD:/sbin/atacontrol
3. Nagiosのコンフィグに以下の設定を追加

コマンドの定義:

define command{
   command_name   check_raid_ar
   command_line   $USER1$/check_raid_ar -a $ARG1$
   }

サービスの定義:

define service{
   use         generic-service # (割愛)
   host_name      <ホスト名>
   service_description  System Disk RAID
   check_command     check_raid_ar!ar0
   }

設定が正しく出来ると、以下のような感じで現在の状態を監視できるようになります(画像はarを使っている他のサーバの物です)。 画像の説明


2009-07-16 [長年日記]

[FreeBSD] NEC Express5800/110GeでFreeBSD(続き)

前回、インストールに成功して喜んでいたオンボードRAIDですが、試しにディスクをわざと抜いて交換し、リビルドをかけようとするとカーネルがパニックしました。いちおうBIOSメニューからリビルドはできるのですが、その間サーバとしては停止してしまうのでよろしくありません。

というわけで、BIOS RAIDはやめてソフトウェアRAIDの設定を試すことにしました。BIOS RAIDの方もmetadata formatがベンダー依存ってだけで、実態はソフトウェアRAIDですが。

一連の作業の前に、RAIDの表示が起動時に出るのは煩わしいので、ケースを開けてジャンパを戻し、RAID機能は切っておきます。

FreeBSDのソフトウェアRAID実装

FreeBSDのソフトウェアRAID実装としては、最近はgmirrorが主流のようです。ataraid(ar)はインストーラが対応してて、rootパーティションのミラーをする場合Fixitとか面倒な手順を踏まなくていいんですが、どうも上記パニックとか、以前send-prしたMatrixRAIDの不具合が放置されていたりと、あまり活発にメンテナンスされていない印象を受けます。ccdやgvinumというのも一応ありますが、ccdはルートパーティションのミラーが出来ない、gvinumはGEOM対応で不安定になったまま放置(たぶん)という状態だったと思います。 よって今回はgmirrorを使うことに。ちなみに、私が調べた範囲でのarとgmirrotの比較表を以下に示します。

.border.

デバイスargmirror
インストーラ対応×
ホットスワップ
オートリビルド××
リビルド中の再起動×

ちょっと実験

最新版(7.2)ではgmirrorでのインストールに対応してないかなぁ、と淡い期待を寄せてarでミラーを組むときの手順を試してみました。参考までに、arでの手順も併記します。

インストーラが起動したら、インストール作業に入る前にまずFixitに入ってRAIDを構築します。

(((.half clear.

# chroot /dist
# mount -t devfs devfs /dev
# kldload geom_mirror.ko
# gmirror label gmir0 ad6 ad8

)))

.half. (((

# chroot /dist
# mount -t devfs devfs /dev
# atacontrol create RAID1 ad6 ad8

)))

.clear. ここでいったん再起動します。

gmirrorの場合、FreeBSDのブート画面が表示されたら「6」を押してloader promptに入り、geom_mirrorモジュールをロードします。arの場合はここでは何もせずとも認識されます。

(((.half.

OK load geom_mirror
 :
OK boot

)))

.clear. これでインストールに進みSelect Drive(s)の画面で「gmir0」が選択肢に挙がれば成功!なのですが、残念ながら表示されませんでした。インストーラのCDにもgeom_mirrorモジュールは入っていたので、ディスク検知の処理を書き換えればうまくいくんでしょうか?

ともあれ、現状ではインストール後にFixitでミラー構築するしかないようなので、そちらの方向で行くことに。

ミラーの設定

ミラーの設定をする前に、OSを通常の手順でマスターにするHDDへとインストールします。今後の作業もあるので、livefsのイメージを焼いてインストールするのが良いと思います。^J .orangered.以下の作業は一発勝負です。ミスすると最悪ディスクの内容が壊れて再インストールとなります。

インストールが完了したらいったんリブートします。インストーラが起動したらFixitを選択して、以下の作業を行います。

livefs環境の利用準備

Fixit# chroot /dist
Fixit# mount -t devfs devfs /dev

gmirrorカーネルモジュールのロード

Fixit# gmirror load -v
Module available.
Done.

ミラーの設定

Fixit# gmirror label gm0 ad6
Fixit# gmirror insert gm0 ad10

これでディスクのコピーが始まるので、以下のコマンドで完了するまで待つ。

Fixit# gmirror status gm0
      Name    Status  Components
mirror/gm0  DEGRADED  ad6
                      ad8(xx%)

完了するとstatusが次のようになる

Fixit# gmirror status gm0
      Name    Status  Components
mirror/gm0  COMPLETE  ad6
                      ad8

コピーが完了したら、構築したRAIDから起動するように設定を変更します。

/をマウント

Fixit# mount /dev/mirror/gm0s1a /mnt

/mnt/etc/fstabを修正して、"ad6"→"mirror/gm0"に変更する。

Fixit# sed 's#ad6#mirror/gm0#' < /mnt/etc/fstab > /mnt/etc/fstab.tmp
Fixit# cp /mnt/etc/fstab /mnt/etc/fstab.bak
Fixit# mv /mnt/etc/fstab.tmp /mnt/etc/fstab
Fixit# cat /mnt/etc/fstab
# Device            Mountpoint FStype Options Dump Pass#
/dev/mirror/gm0s1b  none       swap   sw      0    0
/dev/mirror/gm0s1a  /          ufs    rw      1    1
  :

/mnt/boot/loader.confにgeom_mirrorカーネルモジュールの読み込み指定を追加する

# echo 'geom_mirror_load="YES"' >> /mnt/boot/loader.conf

以上で設定は完了です。シェルを抜けてインストーラを終了し、CDを取り出して再起動をかけます。設定に問題なければ、gm0のディスクをマウントした状態でOSが起動します。

動作確認

障害のエミュレーションとして、atacontrol detach、HDDを片方いきなり抜く。シャットダウンしてHDDを抜いて再起動、と試してみましたが、パニックなど起こすこともなくDEGRADED状態に移行しました。

あと、最初は日本語マニュアルだけ読んでオートリビルドができるのかと勘違いしてたんですが、英語のマニュアルには、

The component with the biggest priority
is used by the prefer balance algorithm and is also used as a
master component when resynchronization is needed, e.g. after
a power failure when the device was open for writing.

と説明があるのでどうも出来ないようです。

リビルドの手順は以下の通りになります。 障害の発生したHDDがad10の場合、ata5のデバイスを切り離します。

# atacontrol detach ata5

この状態でHDDを交換し、デバイスを(再)接続して認識させます。

# atacontrol attach ata5

この状態ではまだad10の情報がgm0に残っているので、消します。

# gmirror forget gm0

最後に取り付けたHDDをgm0に追加します。

# gmirror insert gm0 ad10

これでリビルドが自動的に始まります。

ちなみに、リビルド中にリブートすると途中から再開しました。

metadataを削除する

最後に余談です。

gmirrorやarはHDDの後ろの方にmetadata(RAIDの管理情報)を書き込んで管理しています。で、atacontrolコマンドでいったん書きこまれたmetadataはごみデータが残りやすく、かなり厄介です。 特にテストのために一度抜いたHDDを戻したりすると、整合性が取れていないはずなのにReady状態に戻ってしまう(リビルドする方法が無い)ため、非常に危険です。

こういうどうしようもなくなったディスクからmetadataを削除する場合、ディスクの後の方の領域をクリアします。以下の例ではかなり適当に後の方をゴソッと消してますが、実稼働中のディスクでやる場合はちゃんと計算した方が良いと思います。

例えばad8のmetadataを消す場合、まずディスクの容量を調べます。

# diskinfo ad8
ad8     512     164696555520    321672960       319120  16      63

2番目の数字がセクタサイズで4番目の数字が総セクタ数です。これらの数字を参考に、最後の方を適当に消してしまいます。

# dd if=/dev/zero of=/dev/ad8 bs=512 seek=321650000

これで最後の約10MB(512×22960 byte)が消えます。seek=を省略すればディスク全体をクリアしますが、その場合はbsの値を大きめにすると早く終了します。

ちなみに、RAIDの情報が書き込まれる位置は/usr/src/sys/dev/ata/ata-raid.hに'*_LBA'という名前で定義されていますので、BIOS RAIDを使っている場合はRAIDコントローラのメーカを調べてそこから計算します。あるいは、起動画面で「5. Boot FreeBSD with verbose logging」を選択して起動し、dmesgをMetadataで検索すると、どのタイプのmetadataがディスクに記録されているか調べることができます。


2009-07-25 [長年日記]

[tDiary] footnote2.rb公開

前のエントリでも使っている、オリジナルのfootnote.rbを改造したものを公開しました。ダウンロードや説明は、こちらのページからどうぞ。

例えば、こんな感じのソースが、

* markごとに連{{fn '*の1個目'}}番{{fn '*の2個目'}}を{{fn '+の1個目', '+'}}付{{fn '*の3個目'}}加{{fn '+の2個目', '+'}}
* 指定したmarkの脚注だけを任意の場所に表示{{fn 'こんな風に"-"の脚注だけここに表示し、残りの脚注はエントリの末尾に表示するといったことができます', '-'}}
* 自動括弧閉じ&参考文献的表示{{cite '文献名'}}
{{fn_put '-'}}

こんな風に表示されます。

  • markごとに連番を付加
  • 指定したmarkの脚注だけを任意の場所に表示
  • 自動括弧閉じ&参考文献的表示

末尾に表示されないものをfootnoteと呼ぶのはどうかと言われそうですが、どうせWebと印刷物ではページの概念が違うし、印刷物だって見方によっては本文をぶち切って脚注が表示されてるって解釈も出来るじゃんと開き直ることにしました。と、穴埋めが出来たところで、この下に表示されるのが従来どおりのfootnoteです。