2007年5月10日木曜日

iptables起動設定

 Debianにてiptablesを起動時に復元するための設定方法を調査した。どうやら、きちんと決められた方法はなく、自分でシェルスクリプトを書く必要があるらしい。 /etc/network/interfacesのpre-upに書くという方法が一番簡単そうだったが、現在のiptablesの設定はインターフェース単位ではないのでその方法はとらず、/etc/init.dにスクリプトを置くという方法をとることにした。

 /etc/init.d配下のシェルスクリプトには決められた様式があり、ひな型としては、/etc/init.d/skeletonがあるが、これはデーモン用のものであるため、とりあえず、/etc/init.d/ifupdownを参考にして、前に作成したSSHのフィルタリングを行うルール(この記事を参照)を設定するように以下のスクリプトmyfirewallを作成した。

#!/bin/sh
### BEGIN INIT INFO
# Provides:          myfirewall
# Required-Start:    
# Required-Stop:     $local_fs
# Default-Start:     S
# Default-Stop:      0 6
# Short-Description: Set firewall.
### END INIT INFO

[ -x /sbin/iptables ] || exit 0

. /lib/lsb/init-functions

MYNAME="${0##*/}"
report() { echo "${MYNAME}: $*" ; }
report_err() { log_failure_msg "$*" ; }
[ -r /etc/default/$MYNAME ] && . /etc/default/$MYNAME

start_firewall () {
  iptables -N SSHEvil
  iptables -N SSH
  iptables -A INPUT -j SSH -p tcp --dport 22

  iptables -A SSHEvil -m recent --name badSSH --set
  iptables -A SSHEvil -j LOG --log-level DEBUG --log-prefix "evil SSH user:"
  iptables -A SSHEvil -j DROP

  iptables -A SSH -j ACCEPT -s 192.168.0.0/24
  iptables -A SSH -p tcp ! --syn -m state --state ESTABLISHED,RELATED -j ACCEPT
  iptables -A SSH -p tcp --syn -m recent --name badSSH --update --seconds 600 -j REJECT
  iptables -A SSH -p tcp --syn -m recent --name conSSH --rcheck --seconds 60 --hitcount 5 -j SSHEvil
  iptables -A SSH -p tcp --syn -m recent --name conSSH --set
  iptables -A SSH -p tcp --syn -j ACCEPT
}

stop_firewall () {
  iptables -F SSHEvil
  iptables -F SSH
  iptables -F INPUT
  iptables -X
}

case "$1" in
  start)
    log_begin_msg "Setting up firewall..."
    start_firewall
    log_end_msg 0
    exit 0
    ;;

  stop)
    log_begin_msg "Stopping firewall..."
    stop_firewall
    log_end_msg 0
    exit 0
    ;;

  restart|force-reload)
    log_begin_msg "Restarting firewall..."
    stop_firewall
    start_firewall
    log_end_msg 0
    exit 0
    ;;

  *)
    echo "Usage: $0 {start|stop|restart|force-reload}" >&2
    exit 3
    ;;
esac

exit 0

 次に実際に起動時にinitに起動してもらうため、必要なランレベルのディレクトリィにリンクを張る必要がある。Debianの/etc/inittabは以下のようになっている。

# The default runlevel.
id:2:initdefault:

# Boot-time system configuration/initialization script.
# This is run first except when booting in emergency (-b) mode.
si::sysinit:/etc/init.d/rcS

# What to do in single-user mode.
~~:S:wait:/sbin/sulogin

# /etc/init.d executes the S and K scripts upon change
# of runlevel.
#
# Runlevel 0 is halt.
# Runlevel 1 is single-user.
# Runlevels 2-5 are multi-user.
# Runlevel 6 is reboot.

l0:0:wait:/etc/init.d/rc 0
l1:1:wait:/etc/init.d/rc 1
l2:2:wait:/etc/init.d/rc 2
l3:3:wait:/etc/init.d/rc 3
l4:4:wait:/etc/init.d/rc 4
l5:5:wait:/etc/init.d/rc 5
l6:6:wait:/etc/init.d/rc 6
Debianではランレベル0がhalt, 6がrebootとなりそれぞれ/etc/rc{0,6}.d/の下のスクリプトが実行される。そして、/etc/rcS.dの下はsysinit、つまりランレベルに関わらず最初に実行されるディレクトリィとなっている。

 基本的にネットワーク起動前にiptablesを設定する必要があるのだが、Debianの場合は起動は以前に調査した結果、/etc/init.d/udev startでeth0が起動し、/etc/init.d/networking stopでeth0が停止することがわかっている(詳細はこの記事参照)。/etc/init.d/udevは/etc/rcS.d/S03udevで開始され、/etc/init.d/networkingは/etc/rc{0,6}.d/S35networkingで停止される。続くS36ifupdownもネットワークに関連するスクリプトであり、状態ファイルの削除を行っているものである。

 通常Sから始まるスクリプトは起動スクリプトであるが、initよりランレベルを引数として実際に起動されている/etc/rcスクリプトを見てみると、以下のようになっている。

(省略)
case "$runlevel" in
  0|6)
    ACTION=stop
    # Count down from 0 to -100 and use the entire bar
    first_step=0
    progress_size=100
    step_change=-1
    ;;
(省略)
# First, run the KILL scripts.
if [ "$previous" != N ]
then
  # Run all scripts with the same level in parallel
  CURLEVEL=""
  for s in /etc/rc$runlevel.d/K*
  do
    level=$(echo $s | sed 's/.*\/K\([0-9][0-9]\).*/\1/')
    if [ "$level" = "$CURLEVEL" ]
    then
      continue
    fi
    CURLEVEL=$level
    SCRIPTS=""
    for i in /etc/rc$runlevel.d/K$level*
    do
      (省略)
      SCRIPTS="$SCRIPTS $i"
    done
    startup stop $SCRIPTS
  done
fi
# Now run the START scripts for this runlevel.
# Run all scripts with the same level in parallel
CURLEVEL=""
for s in /etc/rc$runlevel.d/S*
  do
    level=$(echo $s | sed 's/.*\/S\([0-9][0-9]\).*/\1/')
    if [ "$level" = "$CURLEVEL" ]
    then
      continue
    fi
    CURLEVEL=$level
    SCRIPTS=""
    for i in /etc/rc$runlevel.d/S$level*
    do
      [ ! -f $i ] && continue
      (省略)
      SCRIPTS="$SCRIPTS $i"
    done
    startup $ACTION $SCRIPTS
  done
fi
ランレベル0,6のときは、先頭文字がS,Kに関わらずstopが引数として渡されている。そして、スクリプトの実行順番は、K,Sの順序である。これらの結果より、/etc/rcS.dにS03udevよりも早く/etc/rc{0,6}.dにS36ifupdownより遅くなるように、/etc/rcS.d/S03myfirewallと/etc/rc{0,6}.d/S37myfirewallというリンクを作成すればよいことがわかる。

 Debianではinitスクリプトのリンクを作成するコマンドupdate-rc.dがある。事前に/etc/init.d/配下にスクリプトが存在する必要があるので、上記のmyfirewallを/etc/init.d/へコピーする。update-rc.dコマンドの書式は以下となる。

