読者です 読者をやめる 読者になる 読者になる

Postfix のデーモン

Postfix の master から起動されるデーモンの動きについて調べてみました。

Postfix は smtpd, qmgr, cleanup 等の複数のデーモンが連携して動作しています。 すべてのデーモンは master から起動されますが、master は各デーモンの起動と監視をしているだけで、master 自身はメールの配送には絡んでません。デーモン起動後は各デーモン同士が連携してメール配送を行います。

実際、メール配送に関係ないデーモンを master から起動しても何の問題もありません。

master は master.cf の記述に従って、TCP ソケットまたは UNIX ドメインソケットでクライアントからの接続を待ち受けます。クライアントから接続の要求があると、デーモンを起動し、接続以降の処理は基本的に起動したデーモンに任せます。

しかし master から起動されるデーモンは、起動されたあと好きに動けばいいというわけではなく、作法にしたがって master と会話をする必要があります。

デーモンは次のファイル記述子の状態で起動されます。

ファイル記述子 内容
0 /dev/null
1 /dev/null
2 /dev/null
3 MASTER_FLOW_READ : メール流量制御用(?)
4 MASTER_FLOW_WRITE : メール流量制御用(?)
5 MASTER_STATUS_FD : デーモンの状態通知用
6以降 MASTER_LISTEN_FD : 待ち受け用ソケット

MASTER_LISTEN_FD は待ち受け用のソケットです。 複数のIPアドレス(IPv4/IPv6/lo/eth0等)で待ち受ける場合は、6番以降その数だけのファイル記述子が待ち受け用ソケットとなっています。 ソケットがいくつあるかは、master がデーモンを起動する際に -s オプションで指定します。

起動されたデーモンは、このソケットに対して accept() し、クライアントとの接続を得ます。 その後、master に対して処理の開始を通知し、処理を実行します。 処理が終了したら master に処理の終了を通知します。

処理の開始/終了の通知は MASTER_STATUS_FD に対して次のデータを送信することで行います。

意味
int プロセスID
unsigned int GENERATION 環境変数の値(8進数)を数値化したもの
int 開始時は 0, 終了時は 1

処理が終わった後はプロセスを終了してもいいですし、accept() に戻ってもいいです。

以上を踏まえて、デーモンプログラムを作ってみます。C だと面倒なので Ruby で書きます。

#!/usr/local/bin/ruby
require 'optparse'
require 'socket'

MASTER_STATUS_FD = 5
MASTER_LISTEN_FD = 6

opt = ARGV.getopts('cdDi:lm:n:o:s:St:uvVz')  # -s 以外にもオプションがある
nsocks = opt['s'] ? opt['s'].to_i : 1        # ソケットの数
socks = []
fd = MASTER_LISTEN_FD
nsocks.times do
  socks.push TCPServer.for_fd(fd)
  fd += 1
end
stat_fd = IO.for_fd(MASTER_STATUS_FD)
generation = ENV['GENERATION'].to_i(8)

# 100回処理したら終了
100.times do
  rs, = select(socks, nil, nil, 100)
  break unless rs                            # 100秒接続がなかったら終了
  conn = rs[0].accept
  stat_fd.syswrite [$$, generation, 0].pack('iIi')
  conn.puts $$
  conn.close
  stat_fd.syswrite [$$, generation, 1].pack('iIi')
end

このプログラムを $daemon_directory (Ubuntu では /usr/lib/postfix) に hoge という名前で置いておき、master.cf に次のように書いておきます。

12345  inet    n   n   n   -   -   hoge

postfix reload 後、12345 ポートに繋ぐと、hoge プロセスのプロセスIDが返されます。

なお、master を inetd のように使いたい場合は、こんなデーモンプログラムを作るよりも spawn デーモンを使うのが簡単です。