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 のラベル指定)

2011年3月11日金曜日

Go言語でzcat

Go言語の練習に、zcat (gunzip -c) 相当を行う処理を書いてみました。これもGo言語らしくはない題材ですが。
// my_zcat.go

package main

import (
        "fmt"
        "os"
        "bufio"
        "compress/gzip"
)

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

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

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

        b_rd,err := bufio.NewReaderSize(gz, 32768)
        if err != nil { err_exit(err) }

        gz_rd,err := gzip.NewReader(b_rd)
        if err != nil { err_exit(err) }

        buf := make([]byte, 32768, 65536)

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

        for {
                n,err := gz_rd.Read(buf)
                if err != nil {
                        if err == os.EOF {
                                break
                        }
                        err_exit(err)
                }
                os.Stdout.Write(buf[0:n])
                // b_wr.Write(buf[0:n])
        }

        gz_rd.Close()
        gz.Close()
        return
}
makeの第3引数で指定するキャパシティの意味するところが、理解出来ていません。

それから、
err != nill && err_exit(err)
のようには書けませんでした。コンパイルエラーになります。
[user00@fedora14 ~]$ 6g my_zcat.go 
my_zcat.go:24: err_exit(err) used as value

2011-04-02追記
実行イメージを書いていなかったので、追記です。
[user00@fedora14 ~]$ 6g my_zcat.go 
[user00@fedora14 ~]$ 6l -o my_zcat my_zcat.6
[user00@fedora14 ~]$ ls -l my_zcat
-rwxrwxr-x 1 user00 user00 2143092 Apr  2 21:17 my_zcat
[user00@fedora14 ~]$ echo hello | gzip -c > hello.gz
[user00@fedora14 ~]$ ./my_zcat hello.gz 
hello
[user00@fedora14 ~]$ cat /etc/services | gzip -c > test_data.gz    ※少し大きいテストデータ
[user00@fedora14 ~]$ ./my_zcat test_data.gz | md5sum
77a7f18fe1508eec6c0f2b5e15b8804e  -
[user00@fedora14 ~]$ md5sum /etc/services 
77a7f18fe1508eec6c0f2b5e15b8804e  /etc/services

2011年3月9日水曜日

Go言語で行単位のI/O処理

Go言語らしくない題材だとは思いますが、練習に行単位のI/O処理を書いてみました。
// my_cat.go

package main

import (
        "fmt"
        "os"
        "bufio"
)

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

        f,err := os.Open(os.Args[1], os.O_RDONLY, 0666)
        if err != nil {
                fmt.Fprintf(os.Stderr,"ERROR: %s\n", err.String())
                os.Exit(1)
        }

        reader := bufio.NewReader(f)
        writer := bufio.NewWriter(os.Stdout)
        // writer,err := bufio.NewWriterSize(os.Stdout, 4096)

        for {
                line,err := reader.ReadString('\n')
                if err != nil {
                        break
                }
                writer.WriteString(line)
        }

        writer.Flush()

        f.Close()
        return
}
bufio.NewWriterSize で書き込みバッファサイズを指定することもできますが、わたしの環境の場合には、デフォルトで 4096 バイトぐらいのようです。次のように、少々ぶれます。
[user00@fedora14 ~]$ strace -e write ./my_cat /etc/init.d/functions >/dev/null
write(1, "# -*-Shell-script-*-\n#\n# functio"..., 4094) = 4094
write(1, "\t\t\t\taction $\"Detaching loopback "..., 4067) = 4067
write(1, "                [ \"$BOOTUP\" = \"v"..., 4092) = 4092
write(1, "# Log that something succeeded\ns"..., 4080) = 4080
write(1, "\t# Parse the options field, conv"..., 2837) = 2837

2011年3月8日火曜日

Fedora 14 に Go 言語をインストール

ふと目にとまってしまった Go 言語を、少し試してみようと、まずは、Fedora 14 x86_64 へインストールしてみました。
手順(各所に情報があるようですが)とログをメモしておきます。

まず、root で必要なものをインストールします。特に、Mercurial を。
[root@fedora14 ~]# cat /etc/redhat-release 
Fedora release 14 (Laughlin)
[root@fedora14 ~]# uname -a
Linux fedora14 2.6.35.10-74.fc14.x86_64 #1 SMP Thu Dec 23 16:04:50 UTC 2010 x86_64 x86_64 x86_64 GNU/Linux

[root@fedora14 ~]# yum install '*mercurial*'
途中省略