update-rc.d {スクリプト名} {start | stop} {2桁連番} {runlevel} {.} ...

{start | stop}から{.}までは複数個記述することができる。よって、以下のコマンドを実行すればよい。
$ update-rc.d -n myfirewall start 03 S . start 37 0 6 .
 Adding system startup for /etc/init.d/myfirewall ...
  /etc/rc0.d/S37myfirewall -> ../init.d/myfirewall
  /etc/rc6.d/S37myfirewall -> ../init.d/myfirewall
  /etc/rcS.d/S03myfirewall -> ../init.d/myfirewall

■ 参考資料

Software/iptables - Debian GNU/Linux スレッドテンプレ
Manpage of IPTABLES
Manpage of BASH

2007年5月7日月曜日

getopt

概要

 getoptは主にシェルスクリプトでコマンドオプションを使いやすい形に変形するために使う。いくつか書式があるが、伝統的なgetoptでは以下のように使う。

$getopt optstring parameters
引数はoptstringとparamtersに分かれる。optstringにはオプションと認識する一文字を連続して書く。その文字の最後に:をつけた場合は、そのオプションはパラメータをとるという意味になる。parametersは、解析する引数を書く。

伝統的なコマンド使用例

 例えば ab:cとした場合は、-a -b -cをオプションとして認識し、-bオプションはパラメータをとることになる。出力の順番はオプションと必要があればそのパラメータがすべて出力され、'--'を出力後にどれでもないものが出力される。

$ getopt ab:c -a
-a --
上の例では、parametersが-aオプションのみなので-aと'--'が出力される。
$getopt ab:c -b
getopt: オプションには引数が必要です -- b
--
今度は-bオプションを指定したが、-bオプションには引数が必要となるためエラーとなる。
$ getopt ab:c -b aaa
-b aaa --
上記のように書くと、aaaが-bオプションの引数になるので正常に出力される。
$ getopt ab:c -b aaa -a bbb -c
-b aaa -a -c -- bbb
-aの後にbbbを書くと、-aは引数をとらないのでbbbはオプションでもオプションの引数でもないことになり、--の後ろに出力され、順番が入れ替わる。
$ getopt ab:c -b aaa -a -c -d
getopt: オプションが違います -- d
-b aaa -a -c --
-dはoptstringに定義されていないため、エラーとなる。

 このようにgetoptでオプション文字並びにオプションの引数チェックとオプション文字でもオプションの引数でもないものを後ろに並べかえてくれる。

拡張コマンド使用例

 GNUのgetoptの場合は以下のような書き方ができる。(他にもいくつか書き方があるが詳細はgetop(1)を参照のこと)

$getopt -o optstring -l longoptstring -- parameters
-oは短いオプションを上記と同じ形式で記述する。-lには、長いオプション名を','で区切りながら記述できる。また、短いオプションと同様に最後に':'をつけるとそのオプションは引数を取ることになる。paramtersで長いオプションは'--'で始まる。
$getopt -o ab:c -l dfg,hij: -- -a --dfg abc --hij aaa
-a --dfg --hij 'aaa' -- 'abc'
--dfgと--hijがオプションとして認識され、aaaは--hijの引数となる。また、この書式を使うとシェルでのメタ文字の展開を避けるため、オプションの引数とオプション文字でもオプションの引数でもないものはシングルクォートされて出力される。

シェルスクリプト利用例

 シェルスクリプトで使う場合は以下のようにして使う。

OPTIONS=`getopt -o ab:c --long dfg,hij: -- "$@"`
if [ $? != 0 ] ; then
 exit 1
fi
eval set -- "$OPTIONS"
while true; do
    case "$1" in
        -a)
        # -aのときの処理
        shift
        ;;
    -b)
        # -bのときの処理
        shift 2
        ;;
    -c)
        # -cのときの処理
        shift
        ;;
    --dfg)
        # --dfgのときの処理
        shift
        ;;
    --hij)
        # --hijのときの処理
        shift 2
        ;;
    --)
        shift
        break
        ;;
    *)
        echo "Internal error!" >&2
        exit 1
        ;;
    esac
done
# オプションでもオプションの引数でもないものの処理
$@はシェルスクリプトの位置パラメータ($1,$2,...)に展開されるので、それをgetoptにかけ、オプションとそのパラメータが先になるように並べ変える。その際、認識できないオプションがあったり、オプションのパラメータが存在しない場合はエラーとなる。その出力結果をもう一度「set -- 」で位置パラメータに入れ直す。ただし、シェルの特別な文字を展開しないように、$OPTIONSをダブルクォート("$OPTIONS")としてパラメータ展開を行っているため、evalとして実行する。(そうしないと、setのパラメータとして展開されるので、ひとつの単語とみなされ$1にまとめて設定されてしまう。evalをつけるとevalのパラメータとして展開された後、「set --」が実行されるため、スペース区切りでそれぞれ位置パラメータに設定される。)

■ 参考資料

Manpage of GETOPT
Manpage of BASH

2007年5月5日土曜日

SSHのbrute forceアタック対応

 自宅サーバーのログをチェックしていると、/var/log/auth.logにSSHに対してユーザを変えながら何度もアクセスしているログが大量に残っていた。どうもbrute forceアタックをされているようだ。brute forceアタックというのはユーザ、パスワードを総当りで破ろうとする力ずくの攻撃のこと。

● 主な対策方法

 SSHのbruteforceアタック対しての対策方法は主に以下があるようだった。
1.ポート番号を1024番以降(well-knownポート以外)に変える。
2.TCPWrapperやiptablesで特定のIPアドレスのみ接続を受け付ける。
3.iptablesのipt_recentを使って一定時間にアクセスがあったホストをはじく。
2は、今後自分が外からSSH経由でログインする場合は、IPアドレスを固定できないので使えない。1が、もっとも簡単なのだが、勉強がてら3の方法に挑戦してみた。

● iptables概要

iptablesの最小単位はパケットをどう扱うかというルールで、それをチェインというルールをひとまとめにしたものに追加していく。チェインは組み込み済みのもの以外にユーザが自分で定義することができる。そして、チェインはテーブルに属しており、テーブルには、用途に応じてfilter,nat,mangleという3種類がある。filterは通常のパケットの送受信に使うもので、INPUT,OUTPUT,FORWARDの組み込みチェインがある。他にNAT変換時に使うnatと、何に使うのかよくわからないがmangleというテーブルがある。今回はfilterテーブルに設定を行う。

● iptablesの設定

 以下のようにiptablesの設定を行う。

iptables -N SSHEvil
iptables -N SSH
iptables -A INPUT -j SSH -p tcp --dport 22

iptables -F SSHEvil
iptables -A SSHEvil -m recent --name badSSH --set
iptables -A SSHEvil -j LOG --log-level DEBUG --log-prefix "evil SSH user:"
iptables -A SSHEvil -j DROP

