      Linux オペレーティングシステム向け Mandatory File Locking

                Andy Walker <andy@lysaker.kvaerner.no>

                         1996 年 4 月 15 日

======================================================================
                日本語訳：野本浩一 <hng@ps.ksky.ne.jp>
                    校正：堀田倫英さん <hotta@net-newbie.com>

1. 強制ロック (mandatory lock) とは？
-------------------------------------

強制ロックは、プロセス間で扱われるファイルのシーケンシャルアクセスを保
証するために、より一般的に使われる協調 (cooperative) ファイルロックに
対し、カーネルが強制的にファイルをロックするものです。ファイルロックは
flock() および fcntl() システムコール (さらに fcntl() のラッパーである
lockf() ライブラリルーチン) を用いてかけられます。更新したいファイルに
ロックをかける前に、ロックをチェックすることは一般的にプロセスが行い、
ロック後ファイルを更新し、そしてファイルをアンロック状態に戻します。最
も一般に用いられているこれの例はユーザの mailbox にアクセスすることで
す (sendmail の場合は最も面倒です)。Mail User Agent と Mail Transfer
Agent は mailbox を同時に更新してはならず、mailcox が更新されている間
はそれを読み出すことを防止しなければなりません。

完全な世界では、全プロセスが協調すなわち、推奨 (advisory) ロックスキー
ムを使用し遵守するでしょう。しかしながら、世界は完全ではなく、とても貧
弱に書かれたコードが出回っています。

この問題に取組む中で、System V UNIX の設計者は「強制」ロックスキームを
考え出しました。これにより、オペレーティングシステムのカーネルは、ある
プロセスが "read" もしくは "shared" ロックを保持しているファイルへ、別
のプロセスが書こうとするのを阻止します。また、あるプロセスが "write"
もしくは "exclusive" ロックを保持しているファイルへの読出しと書込みの
両方を阻止します。

System V の強制ロックスキームは、既存のユーザコードにできるだけ影響を
与えないよう作られました。スキームは強制ロックの候補として個々のファイ
ルをマークし、ロックをかけるには通常の推奨ロックと同様、既存の
fcntl()/lockf() インターフェースを用います。

注釈 1 - 上記の「ファイル」という表現は、実像をいいえていません。
System V のロックは fcntl() に基づきます。fcntl() の粒度はファイル全体
のロックに加え、ファイル内のバイト範囲のロックを許すので、強制ロック
のルールもバイトレベルになります。

注釈 2 - POSIX.1 は System V の fcntl() ロックのスキームを取り入れてい
るにも関わらず、強制ロックのどんなスキームも定義していません。強制ロッ
クのスキームは System V Interface Definition (SVID) Version 3 で定義さ
れています。

2. 強制ロック用にファイルをマーク
---------------------------------

ファイルを強制ロックの候補としてマークするには、そのファイルのモードの
グループ id ビットをセットし、グループ実行ビットをクリアします。この組
合せ以外は無意味で、この組合せは既存のユーザプログラムを壊さないように
System V の実装者が選択しました。

注記、setgid されているファイルに書込みが行われた場合、通常はカーネル
が自動的にグループ id ビットをクリアします。これはセキュリティ対策です。
カーネルは強制ロック候補の特別な場合を認識し、このビットをクリアしない
ように修正されました。同様にカーネルは setgid 特権を持った強制ロック候
補を実行しないように修正されました。

3. 利用可能な実装
-----------------

SunOS 4.1.x, Solaris 2.x, HP-UX 9.x で利用可能な強制ロックの実装を検討
しました。

これら三つのリファレンスシステムが示す振舞いから一般的に言えることを導
こうとしました。例外も多くあります。

どのリファレンスシステムも、別のプロセスが強制ロックをかけているファイ
ルに対する open() コールを拒否するようになっています。これは SVID 3 に
直接違反するものです。SVID 3 は、 O_TRUNC フラグがセットされた open()
へのコールのみ拒否されるべきとしています。Linux の実装は、SVID の定義
に準拠していて、O_TRUNC を用いたコールのみファイルの内容を修正できると
いう「正当な」振舞いをします。