[root@fedora14 ~]# rpm -q bison gcc libc6-dev ed gawk make  ※必要なものを確認
bison-2.4.3-1.fc14.x86_64
gcc-4.5.1-4.fc14.x86_64
package libc6-dev is not installed  ※これは、既に名前が変わったのか、不要でした
ed-1.4-2.fc14.x86_64
gawk-3.1.8-3.fc14.x86_64
make-3.82-3.fc14.x86_64
次に一般ユーザでの作業です。
[user00@fedora14 ~]$ cat .bashrc  ※環境変数を設定します
# .bashrc

# Source global definitions
if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi

# User specific aliases and functions

export GOROOT=$HOME/go  ※この4行をvi等で追加
export GOOS=linux
export GOARCH=amd64
export GOBIN=$HOME/bin

[user00@fedora14 ~]$ source .bashrc
[user00@fedora14 ~]$ mkdir $GOROOT
[user00@fedora14 ~]$ mkdir $GOBIN

※次の Mercurial のコマンド hg で、リポジトリからソースその他一式をゲット
[user00@fedora14 ~]$ hg clone -r release https://go.googlecode.com/hg/ $GOROOT
warning: go.googlecode.com certificate with fingerprint b1:af:83:76:f3:81:b0:57:70:d8:07:42:c8:c1:b3:67:38:c8:7a:bc not verified (check hostfingerprints or web.cacerts config setting)
adding changesets
adding manifests
adding file changes
added 7570 changesets with 29247 changes to 4124 files
updating to branch default
2508 files updated, 0 files merged, 0 files removed, 0 files unresolved

※ビルドを実行
[user00@fedora14 ~]$ cd $GOROOT/src
[user00@fedora14 src]$ ./all.bash 
rm -f *.o y.tab.[ch] y.output a.out lib9.a
rm -f *.o y.tab.[ch] y.output a.out libbio.a
rm -f *.o y.tab.[ch] y.output a.out libmach.a
rm -f *.o y.tab.[ch] y.output a.out cc.a
途中省略

--- cd ../test
1 known bugs; 0 unexpected bugs  ※ちょっと気になりますが…後述の helloworld.go は動きました。

ALL TESTS PASSED

---
Installed Go for linux/amd64 in /home/user00/go.
Installed commands in /home/user00/bin.
The compiler is 6g.
[user00@fedora14 src]$ 
お試しに helloworld.go です。
package main

import "fmt"

func main() {
        fmt.Printf("hello, world\n")
        return
}
これを、6g および 6l にかけると、6.out という実行モジュールが生成されます。
[user00@fedora14 ~]$ 6g helloworld.go    ※コンパイル
[user00@fedora14 ~]$ ls -l helloworld.6
-rw-rw-r-- 1 user00 user00 5750 Mar  8 22:52 helloworld.6
[user00@fedora14 ~]$ 6l helloworld.6     ※リンク
[user00@fedora14 ~]$ ls -l 6.out 
-rwxrwxr-x 1 user00 user00 1842238 Mar  8 22:53 6.out
[user00@fedora14 ~]$ ./6.out    ※実行
hello, world
コマンド先頭が数字というのは、結構、違和感があります。bash の history もうまく動かないです。!6g などとやると、history の 6番目 のコマンドが呼び出されてしまいます。

2011年3月6日日曜日

Fedora 14 でコマンドを打ち間違えると・・・

Fedora 14 でコマンドを打ち間違えると、何やらネットアクセスを行って、どこかにその(打ち間違えた)コマンドがないかを探しにいく。これって、アイデアとしてはありなのでしょうけど、わたしは邪魔に感じます。(デフォルトでその動きは、どうなのかな。好みの問題もありますが。)