iptables -F SSH
iptables -A SSH -j ACCEPT -s 192.168.0.0/24
iptables -A SSH -p tcp ! --syn -m state --state ESTABLISHED,RELATED -j ACCEPT
# SSHルール1
iptables -A SSH -p tcp --syn -m recent --name badSSH --update --seconds 600 -j REJECT
# SSHルール2
iptables -A SSH -p tcp --syn -m recent --name conSSH --rcheck --seconds 60 --hitcount 5 -j SSHEvil
# SSHルール3
iptables -A SSH -p tcp --syn -m recent --name conSSH --set
iptables -A SSH -p tcp --syn -j ACCEPT

 まず、filterテーブルに新規にSSHEvilとSSHというチェインを作成。3行目でINPUTチェインに対して22番ポート(SSHのポート)を受信した場合は、SSHチェインへ渡すように設定。filterテーブルには組み込みでINPUT,OUTPUT,FORWARDの3つのチェインが組み込まれている。INPUTは受信したパケットにFORWARDは他のインターフェースへ流すパケットに、OUTPUTは送信するパケットに対して適用される。

 4行目からはSSHEvliチェインに対してルールを設定する。 まず、-Fで全てのルールを削除。次にipt_recentモジュールのルール設定でbadSSHというリスト(-name)に送信元IPアドレスを記憶し(--set)、ログを出力してパケットを捨てるというルールを記述して、SSHEvliチェインは終了。 このように、SSHEvilチェインは一本道の単純なものでパケットは最終的に全て捨てられることになる。

 続いてSSHチェインのルール設定を行う。8行目で-Fで全てのルールを削除。ローカルアドレスは無条件で受信を許可する。続いてSYNフラグなし(!--syn)、TCP状態がESTABLISHEDならびにRELATED(--state)を指定することでTCPでの接続確立後のデータパケットは許可する。以降のルールは接続確立要求のパケット(--syn)を対象に設定する。送信元IPアドレスがbadSSHリストで(-name)600秒以内にアクセスがあるか(--seconds)チェックし、あった場合は受信時間を更新し(--updates)パケットを拒否する。  次のルールでは、送信元IPアドレスがconSSHリスト(-name)で60秒以内のアクセスが(--seconds)、5回以上ある(--hitcount)かチェックしあった場合は(--rcheck)SSHEvilチェインへ渡す設定をし、さらにconSSHというリスト(-name)に送信元IPアドレスを記憶し(--set)、最後にパケットを受け入れる。

● パケットフィルタの動作

SSH接続を要求してきたホストは、SSHルール3によりconSSHリストに登録された後要求を受け付ける。しかし、60秒に5回を超えてSSH接続をする場合は、その前にあるSSHルール2に該当することになりSSHEvilチェインへと移動し、badSSHリストに登録されてパケットは捨てられることになる。さらに接続しようとすると今度はSSHルール1に引っかかり、パケットは拒否されることになる。SSHルール1に引っかかった場合は、その都度時間が更新されるため、このルールから抜け出すためには600秒間アクセスを行わない必要がある。

● 問題点

 ただし、ここでいう接続はTCPレベルのものであり、SSHの接続が成功したかどうかを見ているわけではない。SSH接続が成功したとしても1回にカウントされてしまうので、正しいホストも1分間に5接続以上はできないことになる。

 また、IPアドレスで見ているため、NAT下からの複数ユーザの接続の場合は同じIPアドレスとみなされてしまうため接続できなくなる可能性がある。

とりあえず、自分の運用ではどららも問題になりそうもないので、この設定を使うことにする。

● 参考資料

iptables の ipt_recent で ssh の brute force attack 対策

2007年4月28日土曜日

udevd init_uevent_netlink_sock

udevdのinit_uevent_netlink_sock関数の解析メモ

static int init_uevent_netlink_sock(void)
{
    struct sockaddr_nl snl;
    const int buffersize = 16 * 1024 * 1024;
    int retval;

    memset(&snl, 0x00, sizeof(struct sockaddr_nl));
    netlinkソケットはカーネルとユーザ空間の通信の通信に使用。
    アドレスはstruct sockaddr_nlを使う。

    snl.nl_family = AF_NETLINK;
    snl.nl_pid = getpid();
    snl.nl_groups = 1;

    netlinkソケットを作成する
    uevent_netlink_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    if (uevent_netlink_sock == -1) {
        err("error getting socket: %s", strerror(errno));
        return -1;
    }

    SO_RCVBUFFORCEは、特権プロセスがカーネルパラメータであるrmem_maxを
    上書きして受信バッファの最大サイズを設定することができる。
    rmem_maxは、/proc/sys/net/coreに存在する。

    /* set receive buffersize */
    setsockopt(uevent_netlink_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));

    retval = bind(uevent_netlink_sock, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl));
    if (retval < 0) {
        err("bind failed: %s", strerror(errno));
        close(uevent_netlink_sock);
        uevent_netlink_sock = -1;
        return -1;
    }
    return 0;
}

■ 参考資料

JM Manpage of NETLINK

2007年4月26日木曜日

OOM Killer

● 概要

 Out Of Memory Killerのこと。Linuxのデフォルトの動作では、プロセスがメモリを要求した場合、総メモリ使用量が実メモリ+swap以上であっても、ある程度許可するようになっている。これは、各プロセスが要求したメモリをすべて使うわけではないという経験的な法則により、できるだけ多くのプロセスを起動するためにそのように動作になっているようである。そのため、あるプロセスが確保できたはずのメモリを使おうとし、実際にメモリが足りない場合カーネルが適当なプロセスを選択し、そのプロセスをkillしてしまうことをOut Of Memory Killerという。

● 特定プロセス除外

このOOM Killerで重要なプロセスを除外するためには、/proc/(プロセスID)/oom_adjに-17を書き込む。

$ echo -17 > /proc/(プロセスID)/oom_adj

● OOM Killer停止

 また、/proc/sys/vm/overcommit_memoryの値でLinuxの動作そのものを変更することもできる。

説 明
0メモリ確保要求があった場合、カーネルが現在未使用のメモリ量から経験的手法で判断する。(デフォルト)
1メモリ確保要求は全て許可する。
2メモリ確保要急がswapメモリ + (物理メモリ * /proc/sys/vm/overcommit_ratio / 100)まで許可する。

 2に設定することで実メモリ以上のメモリ確保要求は失敗するため、OOM Killerは動作しない。

● 参考 カーネルソース

overcommit_memory定義値

(include/linux/mman.h )
#define OVERCOMMIT_GUESS 0
#define OVERCOMMIT_ALWAYS 1
#define OVERCOMMIT_NEVER 2


int __vm_enough_memory(long pages, int cap_sys_admin)
mm/mmap.c
OOM Killerを実行するか判定を行う。

unsigned long badness(struct task_struct *p, unsigned long uptime)
mm/oom_kill.c
OOM Killerの対象プロセスを判定する

■ 参考資料

OOM Killer とは:IPpro
路地裏 ソース解読研究所: Number -17
OOM Killer によるプロセスの強制終了を防ぐ
JF: Linux Kernel 2.6 Documentation: vm.txt
memologue Linux Memory Overcommit

udevd init_udevd_socket

udevdのinit_udevd_socket関数の解析メモ

