エラー時にexit(1)しちゃうようなプログラムにコアダンプさせる

かなり久々の記事。

昨日、一昨日とmixiが障害発生のために閲覧できなくなっていた。
DDoSアタックでもないらしいし、なにが原因かなーと思ってたら、
mixiのCTO @nealsato氏がtwitterで下記のツイートをしてた。

二日とも複数台のmemcachedが連続して落ちました。コアは吐かずにストンと落ちるので、原因追及に時間がかかりましたが、memcachedへの接続数が異常に多いと落ちる事は再現できました。
#mixi

http://twitter.com/nealsato/status/20903258605

コア吐かないってことはプログラム側でexitしてるんだろうなーと思いつつ、
またまたtwitterで@kazuho氏による下記ツイートを発見。

「正常終了してるなら exit を LD_PRELOAD して SEGV させてコアダンプ採取すればいいじゃない」ってじっちゃんが言ってた

http://twitter.com/kazuho/status/21021589792

おーなるほどと思い、実際やってみた。

まずコアダンプしてくれるか設定確認。

# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 4096
max locked memory       (kbytes, -l) 32
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) unlimited
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

core file sizeが0だとコアダンプしてくれないので、
下記コマンドを実行。

# ulimit -c 1048576

これでコアファイルのサイズは1MBになり、
コアダンプしてくれるようになる。

んで、テストプログラム。

/////
// test.c

#include <stdio.h>
#include <stdlib.h>

int main() {
    puts("hello, world!");
    exit(0);
}


/////

コンパイルして実行。

# gcc -o test test.c
# ./test
hello, world!

次はexitのラッパー関数。

/////
// sigsegv.c

#include <signal.h>

void exit(int status) {
    raise(SIGSEGV);
}

/////

コンパイル

# gcc -fPIC -shared -o sigsegv.o sigsegv.c

んでexitを置き換えて実行。

# LD_PRELOAD=./sigsegv.o ./test
hello, world!
セグメンテーション違反です (core dumped)

成功!

これは色々使えそう。

ASP.NETにおけるformタグのaction属性書換

ASP.NETにおけるformタグのaction属性を書き換える方法を書いてみます。