HP-UX は、強制ロックではなく、推奨ロックされたファイル向けの O_TRUNC
を用いた open() でさえ許可しません。これは POSIX.1 に違反するように思
えます。

mmap() もまた興味深いケースの一つです。前記のオペレーティングシステム
はどれも mmap() されたファイルに対して強制ロックをかけることを防止しま
すが、HP-UX については、そのようなファイルに対する推奨ロックをも許可し
ていません。SVID では、HP-UX に関するこのような偏執的な振舞いについて
も規定しています。

私の見解では、MAP_SHARED マッピングだけがロックを受付けるべきではなく、
そして強制ロックだけが - 現状の実装はどうなっているのだろう。

SunOS は非常に絶望的で、それは強制ロックのための O_NONBLOCK フラグの遵
守さえしないので、ロックされたファイルへの読み書きはいつも阻止され、
EAGAIN が返るはずです。

私はセマンティクスがこのように難解なことが残念です。下記で述べるセマン
ティクスは要点が一致するようなら他のいずれでも有効です。

4. セマンティクス
-----------------

1. 強制ロックは fcntl()/lockf() ロッキングインターフェース - 言いかえ
   れば System V/POSIX インターフェース - を介してのみかけられます。
   flock() を用いた BSD スタイルのロックは、強制ロックにはなりません。

2. プロセスが読出しの強制ロックでファイルの領域にロックしている場合、
   他のプロセスはその領域からの読出しを許されます。これらのプロセスの
   いずれかがその領域に書込もうとすると、ロックが解除されるまで待た
   (block) されます。但し、プロセスが O_NONBLOCK を用いてファイルをオー
   プンしている場合、システムコールはエラーステータス EAGAIN を伴って
   すぐに返ります。

3. プロセスが書込みの強制ロックでファイルの領域をロックしている場合、
   その領域に対し読出しもしくは書込みしようとすると、ロックが解除され
   るまで待た (block) されます。但し、プロセスが O_NONBLOCK を用いてファ
   イルをオープンしている場合、システムコールはエラーステータス EAGAIN
   を伴ってすぐに返ります。

4. 他のプロセスが強制ロックしている既存ファイルへの O_TRUNC を用いた
   open() もしくは creat() コールはエラーステータス EAGAIN を伴って拒
   否されます。

5. (MAP_SHARED を用いた mmap() を介し) メモリマップされ共有されたファ
   イルに強制ロックをかけようとすると、エラーステータス EAGAIN を伴っ
   て拒否されます。

6. 強制ロックの効果を持つ、(MAP_SHARED を用いた mmap() を介し) 共有さ
   れたメモリマップファイルを作成しようとすると、エラーステータス
   EAGAIN を伴って拒否されます。

5. どのシステムコールが影響されますか？
---------------------------------------

単に inode だけではなく、ファイルの内容を修正するものです。それは
read(), write(), readv(), writev(), open(), creat(), mmap(),
truncate(), ftruncate() です。truncate() および ftruncate() は強制ロッ
クの観点では「書込み」動作と考えられます。

普通、影響する領域は現在の位置から読出しもしくは書込みされる総バイト数
に及ぶものとして、指定されます。truncate コールでは、削除もしくは追加
されるバイト数として指定されます (さらにロックは単に「ファイル全体」を
指定できるので、バイトの範囲指定ではなく、バイトの追加と考えなければな
りません)。

注釈 3 - このコードを作成するために熱心に作業しましたが、強制ロックを
チェックすることが必要なシステムコールをいくつか見落としたかもしれませ
ん。気が付いた方は私に知らせるか、もっといいのはシステムコールをあなた
が修正しパッチを私か Linus に提出いただくことです。

6. 警告！
---------

root でさえ強制ロックを無効にはできないので、致命的なファイルをロック
したまま逝ってしまったプロセスがあると、システムが大混乱に陥ってしまう
かもしれません。これを防ぐには、ファイルを読み書きする前に、そのファイ
ルのパーミッションを変更 (setgid ビットをクリア) するようにします。も
ちろんこれでシステムがハングするようだと、ちょっと小細工が必要かもしれ
ませんが :-(