static int init_udevd_socket(void)
{
    struct sockaddr_un saddr;
    socklen_t addrlen;
    const int feature_on = 1;
    int retval;

    memset(&saddr, 0x00, sizeof(saddr));
    AF_LOCALはAF_UNIXと同じ
    Windowsでも使うため一般的な名前としてAF_LOCALを使う

    saddr.sun_family = AF_LOCAL;
    先頭をNULL文字にすると抽象名前空間となる。
    AF_UNIX(AF_LOCAL)ソケットでのみ使用できる。
    抽象名前空間の実装はLinux2.2以降から。
    JM Manpage of UNIXを参照のこと

    /* use abstract namespace for socket path */
    strcpy(&saddr.sun_path[1], UDEVD_CTRL_SOCK_PATH);
    addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(saddr.sun_path+1) + 1;

    udevd_sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
    if (udevd_sock == -1) {
        err("error getting socket: %s", strerror(errno));
        return -1;
    }

    /* the bind takes care of ensuring only one copy running */
    retval = bind(udevd_sock, (struct sockaddr *) &saddr, addrlen);
    if (retval < 0) {
        err("bind failed: %s", strerror(errno));
        close(udevd_sock);
        udevd_sock = -1;
        return -1;
    }
    SO_PASSCREDはmsgの補助メッセージとして信任状を受信できるようにする。
    信任状は以下の型
    struct ucred {
        pid_t  pid;  /* process id of the sending process */
        uid_t  uid;  /* user id of the sending process */
        gid_t  gid;  /* group id of the sending process */
    };
    補助メッセージはsendmsg()recvmsg()で送受信する。
    JM Manpage of UNIXを参照のこと

    /* enable receiving of the sender credentials */
    setsockopt(udevd_sock, SOL_SOCKET, SO_PASSCRED, &feature_on, sizeof(feature_on));

    return 0;
}

■ 参考資料

JM Manpage of UNIX
JM Manpage of SEND
JM Manpage of RECV

2007年4月24日火曜日

udevd main

udevdのmain関数の解析メモ

int main(int argc, char *argv[], char *envp[])
{
    int retval;
    int fd;
    struct sigaction act;
    fd_set readfds;
    const char *value;
    int daemonize = 0;
    int option;
    static const struct option options[] = {
        { "daemon", 0, NULL, 'd' },
        { "debug-trace", 0, NULL, 't' },
        { "verbose", 0, NULL, 'v' },
        { "help", 0, NULL, 'h' },
        {}
    };
    int rc = 1;
    int maxfd;

    logging_init("udevd");
    udev_config_init();  環境変数からconfig file名などを取得。
    selinux_init();  何もしない
    dbg("version %s", UDEV_VERSION);

    /* parse commandline options */
    while (1) {
        コマンドオプションの解析(省略)
    }

    rootで実行されているかチェック
    if (getuid() != 0) {
        fprintf(stderr, "root privileges required\n");
        err("root privileges required");
        goto exit;
    }

    /* init sockets to receive events */
    制御用のUNIXドメインソケットを作成
    if (init_udevd_socket() < 0) {
        if (errno == EADDRINUSE) {
            fprintf(stderr, "another udev daemon already running\n");
            err("another udev daemon already running");
            rc = 1;
        } else {
            fprintf(stderr, "error initializing udevd socket\n");
            err("error initializing udevd socket");
            rc = 2;
        }
        goto exit;
    }
    カーネルイベント用のソケット作成
    if (init_uevent_netlink_sock() < 0) {
        fprintf(stderr, "error initializing netlink socket\n");
        err("error initializing netlink socket");
        rc = 3;
        goto exit;
    }

    /* setup signal handler pipe */
    シグナルハンドラーからの通知に使用するパイプを作成
    retval = pipe(signal_pipe);
    if (retval < 0) {
        err("error getting pipes: %s", strerror(errno));
        goto exit;
    }

    作成したpipeに対してfcntlが可能かどうかチェックしNONBLOCKに設定する
    retval = fcntl(signal_pipe[READ_END], F_GETFL, 0);
    if (retval < 0) {
        err("error fcntl on read pipe: %s", strerror(errno));
        goto exit;
    }
    retval = fcntl(signal_pipe[READ_END], F_SETFL, retval | O_NONBLOCK);
    if (retval < 0) {
        err("error fcntl on read pipe: %s", strerror(errno));
        goto exit;
    }

    retval = fcntl(signal_pipe[WRITE_END], F_GETFL, 0);
    if (retval < 0) {
        err("error fcntl on write pipe: %s", strerror(errno));
        goto exit;
    }
    retval = fcntl(signal_pipe[WRITE_END], F_SETFL, retval | O_NONBLOCK);
    if (retval < 0) {
        err("error fcntl on write pipe: %s", strerror(errno));
        goto exit;
    }

    ルールファイルをパースしてメモリに展開する。
    /* parse the rules and keep them in memory */
    sysfs_init();
    udev_rules_init(&rules, 1);

    export_initial_seqnum();

    デーモン化するために子プロセスを作成し親は終了する
    if (daemonize) {
        pid_t pid;

        pid = fork();
        switch (pid) {
        case 0:
            dbg("daemonized fork running");
            break;
        case -1:
            err("fork of daemon failed: %s", strerror(errno));
            rc = 4;
            goto exit;
        default:
            dbg("child [%u] running, parent exits", pid);
            rc = 0;
            goto exit;
        }
    }
    /dev/nullを標準入力と標準出力に設定
    /* redirect std fd's */
    fd = open("/dev/null", O_RDWR);
    if (fd >= 0) {
        dup2(fd, STDIN_FILENO);
        if (!verbose)
            dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        if (fd > STDERR_FILENO)
            close(fd);
    } else
        err("error opening /dev/null: %s", strerror(errno));

    /* set scheduling priority for the daemon */
    setpriority(PRIO_PROCESS, 0, UDEVD_PRIORITY);

    chdir("/");
    umask(022);

    /* become session leader */
    セッションリーダーになる
    sid = setsid();
    dbg("our session is %d", sid);

    /* OOM_DISABLE == -17 */
    Out Of Memory Killerを無効にするために/proc/self/oom_adに-17を書く
    OOM Killerについては詳細記事を参照のこと

    fd = open("/proc/self/oom_adj", O_RDWR);
    if (fd < 0)
        err("error disabling OOM: %s", strerror(errno));
    else {
        write(fd, "-17", 3);
        close(fd);
    }

    シグナルハンドラーを設定する
    /* set signal handlers */
    memset(&act, 0x00, sizeof(struct sigaction));
    act.sa_handler = (void (*)(int)) sig_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = SA_RESTART;
    sigaction(SIGINT, &act, NULL);
    sigaction(SIGTERM, &act, NULL);
    sigaction(SIGCHLD, &act, NULL);
    sigaction(SIGHUP, &act, NULL);

    /* watch rules directory */
    ルールディレクトリィの監視を行う。glibc経由ではなくsyscallで直接呼ぶ。
    syscall(__NR_inotify_init),syscall(__NR_inotify_add_watch,・・・など

    inotify_fd = inotify_init();
    if (inotify_fd >= 0)
        inotify_add_watch(inotify_fd, udev_rules_dir, IN_CREATE | IN_DELETE | IN_MOVE | IN_CLOSE_WRITE);
    else if (errno == ENOSYS)
        err("the kernel does not support inotify, udevd can't monitor configuration file changes");
    else
        err("inotify_init failed: %s", strerror(errno));

    /* maximum limit of forked childs */
    子プロセス数の設定値を取得(省略)

    /* export log_priority , as called programs may want to follow that setting */
    sprintf(udev_log, "UDEV_LOG=%i", udev_log_priority);
    putenv(udev_log);
    if (debug_trace)
        putenv("DEBUG=1");

    select用ファイルディスクリプタの最大値を取得
    maxfd = udevd_sock;
    maxfd = UDEV_MAX(maxfd, uevent_netlink_sock);
    maxfd = UDEV_MAX(maxfd, signal_pipe[READ_END]);
    maxfd = UDEV_MAX(maxfd, inotify_fd);

    selectでイベント受信待ち
    while (!udev_exit) {
        struct udevd_uevent_msg *msg;
        int fdcount;

        FD_ZERO(&readfds);
        FD_SET(signal_pipe[READ_END], &readfds);
        FD_SET(udevd_sock, &readfds);
        FD_SET(uevent_netlink_sock, &readfds);
        if (inotify_fd >= 0)
            FD_SET(inotify_fd, &readfds);

        fdcount = select(maxfd+1, &readfds, NULL, NULL, NULL);
        if (fdcount < 0) {
            if (errno != EINTR)
                err("error in select: %s", strerror(errno));
            continue;
        }

            イベントごとの処理を行う。(省略)

    }
    rc = 0;

    後始末を行う
exit:
    udev_rules_cleanup(&rules);
    sysfs_cleanup();

    if (signal_pipe[READ_END] >= 0)
        close(signal_pipe[READ_END]);
    if (signal_pipe[WRITE_END] >= 0)
        close(signal_pipe[WRITE_END]);

    if (udevd_sock >= 0)
        close(udevd_sock);
    if (inotify_fd >= 0)
        close(inotify_fd);
    if (uevent_netlink_sock >= 0)
        close(uevent_netlink_sock);

    logging_close();

    return rc;
}

■ 参考資料

JM Manpage of PIPE
JM Manpage of FCNTL
JM Manpage of SETSID
JM Manpage of SIGACTION

2007年4月23日月曜日

Debian ネットワーク起動

 Debian(etch)のネットワーク起動について調査した。Debianの起動スクリプトは/etc/init.d/にあり、ネットワーク関係は/etc/init.d/networkになる。

start)
        process_options
        log_action_begin_msg "Configuring network interfaces"
        if ifup -v -a; then
            log_action_end_msg $?
        else
            log_action_end_msg $?
        fi
        ;;

 'ifup -v -a'を実行し、ネットワークインターフェースを起動している。ifupはDebian独自のユーティリティのようで、manページによると'-a'オプションは、/etc/network/interfacesの中でautoに指定されいているものを起動する。/etc/network/interfacesは以下のようになっていた。

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