挙動からそういう動きがあるとは思いつつ放ってましたが、ふとどこでその処理をやっているか考えてみたら、そうか bash が何かやっているに違いないと思い。set を打ったところ、次のような関数が設定されていることに気がつきました。
[root@fedora14 ~]# set
...
command_not_found_handle () 
{ 
    runcnf=1;
    retval=127;
    [ ! -S /var/run/dbus/system_bus_socket ] && runcnf=0;
    [ ! -x /usr/libexec/packagekitd ] && runcnf=0;
    if [ $runcnf -eq 1 ]; then
        /usr/libexec/pk-command-not-found $@;
        retval=$?;
    else
        echo "bash: $1: command not found";
    fi;
    return $retval
}
この関数を unset command_not_found_handle すれば、ネットアクセスを停止できます。.bashrc にでも書いておけばよい。いったいどこでこの設定が行われているか探ってみると、次の場所でした。
[root@fedora14 ~]# grep -r command_not_found_handle /etc
/etc/profile.d/PackageKit.sh:command_not_found_handle () {
なお、未インストールのコマンドを打った場合、次のように便利に機能することもあります。
[root@fedora14 ~]# resize
bash: resize: command not found...
Install package 'xterm' to provide command 'resize'? [N/y] 
 * Running.. 
 * Resolving dependencies.. 
 * Downloading update information.. 
 * Waiting for authentication.. 
 * Waiting in queue.. 
 * Resolving dependencies.. 
 * Downloading packages.. 
 * Testing changes.. 
 * Installing packages.. 
 * Scanning applications.. 
COLUMNS=80;                                                                    
LINES=34;
export COLUMNS LINES;

2011-03-07追記
bash のオンラインマニュアルに次のような記述があります。
COMMAND EXECUTION
       After a command has been split into words, if it results in a simple command and
       an optional list of arguments, the following actions are taken.

       If the command name contains no slashes, the shell attempts to  locate  it.   If
       there  exists  a  shell  function  by  that  name,  that  function is invoked as
       described above in FUNCTIONS.  If the name does not match a function, the  shell
       searches  for  it  in  the  list  of  shell builtins.  If a match is found, that
       builtin is invoked.

       If the name is neither a shell function nor a builtin, and contains no  slashes,
       bash  searches each element of the PATH for a directory containing an executable
       file by that name.  Bash uses a hash table to remember  the  full  pathnames  of
       executable  files  (see hash under SHELL BUILTIN COMMANDS below).  A full search
       of the directories in PATH is performed only if the command is not found in  the
       hash  table.   If  the  search is unsuccessful, the shell searches for a defined
       shell function named command_not_found_handle.  If that function exists,  it  is
       invoked  with  the  original command and the original command's arguments as its
       arguments, and the function's exit status becomes the exit status of the  shell.
       If  that  function is not defined, the shell prints an error message and returns
       an exit status of 127.

2011年3月5日土曜日

gawkで配列の要素数を得る方法

gawk で配列の要素数を得る場合 len=0 ; for (e in a) len++ のように書いていました。
しかし、perl なら @a か $#a+1 、bash なら ${#a[@]} 、Ruby なら a.size または a.length などと書けるし、もしかして gawk でも簡単に記述できるのでは?と思って、man を見たところ、gawk-3.1.5 以降であれば、length(a) と書けることを知りました。
ただ、わたしの場合、仕事上、古いバージョンもサポートする必要があり、使えないのですが、ここにメモしておきます。

まずは、man gawk(1) から抜粋です。
       length([s])             Returns the length of the string s, or the length of  $0
                               if s is not supplied.  Starting with version 3.1.5, as a
                               non-standard extension, with an array argument, length()
                               returns the number of elements in the array.
次に、テストスクリプトです。
#!/bin/gawk -f
#
# test_length_array.gawk
#
# memo:
#   length(array) returns the number of elements with gawk-3.1.5 or later
#   see man gawk(1)
#
function my_len(a,      len) {
        len=0
        for (i in a) len++
        return len
}
function chk_support_length_for_array() {
        return system("gawk 'BEGIN {a[1]=1;length(a);exit(0)}' >/dev/null 2>&1")
}
BEGIN {
        for (i=100; i<777+100; i++) {
                a[i]=i
        }
        if (PROCINFO["version"] == "") {
                f="gawk --version" ; f | getline ; ver=$3 ; close(f)
                printf "INFO: gawk-%s does not supports PROCINFO[\"version\"]\n", ver
        } else {
                ver=PROCINFO["version"]
                printf "INFO: gawk-%s supports PROCINFO[\"version\"]\n", ver
        }
        if (chk_support_length_for_array()) {
                printf "WARNING: gawk-%s does not supports length(array)\n", ver
        }
        printf "my_len(a)=%d\n", my_len(a)
        printf "length(a)=%d\n", length(a)
        exit(0)
}
配列に対する length をサポートするか確認する関数も書いてみました。あと、これも知らなかったのですが、gawk-3.1.4 以降であれば、PROCINFO["version"] という組み込み変数を使えるようです。これを、Fedora 14 で実行してみると、次のようになります。
[root@fedora14 ~]# ./test_length_array.gawk 
INFO: gawk-3.1.8 supports PROCINFO["version"]
my_len(a)=777
length(a)=777
古いシステムだと、次のようにエラーになります。
# ./test_length_array.gawk 
INFO: gawk-3.0.6 does not supports PROCINFO["version"]
WARNING: gawk-3.0.6 does not supports length(array)
my_len(a)=777
gawk: ./test_length_array.gawk:33: fatal: attempt to use array `a' in a scalar context
人気ブログランキングへ にほんブログ村 IT技術ブログへ