2011年4月10日日曜日

perl の文字列比較での失敗

perl の文字列比較で失敗したので、メモしておきます。

バグっていたのは、次のようなコードです。
$arch = `uname -m` ;
if ($arch == "x86_64") {
    ...
アーキテクチャが x86_64 の場合に、追加の処理を行うという内容なのですが、perl で文字列比較する場合には、正しくは eq を使う必要があり、== と書いてしまうと、数値比較となり、$arch == "x86_64" は常に真になってしまいます。それで、次のように修正しました。
$arch = `uname -m` ;
if ($arch eq "x86_64") {
    ...
これでうまく行くと思ったのですが、なぜか真にならず、意図したようには動きませんでした。テストプログラムを書いて格闘した結果・・・
$arch=`uname -m` ;
printf "%s\n", $arch ;
printf "%d\n", ($arch eq "x86_64") ;
printf "%d\n", ($arch eq "x86_64\n") ;
実行結果は次のとおりです。
x86_64

0
1
$arch の末尾に改行コードがついていたのが原因でした orz 。というわけで、最終的には次のように修正して、意図したとおりに動くようになりました。
$arch = `uname -m` ;
chomp($arch) ;
if ($arch eq "x86_64") {
    ...
この内容であれば、正規表現を使って $arch =~ /x86_64/ と書いておけば、2度までハマることもなかったのに・・・と思いましたが、最初からバグらないでプログラミングするのは難しいものですね。

2011年3月21日月曜日

CentOS 5 計画停電に伴う処置

東日本大震災に伴う計画停電実施のため、自分の管理しているサーバ(CentOS 5を利用)を計画停電の実施予定に合わせて、シャットダウン&通電後パワーオンを行うようになりました。そのサーバは、SANストレージ上のボリュームをマウントしていますが、ストレージもシャットダウンしており、計画停電終了後に同時に起動を始めると、サーバの方が起動が速いらしく、次のようなメッセージが出力され、システムの起動に失敗してしまいます。
*** An error occurred during the file system check.
*** Dropping you to a shell; the system will reboot
*** when you leave the shell.
Give root password for maintenance
(or type Control-D to continue):
/etc/fstab で fsck 実施を指定(第6フィールド)しているデバイスが見つからないと、fsck が失敗して、このメッセージが出力されるようです。/etc/rc.d/rc.sysinit の次の箇所です。
...
if [ -z "$fastboot" -a "$READONLY" != "yes" ]; then

        STRING=$"Checking filesystems"
        echo $STRING
        if [ "${RHGB_STARTED}" != "0" -a -w /etc/rhgb/temp/rhgb-console ]; then
                fsck -T -t noopts=_netdev -A $fsckoptions > /etc/rhgb/temp/rhgb-console
        else
                fsck -T -t noopts=_netdev -A $fsckoptions
        fi
        rc=$?
        
        if [ "$rc" -eq "0" ]; then
                success "$STRING"
                echo
        elif [ "$rc" -eq "1" ]; then
                passed "$STRING"
                echo
        elif [ "$rc" -eq "2" -o "$rc" -eq "3" ]; then 
                echo $"Unmounting file systems"
                umount -a
                mount -n -o remount,ro /
                echo $"Automatic reboot in progress."
                reboot -f
        fi

        # A return of 4 or higher means there were serious problems.
        if [ $rc -gt 1 ]; then
                if [ -x /usr/bin/rhgb-client ] && /usr/bin/rhgb-client --ping ; then
                    chvt 1
                fi

                failure "$STRING"
                echo
                echo
                echo $"*** An error occurred during the file system check."
                echo $"*** Dropping you to a shell; the system will reboot"
                echo $"*** when you leave the shell."

                str=$"(Repair filesystem)"
                PS1="$str \# # "; export PS1
                [ "$SELINUX_STATE" = "1" ] && disable_selinux
                sulogin
...
対策としては、次の3つ考えられます。
対策1 ストレージの立ち上げが完了してから、サーバを立ち上げる
対策2 fstab の第6フィールドを 0 に設定(ただ、これをやると fsck が行われなくなります)
対策3 rc.local に fsck と mount の記述を行う(もし、失敗しても立ち上がるように)
対策1が1番良いとは思いますが、自分では対策3にしました。
なお、rc.sysinit の動作(システム起動を止める)の意図は、中途半端な状態で立ち上げを継続すると二次災害を起こす可能性があるため、安全策と思われます。なので、もし必要なデバイスが見えていないために、立ち上げが止まったならば、ストレージの状態等を確認後に再起動するのが、安全策であろうと思います。

2011-03-27追記
参考までに、実際には、rc.local に次のような感じで記述しています。CentOS 5.5 + LVM + ext4 を利用。
...
SANDEV=/dev/vg1/lv1
if ! grep -q $SANDEV /proc/mounts ; then
        fsck -C -pv $SANDEV
        mount $SANDEV /mnt_lv1
fi
...
ちなみに、NFS サーバになっており、当該領域を export しています。

2011-03-30追記
実際に、この rc.local でマウント失敗する状況に遭遇しましたが、LVM を使っているため、VG の活性化処理を行ってから、手動マウントする必要がありました。
# vgchange -a y vg1
この操作を行わないと、LV のデバイスファイル(/dev/vg1/lv1)が作成されず、fsck も mount も失敗(No such file)します。

2011-04-02追記
ここまでに書いた処置で大丈夫と思っていましたが、SANストレージの mount に失敗した場合に、NFS クライアントが、root デバイスを mount してしまい、期待する中身が見えないという二次災害が起きることがわかりました。そこで、次のように、SANストレージの mount 後に NFS サービスを起動するように変更を加えました。
# chkconfig nfs off     ※NFS サービスの自動起動をやめる
# chkconfig --list nfs
nfs             0:off   1:off   2:off   3:off   4:off   5:off   6:off
rc.local を次のように書き換え。
...
SANDEV=/dev/vg1/lv1
if ! grep -q $SANDEV /proc/mounts ; then
        fsck -C -pv $SANDEV
        mount $SANDEV /mnt_lv1
        /etc/rc.d/init.d/nfs start
fi
...
やはり、rc.sysinit が fstab 記載のディスクを mount 出来ない場合に、システム起動を停止してしまうという仕様は、このようなケースを避けるために、妥当なようです。だけど、この記事の冒頭に書いたメッセージが出てしまうと、ssh アクセスできずコンソールを見に行かないといけないので、(マシンルームまで距離があり)面倒なのですが。
なお、現在のところ、実際に計画停電に遭遇したことはなく。節電協力のため、毎日シャットダウン・翌日パワーオンを行っているという状況です。

2011年3月13日日曜日

Go言語でbase64デコード処理

Go言語の練習その3として、base64デコード処理を書いてみました。と言っても、添付ライブラリを使っているだけですが。
// my_b64decode.go

package main

import (
        "fmt"
        "os"
        "bufio"
        "regexp"
        "encoding/base64"
)

func err_exit(err os.Error) {
        fmt.Fprintf(os.Stderr,"ERROR: %s\n", err.String())
        os.Exit(1)
}

func my_chop(s string) string {
        return s[0:len(s)-1]
}

func main() {
        if len(os.Args) <= 1 {
                fmt.Fprintf(os.Stderr, "Usage: my_b64decode file\n")
                os.Exit(1)
        }

        f,err := os.Open(os.Args[1], os.O_RDONLY, 0666)
        if err != nil { err_exit(err) }

        b_rd := bufio.NewReader(f)

        dst := make([]byte, 100, 200)

        b_wr,err := bufio.NewWriterSize(os.Stdout, 4096)
        if err != nil { err_exit(err) }

        INPUT: for {
                line,err := b_rd.ReadString('\n')
                if err != nil {
                        if err == os.EOF {
                                break
                        }
                        err_exit(err)
                }

                m,err := regexp.MatchString("^begin-base64 ", line)
                if ! m { continue }

                for {
                        line,err := b_rd.ReadString('\n')
                        if err != nil {
                                if err == os.EOF {
                                        break INPUT
                                }
                                err_exit(err)
                        }
                        m,err := regexp.MatchString("^====\n", line)
                        if m {
                                break INPUT
                        }
                        src := []byte(my_chop(line))
                        n,err := base64.StdEncoding.Decode(dst, src)
                        b_wr.Write(dst[0:n])
                }
        }

        b_wr.Flush()
        f.Close()
        return
}
uuencode -m で作成された base64 エンコードファイルを読み込み、デコードして標準出力へ出力します。動作確認結果を示します。
[user00@fedora14 ~]$ ls -l /etc/services 
-rw-r--r--. 1 root root 651949 Nov 12 20:56 /etc/services
[user00@fedora14 ~]$ uuencode -m /etc/services a.uu > a.uu
[user00@fedora14 ~]$ ls -l a.uu
-rw-rw-r-- 1 user00 user00 883783 Mar 13 22:56 a.uu    ※テストファイルを用意
[user00@fedora14 ~]$ head -2 a.uu
begin-base64 644 a.uu
IyAvZXRjL3NlcnZpY2VzOgojICRJZDogc2VydmljZXMsdiAxLjUxIDIwMTAv

[user00@fedora14 ~]$ 6g my_b64decode.go 
[user00@fedora14 ~]$ 6l -o my_b64decode my_b64decode.6

[user00@fedora14 ~]$ ./my_b64decode a.uu | md5sum
77a7f18fe1508eec6c0f2b5e15b8804e  -
[user00@fedora14 ~]$ md5sum /etc/services 
77a7f18fe1508eec6c0f2b5e15b8804e  /etc/services     ※元ファイルと等しい
[user00@fedora14 ~]$ uudecode -o - < a.uu | md5sum 
77a7f18fe1508eec6c0f2b5e15b8804e  -                 ※いちおうの確認
この練習では、次の3点についても学びました。
・正規表現ライブラリ
・chop() は無いらしい
・二重ループ脱出の方法(break のラベル指定)
人気ブログランキングへ にほんブログ村 IT技術ブログへ