# The loopback network interface
auto lo
iface lo inet loopback

# This is a list of hotpluggable network interfaces.
# They will be activated automatically by the hotplug subsystem.
mapping eth0
        script grep
        map eth0

# The primary network interface
allow-hotplug eth0
iface eth0 inet dhcp

 autoになっているのはループバックデバイスloのみ。試しに、/etc/init.d/networking stopを実行すると、eth0, loともに停止する。しかし、/etc/init.d/networking startを実行しても、loしか起動しない。いろいろ試したところ、/etc/init.d/udev startを実行すると、eth0が起動した。これは、udevdを起動するスクリプトである。

 udevdが何かを調べてみたらSUSE Linuxのマニュアルにいろいろ書いてあった。SUSE Linux Enterprise Server Manual (PDF)の25章udevを使用した動的カーネルデバイス管理によると、udevdは動的にデバイスを管理するためのものであり、それまでのhotplugを置き換えるもののようだ。 udevdは、/etc/udev/rules.d/*.rulesディレクトリィに記述されルールに従って処理を行う。この場所にz55_hotplug.rulesがありここにnetwork関連の記述があった。

SUBSYSTEM=="net",                               RUN+="net.agent"

 net.agentを実行するようになっており、/lib/udev/net.agentがそれに相当するようだ。これはシェルスクリプトであり、net_ifup()関数でifupを実行している。

net_ifup() {
    check_program /sbin/ifup

    if grep -q '^auto[[:space:]].*\<'"$INTERFACE"'\>' \
            /etc/network/interfaces; then
        # this $INTERFACE is marked as "auto"
        IFUPARG='\('$INTERFACE'\|-a\|--all\)'
    else
        IFUPARG=$INTERFACE
    fi

    if ps -C ifup ho args | grep -q "$IFUPARG"; then
        debug_mesg "Already ifup-ing interface $INTERFACE"
        exit 0
    fi

    wait_for_interface lo

    exec ifup --allow=hotplug $INTERFACE
}

 ついでに上記ででてくるwait_for_interfaceは、sysファイルシステムを使ってネットワークインターフェースの状態を判定している。

wait_for_interface() {
    local interface=$1

    while :; do
        local state="$(cat /sys/class/net/$interface/operstate 2>/dev/null || true)"
        if [ "$state" != down ]; then
                return 0
        fi
        sleep 1
    done
}

■ 参考資料

SUSE Linux Enterprise Serverマニュアル(PDF)
ifupのmanページ

2007年4月21日土曜日

bashの%パラメータ展開方法

bashの%と%%のパラメータ展開方法を調べる。
manには以下のようにある。

${parameter%word}
${parameter%%word}
        word が展開され、パス名展開の場合と同じようなパターンを作ります。このパタ
        ーンが parameter を展開した値の末尾の部分とマッチする場合、展 開 結 果 は
        parameter  を展開した値から最短一致パターン (‘‘%’’ の場合) または最長一
        パターン (‘‘%%’’ の場合) を取り除いたものになります。 parameter が @ ま
        は * である場合、パターンを削除する操作は全ての位置パラメータに順番に適用
        され、展開結果はリストとして得られます。 parameter が @ または * が添字に
        な っている配列変数である場合、パターンを削除する操作は配列の全ての要素に
        順番に適用され、展開結果はリストとして得られます。
この記述だと、何をいっているのかよくわからないので簡単なシェルスクリプトを書いて試してみた。
#!/bin/sh

dest="$1"
echo $dest
dest="${dest%/}" #末尾/を削除
echo $dest
dest="${dest%/*}" #末尾の/*を削除(最小一致)
echo $dest
dest="${dest%%/*}" #末尾の/*を削除(最大一致)
echo $dest
これを実行すると以下のようになる。
$ ./test.sh /aaa/bbb/ccc/
/aaa/bbb/ccc/
/aaa/bbb/ccc
/aaa/bbb

$ ./test.sh aaa/bbb/ccc/
aaa/bbb/ccc/
aaa/bbb/ccc
aaa/bbb
aaa
@や*は試していないが、なんとなく使い方はわかった。

2007年4月20日金曜日

bashコマンドオプション

シェルスクリプトを見ていたら、bash -eという記述があった。manで調べてもオプションには見つからない。試しにhelpオプションを実行してみたら、その中にも存在しないが、別のhelpがあるようだ。

$ bash --help
GNU bash, version 3.1.17(1)-release-(i486-pc-linux-gnu)
(省略)
Type `bash -c "help set"' for more information about shell options.
Type `bash -c help' for more information about shell builtin commands.

で、bash -c "helpset"を実行してみた。
set: set [--abefhkmnptuvxBCHP] [-o option] [arg ...]
         -a Mark variables which are modified or created for export.
        -b Notify of job termination immediately.
        -e Exit immediately if a command exits with a non-zero status.
        -f Disable file name generation (globbing).
        -h Remember the location of commands as they are looked up. (省略)
これによると、-eはコマンドが失敗した場合はすぐ終了するというオプションであった。
先頭の行をみると、bashのsetという組み込みコマンドのオプションの説明であるようだ。もう一回manをみてみると、確かにオプションのところに書いてあった。
bash は以下のオプションを起動時に解釈します (組み込みコマンド set の説明で述べられている 1 文字のシェルオプションも使えます):

ということで、bashの起動オプションを調べるには、setの項も見る必要がある。ちなみにsetはbashのめちゃめちゃ長いmanの中でもかなり下の方にある。なので、JM Projectを活用してブラウザで見るか、先の「bash -c "help set"」を使用したほうが見やすい。

■ 参考資料

Manpage of BASH

2007年4月17日火曜日

mt-daapdが起動できない

 Debian(etch)をdist-upgradeしたら、mt-daapdを起動できなくなった。

 まず、APT HOWTO 第 6 章 - ソースパッケージでの作業を参考にパッケージを再コンパイルしてみる。
fakerootツールをインストールする。

# apt-get install fakeroot
mt-daapdのソースとコンパイルに必要なパッケージをインストールする。
# apt-get source mt-daapd
# apt-get build-dep mt-daapd
mt-daapd_0.2.4+r1376.orig.tar.gz, mt-daapd_0.2.4+r1376-1.dsc, mt-daapd_0.2.4+r1376-1.diff.gzの3つがダウンロードされ展開されてコンパイル用のディレクトリィができる。そのディレクトリィに移動して以下のコマンドでパッケージを作成する。
# dpkg-buildpackage -rfakeroot -uc -b
パッケージは最初にソースをダウンロードしたディレクトリィ(1つ上)にできる。
aptでできたパッケージをインストールしてみたが、結果は変わらない。

 ログに何か出力されていないかどうか/var/log下のファイルを調べてみた。messages.logに関係ありそうなログがあった。

Mar 18 20:26:13 gold mt-daapd[4524]: *** WARNING *** The programme 'mt-daapd' uses the HOWL compatiblity layer of Avahi.
Mar 18 20:26:13 gold mt-daapd[4524]: *** WARNING *** Please fix your application to use the native API of Avahi!
Mar 18 20:26:13 gold mt-daapd[4524]: *** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=howl&e=mt-daapd>
上記のURLを見てみると、AvahiはDNS Service Discoveryと Multicast DNSのライブラリ。他にもhowlという同じ機能を提供する別のライブラリもあり、Avahiは完全ではないがその互換APIを提供している。mt-daapdはAvahiのhowl互換APIを使っているのでAvahiネイティブのAPIを使うように警告を出しているということのようだ。これで起動できなくなる問題というわけではなさそう。

 さらにログを調べてみると、daemon.logにmt-daapdが出力しているエラーを見つけた。

Error opening db: No backend database support for type: sqlite
上記メッセージでmt-daapdのソース内をgrepをかけたところ、main.c内でエラーメッセージを出力しているところを見つけた。
   /* this will require that the db be readable by the runas user */
    db_type = conf_alloc_string("general","db_type","sqlite");
    db_parms = conf_alloc_string("general","db_parms","/var/cache/mt-daapd");
    err=db_open(&perr,db_type,db_parms);

    if(err) {
        DPRINTF(E_LOG,L_MAIN|L_DB,"Error opening db: %s\n",perr);
#ifndef WITHOUT_MDNS
        if(config.use_mdns) {
            rend_stop();
        }
#endif
        os_deinit();
        exit(EXIT_FAILURE);
    }
