sendmailのQueueファイルロック処理

外部プログラムからQueueファイルを直接処理する必要があったので
調べた結果をメモ。


同様の処理をしているプログラムとして contrib/qtool.pl 等があるが
これは、一部の環境では動かない。(理由は後述)
contrib以下だし2002年から更新されていないのでそんな物かもしれない。


Queue処理にて使用されるファイルは基本的に以下の3種類。
プレフィックスでファイルの種類が付きその後にユニークなIDが付加される。
dfファイルが更新されるのはQueueに入ったときのみなので、このファイル
のmtimeを調べることで滞留時間も測定可能。

 qfファイル ヘッダ+制御情報
 dfファイル メールBODY
 tfファイル qfファイル更新時に使用される、renameにてatomicにqfを置き換える


sendmailのQueue処理は並列で行う事を想定しているため
以下のような手順を踏んで排他処理をしている。

  • sendmailのQueueファイル処理ロック取得
 1. qfファイルをopen
 2. qfファイルをlock、失敗したらlock失敗
 3. qfファイルをstat
 4. 1 にて取得したファイルディスクリプタに対してfstat
 5. 3と4にて取得したstat結果を比較し、違いがあった場合はlock失敗
 6. ロック成功


また更新時は以下のような手順でqfファイルの置き換えが行われる。

  • sendmailのqfファイル更新時の処理(配送結果反映等)
 0. 処理中なので既にqfファイルに対してlockは保持している
 1. tfファイルを作成しlockを取得する
 2. tfファイルに対して更新後のqfデータを書き込む
 3. tfファイルをqfファイルにrenameしてatomicに上書きする
 4. qfファイル(renameで潰された方)のファイルディスクリプタをclose


この様になっているので、外部プログラムからQueueファイルを弄る場合にも
同様の手順でロックを取得すれば問題ないことになる。が、一つ落とし穴があり
sendmailは環境によって、flockとfcntlのどちらかを使用してロックを取得している。
該当箇所は sendmail/conf.c:lockfile 関数。


処理としてはどちらもアドバイザリロックで、かつflockとfcntlで別に取得すると
排他が保証されない(fcntlで取得してもflockでは見えない)ので、qtool.pl の様に
flockのみを使用したコードでは一部の環境では動かない事になる。
(少なくともlinuxでは動かない)


sendmailがどちらを使用しているかはコンパイル時に決まる為、バイナリに HASFLOCK が
含まれていればflock、それ以外ならfcntlとなる。
strings sendmail | grep HASFLOCK 等で判別可能。


また、実装時に注意する点としてはqfファイルは処理する度に上記手順で置き換えられるので
ロック取得時に待つのは意味がない。(取得出来た時点で既に破棄されたqfファイルなので)
そのため、ノンブロッキング(flockのLOCK_NB、fcntlのF_SETLK)でビジーループをまわす
必要がある。