以下は、 RFC3530 の kanda.motohiro@gmail.com による抄訳です。手っ取り早く日本語で NFS v4 を理解するのが目的なので、はしょった訳になっています。ことわりなく原文の部分を省略しているところがあります。23節によると、派生物のライセンスは、規定があるのでしたっけ。なければ、Creative Commons Attribution-Share Alike, Version 3.0 で提供します。他の nfsv4 RFC 訳は、ここを参照。
Network Working Group S. Shepler
Request for Comments: 3530 B. Callaghan
Obsoletes: 3010 D. Robinson
Category: Standards Track R. Thurlow
Sun Microsystems, Inc.
C. Beame
Hummingbird Ltd.
M. Eisler
D. Noveck
Network Appliance, Inc.
April 2003
Network File System (NFS) バージョン 4 プロトコル
Copyright Notice
Copyright (C) The Internet Society (2003). All Rights Reserved.
概要
Network File System (NFS) バージョン4は、 NFS プロトコルバージョン 2, RFC 1094, と、バージョン 3, RFC 1813 の後継となる分散ファイルシステムプロトコルです。以前のバージョンとは異なり、NFS バージョン4プロトコルは、伝統的なファイルアクセスをサポートし、ファイルロックとマウントプロトコルを統合しています。さらに、強いセキュリティ(そしてそのネゴシエーション)、複合操作、クライアントキャッシュ、そして国際化のサポートが追加されました。もちろん、 NFS バージョン4が、インターネット環境でうまく動作するために注意が払われました。
この文書は、 NFS バージョン4プロトコルの定義として、RFC 3010 を置換します。
訳注。長くて編集しにくいので、RFC3530 訳の1章から7章を、一時的に別ファイルにしました。
目次
1 はじめに
2 プロトコルデータ型
3 RPCとセキュリティフレーバー
4. ファイルハンドル
5 ファイル属性
6 ファイルシステム移行と複製
7 NFS サーバーネームスペース
8 ファイルロックと共有予約
ロックをNFSプロトコルに統合するということは、必然的にそれはステートフルになります。共有予約も含むと、プロトコルは、以前の NFS と NLM [XNFS] 以上に状態に依存するようになります。この状態管理をうまくやるために必要なものが3つあります。
o クライアントとサーバの厳密な区別
o クライアントとサーバーの状態のくいちがいを確実に検出すること
o 単純でロバストなリカバリー機構
このモデルでは、サーバーが状態の情報を持っています。クライアントは必要に応じて、自分のその状態の認識をサーバーに伝えます。クライアントは、ファイルを更新する前に、状態の矛盾を検出できます。
Win32 の共有予約をサポートするためには、アトミックな OPEN/CREATE ファイルが必要です。共有/共有解除操作を別にもうけるのでは、 Win32 OpenFile API の正しい実装は不可能です。共用のセマンティクスを正しく実装するためには、以前のNFSでファイルを開いたり作成したり(LOOKUP, CREATE, ACCESS)するときの機構の代わりとなるものが必要です。NFSv4の OPEN 操作は、NFSv3のLOOKUP, CREATE, ACCESSを包含します。しかし、多くの操作がファイルハンドルを必要とするので、昔からの LOOKUP は、サーバーにおいて状態を確立することなくファイル名をファイルハンドルにマップするために残されました。アクセス許可や、ファイルの更新などのポリシーは、クライアントの状態をもとに、サーバーで管理されます。その機構では、アドバイザリーだけのロックから、完全なマンダトリーロックまでのいろいろなポリシーが提供されます。
8.1 ロック
READ と WRITE 操作に比べると、ロック操作はまれだと思われます。また、クラッシュとネットワーク分断は比較的まれだと思われます。この結果、READ と WRITE 操作が、ロックを持っていることを示すことのできる軽量の機構があることは重要です。ロック要求は、ロックを確立して、ロック所有者を一意的に定義するために必要な重量級の情報を持ちます。
以下の節は、重量級の情報から、最終的に、クライアントとサーバーのロックとリースのほとんどのやりとりに使われる stateid への変換を示します。
訳注。ようするに、
SETCLIENTID: クライアントIPアドレスと、ベリファイア==>
<== clientid
LOCK: clientid, プロセスID==>
<== stateid
READ: stateid ==> ロック保有者と判断。
8.1.1 クライアントID
LOCK要求のたびに、クライアントは自身をサーバーに身分証明しなくてはいけません。
これは、ロック識別と障害回復が正しくできるように行われます。SETCLIENTID 操作と、SETCLIENTID_CONFIRM 操作の連続が、サーバーに識別情報を確立するために必要です。新しくブートしたクライアントが識別情報を確立するときには、以前のそのクライアントの実行がサーバーに持っていたリース状態を直ちに破棄する効果を持ちます。そのとき、新しいクライアントが、リースが満了するまで待たされるということはありません。リース状態の破棄とは、同じIDを持つ同じクライアントの、すべてのロック、共有予約、サーバーが CLAIM_DELEGATE_PREV クレームタイプをサポートしないときにはすべての委譲状態を削除するということになります。委譲状態の回復については、「委譲の回復」の節を参照ください。
クライアントIDは、以下の構造体に入ります。
struct nfs_client_id4 {
verifier4 verifier;
opaque id<NFS4_OPAQUE_LIMIT>;
};
最初のフィールド、verifier は、クライアントのリブートを検出するのに使います。IDが同じでベリファイアが違うときに、サーバーはクライアントのリース状態をキャンセルします。
2つめの、id は、クライアントを一意に識別する可変長文字列です。
クライアントが id を生成するにあたっては、以下の注意があります。
o 複数のクライアントが同じIDを使ってはいけません。知らない間に、他の人のリースを解放するなどの問題がおきます。
o クライアントがリブートしても、IDは同じでなくてはいけません。IDを、ローカルファイルに記録するような実装は、ローカルディスクがなくて、すべてのファイルをNFSv4サーバーから得るようなクライアント実装では不可能なので、注意が必要です。
o IDは、サーバーのネットワークアドレスごとに、異なるべきです。サーバーが複数のネットワークアドレスをリスンしているかは、クライアントにはわからないためです。もし、クライアントが、同じIDを使って、SETCLIENTID を同じサーバーの複数のネットワークアドレスに発行した場合、サーバーは2つめ以降は、リースの破棄を始めます。
o IDを生成するアルゴリズムは、クライアントのネットワークアドレスが不変であることを前提としてはいけません。もし、クライアントがあるネットワークアドレスを使わなくなった後に、他のクライアントがそのアドレスを使い、同じIDを生成することがあるためです。
そうすると、結局、良いIDの文字列というのは、以下の組み合わせとなります。
o サーバーのネットワークアドレス
o クライアントのネットワークアドレス
o ユーザーレベルのNFSv4クライアントならば、同じホストの他のクライアントと区別がつくための、プロセスIDなどのユニークなシーケンス番号
o その他、ユニーク性が保証される情報
- クライアントマシンのシリアル番号。(プライバシーのために、1方向ハッシュをかけること)
- MAC アドレス
- NFSv4ソフトウエアがインストールされたときのタイムスタンプ(ただし、これはローカルファイルに依存することになります。)
- 乱数。ただし、クライアントがリブートしても同じであるためには、ローカルファイルが必要です。
セキュリティのために、サーバーは、あるIDでリース状態を確立したプリンシパルが、SETCLIENTID を発行したプリンシパルと異なるときには、クライアントのリース状態を破棄してはいけません。
SETCLIENTID と SETCLIENTID_CONFIRM は、委譲をサポートするために、サーバーがクライアントにコールバックを行うために必要な情報を確立するという2つめの目的もあります。クライアントは、リブートせずに、SETCLIENTID とSETCLIENTID_CONFIRM を出すことで、自分のリース状態を失うことなく、この情報を変更することができます。
一度、SETCLIENTID と SETCLIENTID_CONFIRM シーケンスが完了したら、クライアントは、長くてコンパクトでない nfs_client_id4 構造体の代わりに、clientid4 型の短縮形クライアント識別子を使うことができます。このIDはサーバーによりアサインされ、そのサーバーがかつて発行した他のIDとあたらないように選択されるべきです。サーバーの再開始やリブートを越えても同じことです。サーバーが、受け取った clientid がわからないときは、サーバーは、NFS4ERR_STALE_CLIENTID エラーを返します。クライアントは、これを受けたら、SETCLIENTID で新しいIDをもらった後、「サーバー障害と回復」の節にあるその他の必要な回復処理をしなくてはいけません。
クライアントは、stateid を使った結果、NFS4ERR_STALE_STATEID を受け取ることもあります。その場合、サーバーリブートの結果、昔の clientid およびそれに関連する stateid がすべて無効になったことを意味するため、SETCLIENTID が必要です。詳しくは、「lock_owner と stateid の定義」の節を参照ください。
SETCLIENTID と SETCLIENTID_CONFIRM 操作の完全な仕様は、その節を参照ください。
訳注。私は、SETCLIENTID に、ACE で使われる、文字列のユーザー名が入ってくるものだと思っていましたが、NFSv4の外側の、RPCのところに、GSSだっけ、でケルベロスから得たチケットが入るわけですかね。もうひとつ、誤解。クライアントというのは、マシンなので、ACL に入るような、ユーザーアカウントは持たないですね。
8.1.2 サーバーが clientid を解放するとき
サーバーは、クライアントが、clientid に関連する状態を何も持たないと判断したら、clientid を解放することがあります。インアクティブなクライアントのために、資源が消費されるのを防ぐ意味があります。この後に、クライアントがサーバーにコンタクトすると、サーバーは、クライアントが SETCLIENTID/SETCLIENTID_CONFIRM シーケンスを使って、新しいID を確立するように、適当なエラーを返さなくてはいけません。もちろん、サーバーはできるだけ clientid を解放しないにこしたことはありません。クライアントにとって必要な回復処理は、サーバーの再開始のときと同じに大変ですから。典型的には、クライアントから何分もアクティビティがなかったときだけ、解放を考えるのが良いでしょう。
SETCLIENTID のID 文字列が適当に構成され、クライアントが以降の SETCLIENTID で同じプリンシパルを使うように注意すれば、アクティブサービス拒否攻撃を防ぐためにも、NFS4ERR_CLID_INUSE は返されるべきではありません。しかし、そのエラーは、サーバーあるいはクライアントのバグ、あるいは意図的なプリンシパル所有者の変更などの時にはおきることもあるでしょう。
そのような場合でも、もし、SETCLIENTID の示す clientid が現在状態を持っていないか、持っていても失効しているときは、サーバーは NFS4ERR_CLID_INUSE を返すのではなく、SETCLIENTID を受け付けるべきです。
8.1.3 lock_owner と stateid の定義
クライアントは、ロックを要求するときは clientid と、ロック所有者のIDをサーバーに提供します。この2つを、lock_owner と呼び、その定義は、以下です。
o サーバーが、SETCLIENTID の結果、返した clientid
o クライアントが管理する、ロックの所有者を一意に識別する可変長の不透明バイト列
これは、スレッドやプロセスのIDなどでしょう。
サーバーがロックを許可すると、ユニークな stateid を返します。stateid は、lock_owner の短縮形の参照として使われます。サーバーはこの2つの対応を管理します。
サーバーは、不正、あるいは失効したものを区別できるなら、どのように stateid を構成してもよろしい。サーバーがリブートする前に発行した IDも区別する必要があります。クライアントが、サーバーのリブートに気づくことができるためです。それは、クライアントが以前のサーバーの発行した stateid を使ったときに検出されます。
サーバーは以下の状況を区別して、指定されたエラーを返さなくてはいけません。
o stateid は、リブート前のサーバーが発行したもの。NFS4ERR_STALE_STATEID エラーを返すこと。
o stateid は、現在のサーバーが発行したもの。しかし、IDは、目的ファイルの、現在のロック所有者とファイルの対のロック状態に対応しない。(つまり、1つ以上のロック操作が行われた。)NFS4ERR_OLD_STATEID を返すこと。
このエラー条件は、クライアントが stateid を変更するロック要求を発行したときに、まだ、その stateid を使う I/O要求が実行中であるときに限って起きます。
o stateid は、現在のサーバーが発行したもの。しかし、IDは、アクティブな、どのロック所有者とファイルの対のロック状態にも対応しない。NFS4ERR_BAD_STATEID を返すこと。
これは、サーバーかクライアントの論理エラーでしか起きえません。
これらの条件を満たす1つの例は、
o stateid の「other」フィールドを2つに分ける。
- サーバーがリブートしたら区別できる、サーバーのベリファイア
- ロック状態のテーブル内のインデックス
o stateid の「seqid」フィールドを使って、それが、同じロック状態のテーブル内のインデックスに対応する stateid ごとに、単調増加するようにする。
受信した stateid と、そのフィールド値を、サーバーが持っているものと比べることで、サーバーは stateid が現在の実行サーバーと状態にとって有効かどうか、簡単に判断できます。 stateid が無効なときは、クライアントに適切なエラーを返すことができます。
8.1.4 stateid の使用とロック
すべての READ, WRITE と SETATTR 操作は stateid を持ちます。この節では、ファイルの長さ属性を変える SETATTR 操作は、元のファイル長と新しいファイル長の間のデータに対しての書き込みとして扱われます。ライトは SETATTR を含むと、断ってないこともあります。
lock_owner が、サーバーでロックあるいは共有予約を確立した後に、READ あるいは WRITE を実行するとき、どのようなロックと共有予約を lock_owner が持っているかを示すために、 以前サーバーが返した stateid が使われます。クライアントがいかなるロック、共有予約も持たないならば、全部ゼロの stateid が使われます。競合する共有予約もしくは、マンダトリーロックがあるときは、サーバーは READ あるいは WRITE を失敗させなければいけません。
共有予約は OPEN で確立され、定義によってマンダトリーなので、READ あるいは WRITE が OPEN と競合して失敗するときは、NFS4ERR_LOCKED が返ります。レコードロックはサーバーによって、マンダトリーであったり、アドバイザリーであったりします。アクセス対象ファイルによって、どちらであるかが決まるときもあります。(UNIX ベースのサーバーには、モード属性に、「マンダトリーロックビット」を持つものがあり、読み書きするときにはロックが必要とするものがあります。)アドバイザリーロックは、読み書きに影響を与えません。マンダトリーロックは、競合する読み書きを、NFS4ERR_LOCKED で失敗させます。クライアントがそのエラーを受けた時に、自分が正しい共有予約を持っているならば、さらに、これから読み書きするファイルの部分に、READ*_LT もしくは、WRITE*_LT 型のロックをかける必要があります。
NFSv3では、 stateid というものがありませんでしたから、読み書きしているクライアントが、正しいファイルロックを持っているかを知ることができませんでした。なので、マンダトリーロックは、実現できませんでした。 stateid により、それが可能となりました。
マンダトリーロックをサポートする UNIX 環境にとって、アドバイザリーとマンダトリーの実装上の差は小さいです。訳注。このあたり、わかりません。
Windows では、アドバイザリーロックはないので、サーバーは必ずレコードロックをチェックします。
NFSv4の LOCK 操作は、アドバイザリーとマンダトリーの区別をしません。
オープンタイプの操作と、ロックタイプの操作は、ファイルの「アクセスモード」を決めます。書き込みであれば、アクセスモードが書き込みを許さないときはサーバーは NFS4ERR_OPENMODE を返します。リードの場合も、アクセスモードに従ってチェックをしますが、ライトオンリーのファイルであっても、バッファキャッシュの実装上の都合でライトのためにリードが出ることがあり、サーバーは、それだけは許すこともあります。その場合も、ロックのチェックは必要です。
全部1の stateid は、サーバーでのリードのロックチェックをバイパスすることができることがあります。しかし、ライトのロックチェックをバイパスすることはできません。そのときは、全部ゼロとして扱われます。
前記特殊な stateid を使った読み書きが実行中の時は、それと重なる範囲の他のロック操作はできないことがあります。
8.1.5 ロック要求のシーケンシング(順序番号付け)
ロックは他の NFS 操作と違って、「at-most-one」セマンティクスが必要です。それは、ONCRPC が提供できないものです。ONCRPC が、信頼性のあるトランスポートに乗っていても、TCP の再送や順序逆転があり、ロックとアンロックが正しく振る舞う保証はありません。このため、ロック要求は単調増加する整数のシーケンス番号を持ちます。シーケンスはlock_owner ごとにあります。最初はゼロです。サーバーは、最後に受け取ったシーケンス番号 (L) と、そのときに返した結果を保持します。
lock_owner は、応答を得ていないロック要求を1つ以上持ってはいけません。
以前のシーケンス番号より小さい要求 (r < L) が届いたら、 NFS4ERR_BAD_SEQID エラーを返します。同じ物 (r == L) が届いたら、保存してある応答を返します。次のシーケンスより大きいもの (r == L + 2) が届いたら、NFS4ERR_BAD_SEQID エラーを返します。シーケンスの履歴は、クライアントが SETCLIENTID/SETCLIENTID_CONFIRM でクライアントベリファイアを変更した時に初期化されます。
シーケンス番号は32ビットなので、演算は、2^32 のモジュロ演算です。詳しくは、 [RFC793] を参照下さい。
従来の、[Juszczak] で述べるキャッシュと違い、最後の応答をいつまでも保持するこのキャッシュは、等べきでない要求を扱うのに必要です。LRU で追い出してはいけません。
クライアントは、CLOSE, LOCK, LOCKU, OPEN, OPEN_CONFIRM, とOPEN_DOWNGRADE 操作のシーケンス番号を単調増加する必要があります。要求がエラーリターンしてきても、同じ事です。ただし、NFS4ERR_STALE_CLIENTID, NFS4ERR_STALE_STATEID, NFS4ERR_BAD_STATEID, NFS4ERR_BAD_SEQID, NFS4ERR_BADXDR, NFS4ERR_RESOURCE, NFS4ERR_NOFILEHANDLE
は除きます。
8.1.6 再送された要求からの回復
サーバーが、ファイルがオープンしている限り、あるいは、ロックがかかっているクローズしたファイルがある限り、(lock_owner 、最後のシーケンス番号)の組を管理する限り、ビザンチンルーターが最後の要求を再送してきても、問題ないはずです。
LOCK, LOCKU, OPEN, OPEN_DOWNGRADE, とCLOSE はシーケンス番号を持っているので、再送されても、おかしなことにはなりません。
8.1.7 lock_owner 状態を解放する
あるlock_owner がオープンやファイルロック状態をサーバーでもはや持たないならば、サーバーは、lock_owner に対応するシーケンス番号の状態を解放することを望むかもしれません。それは、リースの失効、サーバーのメモリ不足、他の実装上の都合のためかもしれません。いずれの場合も、クライアントがlock_owner をもはや使っていないときに限り、サーバーは、安全にこれが行えます。サーバーは、再送された要求が届いたときのために、lock_owner 状態を保持することを選択することもあるでしょう。しかし、保持時間は、実装依存です。
サーバーが状態を解放した後に、LOCK, LOCKU, OPEN_DOWNGRADE, あるいは CLOSE が再送されてきたら、サーバーは lock_owner がオープンファイルを持たないことがわかるので、エラーを返します。もしも、オープンファイルがあっても、 stateid があわないはずなので、いずれにせよエラーを返します。
8.1.8 オープン確認の使用
オープンが再送され、 lock_owner が初めて使われたか、あるいはそれが以前にサーバーで解放されていた場合、 OPEN_CONFIRM 操作が、誤動作を防ぎます。サーバーが初めて lock_owner を見たとき、クライアントに、オープンに対応する OPEN_CONFIRM 操作をするように指示します。このシーケンスにより lock_owner の使用と、シーケンス番号が確立されます。 OPEN_CONFIRM シーケンスは、クライアントの既存の open_owner と、サーバーの新しい open_owner を結び付けるので、シーケンス番号は任意でかまいません。OPEN_CONFIRM ステップにより、サーバーは受け取ったものが正しいことを確認できます。詳しくは、「OPEN_CONFIRM - Confirm Open」の節を参照ください。
オープンの確認が、クライアントとサーバーにとって、困難となる状況がいくつかあります。受け取った情報からでは、短時間での判断ができないことがあるためです。なぜならば、情報は暫定的なものであり、確認がない場合は削除されるべきだからです。幸い、サーバーがオープン要求に応答するときに確認が必要ない状況がいくつかあります。2つの条件があります。
o サーバーは、確認を必要とするオープンに対して、委譲を与えてはいけません。
o サーバーは、リクレームタイプのオープン(つまり、クレームタイプ CLAIM_PREVIOUS か、CLAIM_DELEGATE_PREV )に対して、確認を求めてはいけません。
これらの条件は、リクレームタイプのオープンだけが、サーバーが委譲を送る必要があるものであるという点で、関連があります。CLAIM_NULL なら、委譲の送付はオプショナルで、CLAIM_DELEGATE_CUR なら、委譲は送られません。
確認を必要とするオープンに伴って送られる委譲は面倒です。確認がないことからの回復はプロトコルに複雑さをもたらし、リクレームタイプのオープンに確認を必要とすることは困難だからです。つまり、リースが満了するまでリクレーム状態が解決できないということは、複数のロックがリクレームできるかどうかの判断を短時間で行うことを不可能とします。(グレースピリオドの満了を待つ必要があります。)
リクレームタイプのオープンに確認を必要とすることは、オープンが行われる環境によっては避けることができます。 CLAIM_PREVIOUS ならば、これはサーバーリブートの直後のはずなので、 lockowner が作成され、使われないことがわかり、捨てられるということは起きる時間がないはずです。 CLAIM_DELEGATE_PREV ならば、クライアントリブートのはずです。サーバーが委譲をサポートしているならば、そのクライアントに関して、クライアントの初期化以来、 lockowner が捨てられていないことを確認できるはずです。このため、確認は不要とわかります。
8.2 ロックレンジ
プロトコルでは、バイトレンジロックをかけた後、最初のロックのサブレンジ、より狭い範囲をアップグレードしたり、アンロックしたりすることが許されます。これは、まれな操作であると仮定されます。いずれにしても、サーバーあるいはサーバーのファイルシステムがサブレンジロックのセマンティクスをサポートしていないこともあります。その場合、サーバーは、NFS4ERR_LOCK_RANGE を返すことができます。クライアントは、これに備え、発行元アプリケーションにエラーを報告するように。
クライアントが、隣接する複数のロックレンジを1つにまとめる操作は、しないことが薦められます。サーバーは、サブレンジロックをサポートしないかもしれませんし、サーバーの障害回復のときの、ファイルロック状態の回復のためにも、避けるべきです。以下の「サーバー障害と回復」の節で述べるように、サーバーが回復のときに行う最適化は、クライアントのロック回復のふるまいが、サーバー障害の前にクライアントがロックを確保したときのふるまいにきちんと対応つくときでないと、うまくいかないことがあります。
8.3 ロックのアップグレードとダウングレード
クライアントがライトロックを持っている場合、 READ_LT をつけた LOCK 要求を使って、それをアトミックに、リードロックにダウングレードすることができます。サーバーがアトミックなダウングレードをサポートしているなら、それは成功し、していないなら、 NFS4ERR_LOCK_NOTSUPP が返ります。
クライアントがリードロックを持っている場合、 WRITE_LT あるいは WRITEW_LT をつけた LOCK 要求を使って、それをアトミックに、ライトロックにアップグレードすることができます。サーバーがアトミックなアップグレードをサポートしていないなら、NFS4ERR_LOCK_NOTSUPP が返ります。他にロック競合がある場合、 NFS4ERR_DENIED あるいは NFS4ERR_DEADLOCK が返ります。後者は、クライアントが、WRITEW_LT をつけ、サーバーがデッドロックを検出したときに使われます。
8.4 ブロッキングロック
クライアントによっては、ブロッキングロックのサポートが必要なものもあります。NFSv4 はコールバックに依存してはいけないので、クライアントが以前取れなかったロックが取れるようになったことを通知する手段がありません。クライアントは定期的にロックをポーリングするしかありません。これは公平性の問題があります。 READW, WRITEW という2つの新しいロックタイプが導入され、クライアントがブロッキングロックを要求していることをサーバーに伝えます。サーバーは、待っているブロッキングロックの順序付けられたリストを維持するべきです。競合するロックが解放されたとき、サーバーは、リース期間だけ待って、最初に待っているクライアントがロックを再確保に来るのを待っても良いです。リース期間が満了したら、次に待っているクライアントにロックが与えられます。クライアントは、すみやかにロックの再確保ができるように、適当に短いインターバルでポーリングをするべきです。ここで述べたブロッキングロックのリストは、公平性を高めるのが目的で、正しいプロトコル動作のためでないので、サーバーはそれをしなくてもよいです。障害回復は順序よく行われることは期待できないので、ブロッキングロックの順序を保証するためには、ロック状態をステーブル記憶域に書く必要があるでしょう。
サーバーは、ロックタイプを見て、競合するロックがある場合、ロックが外れるまで少し待ってもよいです。そうすれば、クライアントがポーリングをしなくて済みます。クライアントがロック要求を再送してくることもあるので、サーバーは待ちすぎないように。
8.5 リース延長
リースの目的は、クラッシュあるいは、他の原因で通信できないクライアントが持っている古いロックを、サーバーが、削除できるようにすることです。それはキャッシュ同期のための機構ではなく、リース期間が残っているならば、リース延長は許可されなくてはいけません。
以下のイベントは、そのクライアント(その clientid を使っているすべて)の持っているすべてのリースを暗黙のうちに延長します。
o 有効な clientid を使った OPEN 。
o 有効な stateid を使った任意の操作(CLOSE, DELEGPURGE, DELEGRETURN, LOCK, LOCKU, OPEN, OPEN_CONFIRM, OPEN_DOWNGRADE, READ, RENEW, SETATTR, WRITE)。これには、すべてゼロあるいは1の特別な stateid は含まれません。
クライアントが再開始あるいはリブートしたら、クライアントは SETCLIENTID/SETCLIENTID_CONFIRM シーケンスを発行するまで、前記の操作をすることはできないはずです。SETCLIENTID/SETCLIENTID_CONFIRM シーケンス(クライアントベリファイアを変えますから)を使うと、サーバーはそのクライアントに関するロック状態を消すことができます。SETCLIENTID/SETCLIENTID_CONFIRM がリースを延長することは決してありません。
サーバーがリブートしたら、stateid (NFS4ERR_STALE_STATEID エラー)あるいは clientid (NFS4ERR_STALE_CLIENTID エラー) が無効になるため、余分なリース延長は起きません。
このアプローチは、低いオーバーヘッドのリース延長が可能で、うまくスケールします。典型的なケースではリース延長のための追加の RPC 呼び出しは不要で、最悪の場合でもリース期間ごとに1つの RPC (つまり、 RENEW 操作です。)で済みます。クライアントが持っているロックの数はファクターには含まれません。クライアントのすべての状態が、リース延長の影響を受けるためです。
新しいリースを作るすべての操作は既存のリースを延長しますから、サーバーはあるクライアントのすべての有効なリースに共通のリース満了時刻を管理しなくてはいけません。
訳注。一番長い満了時刻にあわせればいいということ?
このリース時刻は、暗黙のリース延長アクションによって簡単に更新することができます。
8.6 障害回復
障害回復で重要なのは、クライアントとサーバーが、相手が落ちたときにそれがわかることです。さらに、クライアントが、サーバーの再開始あるいはリブートを超えて、一貫したデータのビューを持てることも必要です。すべての READ と WRITE 操作は、クライアントあるいはネットワークバッファにキューイングされた後、クライアントがそれらの操作を保護するロックをきちんと回復するまで待つ必要があります。
8.6.1 クライアント障害と回復
クライアントが障害になったとき、サーバーは、関連するリースが満了した後、クライアントのロックを解放することができます。他のクライアントからの競合するロックは、リースが満了したあとでないと、許可されません。クライアントがリース期間の間に再開始してきたときは、そのクライアントは、新しいロックを取ることができるまで、リース期間の間、待たされることがあります。
再開始したクライアントの待ち時間を少なくするために、ロック要求はクライアントの提供したベリファイアによって、クライアントのインスタンスと対応付けられています。このベリファイアは、最初の SETCLIENTID 呼び出しの一部です。サーバーはその応答に、 clientid を返します。さらにクライアントは、 clientid を SETCLIENTID_CONFIRM で確認します。この clientid と、所有者フィールドの組み合わせが、OPEN のロック所有者を識別するのに使われます。この関連のチェーンは、特定のクライアントの持つすべてのロックを識別するのに使われます。
クライアントがリブートすると、ベリファイアは変わるので、サーバーは新しいベリファイアと、今、保持されているロックに関連づくベリファイアを比較して、違うことがわかります。クライアントは再開始し、ロック状態を失ったわけです。その結果、サーバーは以前のベリファイアに対応する clientid の持つすべてのロックを解放することができます。
なお、ベリファイアは、 COMMIT 操作で要求されるのと同じ、一意性が必要です。
8.6.2 サーバー障害と回復
サーバーがロック状態を失ったとき、(通常は、再開始やリブートの結果)サーバーはクライアントに、それを気づかせ、失われたロック状態を再確立するための時間を与えなければいけません。サーバーが、他のクライアントに競合するアクセスを許可してしまった結果、クライアントのロック状態再確立が失敗することはあってはいけません。同様に、クライアントがあるファイルにロック状態を再確立していないかもしれない場合、サーバーはそのファイルの読み書きを許可してはいけません。この回復期間の長さは、リース期間と同じです。
クライアントは、以下の2つのエラーを受け取ったときに、サーバー障害(そしてロック状態の喪失)が起きたと判断できます。NFS4ERR_STALE_STATEID は、 stateid が、 NFS4ERR_STALE_CLIENTID は、clientid が無効になったことを示します。クライアントはこれを受け取ったら、新しい clientid (「Client ID」の節を参照)を確立して、以下に述べるようにロック状態を再確立しなくてはいけません。
ロックと読み書きの特別の扱いが必要で、リース期間と同じ長さを持つ期間を、「グレースピリオド」と呼びます。グレースピリオドの間、クライアントはロックと関連する状態を回復するために、リクレームタイプのロック要求を出して行います。(具体的には、 reclaim が真である LOCK 要求と、クレームタイプが CLAIM_PREVIOUS である、 OPEN 操作です。)グレースピリオドの間、サーバーは、READ と WRITE 操作、それにリクレーム以外のロック要求(前記以外の LOCK, OPEN )を、NFS4ERR_GRACE で失敗させなくてはいけません。
サーバーが、あるリクレーム以外の要求を許可しても、他のクライアントのロックのリクレームと競合しないことが確実に判断できるならば、前記エラーを返さずに、リクレーム以外の要求を許可することができます。同様に、グレースピリオドの間でも、ロックのリクレームと競合しないと判断できれば、サーバーは、READ と WRITE 操作を受け付けることができます。その保証ができないときは、NFS4ERR_GRACE を返さなくてはいけません。
サーバーにとって、最も簡単、単純、有効な、グレースピリオドの扱いは、その間はすべてのリクレームでないロック要求と読み書きを NFS4ERR_GRACE で失敗させることです。しかし、サーバーは、ステーブル記憶域に、許可したロックの情報を保持することもできます。これを使えば、サーバーは通常のロックや読み書きが安全に行えるかを判断できます。
例えば、あるファイルのロックの数がステーブル記憶域にあれば、サーバーはそのファイルのロックのリクレームを追跡することができ、すべてのリクレームが終わったら、リクレームでないロック要求を受け付けることができます。こうすればサーバーは、リクレームでないロック要求が、潜在的なりクレームと競合することがないようにできます。読み書きについても同じことで、現在、未完了のリクレーム要求がないことがステーブル記憶域などにより判断できれば、そのファイルについて通常の読み書きをさせることができます。
くりかえしますが、サーバーがグレースピリオドの間にリクレームでないロックと読み書きを許可するためには、サーバーは、それ以降受け取るであろうロックのリクレームが競合のために失敗することなく、そのロックのリクレームが読み書きと競合しないはずであることを、保証する必要があります。
クライアントは、リクレームでないロックと読み書きの結果、 NFS4ERR_GRACE が返ることにそなえてください。要求をリトライするのが良いでしょう。サーバーを過負荷にしないために、数秒の遅延を入れるべきです。その他、注意する点は [Floyd] を参照ください。クライアントは、グレースピリオドの間に、リクレームでないロックと読み書きを許可するサーバーも、しないサーバーもあることに注意ください。
サーバーのグレースピリオドの外での、リクレームタイプのロック要求は、サーバーが、リブート以降、競合するロックと読み書きを許可していない場合に限り、成功することがあります。
サーバーは、再開始したときに、新しいリース期間を採用するかもしれません。このため、クライアントは、新しい clientid を確立したら、 lease_time 属性を取得しなおして、そのサーバーとの以降のリース更新のベースとして使うのが良いでしょう。しかし、サーバーは、このような場合、前回のリース期間より短いグレースピリオドを使ってはいけません。前回の値を使っているクライアントに十分な時間を与えるためです。
8.6.3 ネットワーク分断と回復
ネットワーク分断の期間が、サーバーが提供するリース期間より長いと、サーバーはクライアントからリース更新を得られません。そうなると、サーバーはクライアントが持っているロックを全部放してしまうかもしれません。その結果、クライアントが持っていた stateid は全部、不正あるいは無効となります。ネットワーク分断がなおってから、クライアントがサーバーにアクセスすると、不正となった stateid のもとで行われるすべてのI/Oは NFS4ERR_EXPIRED となります。このエラーを受けたクライアントは、ロックを持っていたアプリケーションに適切にそれを通知するでしょう。
クライアントに親切にするため、あるいは、最適化のために、サーバーはリース期限を越えても通信してこないクライアントのロックを保持し続けることもあるでしょう。しかし、そのロックと競合するロックやI/Oを受けた場合は、ロックを解放して新しい要求を受けないといけません。
ネットワーク分断がサーバーのリブートと重なると、サーバーが注意しないと、知らない間にデータがこわれるという境界条件が存在しえます。2つの場合があることが知られており、以下に説明します。
最初の境界条件は、このシナリオです。
1 クライアントAがロックを取る。
2 クライアントAとサーバーは互いにネットワーク分断を経験し、クライアントAはリースを更新できない。
3 クライアントAのリースが満了して、サーバーはロックを放す。
4 クライアントBが、クライアントAと競合するロックを取る。
5 クライアントBがロックを放す。
6 サーバーがリブートする。
7 クライアントAとサーバーのネットワーク分断がなおる。
8 クライアントAは RENEW 操作を送り、NFS4ERR_STALE_CLIENTID エラーをもらう。
9 クライアントAは、サーバーのグレースピリオドの間に、ロックをリクレームする。
このように、最後のステップで、サーバーは誤って、クライアントAのロックリクレームを許可してしまいました。もしクライアントBが、ロック対象のオブジェクトを更新していたら、クライアントAのデータがこわれます。
2つめの境界条件は以下です。
1 クライアントAがロックを取る。
2 サーバーがリブートする。
3 クライアントAとサーバーは互いにネットワーク分断を経験し、クライアントAはグレースピリオドの間に、ロックをリクレームできない。
4 サーバーのグレースピリオドが終わり、クライアントAのロックはサーバーに記録されていない。
5 クライアントBが、クライアントAと競合するロックを取る。
6 クライアントBがロックを放す。
7 サーバーが再度リブートする。
8 クライアントAとサーバーのネットワーク分断がなおる。
9 クライアントAは RENEW 操作を送り、NFS4ERR_STALE_CLIENTID エラーをもらう。
10 クライアントAは、サーバーのグレースピリオドの間に、ロックをリクレームする。
最初の境界条件のときと同じように、シナリオの最後のステップでサーバーは誤って、クライアントAのロックリクレームを許可してしまいました。
境界条件1と2を防ぐには、サーバーは自分がリブートしたら、境界条件が起きたものだと思って、すべてのリクレーム要求に NFS4ERR_NO_GRACE を返すか、あるいは、サーバーがなんらかの情報をステーブル記憶域に覚えておくかです。どれだけの情報を覚えればいいかは、サーバーが境界条件が起きた時にどれだけ、寛容となるかによって決まります。すべての境界条件に対処しようとするサーバーは、すべての確保されたロックを覚える必要があります。ロックを忘れるのは、クライアントがアンロックしたときだけで、ロック保持者がシーケンス番号を進めることで、ロック保有者のシーケンスの中で、ロック解放が最後の状態変化のイベントでなくなったときに限ります。前記の2つの境界条件の場合、サーバーが最も厳しいとして、なお、リクレームのグレースピリオドをサポートするには、サーバーがステーブル記憶域に最低限の情報を覚える必要があります。例えば、クライアントごとに、以下を覚えればよいでしょう。
o クライアントの ID 文字列
o クライアントのリースが満了したか、あるいは管理上の介入があって(「サーバーがロックを失効させる」の節を参照。)レコードロック、共有予約、あるいは委譲を失効させたことがあったかを示すブーリアン。
o サーバーがブートあるいはリブートして以降、最初にクライアントがレコードロック、共有予約、あるいは委譲をサーバーにおいて確保した時のタイムスタンプ。タイムスタンプは、サーバーがリブートしない限り、以降のロック確保の時には更新されなくてもよいです。
さらに、サーバーはステーブル記憶域に、最近の2回のサーバーリブートのタイムスタンプを記録します。
上記情報が記録されていたとすると、最初の境界条件では、サーバーのリブートの後、クライアントAのリースが満了したという記録があるという事は、他のクライアントが競合するロック、共有予約、委譲を確保したかもしれないことを示すため、サーバーはクライアントAのリクレームを NFS4ERR_NO_GRACE で失敗させなくてはいけません。
2つめの境界条件では、サーバーが2度めにリブートした時、クライアントが満了していないレコードロック、共有予約、委譲を、サーバーの以前の実行の前に保持していたという記録があるという事は、同じように、クライアントAのリクレームを NFS4ERR_NO_GRACE で失敗させるべきだということです。
記録を保持するレベルとアプローチにかかわらず、サーバーは以下の戦略(レコードロック、共有予約、委譲のレクレームに適用されます。)のどれかを実装しなくてはいけません。
1 すべてのリクレームを、 NFS4ERR_NO_GRACE で失敗させる。これはとても厳しいですが、サーバーがロック状態をステーブル記憶域に覚えないならば必須です。
2 ステーブル記憶域に十分な情報を覚えて、サーバーリブートを含み、この節で説明した2つの境界条件を含むすべての既知の境界条件が検出されるようにする。誤って危険と判断するのは許されます。なお、現在、これ以外に境界条件があるかは、不明です。
サーバーがリブートした後、ステーブル記憶域がこわれていることがわかった場合、サーバーは関係するすべてのクライアントとロックに、NFS4ERR_NO_GRACE を返さないといけません。
NFS4ERR_NO_GRACE を受けたクライアントがどうしたらいいかは、この規格の範囲外のことです。それは、クライアントの運用環境に依存するでしょうから。例として1つのアプローチを以下に示します。
クライアントが NFS4ERR_NO_GRACE を受けたら、クライアントは状態をリクレームしようとしているオブジェクトの change 属性を見て、通常のオープンやロック要求で状態を再確立するかを決めます。それでよいかは、クライアントの運用環境によります。クライアントはユーザーにそういうことがあることを文書で伝えておくのがよいでしょう。また、アプリケーションに、レコードロックあるいは共有予約(委譲されたものも、そうでないものも)が失われたことを、UNIX シグナルや、GUI ポップアップなどで伝えるのもよいでしょう。リクレームできなかった委譲とクライアント状態についてクライアントがどうするべきかは、「データキャッシュと失効」の節を参照下さい。
ロックの失効については、「サーバーがロックを失効させる」の節を参照下さい。
8.7 ロック要求のタイムアウトとキャンセルからの回復
ロック要求がタイムアウトしたとき、クライアントは、要求をリトライしないことにするかもしれません。あるいは、ロックを発行したプロセスが、シグナルを受けて終了したために、要求をキャンセルしたいこともあるでしょう。しかし、サーバーでは、要求を受けて、処理してしまっているかもしれません。この結果、クライアントの知らないうちに、サーバーでの状態が変化します。クライアントが、同じ lock_owner のもとで、seqid あるいは stateid を持つ何らかの操作をする前に、サーバーとの間で状態を再同期することができればよいのですが、それは簡単ではありません。以下に説明します。
サーバーは、 lock_owner ごとに、最後に受けたロック要求とその応答を保持しているはずなので、クライアントはロック要求が応答を得られなかった時のために、最後のロック要求をキャッシュするのがよいでしょう。これにより、クライアントが次回、ロック操作をするときに、キャッシュされた要求があれば、それを送ることができます。要求が、ロックを確立する、オープンやロックであれば、サーバーはキャッシュされたものがあればそれを返し、なければ、新たに実行します。クライアントは、次に、状態を削除するアンロックあるいはクローズを出せばよいです。このアプローチにより、ある lock_owner に関して、クライアントとサーバーの間で、シーケンシングと stateid の再同期が取られるので、ロック状態も再同期することができました。
8.8 サーバーがロックを失効させる
サーバーは任意の時点で、あるクライアントの持っているロックを失効させることができ、クライアントはそれに備えなくてはいけません。クライアントは、自分のロックが失効したらしいと気がついたら、自分とサーバーの間の状態を再確認する責任があります。ロック状態の確認とは、クライアントが、現在持っているロックすべてについて、状態を確認あるいはリクレームするということです。
最初のロック失効イベントは、サーバーのリブートあるいは再初期化です。この場合、クライアントは (NFS4ERR_STALE_STATEID or NFS4ERR_STALE_CLIENTID) エラーを受けるので、前の節で示した通常の障害回復を始めます。
2つめのロック失効イベントは、リースを期限内に延長できなかったときです。これは、まれであると考えられますが、クライアントは回復できなくてはいけません。リース延長に失敗した場合、クライアントもサーバーもそれに気がつくことができ、データ破壊なしに回復することが可能です。サーバーは、クライアントごとに、受け付けた最後のリース延長を追跡しており、リースがいつ満了するか、わかります。同様にクライアントは、リースを延長する操作を追跡する必要があります。そのような要求が送られた時刻と、応答が戻った時刻を使って、クライアントは、リース更新が起こるべき時刻がわかり、失効が起こったかどうか、わかります。
3つめのロック失効イベントは、リース期間内の、管理上の介入によります。まれでしょうが、サーバー管理者が、クライアントの持つ特定のロックを解放、あるいは失効させることがあります。この場合、クライアントは、 NFS4ERR_ADMIN_REVOKED を受けます。クライアントは、 lock_owner のロックだけが失われたと仮定することができます。クライアントは、ロック所有者に、適当にそれを伝えます。失敗した操作の結果、リース期間が延長されたと思うことはできません。
クライアントが、リースが満了したと判断した場合、リースに関連するすべてのロックは「未確認」とされます。これは、クライアントがサーバーとロック状態を再同期あるいは確認できていないということです。以前の障害回復の節で述べたように、クライアントのリース期間が満了した後、サーバーは競合するロックを許可してしまうシナリオがあります。リースが満了したかもしれないならば、クライアントは現在持っているロックすべてについて、競合するロックが許可されていないか確認しなくてはいけません。クライアントは、問題となっているロックに関連した stateid を指定して、実際に、以前発行して応答が来ていない要求、あるいは長さゼロのリードを発行してみれば、それがわかります。成功すれば、その stateid に関するすべてのロックが確認できたことになり、サーバーと自分の状態が再同期できたことになります。
失敗すれば、その stateid に関する1つ以上のロックは失効しており、クライアントは所有者にそれを伝えなければいけません。
8.9 共有予約
共有予約は、ファイルへのアクセスを制御する方法です。それは、レコードロックとは別で、独立したものです。クライアントは、ファイルをオープンするとき、サーバーに、希望するアクセスタイプ (READ, WRITE, or BOTH) と、他の人に拒否したいアクセスタイプ (deny NONE, READ, WRITE, or BOTH) を指定して、 OPEN 要求を発行します。 OPEN が失敗したら、クライアントはアプリケーションのオープン要求を失敗させます。
セマンティクスを示す擬似コードです。
if (request.access == 0)
return (NFS4ERR_INVAL)
else
if ((request.access & file_state.deny)) ||
(request.deny & file_state.access))
return (NFS4ERR_DENIED)
この、オープン時の共有予約のチェックは、同じ open_owner による既存のオープンについても行われます。
OPEN と OPEN_DOWNGRADE で使われるアクセスと拒否フィールドの定数は、以下です。
略
8.10 オープンとクローズ操作
正しい共有セマンティクスを提供するため、クライアントは最初のファイルハンドルを得るために、オープン操作を使い、希望するアクセスと拒否したいアクセスを指定しなくてはいけません。クライアントが、すべて0あるいは1の stateid を使うつもりであっても、適当な共有セマンティクスが適用されるように、オープンを使うべきです。自分のオープンプログラミングインタフェースに、拒否モードがないクライアントは、deny None を使うように。
以前のバージョンの NFS で使われた、通常ファイルの CREATE 操作も、 CREATE フラグをつけたオープンに置き換わります。これにより、作成と共有がアトミックに行えます。
クローズは、lock_owner がそのファイルに持つすべての共有予約を削除します。レコードロックがあるときは、クライアントは、クローズの前にすべてのロックを解放するべきです。サーバーがクローズですべての残っているロックを解放するのもよいですが、そうしないサーバーもあるかもしれません。サーバーは、クローズのときにロックが残っていたら、 NFS4ERR_LOCKS_HELD を返さなくてはいけません。
LOOKUP 操作は、サーバーにロック状態を確立することなく、ファイルハンドルを返します。有効な stateid がない状態のとき、サーバーは、クライアントが最低限のアクセスを持つと仮定します。例えば、 deny READ/WRITE でオープンされたファイルを、 LOOKUP で得たファイルハンドルを使ってアクセスすることはできません。それは、有効な stateid を持たないためです。
8.10.1 クローズと、状態の保持
クローズ操作は、stateid を破棄することを要求するものなので、クローズの再送を扱うのは特別な注意が必要です。通常は、対象となるオープンファイルの状態を指定するのに使われる状態がなくなっている結果、 NFS4ERR_BAD_STATEID になるかもしれないからです。
サーバーは、これに対処するいくつかの方法があります。完全を追求するなら、 stateid を破棄せずに、クローズ中としるしをつけて持ち続ける手があります。再送されたクローズは、対応する stateid が見つかるので、エラーにせずに済みます。
いつまで持っている必要があるかというと、
o 同じ lockowner からの、再送ではない、正しくシーケンス番号がついた要求を受けた。
o しばらくアクティビティがなかったために、サーバーが lockowner を解放した。
o SETCLIENTID の結果、そのクライアントのすべてのロックが解放された。
こんな面倒なことがいやなサーバーは、プロトコルのエラーチェックは多少いい加減になりますが、解放された stateid を指定したクローズは再送であろうから、単純に NFS4_OK を返すこともできます。その場合、せめて、ログにはエラーを記録しておくのがよいでしょう。サーバーが応答キャッシュ機構を使っているならば、クローズが再送か判断できるはずなので、ログの必要もないでしょう。
8.11 オープンアップグレードとダウングレード
同じ open_owner で、複数回、ファイルをオープンすると、アクセスと拒否ビットを、マージした、オープンのアップグレードをすることになります。プロトコル上は、1つのオープンファイルがあり、それは今までのすべてのアクセスと拒否ビットのユニオンを持ちます。これをクローズするには、1回でよろしい。この場合、クライアントは同じファイルがオープンされたことには気づかないかもしれません。上記は、同じファイルのオープンの結果、同じファイルハンドルが返る場合に限ります。
サーバーが、同じファイルに対して複数のファイルハンドルを公開することがあるならば、サーバーは同じファイルのオープンがされた場合に、アクセスと拒否ビットを「OR」して、1つのオープンとして扱ってはいけません。サーバーは、オープンごとに別々の stateid を管理して、クローズもその数だけ必要とするべきです。
クライアントの複数のオープンがサーバーで1つのオープンになる場合、クライアントでのファイルのクローズは、サーバーでのアクセスと拒否状態を変化させることがあります。アクセスと拒否ビットはユニオンで、クローズの結果、小さくなることがありますから。その場合、クライアントは OPEN_DOWNGRADE 操作を使って、それを伝え、他のクライアントの共有予約の要求が正しく扱われるようにするべきです。
8.12 短い、長い、リース
サーバーリースの長さを考えるとき、通常のトレードオフがあります。短いリースだと、サーバーの回復が早いですが、 RENEW, READ (長さゼロの)要求の頻度が上がります。とても多くのクライアントを扱うサーバーにとって、長いリースはたしかに、優しいでしょう。リース時間が長いと、比例して RENEW 操作は少なくなります。長いリースの欠点は、サーバーの障害からの回復が遅いことです。(サーバーは新しいロック要求を受け付ける前に、リースが満了して、グレースピリオドが終わるまで待たないといけません。)また、ファイルのアクセス競合も増えます。(クライアントがアンロック要求を送ってこないときは、サーバーは新しいロックを受け付ける前にリースが満了するまで待ちます。)サーバーがリース状態を不揮発メモリに記録できる場合は、長いリースも有効でしょう。回復のときは、サーバーはそこからリース状態を回復して、クライアントと処理を続けることができるので、問題とはなりません。
8.13 時刻、伝送遅延、リース期間の計算
時刻の同期が不要となるよう、リース時間は絶対時刻ではなくて時間の差分として与えられます。しかし、ロック保持期間の間、クライアントとサーバーで時刻のずれがとても大きくなるのは困ります。また、ネットワークでの伝送遅延、時には、数100ミリ秒になることもあるでしょう、の問題や、要求が失われて、再送の必要があることもあるでしょう。
伝送遅延を考慮に入れると、クライアントはリース時間からそれを引くべきです。(つまり、片道の伝送遅延が200ミリ秒ならば、クライアントがリースを得たときには、すでに200ミリ秒が使われているわけです。)さらに、応答に200ミリ秒かかります。なので、クライアントはロック更新や、遅延書き込みを、リースが満了する400ミリ秒前に送らなくてはいけません。
サーバーのリース期間の設定は、サーバーの資源をアクセスする予定のクライアントのネットワーク距離を考慮するべきです。リース期間はネットワークの伝送遅延や、多数のクライアントによるネットワーク遅延要因を考慮するべきです。プロトコル上は最適なリース期間を自動で決定するしかけはありませんから、サーバー管理者はリース期間のチューニングが必要です。
8.14 移行、複製と状態 略
9 クライアント側のキャッシュ
ファイルデータ、属性、名前をクライアント側でキャッシュすることは、NFSプロトコルで良い性能を得るために重要です。分散キャッシュを同期させることは難しい問題で、以前のNFSもそんなことは追及してきませんでした。その代わりに、いくつかのNFSクライアント実装技術が、同期の欠如がユーザーに与える影響を少なくするために使われてきました。これらの技術は、以前のプロトコル仕様書では明確に定義されておらず、クライアントの振る舞いが正しいのかどうか、よくわからないこともありました。
NFSv4は、以前のNFSで使われてきた多くの技術に似たものを採用します。NFSv4も分散キャッシュ同期を提供しません。その代わりに、より限定されたキャッシュの保証を定義して、クライアント側のキャッシュとロック、共有予約が共存できるようにします。
さらに、NFSv4は委譲機構を導入し、普通はサーバーが行う判断の多くをクライアントがローカルにできるようにします。これは、共有がまれであるか、リードオンリーである、よくあるケースを高速にします。
9.1 クライアント側のキャッシュの性能問題
以前のバージョンのNFSで使われたキャッシュ技術は、高い性能を提供してきました。しかし、クライアントの数がとても多くなると、スケーラビリティの問題が表れました。特に、クライアントが地理的に分散しているとき、キャッシュの再確認のための遅延が大きな問題となりました。
以前のNFSは、ファイルデータのキャッシュの確認を、ファイルがオープンされるときに毎回行いました。これは、深刻な性能低下をもたらすことがありました。よくあるケースは、ファイルは1人のクライアントだけからアクセスされ、共有はまれという場合です。
このとき、毎回サーバーに競合がないか問い合わせるのは高価です。性能面で有利なのは、何回もファイルを開くクライアントが、サーバーに聞かないでそうできるようにすることです。他のクライアントが潜在的に競合する操作を出すまで、続けることができます。
ファイルロックでも似たことが起きます。ファイルの読み書きと同時に、データキャッシュをロックセマンティクスのもとで正しく保つために、(「データキャッシュとファイルロック」の節を参照。)ロックとアンロックをサーバーに送るのは、大きく性能を低下させることがあります。競合がまれならば、ロックのペナルティは無視できないものとなり、アプリケーションはロックを使わなくなるかもしれません。
NFSv4は以下をゴールとして、よりアグレッシブなキャッシュ戦略を使います。
o 複数のサーバーセマンティクスと互換である。
o アグレッシブなモードが使えないときは、以前のNFSと同じキャッシュのメリットを提供する。
o アグレッシブキャッシュのための要件が一部、満たされないときでも、メリットの多くを享受できること。
サーバーの要件は、「オープン委譲」の節で詳しく議論されます。
9.2 委譲とコールバック
ファイルについてのサーバーの責任を回収可能な委譲としてクライアントに託すと、クライアント間の競合がない場合、サーバーへの要求を繰り返す必要がないため、性能が向上します。サーバーからクライアントへの「コールバック」RPCを使って、サーバーは、他のクライアントが委譲されたファイルの共有を望んだ場合に、委譲を回収します。
委譲は、対象のオブジェクトと、委譲のタイプを指定して、サーバーからクライアントに渡されます。委譲にはいくつかのタイプがありますが、すべて、 stateid を持ちます。それは、委譲に依存する操作をするときに、委譲を示すのに使います。 stateid は、ロックと共有予約で使われたものと似ていますが、委譲で使われる stateid は clientid と関連づいており、そのクライアントのすべての open_owners の代わりに使われることがあるところが違います。委譲はクライアント全体に与えられるのであり、特定のプロセスやスレッドに与えられるのではありません。
コールバックRPCはどんな環境でもはたらくわけではないので(例えば、ファイアウオールのため)、プロトコルの正確な動作は、それに依存してはいけません。最初に、CB_NULL 手続きを使ったコールバック機能の事前テストがあり、サポートされているかがわかります。それは、コールバックパスの連続性を確認します。サーバーは、指定クライアントにコールバックの事前テストをして、成功したときだけ、委譲をします。委譲が可能なのは、他の競合するアクセスがないときに限るので、クライアントは、委譲がされないこともあり、 OPEN が委譲なしに成功してきても驚かないでください。
一度許可されたら、委譲はロックと同じようにふるまいます。委譲にはリースが関連ついており、それはそのクライアントの持っているすべてのリースと同じように、延長することができます。
ロックとは違って、委譲されたファイルに他のクライアントが操作をすると、サーバーはコールバックを使って委譲を回収します。
回収のとき、委譲を持っているクライアントは変更された状態(変更済みデータなど)をサーバーにフラッシュしてから委譲を返さないといけません。競合する要求は、回収が完了するまで応答をもらえません。クライアントが委譲を返したとき、あるいは、サーバーが回収にタイムアウトして、その結果、委譲を失効させたときに、回収は完了したとみなされます。回収の完了後、サーバーは2つめのクライアントの要求を許可するか拒否するかを決める十分な情報を持ちます。
クライアントが委譲の回収を受信したとき、サーバーにフラッシュしなくてはいけない状態をたくさん持っているかもしれません。このため、サーバーは委譲が戻ってくるまで、十分な時間を与えるべきです。多くのサーバーへのRPCが必要かもしれないからです。サーバーは、クライアントがいそがしく、状態のフラッシュを行っていると判断できるならば、回収に必要な時間を通常より延長してやることもよいかもしれません。ただ、回収の完了に必要な時間に上限は必要です。
この1つの例は、指定ファイルのオープン権限がクライアントに委譲された(「オープン委譲」の節を参照ください。)ときです。サーバーは、クライアントでどんなオープンがされているか知りません。このため、委譲が返ってくるまでサーバーは、ファイルの access and deny 状態が、他の新しいオープンを許可するのかわかりません。
クライアント障害やネットワーク分断の結果、回収コールバックの応答が失敗することがあります。そのときサーバーは、委譲を失効させます。その結果、クライアントに残っている変更済み状態はすべて無効になります。
9.2.1 委譲の回復
委譲の回復が対処すべき3つの状況があります。
o クライアントリブートあるいは再開始
o サーバーリブートあるいは再開始
o ネットワーク分断(完全あるいはコールバックのみ)
クライアントがリブートすると、リースを延長できないために、レコードロックと共有予約が破棄されます。しかし、委譲は、少し違った扱いが必要です。
クライアントリブートの後、委譲を再確立する必要があることがあります。クライアントが、ローカルにファイルデータを格納しており、以前に保持していた委譲と関連ついている場合です。クライアントは、サーバー上のファイル状態を再確立する必要があります。
このようなクライアントの回復を許すため、サーバーは、委譲回復の期間を、通常のリースの失効期間より長くしてもかまいません。そのとき、その委譲と競合する他のクライアントからの要求は余分に待たなくてはいけません。通常の回収処理は、クライアントが変更済み状態をサーバーにフラッシュするために長時間を要することがありますから、他のクライアントは競合する委譲がある時の遅れに備える必要があります。こうして、余分の時間を与えられた結果、クライアントがリブートした後、ステーブル記憶域を調べて、委譲を回復することのできる機会を増すことが期待されます。オープン委譲は、クレームタイプが CLAIM_DELEGATE_PREV の OPEN で回復されます。(オープン委譲と OPEN の詳細は、「データキャッシュと失効」の節及び、「Operation 18: OPEN」を参照ください。)
サーバーはクレームタイプ CLAIM_DELEGATE_PREV をサポートしてもしなくてもよいのですが、もしするならば、サーバーは SETCLIENTID_CONFIRM を受けても委譲を削除してはならず、その代わりに、 lease_time 属性以上の期間、クライアントの委譲を保持し、クライアントが CLAIM_DELEGATE_PREV を出すための十分な時間を確保してやらなくてはいけません。 CLAIM_DELEGATE_PREV をサポートするサーバーは、 DELEGPURGE 操作をサポートしなくてはいけません。
サーバーがリブートあるいは再開始したとき、委譲は再確保されます。( CLAIM_PREVIOUS つきの OPEN 操作により)それは、レコードロックと共有予約のときと似ています。しかし、小さな、セマンティクスの違いがあります。通常の場合は、サーバーが委譲を許可すべきでないと判断したときは、サーバーは、要求されたアクション(例えば、OPEN )を、委譲なしで実行します。これに対して、回収のときには、サーバーは委譲を許可しますが、特別な指示がされていて、クライアントはその委譲を、許可されたけれど、サーバーに回収されたものとして扱います。このため、クライアントはすべての変更された状態をサーバーに書き戻して、委譲を返却しなくてはいけません。この委譲回収の扱いは、NFSv4の3つの原則を満足させるためのものです。
o 再確保のとき、クライアントが以前のサーバーインスタンスにより許可された資源を持っていると申告したならば、その資源は、許可されなくてはいけません。
o サーバーは委譲を許可するかしないか、延長するか、を判断する絶対的権威を持ちます。
o クライアントが確実にコールバックを受け取れると証明するまで、コールバックの使用に依存してはいけません。
ネットワーク分断が起きたら、サーバーはリース再延長期間が満了した後、委譲を解放することができます。これは、ロックと共有予約のふるまいと似ています。しかし、委譲の場合は、サーバーは競合する要求が待たされる期間を延ばすことができます。最終的には、他のクライアントからの競合する要求により委譲は失効します。コールバックパスが失われたとき(後で、ネットワーク構成変更があったなど)も、同じことになります。回収の要求は失敗して、委譲は失効します。
クライアントは通常、委譲の失効を、その委譲と関連付いた stateid を使ったときに NFS4ERR_EXPIRED エラーを受けて知ります。あるいは、クライアントがリブートした後、委譲を再確保しようとして同じエラーを受けることもあるでしょう。ライトオープン委譲が失効したときは、クライアントはローカルにデータを変更していて、他の人がその委譲を確保していることもあるというめんどうな問題が起きます。これについては、「ライトオープン委譲の失効からの回復」の節を参照ください。また、委譲が失効したときは、サーバーはその委譲の情報をステーブル記憶域に書きます。(「障害回復」の節に書いてあるように)これは、サーバーが委譲を失効させた後、失効した委譲を持っているクライアントにそれを伝える前に、サーバーがリブートしてしまうケースに対処するためです。
9.3 データキャッシュ
複数のアプリケーションが、ファイルの集まりへのアクセスを共用するとき、他のアプリケーションによるアクセスの競合を考えて実装されなくてはいけません。それは、アプリケーションが異なるクライアントにあっても、同じクライアントにあっても同じことです。
共有予約とレコードロックは、NFSv4が提供する、アプリケーションが排他制御によりアクセスを調整するための機構です。NFSv4のデータキャッシングはこれらが前提とするものをこわさないように実装されなくてはいけません。
9.3.1 データキャッシュと OPEN
アプリケーションが依存する共有の前提をこわさないために、NFSv4は、 READ あるいは WRITE で読み書きできないはずのデータをアプリケーションにキャッシュ上で読み書きさせてはいけません。
さらに、オープン委譲(「オープン委譲」の節を参照)がないときは、もう2つの規則が適用されます。これらの規則は、多くのNFSv2と3のクライアントにおいて、事実上、守られてきたものです。
o その1は、クライアントにあるキャッシュされたデータは、OPEN の後で再確認されなくてはいけません。再確認とは、クライアントがサーバーから、change 属性を取得して、キャッシュされた値と比較し、異なる場合は、キャッシュデータとキャッシュされた属性を無効とすることです。これは、 OPEN されたファイルのデータが、クライアントキャッシュに正しく反映されるためです。この確認は、少なくとも、 OPEN 操作が、DENY=WRITE or BOTH を指定してあり、他のクライアントが WRITE アクセスを持ってそのファイルを開くことのできた期間を終わらせるときに行われなくてはいけません。クライアントは、もっとひんぱんに( DENY=NONE 指定の OPEN のときも)確認をしてもよいです。それは、NFSv3の慣習を期待するユーザーのためでもあります。
change 属性はデータとメタデータの変更で変化しますから、キャッシュデータの確認に、time_modify 属性を使い、 change 属性を使わないクライアント実装もあるかもしれません。そうすれば、メタデータ変更のときに、クリーンなデータを不必要に無効にすることはしなくてすみます。そのアプローチは誤りです。 change 属性は、ファイルの変更のたびに変化することが保証されますが、 time_modify 属性は、 time_delta 属性の粒度でしか変化することが保証されません。クライアントデータキャッシュの確認論理に、change でなく、time_modify を使うならば、クライアントが古いデータを有効と判断する危険があります。
o その2は、変更されたデータは、書き込み用に OPEN されたファイルがクローズされる前にサーバーにフラッシュされなくてはいけません。これは、第1の規則を補足するものです。もしもデータが CLOSE でフラッシュされないならば、クライアントが OPEN の後に行う再確認は目的を達することができません。クローズの前にフラッシュするもう1つの意味は、データはクライアントが CLOSE 操作を要求する前に、サーバーでステーブル記憶域に書き込まれなくてはいけないということです。もしサーバーのリブートあるいは再開始があったときにファイルがクローズされていたのでは、クライアントから書き込むべきデータを再送することはできないでしょうから。
9.3.2 データキャッシュとファイルロック
ファイルへの競合するアクセスを制御するために共有予約の代わりにファイルロックを使うアプリケーションには、クライアント側のキャッシュに関して、類似の条件があります。以下の規則は、ファイルロックが、実際の読み書きの実行に対応するように使われるときだけ、意味があります。
以下のような、決め事によるロックの場合には適用されません。
例えば、2MBのファイルを1MBずつの領域に分けて、それぞれの領域へのアクセスを、バイト0と1のロックで代表させます。バイト0をロックしたら、領域1を読み書きする権利を得て、バイト1は領域2、というわけです。そのファイルを扱うすべてのアプリケーションがこの決め事に従うならば、ローカルファイルシステムに限れば、うまくいきます。しかし、クライアントがデータキャッシュをする NFSv4 では、動きません。
ファイルロック環境でのデータキャッシュの規則は:
o その1は、クライアントがある領域にファイルロックを確保したら、その領域内のデータキャッシュがあれば、すべて再確認されなくてはいけません。 change 属性が、キャッシュデータが得られたとき以降、ファイルが変更されているかもしれないことを示したならば、クライアントは新しくロックされた領域内のキャッシュデータをフラッシュあるいは破棄しなくてはいけません。正しい動作のために必要なことは、今ロックされた領域内のデータを破棄することですが、クライアントによっては、そのファイルの変更してないキャッシュデータをすべて破棄する実装をとることもあるでしょう。
o その2は、領域のライトロックを放す前に、クライアントはその領域のすべての変更済みデータをサーバーにフラッシュしなくてはいけません。データは、ステーブル記憶域に書かれる必要もあります。
なお、データをサーバーにフラッシュしたり、破棄したりするのは、実際にロック、アンロックされるバイトレンジに対応しなくてはいけません。クライアントのキャッシュブロック境界にまるめて上げたり下げたりすることは、注意深く行われないと、問題となります。例えば、変更されたブロックを書き戻すときに、そのブロックの半分だけがアンロックされた領域に含まれるならば、残りの半分までも書き込むことは、不正な変更となることがあります。その部分は、他のクライアントがロックしている領域に含まれるかもしれないからです。クライアントがこれを避けるには、フルブロックでない部分の書き込みは同期で行うのがよいでしょう。
同じように、フルブロックの整数倍でない領域をロックしたときに、キャッシュを破棄して読みなおすときは、クライアントは、両端の1あるいは2ブロック(訳注。ロックされた領域の外にある部分も含めて、ということでしょうね。)をサーバーから読みなおす必要があります。
領域をアンロックする前にサーバーに書き戻されたデータは、サーバーのステーブル記憶域に書く必要があります。クライアントがこれを行うには、同期ライトを使うか、非同期ライトの後、 COMMIT 操作を使います。これは、サーバーがリブートしたときに、変更済みデータを再送しても、そのときにはその領域が他のクライアントによってロックされているということがないようにするためです。
レコードロッキングを標準でない使い方(レコードロックをグローバルセマフォーのように使うなど)をするアプリケーションに対応するために、クライアントは、 LOCKU において、ロック範囲でカバーされるより多くのデータをサーバーにフラッシュする実装をすることがあるでしょう。その場合、そのファイルの、アンロックされる範囲以外の変更済みデータもフラッシュされるかもしれません。そうすると、ロックしている範囲だけを読み書きするアプリケーションと悪い干渉を起こすことがあります。例えば、アプリケーションが1バイトをロックして、そのバイトを書き込むとします。 LOCKU でサーバーにすべての変更済みデータをフラッシュするクライアントは、アンロックのときにその1バイトをフラッシュするのは正しい動作ですが、その1バイトを含むブロック全体を書き戻すのは、そこはロックされておらず、他のクライアントがロックを持っているかもしれないので、不正な動作となります。これを避けるには、ファイルの変更済みデータを、レコードロックでカバーされているものとそうでないものに分け、前者の書き込みにおいては、ロックされていない領域とクライアントで変更されていない領域がついでに含まれることがないようにするのがよいでしょう。
9.3.3 データキャッシュとマンダトリーファイルロック
クライアント側でのデータキャッシュは、マンダトリーファイルロックが有効ならば、それを尊重するべきです。マンダトリーファイルロックがあることは、クライアントが正しい共有予約を持っているファイルの読み書きで NFS4ERR_LOCKED を受けることでわかります。マンダトリーファイルロックが有効なとき、クライアントは読み書きするデータに正しいロックがされているか確認する必要があります。クライアントが読み書きする範囲のロックを持っているならば、クライアントは自分の確認されたキャッシュで読み書きの要求を処理することができます。そうでないときは、キャッシュを使うのではなく、要求はサーバーに送られて処理される必要があります。読み書きが、ロック範囲と部分的に重なるならば、要求はロックされている領域とされていない領域に分割され、別々に処理されなければいけません。
9.3.4 データキャッシュとファイルの識別
クライアントがデータをキャッシュする場合、ファイルのデータは、そのデータの属するファイルシステムのオブジェクトごとに管理される必要があります。 NFSv3 のクライアントでは、キャッシングのためには、異なるファイルハンドルは異なるファイルシステムオブジェクトをあらわすことを前提とするのが一般的な慣習でした。クライアントは、データキャッシュをファイルハンドルをベースに行いました。
NFSv4 では、ファイルハンドルはオブジェクトのパス名をもとに作られることがあるので、「オブジェクトに1つのファイルハンドル」モデルから、大きく外れることがありえます。このため、クライアントが、2つのファイルハンドルが同じファイルシステムオブジェクトを指しているのかを判断する信頼できる方法が必要となります。もし、クライアントが単純に、別のファイルハンドルは別のファイルであると信じて、それを元にキャッシュを行うならば、1つのサーバー側オブジェクトがクライアント側では別々にキャッシュされ、データの不整合が起きるでしょう。
NFSv4 は、ファイルハンドルを区別する方法を提供することで、NFSv3 と比較したときの機能低下を防ぎました。これがないと、同じクライアントの中でのキャッシュ不整合が起き、これは以前の NFS では起きないものでした。なお、似たようなキャッシュ不整合は、複数クライアントで動作するアプリケーションで起きることがありますが、それはここでの問題ではありません。
データキャッシュのために、NFSv4 クライアントは、以下の手順で、2つの別のファイルハンドルが同じサーバー側のオブジェクトを指しているかを判断できます。
o 2つのファイルハンドルを指定した GETATTR が返す fsid が違うなら、別のファイルです。
o fsid が同じ時、その fsid を持つ任意のファイルへの GETATTR が unique_handles 属性 True を返すなら、別のファイルです。
o 2つの GETATTR のうち、 fileid が返らないことがあるならば、同じかどうかは判断できません。つまり、この場合、クライアント側キャッシュは正しく行えません。
o 2つの GETATTR が返す fileid が違うなら、別のファイルです。
o そうでない場合、同じファイルです。
9.4 オープン委譲
ファイルが OPEN されたとき、サーバーはそのファイルの以降のオープンとクローズの処理を、そのファイルをオープンしたクライアントに委譲することができます。そのような委譲は、委譲を可能とする条件が変わると回収されます。特に、他のクライアントから競合するオープンを受けたら、サーバーはそのオープンを許可するかどうか判断する前に委譲を回収しなくてはいけません。委譲をするのはサーバーが決めることで、クライアントは特定のオープンがオープン委譲を許可されるかどうか、前提とすることはできません。以下は、サーバーがオープン委譲を行うかを決めるために、典型的な条件です。
o クライアントはサーバーのコールバック要求に応えなくてはいけません。サーバーはそれが可能か、 CB_NULL 手続きを使って確認します。
o クライアントは以前の回収に正しく応答したことがあること。
o 現在、要求された委譲と競合するオープンがないこと。
o 現在、要求された委譲と競合する委譲がないこと。
o そのファイルの最近の履歴から見て、将来、競合するオープンが来る可能性が少ないこと。
o サーバー固有のオープン、クローズのセマンティクスが、委譲されたクライアントが適用するであろうことと、競合することがあるか。(以下を参照。)
オープン委譲には2種類あります。リードとライトです。リードオープン委譲は、クライアントが自分だけで、ファイルを読むためにオープンすることを許します。他のリードも許可されます。複数のリードオープン委譲は同時に存在することができ、競合はしません。ライトオープン委譲は、クライアントがすべてのオープンをすることを許します。ある時点で1つのファイルには、1つだけのライトオープン委譲が存在でき、それはリードオープン委譲と競合します。
クライアントがリードオープン委譲を持つとき、クライアントはファイルの内容や属性を変更することはできません。しかし、他のクライアントもそうしないことが保証されます。ライトオープン委譲を持つときは、他のクライアントがそのファイルのデータをアクセスしていないことが明らかなので、ファイルのデータを変更できます。ただし、ファイル属性に関しては、ファイルデータに緊密に関連する size, time_modify, change の属性だけを変更できます。
クライアントがオープン委譲を持っているときは、OPEN、 CLOSE をサーバーに送らず、内部的な状態を変更します。リードオープン委譲の場合、ローカルに処理できないオープン(書き込みのためのオープンや、リードアクセスを拒否するオープン)は、サーバーに送られなくてはいけません。
オープン委譲がされたときは、OPEN の応答は、オープン委譲構造体を含み、それは以下を示します。
o 委譲のタイプ(リードかライト)
o クローズでデータをフラッシュするときに使われる、ディスク領域の制限情報。(ライトオープン委譲でのみ。「オープン委譲とデータキャッシュ」の節を参照。)
o 読み書きパーミッションを示す nfsace4
o 読み書きのときに委譲を表す stateid
委譲の stateid は、OPEN用に使われるものとは別で、区別がつきます。後者は、前者と違って、それぞれの lock_owner に関連づいており、委譲が回収されてもファイルがオープンしている限り有効です。
オープン委譲が有効なときにクライアント内部でファイルをオープンする要求が発生したときは、以下の条件だけが、それが受け付けられるか拒否されるかを決めます。委譲に関して、これ以外のチェックが必要であれば、オープン委譲は無効となって、サーバー自身がチェックをしなくてはいけません。
o 「共有予約」の節にある、要求とファイルのアクセスと拒否ビット。
o 以下で決められる、読み書きパーミッション。
委譲で返ってくる、 nfsace4 は、ACCESS 呼び出しを減らすために使うことができます。パーミッションのチェックは、以下のようにします。
o nfsace4 が、オープンを許すならば、サーバーに聞くことなく、許可されます。
o オープンを許さないかもしれないならば、ACCESS 要求をサーバーに送って、最終的な回答を得る必要があります。
サーバーは、実際のファイルの ACL よりも制限の多い nfsace4 を返すことがあります。すべてのアクセスを拒否する nfsace4 ということもあります。伝統的な「root」ユーザーを「nobody」にマップするような場合、ファイルの実際の ACL を委譲の応答に返すのは誤りです。
委譲と、他のいろいろなキャッシュを使うと、あるユーザーに関して、サーバーの認証が全く行われないことがありえます。そのユーザーのすべての要求がローカルに満足される場合です。クライアントが、認証に関してサーバーに依存している場合、クライアントはユーザーごとに認証が必ず行われるようにする必要があり、それは、 ACCESS 操作でできます。認証以外のために ACCESS が不要の場合もです。以前に述べたように、サーバーはすべてのオープン委譲に、すべてのアクセスを拒否する nfsace4 を返すことで、認証を強制することもできます。
9.4.1 オープン委譲とデータキャッシュ
オープン委譲によって、ファイルをオープン、クローズするメッセージのオーバーヘッドの多くを除くことができます。オープン委譲が効いている時のオープンは、サーバーに確認メッセージを送る必要がありません。「リードオープン委譲」が続いている間は、ライトのためのオープンがなく、ライトが発生しないことが保証されます。同じように、ライトオープン委譲が効いている時の、ライトのためにオープンされたファイルのクローズは、オープン委譲が回収されるまで、それまでに書かれたデータがサーバーにフラッシュされる必要はありません。ライトオープン委譲が続いている間は、他にオープンがなく、つまり、他のクライアントによるリードもライトもないことが保証されます。
オープン委譲に関し、オープンなしに発行される READ と WRITE は、対応するタイプのオープンと同じに扱われます。これは、すべてゼロあるいは1の特別な stateid を使う READ と WRITE のことです。つまり、他のクライアントによる特別な stateid を使う READ と WRITE が来ると、サーバーはライトオープン委譲を回収します。特別な stateid を使う WRITE のときは、リードオープン委譲を回収します。
委譲を持っているクライアントは、クローズの時にサーバーにデータを書き戻さなくてもかまいません。通常は、アプリケーションが作った変更済みデータを置くためのステーブル記憶域がなくなったことは、クローズシステムコールで通知されます。クローズの時に、データはサーバーに書かれ、通常の課金手続きによって、サーバーはデータのためにファイルシステムの使用可能領域が超過したかを決めます。(サーバーは、NFS4ERR_NOSPC あるいは NFS4ERR_DQUOT を返します。)ここでいう課金には、クオータも含まれます。委譲があるときは、同じ事をクライアントとサーバーでやり取りするための別の方法が必要となります。
委譲の応答に、サーバーはファイル長の限界値あるいは、変更済みブロックの限界値とブロック長を返します。そこで返した長さのデータをクライアントがフラッシュすることができることを、サーバーは保証します。サーバーは、すべての発行済の有効な委譲に、この保証をしなくてはいけません。このため、サーバーは、ファイルシステムの使用可能領域、クオータ、新しい、あるいは変更済みデータのための領域管理において、注意が必要です。サーバーは空き領域を増やすために委譲を回収することも可能です。クライアントはサーバーの示した領域制限に従うべきです。もしもそれを超えた時に、サーバーの振る舞いは未定義です。
サーバーの状態、クオータ、ファイルシステムの使用可能領域によって、サーバーはとても小さい限界値を指定してライトオープン委譲を返すことがあるでしょう。限界値によっては、クローズのたびに必ず変更済みデータをサーバーにフラッシュするようにさせることもできます。
認証に関して言えば、クローズの後に変更済みデータをサーバーにフラッシュするのは問題があります。例えば、アプリケーションのユーザーはクライアントからログオフしてしまっていて、ログイン情報も消えているかもしれません。この場合、クライアントはローカルなログイン情報が残っているように、特別な注意が必要です。例えば、ログイン情報の期限切れを追跡して、その前にデータをフラッシュするとか、あるいは、ログイン情報のコピーをずっと持っている、などです。
9.4.2 オープン委譲とファイルロック
クライアントがライトオープン委譲を持っているときは、ロック操作はローカルに実行できます。これには、マンダトリーロックも含まれます。委譲があるということは、競合するロックがないためです。同じように、ロックを取るときに通常必要なキャッシュの再確認や、放すときのフラッシュもする必要はありません。
クライアントがリードオープン委譲を持っているときは、ロック操作はローカルに実行されません。排他的でないロックも含めてすべてのロック操作はサーバーに送られなくてはいけません。
9.4.3 CB_GETATTR 処理
サーバーは、 GETATTR の対象ファイルがライトオープン委譲を持っているとき、特別な処理が必要です。これは、ライト委譲をもっているクライアントがデータを変更しているかもしれず、サーバーは GETATTR を送ってきた2つめの別のクライアントにその変更を伝える必要があるからです。このため、委譲を持っているクライアントは問い合わせを受けます。サーバーは CB_GETATTR 操作を使います。サーバーがそれによって確実に得られる属性は、 size, change だけです。
CB_GETATTR は、 GETATTR を出してきたクライアントの要求を満足するために使われるため、サーバーが知る必要があるのは、委譲を持っているクライアントがそのファイルの変更済みバージョンを持っているかどうかです。委譲されたファイルのクライアント側のコピーが変更されていないならば、(データあるいはサイズ)サーバーは2つめのクライアントの GETATTR 要求に、サーバーにローカルに格納されている属性を使って答えることができます。変更されているならば、サーバーはその変更された属性が必要です。サーバーは2つめのクライアントにファイルがサーバーにおいてローカルに変更されたかのように応答します。
change 属性の形式はサーバーが決めるものであり、クライアントには不透明ですから、クライアントとサーバーはファイルの変更状態を通知しあう方法について合意しておく必要があります。 size 属性については、クライアントは現在のファイル長を報告すれば済みます。
change 属性については、ずっと大変です。
クライアントがライト委譲を受け取るときには、以下のステップを経ます。
o change 属性の値がサーバーから得られ、キャッシュされます。この値を、 c とします。
o クライアントは、 c より大きい値を作り、クライアントが変更済みデータを持っていることを示すために使います。この値を、 d とします。
o クライアントが CB_GETATTR によって change 属性をたずねられたら、変更済みデータがあるか、チェックします。あれば、change 属性の結果として、 d が返されます。なければ、c が返されます。
実装を簡単にするため、クライアントはすべての CB_GETATTR に、同じ d を返してもよろしい。2つの CB_GETATTR の間に、クライアントがキャッシュにおいてファイルのデータあるいはメタデータを変更してもそれでかまいません。クライアントにとって必要なのは、クライアントが変更済みデータを持っていることをサーバーに伝えることができることであるため、同じ値、 c+1 を d として使うのはかまいません。
change 属性がクライアントに不透明だといっても、クライアントとサーバーで大小の判定が同じにできないと仕方ないので、それはネットワークバイトオーダーの、符号なし整数とします。
サーバーは、ライト委譲を提供するときに、以下のステップを経ます。
o サーバーは、委譲を記録するデータ構造に、change 属性のコピーをキャッシュします。この値を、 sc とします。
o 2つめのクライアントが GETATTR を出してきたとき、サーバーは最初のクライアントから change 属性を得ます。この値を、 cc とします。
o cc と sc が同じなら、ファイルは変わってないので、サーバーは、change, time_metadata, time_modify (例えば)の現在の値を2つめのクライアントに返します。
o cc と sc が違うなら、ファイルは現在最初のクライアントにより変更されており、いずれサーバーでも変更されるでしょう。サーバーは、time_metadata とtime_modify の属性値として、現在の自分の時刻を使います。nsc >= sc + 1 である、新しい値 nsc がサーバーにより作られます。サーバーは、time_metadata, time_modify, とnsc を返します。サーバーは自分の委譲データ構造の sc を、nsc に変えます。time_modify, time_metadata と change が逆走しないように(委譲を持っているクライアントが、委譲が回収あるいは失効する前にサーバーに変更済みデータを書き戻すことができないと、そうなります。)サーバーは、ファイルのメタデータを、自分で今作った属性値に変更するべきです。適切な性能を保つために、その属性値をステーブル記憶域にコミットするかどうかは、オプショナルです。
この節で以前に述べたように、クライアントは、連続する CB_GETATTR の間に自分のキャッシュでファイルが変更されていても、同じ cc 値を返すことができます。このため、サーバーは、 CB_GETATTR ごとにファイルが変更されていると思うのが安全で、生成する nsc 値は前回自分が返した nsc 値よりも大きくなるようにしなければいけません。委譲レコードの実装の例を考えます。それは、ブーリアンの「modified」を持ち、それは委譲が許可されると偽になります、さらに、許可された時の change 属性の値を持つ sc があります。modified は、最初に cc != sc であるときに真に設定され、委譲が返却あるいは失効するまで真のままです。nsc, time_modify, time_metadata を生成する処理は、この擬似コードで表されます。
if (!modified) {
do CB_GETATTR for change and size;
if (cc != sc)
modified = TRUE;
} else {
do CB_GETATTR for size;
}
if (modified) {
sc = sc + 1;
time_modify = time_metadata = current_time;
update sc, time_modify, time_metadata into file's metadata;
}
return GETATTR を送ってきたクライアントに、それが要求した属性を返します。
ただし、size は、CB_GETATTR が返したものを使います。ファイルのメタデータを、クライアントの変更済みサイズにしてはいけません。
o ファイル属性の size が、サーバーの現在値と違うときは、CB_GETATTR が返した change 属性にかかわらず、サーバーはこれを変更とみなし、2つめのクライアントに最後のステップと同じように応答します。
この方法を使えば、クライアントとサーバーで時計の差があるために起きる問題が解決しますし、CB_GETATTR がはたらかないときのシナリオもうまくいきます。
なお、サーバーはCB_GETATTR を使う必要は全くなく、単純に委譲を回収することもできます。
9.4.4 オープン委譲の回収
以下のイベントは、オープン委譲の回収を必要とします。
o 競合する可能性のある OPEN 要求(あるいは、「特別の」stateid で行われる READ/WRITE )
o 他のクライアントによる SETATTR
o 対象ファイルの REMOVE
o 対象ファイルをソースあるいはターゲットとする RENAME
対象ファイルのパスの途中のディレクトリが RENAME されたときに、回収が必要かは、サーバーファイルシステムのセマンティクスに依存します。サーバーが、ファイルがオープンされているときにそのような RENAME を禁止しているならば、対象ファイルが本当にオープンされているかを判断するために回収が必要です。
上にあげた以外にも、サーバーは資源の都合でいつでも、委譲を回収することができます。クライアントは回収に備えること。
クライアントは、オープン委譲の回収要求を受けたら、それを返す前に、サーバーの状態を更新しなくてはいけません。クライアントが自分から委譲を返すときも、同じ事です。状態には、以下があります。
o 委譲に関するファイルが、そのときにオープンされておらず、サーバーにクローズが送られてないなら、クローズを送ること。
o ファイルが、クライアントにおいて、他のオープン参照を持つならば、サーバーにオープンを送ること。委譲の stateid はもう無効なので、以降の処理のために別の stateid がサーバーから送られてくるはずです。この場合のオープンは、クレームタイプ CLAIM_DELEGATE_CUR を使います。その要求には、 stateid を含めることができるので、クライアントは、サーバーに、自分を資格証明できます。(詳しくは、「Operation 18: OPEN」節を参照下さい。)
o クライアントで許可されたファイルロックがあるなら、対応するロックをサーバーに送ること。ライトオープン委譲だけに当てはまります。
o ライトオープン委譲の場合、回収時にファイルがライト用にオープンされていないならば、すべての変更済みデータはサーバーにフラッシュされなくてはいけません。委譲がない場合は、クライアントはクローズの前にフラッシュをします。
o ライトオープン委譲の場合、回収時にファイルがオープンされているならば、すべての変更済みデータはサーバーにフラッシュされなくてはいけません。訳注。上のとどう違う?
o ライトオープン委譲の場合、委譲の間に、ファイルはトランケートされているかもしれません。例えば、OPEN UNCHECKED で、サイズ属性がゼロの場合、そうなります。トランケートが起こり、それがサーバーに伝わってないならば、それは、変更済みデータがサーバーに書かれるより先に、伝わらなくてはいけません。
ライトオープン委譲の場合、ファイルロックに伴う追加の条件があります。不変条件を保つために、ライト委譲が有効なときにライトロックが解放された領域に含まれる変更済みデータはフラッシュされなくてはいけません。ただ、ライトオープン委譲が有効という事は、他のクライアントがそのファイルをロックすることがありえないので、アンロック領域に限らず、そのファイルのすべての変更済みデータをフラッシュしても構いません。
実装によっては、上記のフラッシュなどを、委譲の回収まで待たずに自分からやっても構いません。ただ、ファイルはクライアント上でオープン、クローズされ続けるでしょうから、ある時のオープンとクローズの状態をサーバーに送っても、すぐにむだになるかもしれません。委譲を得ることになったオープンに対応するクローズのときには、それをやるのもよいでしょう。その場合、クローズしたら、元には戻せませんから。クライアントが上記アクションをどう実行するかにかかわらず、すべては、委譲が回収される前にされなくてはいけません。それらは、回収より以前の要求で行なってもよいですし、同じ COMPOUND 要求内の以前の操作で行なってもよいです。
9.4.5 クライアントが委譲の回収に従わない場合
クライアントは、いろいろな理由で、委譲の回収に応答しないことがあります。サーバーからクライアントへのコールバックのパスが障害となった場合など。クライアントは、コールバックパスの障害に気がついていないかもしれません。その結果、ずっと後になって、自分の委譲が無効となっており、それに関連したデータを他のクライアントが変更していたということに気がつく、ということがありえます。クライアントが、ライト委譲を持っていた場合、特に問題です。
また、回収に応答しないクライアントが、リースの変更を伴う、他のNFS要求を送り続けてくるとき、サーバーは困ったことになります。サーバーがリース変更にエラーを返さないと、クライアントは、自分の委譲がいつまでも有効だと思い続けることになります。
これを解決するには以下の規則が必要です。
o コールバックパスがダウンしたとき、以下のどれかがあてはまるならば、サーバーは委譲を失効させてはいけません。
- クライアントが RENEW 操作をして、サーバーが NFS4ERR_CB_PATH_DOWN を返した。サーバーは、そのクライアントが持っている、サーバーが知っている限りのレコードロックと共有予約のリースを延長しなくてはいけません。(これには、クライアントが確保したロックと予約が、委譲のために、サーバーに送られてきていないものは含みません。)サーバーは、委譲を失効させる前に、クライアントに、委譲をサーバーに返却する十分な時間を与えるべきです。
- サーバーが委譲を回収しようとした後、しばらくしても、クライアントが RENEW 操作をしていないとき。この期間は、lease_time 属性より小さくてはいけません。
o クライアントが委譲を持っている場合、stateid を持つ RENEW 操作以外の操作は、コールバックパス障害をまたいで委譲のリース変更に寄与しません。コールバックパス障害を越えて委譲を保ちたい場合は、クライアントは RENEW 操作をしなくてはいけません。
9.4.6 委譲の失効
委譲が失効した場合、クライアントが関連するオープンをしているならば、アプリケーションはそれを通知される必要があります。この通知は、READ/WRITE のエラーとするか、クローズのエラーとするのが一般的です。
失効のときにオープンがないならば、通知は不要です。しかし、クライアントにそのファイルの変更済みデータがあれば、通知が必要です。そのときに、アクティブなアプリケーションがいないこともあるので、通知ができないこともあります。「ライトオープン委譲の失効からの回復」の節を参照ください。
9.5 データキャッシュと失効
ロックと委譲が失効したとき、有効なキャッシュが依存する前提は保証されなくなります。失効したロックと予約の所有者は通知されなくてはいけません。通知には、失効した委譲に関連するファイルをオープンしているアプリケーションも含まれます。失効に関連するキャッシュデータはクライアントから除かれます。変更済みデータは、サーバーに書き戻されてはいけません。繰り返しますが、前提は無効となったのです。例えば、最初のクライアントのロックが失効した後に、他のクライアントが競合するロックを得ているかもしれません。ロック範囲のデータは、他のクライアントにより変更されている可能性があります。失効の後、最初のクライアントは、アプリケーションに、ファイルに起きたことを何も保証できません。
ロック所有者への通知は、多くの場合、単純に、そのオープンファイルへの、以降のすべての読み書きあるいはクローズを失敗させることです。それができないときは、シグナルあるいは、プロセス終了などのドラスティックなアクションが必要かもしれません。アプリケーションが依存するものが失われたのですから、それくらいは当然といえるときもあるでしょう。クライアントの環境で、エラーが通常、どう扱われるかにより、ログ、コンソールメッセージ、GUIポップアップなどの通知も適当かもしれません。
9.5.1 ライトオープン委譲の失効からの回復
ライトオープン委譲の失効からの回復には、オープンされていないファイルの変更済みデータがクライアントキャッシュに残るという、特別やっかいな問題があります。クローズのときに変更済みデータをサーバーにフラッシュしないクライアントは、ユーザーが失効の結果による問題の適切な通知を受け取ることを保証する必要があります。そのような場合、問題解決に人手の介入が必要となることが多いので、通知のスキーマには適切なユーザーあるいは管理者への通知が不可欠です。ログとコンソールメッセージは良い例です。
クライアントにある変更済みデータは、通常のように、サーバーにフラッシュされてはいけません。クライアントは回復を簡単にするために、ファイルシステムネームスペース内に、委譲の間に変更されたファイルデータのコピーを別の名前で保存するのも良いかもしれません。そのファイルが、他のクライアントに変更されてないと結論できた場合や、クライアントがそのファイルの完全なキャッシュされたコピーを持っている場合は、前記の別名のコピーは回復のために貴重なものとなるでしょう。それ以外の場合は、クライアントのキャッシュに残っているコピーと、サーバーにあって、他のクライアントに変更されたものをつぎあわせるという大変な作業になります。このため、クライアント側のデータを放棄してしまうとか、結果のファイルを目立つようにしてユーザーに問題を知らせることもあるでしょう。
このようなファイル回復が必要なのは、ファイルが大きく、ターゲットファイルシステムに十分な空きがあり、クライアントにメモリがたくさんあるときに限られるかもしれません。訳注。このパラグラフ、よくわかりません。
9.6 属性キャッシュ
この節で論じられる属性は、名前つき属性を含みません。それぞれの名前つき属性はファイルに似ているので、そのキャッシングは、通常ファイルデータと同じに扱われるのがよいでしょう。同じように、 OPENATTR ディレクトリの LOOKUP 結果は他のパス名やディレクトリ内容と同じキャッシングがされるのがよいでしょう。
クライアントは、サーバーから得たファイル属性をキャッシュして、以降の GETATTR 要求の代わりに使うことができます。そのキャッシュは、ライトスルーです。つまり、属性の変更は必ずサーバーに要求することで実行され、ローカルに行われたものがキャッシュされてはいけません。例外は、データキャッシュと密接に関連する属性です。例えば、ファイルにデータを書いて大きくする場合、ローカルキャッシュへの書き込みならば、クライアントに直ちにファイル長の変化が見えますが、それはサーバーに同時に反映される必要はありません。普通、その変化は直接サーバーに伝わるのではなくて、変更されたデータがサーバーにフラッシュされるときに、関連するファイル長属性もサーバー上で変更されます。オープン委譲が有効なとき、変更された属性は、サーバーに、CB_GETATTR の応答として返されることがあります。
属性のローカルキャッシュをすると、それぞれのクライアントの管理する属性キャッシュが同期しないことが起きます。サーバーで、ある順序で行われた変更が、あるクライアントでは別の順序に見え、さらに別のクライアントではもうひとつの順序に見えることがあります。
典型的なファイルシステムAPIでは、複数のファイルの属性をアトミックに変更したり参照したりすることはできません。以下の規則は、上に述べたような同期の矛盾を軽減することができます。これは、以前のNFSプロトコルの経験から導かれたものです。
o あるファイルのすべての属性( fsid ごとの属性を除く)は、クライアントにおいて、1つの単位としてキャッシュされます。これは、1つのファイル内で、同期の問題が起きないためです。
o サーバーからのリフレッシュなしにキャッシュエントリーが存在する、上限の時間を決めます。
o サーバーで属性を変えるような操作をしたら、それを含むRPCの中で、変更された属性のセットを問い合わせます。これには、間接的に属性を変えるディレクトリ操作も含みます。これをするには、変更操作の後に、 GETATTR を入れて、その結果をクライアントのキャッシュに反映することです。
READDIR がキャッシュされるすべての属性を持ってくる指定になっているときは、その結果は GETATTR の時と同じようにキャッシュに使えます。
クライアントが、ファイルの属性のキャッシュバージョンを確認するためには、change と time_access 属性の2つを持ってくればよいです。change 属性がキャッシュしたときと、同じならば、 time_access 以外の属性は変わってないといえます。 time_accessも持ってくる理由ですが、多くのサーバーは、change が変わる操作で time_access を変えません。例えば、POSIX ファイルセマンティクスでは、ファイルがライトシステムコールで変更されたときに、 time_access を変えません。このため、最新の time_access 値がほしいクライアントは、属性キャッシュの確認プロセスの中で、change と 一緒にそれを持ってきてキャッシュの time_access を変更するのがよいでしょう。
クライアントは、レギュラーファイルのデータの変更に関連する3つの(size, time_modify, and change)属性に限っては、変更済みの値をキャッシュしてもよろしい。それ以外の属性は、直ちにサーバーに書き戻す必要があります。
ある環境では、ファイルオブジェクトの中身を読むたびに、 time_access に相当するものが暗黙のうちに変更されることが期待されます。しかし、NFSクライアントがファイルオブジェクトの中身、つまり、レギュラーファイルのデータ、ディレクトリ、シンボリックリンクなどをキャッシュしているとき、キャッシュに行われるローカルなリードの結果として、サーバーに time_access の変更を送ってはいけません。理由は、それが、キャッシュによる性能向上を低下させるからです。特に、SETATTR で time_access を変えると、サーバーの change 属性も変化し、他のクライアントが、ファイルが変更されたと思って、何も変わってないデータを読み直すからです。また、クライアントが time_access の変更済み値を自分のキャッシュに維持するのもお勧めできません。いずれ、サーバーに書き戻すならば、性能に悪影響をおよぼします。あるいは、サーバーの time_access をまったく変更しないならば、そのクライアントで動作するアプリケーションが、同じファイルのクローズからオープンの間に、 time_access を見たならば、時刻が逆転して見えることがあります。
訳注。よくわからない。クライアントのローカル time_access が、サーバーより進むのが問題なのですよね。
time_access 属性は、常に、サーバーによって処理されたファイルの最後のリードアクセスを示します。なので、クライアントは、逆走する time_access は見えないはずです。
9.7 データとメタデータキャッシュとメモリマップトファイル
大意。クライアントでも、サーバーでも、ファイルをメモリマップすると、アプリケーションがアクセスしても、オペレーティングシステムが気が付かないことがあるので、 change 属性を変更することができない。キャッシュ同期ができないことが多くなる。
9.8 ネームキャッシュ
LOOKUP, READDIR 操作の結果を、以降の LOOKUP 操作の代わりに使うことができます。属性キャッシュと同じように、クライアントキャッシュの間には矛盾が生じることがあります。矛盾の影響を減らすためには、典型的なファイルシステムAPI を考慮すると、ディレクトリキャッシュのエントリーが有効である期間に、上限をもうける必要があります。
ネームキャッシュエントリーのあるディレクトリを、クライアントが変更していないときは、クライアントは、定期的にディレクトリの属性を取得して、変更されていないか確認する必要があります。変更されていなければ、ネームキャッシュエントリーの有効期間が延長されます。
クライアントが、あるディレクトリを変更しているときは、他のクライアントがそのディレクトリを変更したかを知る必要があります。それは、ディレクトリ操作の前と後の change_info4 を比較します。サーバーは、ディレクトリ操作において、 change_info4 がアトミックに提供されるかをクライアントに示します。アトミックならば、クライアントは、操作の前と、クライアントのネームキャッシュで、 change value を比較します。他のクライアントによる変更があれば、キャッシュエントリは破棄されます。なければ、キャッシュエントリは、クライアントの操作の結果を反映して、さらに、有効期間が延長されます。操作後の change value は、将来の change_info4 の比較に必要となるので、とっておきます。
以上のシナリオでわかるように、クライアントは、ネームキャッシュアイテムがキャッシュされたときの change 属性を比較して、キャッシュデータが有効かを判断する必要があります。そのためには、サーバーが、ディレクトリの内容が変更されたら、change 属性を変更する必要があります。クライアントがchange_info4 情報を正しく使うためには、サーバーは操作の前と後のchange 属性をアトミックに報告する必要があります。それができないサーバーは、change_info4 の戻り値にそれを示さなくてはいけません。情報がアトミックに報告されていないときは、クライアントは他のクライアントがそのディレクトリを変更していないと推測してはいけません。
9.9 ディレクトリキャッシュ
READDIR 操作の結果を、以降の READDIR 操作の代わりに使うことができます。属性や名前のキャッシュと同じように、クライアントキャッシュの間には矛盾が生じることがあります。矛盾の影響を減らすためには、典型的なファイルシステムAPI を考慮すると、以下の規則を守る必要があります。
o あるディレクトリに関して、単一の READDIR 操作の結果として得られたのではない、キャッシュされた READDIR 情報は、必ず、そのディレクトリの内容の、一貫したスナップショットでなくてはいけません。これは、最初の READDIR の前と、最後の READDIR の後に、GETATTR を出して、変更がないことを確認すればよいです。
o ディレクトリキャッシュのエントリーが有効である期間に、上限をもうけましょう。
再確認の方法は、名前キャッシュのときの議論と同じです。クライアントがそのディレクトリを変更していないならば、GETATTR により、change 属性を調べればよいです。これらのチェックポイントのときに、キャッシュエントリーの有効期間を延長することもできます。クライアントがそのディレクトリを変更しているときは、クライアントは change_info4 データを使って、他のクライアントがそのディレクトリを変更していないか見る必要があります。他に変更がないならば、自分の変更点をキャッシュに反映すればよいです。
以前に述べたように、ディレクトリキャッシュは、クライアントがディレクトリキャッシュデータを、ディレクトリがキャッシュされた時点のchange 属性を比較することで再確認します。以下、前の節の最後のパラグラフと同じ。
10 マイナーバージョニング
必要に応じてNFSプロトコルを進化させるために、NFSv4は将来の小さな変更、あるいはバージョニングが可能な規則とフレームワークを有します。
略
11 国際化 略
12 エラー定義 略
13 NFSv4 要求
NFSv4 RPC プログラムでは、2つの伝統的RPC手続きがあります。NULL と COMPOUND です。他のすべての機能は、操作の集まりとして定義され、操作は通常の XDR/RPC シンタックスとセマンティクスで定義されます。しかし、これらの操作は COMPOUND 手続きの中にカプセルされています。クライアントは、いくつかの操作を1つの要求にまとめます。
NFS4_CALLBACK プログラムは、サーバーからクライアントへの通知を提供するために使われ、NFSv4 プログラムと同様に構成されます。CB_NULL と CB_COMPOUND 手続きは、 NFSv4 プログラムでの NULL と COMPOUND と同様に定義されます。CB_COMPOUND は、NFS4_CALLBACK プログラムの残りの操作をカプセルします。NFS4_CALLBACK プログラムには、あらかじめ決まった RPC プログラム番号はありません。「一時的」プログラム番号の範囲から使う番号を決めるのは、クライアントの責任です。NFS4_CALLBACK プログラムのプログラムとポート番号は、クライアントが SETCLIENTID/SETCLIENTID_CONFIRM シーケンスの中で提示します。プログラムとポートは、別の SETCLIENTID/SETCLIENTID_CONFIRM シーケンスによって変わることもあります。同じクライアントの実行インスタンスの間に、クライアントのリース状態を削除することなく、それらを変えることもできます。
13.1 複合手続き
COMPOUND 手続きは遅延の大きいネットワークにおいて、性能を上げる可能性があります。クライアントは、複数の依存する操作を1つの COMPOUND 手続きにくっつけることで、複数の RPC による遅延の増大を防ぐことができます。複合手続きは、また、クライアントが基本的な手続きを1つの要求にして、クライアントの環境にカスタマイズすることができる点から、プロトコルを簡略化する効果もあります。
CB_COMPOUND 手続きは、前記 COMPOUND 手続きとまったく同じ特徴を持ちます。
COMPOUND 手続きの基本的な構造は、
+-----+--------------+--------+-----------+-----------+-----------+--
| tag | minorversion | numops | op + args | op + args | op + args |
+-----+--------------+--------+-----------+-----------+-----------+--
そして、応答の構造は、
+------------+-----+--------+-----------------------+--
|last status | tag | numres | status + op + results |
+------------+-----+--------+-----------------------+--
上記の numops と numres フィールドは、要求と応答にエンコードされた引数と結果の数を示す、個数つきの配列で使われる個数です。XDR エンコーディングについて言えば、これらの個数は正確に、エンコードされた操作の引数あるいは結果の数に等しいはずです。
13.2 複合手続きの評価
サーバーは、COMPOUND 手続きをその中のそれぞれの操作を順に評価して処理します。操作は32ビットの操作コードで始まり、引数が続きます。その長さは、操作のタイプで決まります。それぞれの操作の結果は、応答バッファ中に、順にエンコードされます。結果は、操作コードとステータスコード(通常はゼロ)で始まります。操作がゼロでないステータスコードになったときは、ステータスがエンコードされて、複合手続きのシーケンスは停止し、応答が返ります。なお、評価は、「エラーでない」、NFS4ERR_SAME のような条件のときにも停止することがあります。
COMPOUND 手続き内の操作に、アトミシティの要求はありません。COMPOUND 手続きの一部として評価される操作は、サーバーが受信した他の COMPOUND 手続きと同時に評価されるかもしれません。
部分的に完了した COMPOUND 手続きの後始末は、クライアントの責任です。部分的完了は、NFS4ERR_RESOURCE や NFS4ERR_DELAY などのエラーの結果、いつでも起きることがあります。他の条件下では成功する操作シーケンスであっても、エラーになることがあります。さらに、COMPOUND 手続きの実行中にサーバーリブートがあった場合、クライアントはCOMPOUND 手続きがどこまで処理されたのかわからないという面倒な状況になります。なので、クライアントは、手続きの中の操作の失敗を考えると、必要以上に複雑な COMPOUND 手続きを避けるのがよいでしょう。
複合手続きの実行コンテキストの一部として、それぞれの操作は、「現在」と「セーブされた」ファイルハンドルを前提とします。操作は、現在のファイルハンドルを設定、変更、取得、することがあります。「セーブされた」ファイルハンドルは、一時的変数として、あるいは、RENAME と LINK 操作のオペランドとして使われます。
13.3 同期的な更新操作
ファイルシステムを変更する NFS v4 操作は、同期的です。サーバーにおいて操作が成功したら、クライアントはその要求に関するすべてのデータはステーブル記憶域にあることを期待できます。1つの例外は、 UNSTABLE オプションが指定された WRITE 操作のファイルデータです。
これは、同じ複合手続き内の、それ以前のすべての操作も、ステーブル記憶域に反映されているということです。このふるまいは、サーバーの障害によって、部分的に実行された複合手続きの後始末をするクライアントにとって便利です。たとえば、複合手続きに操作AとBがあり、サーバーがクライアントに応答を返せなかったとします。サーバーが要求を処理した進行度によりますが、両方の操作の結果がステーブル記憶域に反映されているか、あるいは操作Aだけが反映されているかのいずれかが起き、操作Bだけがステーブル記憶域にあるということはありえません。
13,4 操作の値
COMPOUND 手続きにエンコードされた操作は、操作の値で識別されます。RPC 手続き番号とのオーバーラップを防ぐために、操作0(ゼロ)と1は定義されていません。操作2は、定義されておらず、将来のマイナーバージョンのために予約されています。
14 NFS v4 プロトコル
14.2.3. Operation 5: COMMIT - キャッシュされたデータをコミットする
書式
(cfh), offset, count -> verifier
引数
struct COMMIT4args {
/* CURRENT_FH: file */
offset4 offset;
count4 count;
};
結果
struct COMMIT4resok {
verifier4 writeverf;
};
union COMMIT4res switch (nfsstat4 status) {
case NFS4_OK:
COMMIT4resok resok4;
default:
void;
};
説明(大意)
コミット操作は、カレントファイルハンドルで指定されるファイルのデータを、ステーブル記憶域にフラッシュします。データは、以前に、 UNSTABLE4 を指定したライトで書かれたものです。
オフセットと長さは、フラッシュ位置と長さを示します。両方がゼロのときは、ファイル全体ということです。
サーバーはベリファイアを返します。クライアントは、以前のライトのときのベリファイアと比較して、その間にサーバーリブートがあり、データが喪失したかを知ることができます。
実装
コミット操作は、 posix fsync(2) に似ており、ファイルのデータとメタデータをフラッシュします。書き込むべきデータがない場合でも、サーバーはNFS4_OK を返すべきです。
コミット操作には、フラッシュ範囲が指定できるとことが、 fsync と違います。
コミット操作の実装は、指定範囲をステーブル記憶域に書くだけです。
クライアントがコミットをする理由は、バッファを空けたい場合と、ファイル全体をフラッシュしたい場合があるでしょう。
クライアントは UNSTABLE4 指定で書いたならば、以降のコミットあるいは、FILE_SYNC4、DATA_SYNC4 指定のライトがされるまで、そのバッファを保持する必要があります。
クライアントは以前と異なるベリファイアを受け取ったら、自分が持っているダーティなバッファをサーバーに書き戻す必要があります。
上記は、バッファキャッシュをベースにするシステムに加えて、ページキャッシュをベースとするシステムにも適用されます。
エラー 略
14.2.10. Operation 12: LOCK - ロックを作成する
書式
(cfh) locktype, reclaim, offset, length, locker -> stateid
引数
struct open_to_lock_owner4 {
seqid4 open_seqid;
stateid4 open_stateid;
seqid4 lock_seqid;
lock_owner4 lock_owner;
};
struct exist_lock_owner4 {
stateid4 lock_stateid;
seqid4 lock_seqid;
};
union locker4 switch (bool new_lock_owner) {
case TRUE:
open_to_lock_owner4 open_owner;
case FALSE:
exist_lock_owner4 lock_owner;
};
enum nfs_lock_type4 {
READ_LT = 1,
WRITE_LT = 2,
READW_LT = 3, /* blocking read */
WRITEW_LT = 4 /* blocking write */
};
struct LOCK4args {
/* CURRENT_FH: file */
nfs_lock_type4 locktype;
bool reclaim;
offset4 offset;
length4 length;
locker4 locker;
};
結果
struct LOCK4denied {
offset4 offset;
length4 length;
nfs_lock_type4 locktype;
lock_owner4 owner;
};
struct LOCK4resok {
stateid4 lock_stateid;
};
union LOCK4res switch (nfsstat4 status) {
case NFS4_OK:
LOCK4resok resok4;
case NFS4ERR_DENIED:
LOCK4denied denied;
default:
void;
};
説明
LOCK 操作は、オフセットと長さパラメータで指定されるバイト範囲へのレコードロックを要求します。ロックタイプも、nfs_lock_type4s のどれかが指定されます。これがリクレーム要求なら、reclaim パレメータは真です。
ファイル内のバイトは、そのバイトが現在ファイルにアロケートされていない時もロック可能です。あるオフセットからファイル終端(ファイルがどんなに長くても)まで、ファイルをロックするには、長さフィールドのすべてのビットを1にします。長さがゼロの時、あるいは、すべてのビットが1でない長さが指定され、オフセットと長さの和が64ビット符号なし整数の最大値を超えるとき、 NFS4ERR_INVAL になります。
32ビットの範囲に収まるバイトオフセットへのロックしかサポートしないサーバーもあるかもしれません。クライアントが(訳注。なんかよくわからん。)とき、32ビットサーバーは NFS4ERR_BAD_RANGE を返さなくてはいけません。
ロックが拒否された時、競合するロックの所有者、オフセットと長さが返されます。
成功した時、現在ファイルハンドルはそのままです。
実装
サーバーが、競合するロックの正確なオフセットと長さを決められない場合、引数に与えられたのと同じオフセットと長さを、拒否結果に返すべきです。ファイルロックの節には、この操作とこれ以外のロック操作について完全な記述があります。
LOCK 操作は、パーミッションチェックと、そのファイルのアクセスタイプのチェックに従います。しかし、それぞれのロックを得るために必要な特定の権限とモードは、サーバーがエクスポートするファイルシステムのセマンティクスを反映し、このプロトコルでは規定しません。例えば、 Windows 2000 は READ 向けにオープンされたファイルをライトロックするのを許します。POSIX 準拠のシステムは許しません。
クライアントが、ロック所有者が既に持っているXXX
locker 引数は、LOCK 操作に関連づく lock_owner を指定します。locker4 構造体は、スイッチ付きのユニオンで、サーバーが lock_owner を知っているか初めてかを指定します。lock_owner がサーバーに既知であり、 lock_seqid を確立してある場合、引数はその lock_owner と lock_seqid です。サーバーが lock_owner を知らない場合、引数は、lock_owner と lock_seqid の他に、open_stateid と open_seqid も含みます。新しい lock_owner のケースは、 lock_owner が行う最初のロックをカバーし、open_stateid により確立した状態を使って、 lock_owner の使用に遷移する方法を提供します。
エラー 略
14.2.16. Operation 18: OPEN - 通常ファイルをオープンする
書式
(cfh), seqid, share_access, share_deny, owner, openhow, claim ->
(cfh), stateid, cinfo, rflags, open_confirm, attrset delegation
引数
struct OPEN4args {
seqid4 seqid;
uint32_t share_access;
uint32_t share_deny;
open_owner4 owner;
openflag4 openhow;
open_claim4 claim;
};
enum createmode4 {
UNCHECKED4 = 0,
GUARDED4 = 1,
EXCLUSIVE4 = 2
};
union createhow4 switch (createmode4 mode) {
case UNCHECKED4:
case GUARDED4:
fattr4 createattrs;
case EXCLUSIVE4:
verifier4 createverf;
};
enum opentype4 {
OPEN4_NOCREATE = 0,
OPEN4_CREATE = 1
};
union openflag4 switch (opentype4 opentype) {
case OPEN4_CREATE:
createhow4 how;
default:
void;
};
/* Next definitions used for OPEN delegation */
enum limit_by4 {
NFS_LIMIT_SIZE = 1,
NFS_LIMIT_BLOCKS = 2
/* others as needed */
};
struct nfs_modified_limit4 {
uint32_t num_blocks;
uint32_t bytes_per_block;
};
union nfs_space_limit4 switch (limit_by4 limitby) {
/* limit specified as file size */
case NFS_LIMIT_SIZE:
uint64_t filesize;
/* limit specified by number of blocks */
case NFS_LIMIT_BLOCKS:
nfs_modified_limit4 mod_blocks;
} ;
enum open_delegation_type4 {
OPEN_DELEGATE_NONE = 0,
OPEN_DELEGATE_READ = 1,
OPEN_DELEGATE_WRITE = 2
};
enum open_claim_type4 {
CLAIM_NULL = 0,
CLAIM_PREVIOUS = 1,
CLAIM_DELEGATE_CUR = 2,
CLAIM_DELEGATE_PREV = 3
};
struct open_claim_delegate_cur4 {
stateid4 delegate_stateid;
component4 file;
};
union open_claim4 switch (open_claim_type4 claim) {
/*
* No special rights to file. Ordinary OPEN of the specified file.
*/
case CLAIM_NULL:
/* CURRENT_FH: directory */
component4 file;
/*
* Right to the file established by an open previous to server
* reboot. File identified by filehandle obtained at that time
* rather than by name.
*/
case CLAIM_PREVIOUS:
/* CURRENT_FH: file being reclaimed */
open_delegation_type4 delegate_type;
/*
* Right to file based on a delegation granted by the server.
* File is specified by name.
*/
case CLAIM_DELEGATE_CUR:
/* CURRENT_FH: directory */
open_claim_delegate_cur4 delegate_cur_info;
/* Right to file based on a delegation granted to a previous boot
* instance of the client. File is specified by name.
*/
case CLAIM_DELEGATE_PREV:
/* CURRENT_FH: directory */
component4 file_delegate_prev;
};
結果
struct open_read_delegation4 {
stateid4 stateid; /* Stateid for delegation*/
bool recall; /* Pre-recalled flag for
delegations obtained
by reclaim
(CLAIM_PREVIOUS) */
nfsace4 permissions; /* Defines users who don't
need an ACCESS call to
open for read */
};
struct open_write_delegation4 {
stateid4 stateid; /* Stateid for delegation*/
bool recall; /* Pre-recalled flag for
delegations obtained
by reclaim
(CLAIM_PREVIOUS) */
nfs_space_limit4 space_limit; /* Defines condition that
the client must check to
determine whether the
file needs to be flushed
to the server on close.
*/
nfsace4 permissions; /* Defines users who don't
need an ACCESS call as
part of a delegated
open. */
};
union open_delegation4
switch (open_delegation_type4 delegation_type) {
case OPEN_DELEGATE_NONE:
void;
case OPEN_DELEGATE_READ:
open_read_delegation4 read;
case OPEN_DELEGATE_WRITE:
open_write_delegation4 write;
};
const OPEN4_RESULT_CONFIRM = 0x00000002;
const OPEN4_RESULT_LOCKTYPE_POSIX = 0x00000004;
struct OPEN4resok {
stateid4 stateid; /* Stateid for open */
change_info4 cinfo; /* Directory Change Info */
uint32_t rflags; /* Result flags */
bitmap4 attrset; /* attributes on create */
open_delegation4 delegation; /* Info on any open
delegation */
};
union OPEN4res switch (nfsstat4 status) {
case NFS4_OK:
/* CURRENT_FH: opened file */
OPEN4resok resok4;
default:
void;
};
クライアント実装についての警告
OPEN は、クライアントが使うことのできるファイルハンドルを作成するという点で、 LOOKUP に似ています。しかし、OPEN は、LOOKUP とは違って、ファイルハンドルに関するサーバー状態を作成します。通常の環境では、クライアントは、この状態を、 CLOSE 操作によってのみ解放できます。 CLOSE は、クローズ対象ファイルを決めるために、現在のファイルハンドルを使います。このため、クライアントは、1つの COMPOUND 手続きの中で、それぞれの OPEN の後に、GETFH を置かなくてはいけません。これによって、クライアントが CLOSE で使うことのできるファイルハンドルが得られます。
説明
OPEN は、あるディレクトリに、指定された名前の通常ファイルを、オープンあるいは作成します。サーバーにファイルがなく、作成が要求された場合、 openhow パラメータに作成メソッドを指定します。クライアントは、3つの作成メソッド UNCHECKED, GUARDED, あるいは EXCLUSIVE のどれかを選べます。
現在ファイルハンドルが、名前付き属性のディレクトリである場合、OPEN は、名前付き属性ファイルをオープンあるいは作成します。名前付き属性を exclusive に作成するのはサポートされないことに注意下さい。作成モードが EXCLUSIVE4 で、現在ファイルハンドルが名前付き属性のディレクトリである場合、サーバーは、EINVAL を返します。
UNCHECKED の場合、その名前のファイルがなければ作成され、既にあれば、エラーになりません。この場合、 createattrs に、そのファイルの初期属性セットを指定します。セットには、通常ファイルで有効な任意の書き込み可能属性を含めることができます。UNCHECKED 作成で、既存ファイルがあった場合、createattrs で指定した属性は使われません。ただし、サイズ0が指定された場合だけは、既存ファイルは切り詰められます。GUARDED の場合、サーバーは、作成の前に、同じ名前のオブジェクトが重複してあるかをチェックします。あれば、NFS4ERR_EXIST が返ります。なければ、UNCHECKED に説明したと同じく、要求は実行されます。どちらの場合(UNCHECKED and GUARDED)も、操作が成功した場合、サーバーはクライアントに、属性マスクを返し、そのオブジェクトのどの属性が正しく設定されたかを知らせます。
EXCLUSIVE の場合、サーバーは排他的作成セマンティクスに従います。ベリファイアを使って、ターゲットが排他的に作成されるのを保証します。サーバーは重複した名前のオブジェクトをチェックします。なければ、サーバーはオブジェクトを作成し、ベリファイアをオブジェクトとともに格納します。オブジェクトがあって、格納されているベリファイアがクライアントが提供したものと同じならば、サーバーはそのオブジェクトを、作成されたものとして扱います。ベリファイアが違う時は、NFS4ERR_EXIST が返ります。この場合、属性を指定することはできません。サーバーがターゲットオブジェクトにベリファイアを格納するために属性を使うかもしれないからです。実際にそうした場合、サーバーは、属性マスクで、どの属性を使ったかを返します。
略
クライアントが、サーバー障害の後で、状態を回復しようとしている時は、 OPEN 引数の claim フィールドが、この要求は、以前に保持されていた状態をリクレームしようとしていることを示します。
OPEN 引数の 「claim」 フィールドは、オープンするファイルと、クライアントが保持していると主張する状態を指定するのに使います。OPEN のいろいろな状況で使われる基本的なクレームタイプは4つあります。
CLAIM_NULL
クライアントにとって、これは新しい OPEN 要求であり、クライアントにとってそのファイルに関連づいた過去の状態はありません。
CLAIM_PREVIOUS
クライアントは、サーバーリブートの前に持っていた、そのファイルの基本的 OPEN 状態をクレームしています。通常、サーバーが永続的ファイルハンドルを返した時に使われます。クライアントは OPEN をリクレームするためにファイル名を持っていないことがあります。
CLAIM_DELEGATE_CUR
クライアントは、OPEN でサーバーにより許可された委譲をクレームしています。通常、委譲をリコールする処理の一環で行われます。
CLAIM_DELEGATE_PREV
クライアントは、以前のクライアントインスタンスに許可された委譲をクレームしています。クライアントリブートの後に使われます。サーバーは、CLAIM_DELEGATE_PREV をサポートしてもよいです。もしするなら、SETCLIENTID_CONFIRM はクライアントの委譲状態を削除してはならず、サーバーは DELEGPURGE 操作をサポートしなくてはいけません。
CLAIM_PREVIOUS 以外のクレームタイプの OPEN 要求(つまり、サーバーリブートの後で、オープンをリクレームするためのもの以外)が、サーバーのグレースピリオドあるいはリース満了期間の間に届いたら、サーバーは NFS4ERR_GRACE を返します。
すべての OPEN に対して、サーバーは、委譲を返すことがあります。クライアントはそれを使って、ローカルにオープンとクローズを処理できます。オープン委譲の節を参照。委譲を決めるのはサーバーで、クライアントはそれが許可されるかどうか、期待してはいけません。唯一の例外は、リクレーム(CLAIM_PREVIOUS) の場合で、delegation type がクレームされます。このとき、委譲は必ず許可されます。サーバーが、delegation 構造体で、直ちにリコールを要求することもあります。
OPEN の成功で返る rflags で、オープンファイルをどう扱うかをサーバーが指定することがあります。
OPEN4_RESULT_CONFIRM は、クライアントが、オープンファイルを使う前に OPEN_CONFIRM 操作をしなくてはいけないことを示します。
OPEN4_RESULT_LOCKTYPE_POSIX は、サーバーのファイルロックが、完全な Posix ロック技術をサポートしていることを示します。これにより、クライアントは、適切なファイルロック状態の管理を選択することができます。
component の長さがゼロの場合、NFS4ERR_INVAL が返ります。また、component は、通常の UTF-8 の文字サポートと名前チェックに従います。詳しくは、「UTF-8 関連のエラー」の節を参照下さい。
OPEN がされ、指定された lockowner が既に結果となるファイルハンドルを持っている場合、結果は、新しい共有と拒否ステータスと、既存のものとの「OR」となります。この場合、複数の OPEN がされましたが、CLOSE は1度だけでかまいません。その OPEN では、同じ lockowner が持っている OPEN も含めて、新しい OPEN のための共有予約のチェックが通常通り行われます。訳注。自分の deny にかかることもある。
略
実装
略
エラー 略
14.2.18. Operation 20: OPEN_CONFIRM - オープン確認
書式
(cfh), seqid, stateid-> stateid
引数
struct OPEN_CONFIRM4args {
/* CURRENT_FH: opened file */
stateid4 open_stateid;
seqid4 seqid;
};
結果
struct OPEN_CONFIRM4resok {
stateid4 open_stateid;
};
union OPEN_CONFIRM4res switch (nfsstat4 status) {
case NFS4_OK:
OPEN_CONFIRM4resok resok4;
default:
void;
};
説明
この操作は、クライアントが最初にある open_owner を使った時に、シーケンス ID の使い方を確認するために行われます。OPEN 操作で得た stateid と、 open_owner が次に使うシーケンス ID を、この操作の引数に与えます。OPEN_CONFIRM に与えられるシーケンス ID は、 open_confirm 値を得た OPEN 操作に与えたものより1つ大きくなくてはいけません。サーバーが元のオープンと比べて、期待されないシーケンス ID を受け取った場合、サーバーは、クライアントが元のオープンを確認しないと判断し、元のオープンに関連づいたすべての状態を解放します。
成功した場合、カレントファイルハンドルの値は変わりません。
ーーーーーーここまでーーーーーー
実装
あるクライアントは、ある clientid のもとで、多数の open_owner4 データ構造を生成するかもしれません。クライアントは、定期的にそれら open_owner4 を破棄することもあれば、永遠に使わないこともあります。後者の状況は、 NFSv4 が明示的に open_owner4 を終了する操作を持たない理由です。その状況では、その操作は役に立ちません。その代わり、メモリ使用が無限になるのを防ぐため、サーバーは、現在、どのファイルにもロック、オープン、あるいは委譲を持っておらず、最近使われていない open_owner4 を捨てる作戦が必要です。open_owner4 を捨てるのをいつにするかは、実装上の選択です。期間は、もちろんリース期間以上でないといけませんし、サーバーが許容する期間を加えるのもよいでしょう。OPEN_CONFIRM 操作により、サーバーは、使われていない open_owner4 を安全に捨てることができます。
クライアントが OPEN を発行し、サーバーが open_owner4 を持たない場合、サーバーは、これが新しい OPEN であり、リプレイや再送でないことを確認する必要があります。
サーバーは、委譲を許可するあるいはリクレームをする OPEN に対して、確認を要求することはできません。詳しくは、「Open Confirmation の使用」の節を参照。サーバーは、指定された clientid の open_owner4 を捨てたことを示せば簡単にそれを避けることができます。サーバーが委譲をサポートしないなら、ただ1ビットで、任意のクライアントの任意の open_owner4 を捨てたかどうかを覚えるだけで済みます。
サーバーは、未確認の OPEN 状態を、以下の3つのイベントのどれかが起きるまで保持しなくてはいけません。1つめは、クライアントがリース期間内に適当なシーケンス ID と stateid を持った OPEN_CONFIRM を出す場合。この場合、サーバーの OPEN 状態は確認済みとなり、open_owner4 は確立済みとなります。
2つめは、クライアントが、open_owner4 にとって誤ったシーケンス ID を持つ別の OPEN を発行する場合。この場合、サーバーは2つめの OPEN が有効で、1つめはリプレイだと推定します。サーバーは最初の OPEN の状態をキャンセルし、2つめの OPEN の未確認 OPEN 状態を確立します。そして、2つめの OPEN に対して、 OPEN_CONFIRM が必要だという指示をつけて応答します。このプロセスは、また繰り返されます。クライアントにおいて、サービス拒否攻撃の可能性がありますが、クライアントとサーバーが Kerberos V5, LIPKEY など、暗号を使うセキュリティフレーバーの使用を義務付けるならばそれは緩和できます。
略
3つめは、前記2つのイベントが、リース期間内に起きない場合。この場合、サーバーはOPEN 状態をキャンセルし、open_owner4 を捨てることができます。
エラー 略
ーーーーーーここからーーーーーー
14.2.20. Operation 22: PUTFH - カレントファイルハンドルを設定する
書式
filehandle -> (cfh)
引数
struct PUTFH4args {
nfs_fh4 object;
};
結果
struct PUTFH4res {
/* CURRENT_FH: */
nfsstat4 status;
};
説明
カレントファイルハンドルを、引数で与えられたファイルハンドルにします。
要求者のセキュリティ機構が、この操作に与えられたファイルハンドルの要求を満たさない場合、サーバーは、NFS4ERR_WRONGSEC を返さなくてはいけません。
実装
一般には、 NFS 要求の最初の操作として使われ、以下の操作のコンテキストを設定します。
エラー 略
ーーーーーーここまでーーーーーー
14.2.22. Operation 24: PUTROOTFH - ルートファイルハンドルを設定する
書式
- -> (cfh)
引数
void;
結果
struct PUTROOTFH4res {
/* CURRENT_FH: root fh */
nfsstat4 status;
};
説明
サーバーのネームスペースのルートを表すファイルハンドルを、現在のファイルハンドルとします。このファイルハンドルを使って、 LOOKUP 操作により、サーバーの任意のファイルハンドルを位置づけることができます。このファイルハンドルは、サーバーの他のディレクトリに対応する「パブリック」ファイルハンドルと異なることがあります。
実装
一般には、 NFS 要求の最初の操作として使われ、以下の操作のコンテキストを設定します。
エラー 略
ーーーーーーここからーーーーーー
14.2.33. Operation 35: SETCLIENTID - clientid をネゴシエートする
書式
client, callback, callback_ident -> clientid, setclientid_confirm
引数
struct SETCLIENTID4args {
nfs_client_id4 client;
cb_client4 callback;
uint32_t callback_ident;
};
結果
struct SETCLIENTID4resok {
clientid4 clientid;
verifier4 setclientid_confirm;
};
union SETCLIENTID4res switch (nfsstat4 status) {
case NFS4_OK:
SETCLIENTID4resok resok4;
case NFS4ERR_CLID_INUSE:
clientaddr4 client_using;
default:
void;
};
説明
クライアントは、SETCLIENTID 操作によって、以降の、ロック、共有予約、そして委譲状態のサーバーでの状態作成を必要とする要求において特定のクライアント識別子、コールバック、callback_ident を使うことを伝えます。成功した場合、サーバーは短縮形である clientid を返します。ステップにより確認された後、それは以降のファイルロックとオープン要求で使うことができます。確認は、 SETCLIENTID_CONFIRM 操作で行われ、clientid と setclientid_confirm 値を、ベリファイアとしてサーバーに返します。なぜ、2つのベリファイアが必要かですが、SETCLIENTID とSETCLIENTID_CONFIRM を使って、コールバックと callback_ident を変え、短縮形の clientid を変えないことが可能だからです。その場合、setclientid_confirm だけが、ベリファイアとして使えます。
この操作で提供されるコールバック情報は、以降、クライアントがオープン委譲を得た時に使われます。このため、クライアントは、 SETCLIENTID がされた時のコールバックプログラムの正しいプログラム番号とポート番号を指定する必要があります。
callback_ident は、サーバーがコールバックの時に使います。クライアントは、それを使って、1つのコールバック RPC プログラム番号を使って、複数サーバーから要求を区別することができます。
実装
SETCLIENTID の実装を説明するために、以下の表記を使います。
x は、SETCLIENTID4args 構造体の client.id サブフィールド。
v は、SETCLIENTID4args 構造体の client.verifier サブフィールド。
c は、SETCLIENTID4resok 構造体で返された clientid フィールド。
k は、SETCLIENTID4args 構造体の callback と callback_ident フィールドの値の組み合わせ。
s は、SETCLIENTID4resok 構造体で返された setclientid_confirm 。
{ v, x, c, k, s }
が、クライアントレコードのための5要素のタプルです。クライアントレコードは、SETCLIENTID_CONFIRM がされると、確認済みとなります。そうでないときは、未確認です。SETCLIENTID が、未確認のクライアントレコードを確立します。
SETCLIENTID は、等羃操作ではないので、サーバーは重複要求キャッシュ、duplicate request cache (DRC)、を実装していると仮定します。
サーバーが SETCLIENTID { v, x, k } 要求を受けたら、以下のようにそれを処理します。
o まず、要求を DRC で探します。ヒットすれば、キャッシュされている応答を返します。サーバーは、クライアント状態 (locks, shares, delegations) を削除しません。また、記録されているクライアント { x } のコールバックと callback_ident も変更しません。
DRC ミスしたら、クライアント ID 文字列 x を取り出して、以前の SETCLIENTID でサーバーが覚えているかもしれない、 x を持つクライアントレコードを探します。記録されているプリンシパルが SETCLIENTID のそれと異なる時は、サーバーは NFS4ERR_CLID_INUSE を返します。
議論を簡単にするため、以下の説明では、DRC ミスがあり、クライアント x の確認済みレコードが見つかり、プリンシパルチェックも成功したとします。
o サーバーは、{ v, x, c, l, s } の確認済みレコードがあるかチェックします。なお、l と k は、同じでも違ってもよろしい。あれば、要求のベリファイア v が、確認済みレコードと同じなので、サーバーはこれを、コールバックの更新であると判断し、未確認の { v, x, c, k, t } を記録し、確認済みの { v, x, c, l, s } をそのままとします。t != s なので、 k が l と等しくてもそうでなくても関係ありません。未確認の { v, x, c, *, * } があれば、すべて削除されます。
サーバーは { c, t } を返します。古い clientid4 値 c を返します。クライアントは、コールバックを k から l にしたいだけですから。この要求は、無効なコールバック情報を持つビザンチンルーターから来た可能性もありますが、問題ないです。コールバックの更新が確認されるためには、SETCLIENTID_CONFIRM { c, t } を必要としますから。
サーバーは、SETCLIENTID_CONFIRM { c, t } による k の確認を待ちます。
サーバーは、 x を持つクライアント(ロック、共有、委譲)状態を削除しません。
o サーバーは、以前に、確認された { u, x, c, l, s } レコードを記録したとします。なお、 v != u で、 l は k と同じでも違っても良く、 x を持つ、未確認の { *, x, *, *, * } は無いとします。サーバーは、未確認の { v, x, d, k, t } (d != c, t != s) を記録します。
サーバーは、 { d, t } を返します。
サーバーは、 x を持つクライアント(ロック、共有、委譲)状態を削除しません。
o サーバーは、以前に、確認された { u, x, c, l, s } レコードを記録したとします。なお、 v != u で、 l は k と同じでも違っても良いです。さらに、サーバーは、未確認の { w, x, d, m, t } を記録したとします。 c != d, t != s で、m と k は同じでも違ってもよく、m と l は同じでも違ってもよく、k と l は同じでも違ってもよいです。w == v でも w != v でも同じです。サーバーは、単純に、未確認の { w, x, d, m, t } を削除して、未確認の { v, x, e, k, r } と替えます。なお、e != d, e != c, r != t, r != s です。
サーバーは、 { e, r } を返します。
サーバーは、SETCLIENTID_CONFIRM { e, r } による { e, k } の確認を待ちます。
サーバーは、 x を持つクライアント(ロック、共有、委譲)状態を削除しません。
o サーバーは、 x を持つ確認済みの { *, x, *, *, * } を持ちません。未確認の { u, x, c, l, s } はあるかもしれないし、ないかもしれません。l と k は同じでも違ってもよく、u と v は同じでも違ってもよいです。すべての未確認の { u, x, c, l, * } は、未確認の { v, x, d, k, t } に替わります。u == v あるいは l == k であっても無関係です。なお、d != c, t != s です。
サーバーは、 { d, t } を返します。
サーバーは、SETCLIENTID_CONFIRM { d, t } による { d, k } の確認を待ちます。
サーバーは、 x を持つクライアント(ロック、共有、委譲)状態を削除しません。
サーバーは、clientid と setclientid_confirm 値を生成します。同じ値を生成しないように、特別に注意をはらうこと。
略
エラー 略
ーーーーーーここまでーーーーーー
14.2.36. Operation 38: WRITE - ファイルに書く
書式
(cfh), stateid, offset, stable, data -> count, committed, writeverf
引数
enum stable_how4 {
UNSTABLE4 = 0,
DATA_SYNC4 = 1,
FILE_SYNC4 = 2
};
struct WRITE4args {
/* CURRENT_FH: file */
stateid4 stateid;
offset4 offset;
stable_how4 stable;
opaque data<>;
};
結果
struct WRITE4resok {
count4 count;
stable_how4 committed;
verifier4 writeverf;
};
union WRITE4res switch (nfsstat4 status) {
case NFS4_OK:
WRITE4resok resok4;
default:
void;
};
説明(大意)
ライト操作は、カレントファイルハンドルの指すレギュラーファイルの指定位置に指定長さのデータを書きます。長さは、 data 引数にエンコードされています。長さゼロのライトは、権限チェックの後、成功し、ゼロバイトの書き込み結果を返します。サーバーは、クライアントが指定するより短いデータを書いてリターンすることがあります。
stable が FILE_SYNC4 の場合、ファイルデータと、関連するメタデータのすべてがステーブル記憶域に書かれた後、サーバーは応答を返します。これは、NFS version 2 のセマンティクスです。DATA_SYNC4 の場合は、データと、そのデータを読むために最低限必要なメタデータ(訳注。ファイル長)を書きます。UNSTABLE4 の場合は、何も書かないことがあります。
stateid は、以前のレコードロックあるいは、共有予約で返されたものです。
成功すると、サーバーは、書き込んだバイト数と、committed を返します。committed には、実際のコミット結果が返されます。クライアントが FILE_SYNC4 を要求したのに、サーバーが UNSTABLE4 を返すのはプロトコル違反です。
ベリファイアは、サーバーがリブートしてないことを、クライアントに伝えます。
クライアントは、UNSTABLE4 ライトの後、適当なときに FILE_SYNC4 などを出して、データが失われないようにすること。
stateid がすべてゼロあるいは、1のライトの扱いについては、他のところにも書いてあるので、略。
実装
サーバーはクライアントが指定したより短いデータを書くことがあります。それはエラーではありません。クライアントは、残りのデータをライトすること。
ライトにより、time_modified は更新されます。しかし、長さゼロのときは、更新されません。
ステーブル記憶域の定義は、他のところにも書いてあるので、略。
ベリファイアは、クライアントにサーバーのリブートと、データ喪失を伝えます。
サーバーの開始時刻は、ベリファイアとして、よい例です。
NVRAM アクセラレーターを持つサーバーは、クライアントの stable 指定に無関係に、すべてをステーブル記憶域に書いて、 committed に FILE_SYNC4 を返すかもしれません。そのとき、クライアントは、より効率のよいキャッシュ管理ができます。
実装によっては、クオータ超過のときに、NFS4ERR_DQUOT でなくて NFS4ERR_NOSPC を返すこともあります。訳注。その後の、NFS4ERR_ISDIR は、クオータと関係ない話ですよね。
所有者でない人がマンダトリーロックのかかっているところをライトしようとしたら、サーバーは、NFS4ERR_LOCKED を返します。それを受けたクライアントは、stateid 所有者が、その範囲に、競合するリードロックを持っているか、調べます。持っていないならば、ライトロックを要求した後、もとのライトを発行します。
持っているならば、クライアントはあきらめて、アプリケーションにエラーを返します。可能ならば、サーバーは、リードロックをアップグレードしてくれたか、アップグレードをサポートしないかのいずれかであるため、さらにライトロック要求をするのはむだだからです。
エラー 略
16 セキュリティの考察
認証という観点で、NFS は歴史的に以下のようなモデルを使ってきました。クライアントは完全なマシン、少なくともあるマシンのソース IP アドレスです。NFS サーバーは、 NFS クライアントがエンドユーザーの適切な認証を行っていることに依存します。また、NFS サーバーは、自分のファイルを、クライアントのソース IP アドレスで識別される限定されたクライアントにだけ、公開します。このモデルの下で、 AUTH_SYS RPC セキュリティフレーバーは、NFS サーバーに、クライアントを使用しているエンドユーザーを識別するだけです。NFS 応答を処理するとき、クライアントは、要求が送信された同じ IP アドレスとポートから応答が受信されたことを確認します。
このモデルはたしかに実装が簡単で、設定と使用が単純ですが、安全とはいえません。NFSv4 は、エンドツーエンドの認証を使うセキュリティモデルを実装することを義務付けています。クライアント上のエンドユーザーは、NFS サーバー上のプリンシパルと、相互に認証(パスワードや鍵を平文でネットワークにさらさない暗号スキーマのもとで)します。NFS 要求と応答の改ざん防止と機密性にも注意が払われています。エンドツーエンドの相互認証、改ざん防止、機密性については、「RPC とセキュリティフレーバー」の節の一部で議論されています。
NFSv4 はエンドツーエンドの相互認証を義務付けていますが、IP アドレスチェックによるマシン認証と AUTH_SYS による識別という「古典的」なモデルも依然としてサポートされることができます。ただし、AUTH_SYS フレーバーはこの仕様で必須でも推奨でもないため、相互運用性は保証されません。
管理オーバーヘッドを削減したり、CPU 使用率を減らし、性能を向上させるために、ユーザーは各要求と応答に改ざん防止保証(訳注。ハッシュによるチェックサム計算)をしないことを選択するかもしれません。その場合、クライアントとサーバーの中間に攻撃者が存在し、要求と応答を改変する危険があります。これを考えると、ユーザーの選択がどうであっても、改ざん防止保証を行うべき操作が2つあります。
1つは、 SECINFO です。クライアントは、改ざん防止保証をするセキュリティフレーバーによって守られている環境で SECINFO を発行することが推奨されます。例えば、rpc_gss_svc_integrity あるいはrpc_gss_svc_privacy サービスを使うセキュリティトリプル を使う RPCSEC_GSS です。(rpc_gss_svc_privacy は、改ざん防止保証も含みます。)SECINFO とその応答が、改ざん防止保証されていないならば、中間点の攻撃者は、応答を改変して、サーバーの提示するアルゴリズムの中からより弱いものを選択させ、その後の攻撃を容易にすることができます。
改ざん防止保証をするべき2つめの操作は、 fs_locations 属性の GETATTR です。攻撃は2ステップからなります。まず、攻撃者は保護されていないある操作の結果に、 NFS4ERR_MOVED を返します。
次に、クライアントが fs_locations 属性の GETATTR で応じたときに、攻撃者は応答を改変して、攻撃者の制御の下にあるサーバーに以降のトラフィックを移行させます。
SETCLIENTID/SETCLIENTID_CONFIRM 操作はクライアント状態を解放する役目があるため、これらの操作で使われるプリンシパルは、同じ操作を以前に発行したプリンシパルと同じでなくてはいけません。詳しくは、「クライアント ID」の節を参照ください。
17から22節 略
23. Full Copyright Statement
Copyright (C) The Internet Society (2003). All Rights Reserved.
This document and translations of it may be copied and furnished to
others, and derivative works that comment on or otherwise explain it
or assist in its implementation may be prepared, copied, published
and distributed, in whole or in part, without restriction of any
kind, provided that the above copyright notice and this paragraph are
included on all such copies and derivative works. However, this
document itself may not be modified in any way, such as by removing
the copyright notice or references to the Internet Society or other
Internet organizations, except as needed for the purpose of
developing Internet standards in which case the procedures for
copyrights defined in the Internet Standards process must be
followed, or as required to translate it into languages other than
English.
The limited permissions granted above are perpetual and will not be
revoked by the Internet Society or its successors or assigns.
This document and the information contained herein is provided on an
"AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
Acknowledgement
Funding for the RFC Editor function is currently provided by the
Internet Society.
以上