エラーメッセージは、db_open関数が失敗したときに出力されている。db_open関数はdb-generic.cにあった。
/**

 * Open the database.  This is done before we drop privs, that  * way if the database only has root perms, then it can still  * be opened.
 *
 * \param parameters This is backend-specific (mysql, sqlite, etc)
 */
int db_open(char **pe, char *type, char *parameters) {
    int result;
    DPRINTF(E_DBG,L_DB,"Opening database\n");
    if(pthread_once(&db_initlock,db_init_once))
        return -1;
    db_current = &db_functions[0];
    if(type) {
        while((db_current->name) && (strcasecmp(db_current->name,type))) {
            db_current++;
        }

        if(!db_current->name) {
            /* end of list -- no match */
            db_get_error(pe,DB_E_BADPROVIDER,type);
            return DB_E_BADPROVIDER;
        }
    }
    result=db_current->dbs_open(pe, parameters);
    DPRINTF(E_DBG,L_DB,"Results: %d\n",result);
    return result;
}
db_functions配列から第2引数のtypeと名前が一致するものを検索し、db_functionsのdbs_open関数を呼んでいる。db_functionsの型はDB_FUNCTIONSで、構造体は以下のようになっている。
/** pointers to database-specific functions */
typedef struct tag_db_functions {
    char *name;
    int(*dbs_open)(char **, char *);
    int(*dbs_init)(int*);
    int(*dbs_deinit)(void);
    int(*dbs_add)(char **, MP3FILE*, int*);
    int(*dbs_add_playlist)(char **, char *, int, char *,char *, int, int *);
    int(*dbs_add_playlist_item)(char **, int, int);
    int(*dbs_delete_playlist)(char **, int);
    int(*dbs_delete_playlist_item)(char **, int, int);
    int(*dbs_edit_playlist)(char **, int, char*, char*);
    int(*dbs_playcount_increment)(char **, int);
    int(*dbs_enum_start)(char **, DBQUERYINFO *);
    int(*dbs_enum_size)(char **, DBQUERYINFO *, int *, int *);
    int(*dbs_enum_fetch)(char **, DBQUERYINFO *, int *, unsigned char **);
    int(*dbs_enum_fetch_row)(char **, PACKED_MP3FILE *, DBQUERYINFO *);
    int(*dbs_enum_reset)(char **, DBQUERYINFO *);
    int(*dbs_enum_end)(char **);
    int(*dbs_force_rescan)(char **);
    int(*dbs_start_scan)(void);
    int(*dbs_end_song_scan)(void);
    int(*dbs_end_scan)(void);
    int(*dbs_get_count)(char **, int *, CountType_t);
    MP3FILE*(*dbs_fetch_item)(char **, int);
    MP3FILE*(*dbs_fetch_path)(char **, char *,int);
    M3UFILE*(*dbs_fetch_playlist)(char **, char *, int);
    void(*dbs_dispose_item)(MP3FILE*);
    void(*dbs_dispose_playlist)(M3UFILE*);
}DB_FUNCTIONS;
データベースごとのルーチンを格納している関数テーブルのようだ。
main.cに戻り、db_typeがどのように指定されているか確認してみる。

db_type = conf_alloc_string("general","db_type","sqlite");

でdb_typeが代入されている。
conf_alloc_stringはconf.cにあった。
/**
 * return the value from the CURRENT config tree as an allocated string  *
 * @param section section name to search in  * @param key key to search for  * @param dflt default value to return if key not found  * @returns a pointer to an allocated string containing the required  *          configuration key  */