formタグのaction属性を直接aspxファイルに記述しても、
実行時に上書きされてしまいます。
ポストバック時にページ内リンク(例/index.asp#main)を使用したい場合など、
この仕様だと不都合が生じることがあります。

そこで、HtmlTextWriterクラスのサブクラスを作成し、
そのクラス内でaction属性を書き換えます。

public class MyWriter : HtmlTextWriter
{
    private TextWriter writer;
    private string anchor;

    /// <summary>
    /// formタグのaction属性に設定される値です。
    /// </summary>
    public string Anchor
    {
        get { return this.anchor; }
        set { this.anchor = value; }
    }
    
    public MyWriter(TextWriter writer) 
        : base(writer)
    {
        this.writer = writer;
    }
    
    public MyWriter(TextWriter writer, string tabString) 
        : base(writer, tabString)
    {
        this.writer = writer;
    }
    
    public override void WriteAttribute(string name, string value, bool fEncode)
    {
        if (string.Compare(name, "action", true) == 0)
        {
            // ここでaction属性値を書換
            value = this.anchor == null ? "" : this.anchor;
        }
        base.WriteAttribute(name, value, fEncode);
    }
}

引用元:How to render form tag with action="" - uber1024's WebLog
http://weblogs.asp.net/uber1024/archive/2004/04/08/109720.aspx

引用元では、formタグ検出時の処理もカスタム出来るように
RenderBeginTagメソッドをオーバーライドしていますが、
単なるaction属性の書き換えの場合は必要ないと思われるので省略しました。

上記コードでは、Anchorプロパティの値をaction属性として扱います。
そのため、WriteAttributeメソッドが実行される前に、
Anchorプロパティに値を設定する必要があります。

最後に、ページ本体でRenderメソッドをオーバーライドし、
上記MyWriterクラスを利用してaction属性を書き換える処理を記述します。

public partial class _Default : Page
{
    private MyWriter myWriter;

    protected void Page_Load(object sender, EventArgs e)
    {
    }

    protected override void Render(HtmlTextWriter writer)
    {
        if (this.myWriter == null)
        {
            this.myWriter = new MyWriter(writer);
            this.myWriter.Anchor = "index.aspx#main";
        }
        base.Render(this.mywriter);
    }
}

こんな感じでしょうか。

08/06/27(Fri) at Nakano Moonstep [Deadmen Don't Die!!!]

ライブの告知です。
オールドスクール、ニュースクール、エモーショナル、メロディックと多種多様なハードコアバンドが出演する熱いイベントです!

KILLING OF THE DEAD presents
[DEAD MEN DON'T DIE]
2008/06/27(fri) at Nakano Moon Step
Adv. - \1,300 / Door - \1,500
Open - 18:00 / Start - 18:30

Bands)
KILLING OF THE DEAD (http://www.myspace.com/killingofthedead)
A Branded To Kill (http://www.myspace.com/abrandedtokilldemo)
Everyday New Dare (http://www.myspace.com/everydaynewdare)
The Longisland LxLxP (http://www.myspace.com/thelongislandlxlxp)
BARBAROSSA (http://www.myspace.com/barbarossajapan)
A.L.F.

DJ)
Yasuaki(ex. barbarossa)

SQL Server 2005/データ移行メモ

グループウェアの移行に伴い、
SQL Server 2005のデータ移行も必要になったのでメモ。

移行元サーバ、移行先サーバは、
ファイアウォールの都合上、直接接続出来ないので、
移行元サーバでバックアップをとり、
それを移行先サーバでリストアする、
という「バックアップ/リストア」な手法をとります。

バックアップ

1, 移行元サーバのManagement Studioを起動。
2, 「データベース」から移行対象を右クリックし、「タスク」→「バックアップ」を選択。
3, 「全般」ページで、バックアップ先を任意の場所に設定。
4, 「オプション」ページで、「既存のすべてのバックアップ セットを上書きする」を選択。
5, 「OK」ボタンをクリックし、バックアップを実行。

ファイル転送

バックアップで出力したファイルを何らかの手段で移行先サーバに転送しておく。

リストア

1, 移行先サーバのManagement Studioを起動。
2, 「データベース」から移行対象を右クリックし、「タスク」→「復元」→「データベース」を選択。
3, 「全般」ページで「復元用のソース」として「デバイスから」を選択。
4, 「...」ボタンをクリックし、転送したバックアップファイルを選択。
5, 「復元するバックアップ セットの選択」で、出てきた行にチェックを入れる。
6, 「オプション」ページで「既存のデータベースを上書きする」にチェックを入れる。※これいらないかも
7, 「OK」ボタンでリストアを実行。

以上で移行完了です。
が、この作業でデータベースの移行を行うと、
そのデータベースに含まれるユーザでSQL Server認証経由のログインが出来なくなってしまいます。
なので、必要であれば最後に以下の作業を実行します。

ログインアカウント設定

1, 移行先サーバのManagement Studioを起動。
2, 「新しいクエリ」を開き、以下のコマンドを実行。

USE [データベース名];
EXEC sp_change_users_login 'Update_one', '[ユーザ名]', '[ユーザ名]';

ドメインに応じてメール転送

qmailで受信メールの送信元のドメインを判別し、
特定のドメインであれば転送する、
ということをやってみました。

まず、判定スクリプト
/usr/local/bin/chkdomというパスで作りました。

#!/bin/sh

addr=${1#*\<}
addr=${addr%\>*}

while [ "$3" != "" ]; do
        if expr "$addr" : ".*@$3" > /dev/null; then
                exit 0
        fi
        shift 1
done

exit 1

次に、.qmailの設定。
以下の行を.qmailに追加。

condredirect [転送先アドレス] /usr/local/bin/chkdom $SENDER [ドメイン]

メール転送設定スクリプト

qmail+vpopmailで、ユーザに転送設定するスクリプトを書いてみました。
ちなみに前提は次の通りです。
・vpopmailパス
/home/vpopmail
・vpopmailユーザ
vpopmail
・vpopmailグループ
vchkpw

[setfwrd]

#!/bin/sh

if [ -z $1 ]; then
        echo "please input your name."
        exit 1
fi

if [ -z $2 ]; then
        echo "please input your domain."
        exit 1
fi

if [ -z $3 ]; then
        echo "please specify the spool flag."
        exit 1
fi

name=`echo $1 | tr '[A-Z]' '[a-z]'`
domain=$2
is_spool=$3

flg=0

if [ -d /home/vpopmail/domains/$domain/$name ]; then
        flg=1
        datapath=/home/vpopmail/domains/$domain/$name
else
        count=0
        count_max=20
        while [ $count -le $count_max ]; do
                if [ -d /home/vpopmail/domains/$domain/$count/$name ]; then
                        flg=1
                        datapath=/home/vpopmail/domains/$domain/$count/$name
                        count=$count_max
                fi
                count=`expr $count + 1`
        done
fi

if [ $flg -eq 1 ]; then
        if [ -f $datapath/.qmail ]; then
                mv -f $datapath/.qmail $datapath/.qmail.bak
        fi

        touch $datapath/.qmail

        while [ "$4" != "" ]; do
                echo "&$4" >> $datapath/.qmail
                shift 1
        done

        if [ $is_spool != "0" ]; then
                echo "$datapath/Maildir/" >> $datapath/.qmail
        fi

        chown vpopmail:vchkpw $datapath/.qmail
else
        echo "specified account not found."
fi

使い方は、

setfwrd [ユーザ名] [ドメイン名] [メールを残す=1 残さない=0] [転送先1] [転送先2] [転送先3]...

setfwrd taro.yamada foo.com 1 t.yamada@bar.com taro@hoge.com

みたいな感じです。