char *conf_alloc_string(char *section, char *key, char *dflt) {
    LL_ITEM *pitem;
    char *result;
    char *retval;

    _conf_lock();
    pitem = _conf_fetch_item(conf_main,section,key);
    if((!pitem) || (pitem->type != LL_TYPE_STRING)) {
        result = dflt;
    } else {
        result = pitem->value.as_string;
    }

    if(result == NULL) {
        _conf_unlock();
        return NULL;
    }

    retval = strdup(result);

    if(!retval) {
        DPRINTF(E_FATAL,L_CONF,"Malloc error in conf_alloc_string\n");
    }
    _conf_unlock();
    return retval;
}
コンフィグファイルからkeyの値を持ってくるが、keyが存在しない場合はdfltを値としている。コンフィグファイルである/etc/mt-daapd.confにはdb_typeというkeyは存在しないので、dlftであるsqliteがdb_typeとなる。(daemon.logに書いてあるとおり)
db_functionsの中身は以下のようになっている。
DB_FUNCTIONS db_functions[] = {
#ifdef HAVE_LIBSQLITE     {
        "sqlite",
        db_sql_open_sqlite2,
        db_sql_init,
        (省略)
     },
#endif #ifdef HAVE_LIBSQLITE3     {
        "sqlite3",
        db_sql_open_sqlite3,
        db_sql_init,
        (省略)
    }
sqliteの場合はDBのオープンにdb_sql_open_sqlitesqlite2という関数を使うようになっている。関数名から推測するに、おそらくsqlite2に含まれる関数だと思われる。
で、現在システムにインストールされているsqliteを調べてみた。
# dpkg -l "*sqlite*"

+++-================-================-================================================
un gda2-sqlite      <なし>         (説明 (description) がありません)
ii libsqlite0       2.8.17-2         SQLite shared library
ii libsqlite3-0     3.3.8-1.1        SQLite 3 shared library
ii libsqlite3-dev   3.3.8-1.1        SQLite 3 development files
un sqlite3-doc      <なし>         (説明 (description) がありません)
バイナリはlibsqlite0とlibsqlite3がインストールされているが、sqlite2はインストールされていない。db_functionsからdb_typeをsqlite3にすればsqlite3のライブラリを使うようになることがわかる。また、今までの解析からdb_typeをsqlite3にするためには、mt-daapd.confに以下の行を追加すればよいこともわかる。
(/etc/mt-daapd.confの適当な場所に追加)

db_type sqlite3
これでmt-daapdを起動してみたところ、正常に動作した。

2007年4月15日日曜日

make if関数

 makeのマニュアルとしてはGNU make 日本語訳(Coop編)をいつも使っているのだが、Linux2.6のカーネルのmakeファイルを覗いていたらそこに書いていない構文があった。

KBUILD_OUTPUT := $(shell cd $(KBUILD_OUTPUT) && /bin/pwd) $(if $(KBUILD_OUTPUT),, \       $(error output directory "$(saved-output)" does not exist))

 2行目の$(if ~, ~,)が書いていなかった。上記の日本語訳は1998年5月のGNU Make Version 3.77のものでかなり古いので、英語の方のマニュアルを見てみた。構文は下記のようになって特に難しいことはない。

$(if condition,then-part[,else-part])

 makeにはもうひとつifeq構文もあって(ifeqは3.77のマニュアルにも書いてある)、そっちはmakefile conditionalsを、一方ifは functional contextを判定するものという違いがあるということらしい。この記述だけだとよくわからないが、実行してみないとわからないもの(先のカーネルでは、shellでの実行結果)は、ifの方を使わなければならないということなのだろう。とりあえず、違いがあるということは覚えておこうと思う。

■ 参考資料

GNU `make' Manual(英語)
GNU make 日本語訳(Coop編)

2007年4月12日木曜日

Thinkpad Jetico Firewall設定

FirewallソフトしてフリーのJetico Personal Firewall 1.0を使っている。このソフトはファイアウォールだけでなくシステムフックやネットワークの使用まで制御することができる。
(Jeticoの柔軟な設定が可能だがその分複雑である。概念や操作の詳細は下記の参考資料を参照のこと)
そのJeticoをThinkpad T60へインストールするとサスペンド状態から復帰できなくなってしまう問題が発生した。
具体的には指紋認証を行った後のパスフレーズ入力画面が表示されず、固まってしまう。Jeticoを止めると正常に動作するので、おそらく、復帰時にJeticoがアプリケーションのask userルールに入ってしまい、そこで止まってしまうのだと思われる。
以下の方法で問題を解決した。

● 全許可ルールの作成

まず、アプリケーションルールにすべてのイベントを許可するルールを追加し、ログを残すようにする。


● ログの確認

液晶を閉じてサスペンドしすぐに開いて復帰する。そしてすぐにJeticoを起動し、ログを確認して必要なルールを確認する。 自分の場合は、以下のアプリケーションのaccess networkイベントが発生していた。

C:\WINDOWS\system32\acs.exe
C:\Program Files\Lenovo\PkgMgr\HOTKEY\tpfnf2.exe

acs.exeはAtheros Configuration ServiceでAtherosの無線LANの認証にかかわるもので、tpfnf2.exeは名前からするとFn + F2キー用のプログラムだと思う。Thinkpad T60は、Fn + F2にはデフォルトでコンピュータのロック機能が割り当てられている。

● ルールの追加

上記アプリケーションは許可しても問題ないと思われるので、access networkイベントを許可するルールをアプリケーションルール内に作成する。そして、全許可したルールの左側のチェックをはずし(画像の状態)無効化しておく。この状態で一度サスペンド・復帰が問題なく動くことを確認すればOK。
Jeticoではまったら、全許可ルールを有効化しログを見て必要なアプリケーションを許可すればよい。

■ 参考資料

Jetico Personal Firewall 非公式ヘルプドキュメント

2007年4月9日月曜日

フロッピードライブなしでのGRUBブートフロッピーの作成

stage1, stage2を生書きするやり方しか見つからなかったのでファイルシステム上に作成する方法をソースを解析しながら試してみた。

● フロッピーディスクイメージの作成

Debianの場合はまず、DOSツールをインストールする。

# apt-get install dosfstools
ddコマンドでイメージファイルを作成する。
# dd if=/dev/zero of=floppy.img bs=1k count=1440
作成したイメージファイルをFATでフォーマットする。
# mkdosfs floppy.img
floppy.imgをループバックデバイスとして適当なディレクトリィにマウントする。
# mount -t vfat floppy.img mnt/fd -o loop

● GRUB設定ファイルの作成

マウントディレクトリィを通じてイメージファイルへFATとしてアクセスできるようになったので、必要なファイルをコピーする。
# cd mnt/fd/boot/grub
# cp /usr/lib/grub/i386-pc/stage1 .
# cp /usr/lib/grub/i386-pc/stage2 .
#cp /boot/grub/device.map .
stage1,stage2は/boot/grubにあるものではなく、/usr/lib/grub/i386-pcより持ってくる。
Debianの場合は、/boot/grubにdevice.mapがあるが、ない場合は以下のようにして作成する。
# grub --device-map=device.map

grub> quit
grubを--device-mapオプションを指定して立ち上げ、すぐに終了する。
これでカレントディレクトリィに、device.mapが作成される。
続いてgrubが自動作成したdevice.mapを修正する。
(fd0) /home/floppy.img
(hd0) /dev/hda
fd0のデバイスファイル名としてフロッピーイメージファイルを記述する

● GRUBインストール

device.mapを指定してgrubを起動して、fd0へインストールする。

# grub --device-map=device.map

grub> root (fd0)
grub> setup (fd0)
これで、floppy.imgにgrubがインストールできた。
menu.lstをおけばメニューも出せるはず。

■ 参考資料

GNU GRUB Manual(英語)

2007年4月7日土曜日

M4 括弧つきマクロ

autoconfのm4ライブラリをちょっとのぞいていたら、よくわからないところがあった。
以下は/usr/share/autoconf/m4sugar/m4sugar.m4よりの抜粋。

# m4_include_unique(FILE)
# -----------------------
# Declare that the FILE was loading; and warn if it has already
# been included.
m4_define([m4_include_unique],
[m4_ifdef([m4_include($1)],
    [m4_warn([syntax], [file `$1' included several times])])dnl
m4_define([m4_include($1)])])

# m4_include(FILE)
# ----------------
# As the builtin include, but warns against multiple inclusions.
m4_define([m4_include],
[m4_include_unique([$1])dnl
m4_builtin([include], [$1])])
m4_include_uniqueとm4_includeという二つのマクロを定義している。m4_include_uniqueは複数回includeされているかチェックを行い、m4_includeはm4_include_uniqueで複数includeのチェックしてからincludeを行う。
m4_include_unique内の2行目と4行目に[m4_include($1)]という記述がある。[]はリテラル文字であり、これら2行は要はm4_include($1)という名前のマクロの存在チェックと定義を行っている。
m4のマニュアルによるとマクロ名に使えるのは、アルファベット、数字、_(アンダースコア) を自由に並べたもののうち、先頭の文字が数字でないものとなっており、"()"は許されていない。普通"("以下は引数とみなされる。

どんな動作なのかその部分だけ抜き出して、テストしてみた。
user@local:~/m4test$ less test2.m4
define(`m4_include_unique', `ifdef(`m4_include($1)', `already defined',
    `not defined
    define(`m4_include($1)', `include $1')')')
m4_include_unique(`aaa')
m4_include(aaa)
m4_include_unique(`aaa')
ifdef(`m4_include(aaa)', `already defined', `not defined')
このマクロの実行結果は以下のようになる。
user@local:~/m4test$ m4 test2.m4

not defined

m4_include(aaa)
already defined
already defined
結果からするとdefineやif_defの引数内ではクォートすれば、"()"つきのマクロ名の定義は可能であるようだ。
しかし、定義した後でもm4_include(aaa)がそのまま出力されていることから、マクロとして展開できていない。通常通りm4_includeがマクロ名、aaaが引数と解釈されている。
フラグとしてのみ使うので、展開できないマクロ名を使っているのだと思われる。

■ 参考資料

GNU macro processor: GNU macro processor

2007年4月3日火曜日

Debian apt設定

● 最寄のミラーサーバーを検索

netselectコマンドで探す。
インストールされていなければaptでインストールする。

# apt-get install netselect
ミラーサーバのリストを渡す。
# netselect -vv \
ring.so-net.ne.jp \
ring.ocn.ad.jp \
ring.tains.tohoku.ac.jp \
ring.toyama-ix.net \
ring.u-toyama.ac.jp \
ring.nihon-u.ac.jp \
ring.airnet.ne.jp \
ring.maffin.ad.jp \
ring.sakura.ad.jp
実行結果は下記のようになる。
Running netselect to choose 1 out of 9 addresses.
................................................................................
ring.nihon-u.ac.jp                      32 ms  15 hops   75% ok ( 6/ 8) [  107]
ring.so-net.ne.jp                       22 ms  11 hops  100% ok (10/10) [   46]
ring.ocn.ad.jp                          22 ms  12 hops   90% ok ( 9/10) [   52]
ring.airnet.ne.jp                       22 ms   8 hops  100% ok (10/10) [   39]
ring.maffin.ad.jp                       24 ms  11 hops   90% ok ( 9/10) [   56]
ring.sakura.ad.jp                       32 ms  11 hops  100% ok (10/10) [   67]
ring.u-toyama.ac.jp                     45 ms  12 hops   85% ok ( 6/ 7) [  114]
ring.tains.tohoku.ac.jp                 32 ms  13 hops  100% ok (10/10) [   73]
ring.toyama-ix.net                      30 ms  12 hops  100% ok (10/10) [   66]
一番最後の行に最寄のミラーサーバが表示される。
この場合は、ring.airnet.ne.jp。

● aptの設定変更

aptがパッケージを取得するサーバは/etc/apt/sources.listに書かれている。
上で見つけた最寄のミラーサーバを追加する。

deb cdrom:[Debian GNU/Linux testing _Etch_ - Official Snapshot i386 Binary-1 (20060314)]/etch main
deb http://ring.airnet.ne.jp/pub/linux/debian/debian/ etch main contrib non-free
deb http://ftp2.jp.debian.org/debian/ etch main contrib non-free
deb-src http://ring.airnet.ne.jp/pub/linux/debian/debian/ etch main contrib non-free
deb-src http://ftp2.jp.debian.org/debian/ etch main contrib non-free

deb http://security.debian.org/ etch/updates main contrib non-free
deb-src http://security.debian.org/ etch/updates main contrib non-free
sources.listの書式は、以下のようになっている。

type uri distribution [component1] [component2] [...]

typeパッケージの種別。debはバイナリ、dec-srcはソースパッケージ。
uri取得する場所を指定。HTTP,FTP,CDROMなどが指定できる。
distributionディストリビューション名。sarge,etchなどの名前以外にstable,unstable,testingなど状態でも指定できる。
componentmain, contrib, no-freeのどれかを指定。全て指定しておけばいい。

● パッケージの更新

最新のパッケージリストより更新すべきパッケージを取得する。

# apt-get update
パッケージをダウンロードしアップデートを行う。
# apt-get upgrade
実行時のメッセージに保留パッケージがある場合は、それらのパッケージは更新されない。
(下記の例では0なので保留パッケージは存在しない)
アップグレード: 9 個、新規インストール: 0 個、削除: 0 個、保留: 0 個
保留される理由は、アップグレートすることで他のパッケージが削除される場合、または意図的に保留にするようにマークされている場合がある。
カーネルとかgcc, glibcとかシステムのコア部分などは保留パッケージとなっている。
保留パッケージを更新したい場合は、以下のコマンドを実行する。
# apt-get dist-upgrade
または、-sオプションをつけることで、シュミレーション動作(実際にはアップグレードを行わないがメッセージは表示させる)にし、アップグレードされるパッケージを確認したうえで個別にインストールを行う。
# apt-get -s dist-upgrade

# apt-get install <パッケージ名>

■ 参考資料

install - Debian GNU/Linux スレッドテンプレ
Debian、APTによるパッケージのアップデート
[debian-users:44343] Re:パッケージの保留に関して
apt-getのmanページ