XDR データ型を形式言語で記述する必要があるのと同様に、これらの XDR データ型に対して作用する手続きも、形式言語で記述する必要があります。XDR 言語の拡張版である RPC 言語は、XDR 言語を形式言語で記述するための言語です。次に、RPC 言語についての例を示します。
例 B-4 は、単純な ping プログラムの仕様を示します。
/* * 単純な ping プログラム */ program PING_PROG { version PING_VERS_PINGBACK { void PINGPROC_NULL(void) = 0; /* * 呼び出し側の ping は、往復時間をミリ秒で返します。 * オペレーションがタイムアウトの場合は、-1 を返します。 */ int PINGPROC_PINGBACK(void) = 1; /* void - 上記は呼び出しへの引き数 */ } = 2; /* * オリジナルのバージョン */ version PING_VERS_ORIG { void PINGPROC_NULL(void) = 0; } = 1; } = 200000; const PING_VERS = 2; /* 最新バージョン */
記述された最初のバージョンは、2 つの手続き、PINGPROC_NULL、および PINGPROC_PINGBACK が組み込まれた PING_VERS_PINGBACKです。
PINGPROC_NULL は引数を必要とせず、結果も返しませんが、クライアントとサーバ間の往復時間を計算するときになどに便利です。規則によると、RPCプログラムの手続き 0 はすべて同じセマンティクスを持つことになっているので、認証は必要ありません。
2 番目の手続きは、オペレーションにかかった合計時間を (マイクロ秒で) 返します。
次のバージョンである PING_VERS_ORIG は、プロトコルのオリジナルのバージョンで、PINGPROC_PINGBACK 手続きは含まれません。PING_VERS_ORIG は、古いクライアントプログラムと互換性を持たせる場合に便利ですが、このプログラムが完成すると、プロトコルから完全に削除されることがあります。
RPC 言語 (RPCL) は C に似ています。この節では、例を示しながら RPC 言語の構文を説明します。また、出力ヘッダファイルで、RPC 型定義および XDR 型定義を C 型定義にコンパイルする方法についても説明します。
RPC 言語ファイルは次の一連の定義から構成されています。
definition-list: definition; definition; definition-list
定義には、6 つの型があります。
definition: enum-definition const-definition typedef-definition struct-definition union-definition program-definition
定義は宣言と同じではありません。1 つまたは一連のデータ要素の型定義以外の定義によっては領域を割り当てることはできません。これは、変数は定義するだけでは十分でなく、宣言もする必要があることを意味しています。
RPC 言語は、表 B-2 で追加された定義以外は、XDR 言語と同じです。
表 B-2 RPC 言語定義
用語 |
定義 |
---|---|
program program-ident {version-list} = value |
|
version; version; version-list |
|
version version-ident {procedure-list} = value |
|
procedure; procedure; procedure-list |
|
type-ident procedure-ident (type-ident) = value |
program、version のキーワードが追加されますが、識別子としては使用できません。
バージョン名およびバージョン番号は、プログラム定義範囲内で一度しか指定してはいけません。
手続き名および手続き番号は、バージョン定義内で一度しか指定してはいけません。
プログラム識別子は、定数および型識別子と同じ名前空間にあります。
enum-definition: "enum" enum-ident "{" enum-value-list "}" enum-value-list: enum-value enum-value "," enum-value-list enum-value: enum-value-ident enum-value-ident "=" value
次に、コンパイルされる XDR enum
および C enum
の例を示します。
enum colortype { enum colortype { RED = 0, RED = 0, GREEN = 1, --> GREEN = 1, BLUE = 2 BLUE = 2, }; }; typedef enum colortype colortype;
シンボリック定数は、整数の定数が使用されればどこでも使用できます。たとえば配列サイズ仕様で使用すると、次のようになります。
const-definition: const const-ident = integer
次の例では定数 DOZEN を 12 に定義します。
const DOZEN = 12; --> #define DOZEN 12
XDR typedef
の構文は、C typedef
と同じです。
typedef-definition: typedef declaration
この例では、最大 255 文字のファイル名の文字列を宣言するために使用する fname_type
を定義します。
typedef string fname_type<255>; --> typedef char *fname_type;
XDR には、4 種類の宣言があります。これらの宣言は、struct
または typedef
の中に記述しなければいけません。単独では使用できません。
宣言: 単純宣言 固定長配列宣言 可変長配列宣言 ポインタ宣言
単純宣言: type-ident variable-ident
次に例を示します。
colortype color; --> colortype color;
fixed-array-declaration: type-ident variable-ident [value]
次に例を示します。
colortype palette[8]; --> colortype palette[8];
変数宣言を型宣言と混同するプログラマがよくいます。rpcgen は、変数宣言をサポートしないことに注意してください。この例は、コンパイルされないプログラムです。
int data[10]; program P { version V { int PROC(data) = 1; } = 1; } = 0x200000;
上記の例は、変数宣言なのでコンパイルされません。
int data[10]
代わりに以下を使用します。
typedef int data[10];
または
struct data {int dummy [10]};
可変長配列宣言は、C 構文とはまったく異なります。XDR 言語は、構文を使用しないで山括弧でくくります。
可変長配列宣言: type-ident variable-ident <value> type-ident variable-ident < >
最大サイズは山括弧内で指定します。配列のサイズにこだわらない場合は、サイズを省略することができます。
int heights<12>; /* 最高 12 項目 */ int widths<>; /* 項目数に制限なし */
可変長配列が C 構文とまったく異なるので、これらの宣言はコンパイルされて struct
宣言になります。たとえば、heights 宣言はコンパイルされて struct
になります。
struct { u_int heights_len; /* # 配列の項目番号 */ int *heights_val; /* 配列へのポインタ */ } heights;
配列の項目の番号は、_len 構成要素に、配列へのポインタは _val 構成要素に格納されます。各構成要素名のはじめの部分は、宣言された XDR 変数名 (heights) と同じです。
XDR には、C とまったく同じポインタ宣言が作成されます。アドレスポインタはネットワーク上で送信されませんが。その代わり、XDR ポインタは、リストおよびツリーなどの再帰的なデータ型を送信するのに便利です。この型は、XDR 言語では「ポインタ」ではなく、「オプション・データ」と呼ばれます。
ポインタ宣言: type-ident *variable-ident
次に例を示します。
listitem *next; --> listitem *next;
RPC/XDR struct
は C struct
とほぼ同様に宣言されます。RPC/XDR struct
の宣言は次のようになります。
構造体: struct struct-ident "{" declaration-list "}"
宣言リスト: declaration ";" declaration ";" declaration-list
次の左の部分は二次元の座標の XDR 構造体の例で、右の部分はそれを C 言語にコンパイルした構造体です。
struct coord { struct coord { int x; --> int x; int y; int y; }; }; typedef struct coord coord;
出力は、出力の末端部で追加された typedef
以外は入力と同じです。これによって、項目を宣言する時に、struct
coord のかわりに coordd
を使用することができます。
XDR 共用体は、識別された共用体なので、C の共用体とは似ていません。どちらかというと、Pascal 変数レコードに似ています。
共用体の定義: "union" union-ident "switch" "("simple declaration")" "{" case-list "}" case-list: "case" value ":" declaration ";" "case" value ":" declaration ";" case-list "default" ":" declaration ";"
以下は、「読み取りデータ」操作の結果として返された型の例です。エラーがない場合は、データのブロックが返されます。エラーがある場合は、何も返されません。
union read_result switch (int errno) { case 0: opaque data[1024]; default: void; };
C 言語にコンパイルされると次のようになります。
struct read_result { int errno; union { char data[1024]; } read_result_u; }; typedef struct read_result read_result;
出力 struct の共用体構成要素の名前は、接尾語 _u
を除いて型名前と同じです。
プログラムの定義: "program" program-ident "{" version-list "}" "=" value; version-list: version ";" version ";" version-list version: "version" version-ident "{" procedure-list "}" "=" value; procedure-list: procedure ";" procedure ";" procedure-list procedure: type-ident procedure-ident "(" type-ident ")" "=" value;
-N オプションが指定されると、rpcgen は次の構文も認識できます。
手続き: type-ident procedure-ident "(" type-ident-list ")" "=" value; type-ident-list: type-ident type-ident "," type-ident-list
次に例を示します。
/* * time.x: 時間を取得、または設定します。 * 時間は、1970 年 1 月 1 日 0:00 から経過した秒数で表されます。 */ program TIMEPROG { version TIMEVERS { unsigned int TIMEGET(void) = 1; void TIMESET(unsigned) = 2; } = 1; } = 0x20000044;
void
という引数の型は、引数が引き渡されないことを意味しています。
このファイルは、コンパイルされると、出力ヘッダファイル内で以下の #define 文になります。
#define TIMEPROG 0x20000044 #define TIMEVERS 1 #define TIMEGET 1 #define TIMESET 2
この節では、rpcgen の C-style モードの機能について説明します。これらの機能は、void 引数の引き渡しに関してかかわっています。値が void の場合、引数が引き渡される必要はありません。
C には組み込み型のブール型はありません。ただし、RPC ライブラリは、TRUE または FALSE のうちいずれかの bool_t
と呼ばれるブール値を使用します。XDR 言語で型 bool
として宣言されたパラメタは、コンパイルされると、出力ヘッダファイルでbool_t
になります。
次に例を示します。
bool married; --> bool_t married;
C 言語は組み込み型の文字列型ではありませんが、代わりに null で終了する char *
規則を使用します。C では、文字列は通常 null で終了する単一配列であるとみなされます。
XDR 言語では、string キーワードを使用して文字列が宣言されて、出力ヘッダファイルで char *
型にコンパイルされます。山括弧でくくられた最大サイズは、文字列で使用できる最大文字数を指定します(NULL文字をカウントしません)。 任意の文字列のサイズを表す場合は、最大サイズを省略することができます。
次に例を示します。
string name<32>; --> char *name; string longname<>; --> char *longname;
NULL 文字列は引き渡されません。ただし、0 長の文字列 (つまりターミネータだけ、または NULL バイト) は引き渡されます。
隠されたデータは、未入力のデータ、つまり任意のバイトのシーケンスを記述するために、XDR で使用されます。隠されたデータは、固定長または可変長配列として宣言できます。次に例を示します。
opaque diskblock[512]; --> char diskblock[512]; opaque filedata<1024>; --> struct { u_int filedata_len; char *filedata_val; } filedata;
void 宣言では、変数を指定できません。宣言は、void だけで、Void 宣言が使用されるのは、共用体およびプログラム定義 (遠隔手続きの引数または結果として、引数が引き渡されなかったなどで使用される) の 2 箇所だけです。
rpcbind は RPC のプログラム番号とバージョン番号を汎用アドレスにマップし、遠隔プログラムの動的結合を可能にします。
rpcbind はそれをサポートしているトランスポートのよく知られたアドレスに結合しています。他のプログラムは、動的に割り当てられたアドレスをrpcbind で登録します。rpcbind は、それらのアドレスを一般に使用できるようにします。汎用アドレスとは、トランスポートに依存したアドレスで、文字列で表現されています。汎用アドレスは、各トランスポートのアドレス管理者が定義します。
rpcbind はブロードキャスト RPC にも利用できます。RPC プログラムは異なるマシン上で異なるアドレスを持っているため、これらすべてのプログラムに直接ブロードキャストする方法はありません。ところが、rpcbind のアドレスはわかっているため、クライアントが特定のプログラムにブロードキャストするには、送信先マシン上の rpcbind プロセスにメッセージを送ります。rpcbind はブロードキャストメッセージを取り出し、クライアントが指定したローカルサービスを呼び出します。rpcbind はローカルサービスから応答を受け取ると、それをクライアントに渡します。
/* * rpcb_prot.x * RPCBIND プロトコルを RPC 言語で記述 */ /* * (プログラム、バージョン、ネットワーク ID) を汎用アドレスにマップ */ struct rpcb { unsigned long r_prog; /* プログラム番号 */ unsigned long r_vers; /* バージョン番号 */ string r_netid<>; /* ネットワーク ID */ string r_addr<>; /* 汎用アドレス */ string r_owner<>; /* サービスの所有者e */ }; /* マップリスト */ struct rpcblist { rpcb rpcb_map; struct rpcblist *rpcb_next; }; /* 遠隔呼び出しの引数s */ struct rpcb_rmtcallargs { unsigned long prog; /* program number */ unsigned long vers; /* version number */ unsigned long proc; /* procedure number */ opaque args<>; /* argument */ }; /* 遠隔呼び出しの戻り値 */ struct rpcb_rmtcallres { string addr<>; /* remote universal address */ opaque results<>; /* result */ }; /* * rpcb_entry には、特定トランスポート上のサービスアドレスと、 * それに関連した netconfig 情報がマージされて入っています。 * RPCBPROC_GETADDRLIST は、rpcb_entry のリストを返します。 * r_nc_* フィールドで使用する値については、netconfig.h を参照してください。 */ struct rpcb_entry { string r_maddr<>; /* マージされたサービスアドレス */ string r_nc_netid<>; /* netid フィールド */ unsigned long r_nc_semantics; /* トランスポートのセマンティクス */ string r_nc_protofmly<>; /* プロトコルファミリ */ string r_nc_proto<>; /* プロトコル名 */ }; /* サービスがサポートしているアドレスリスト */ struct rpcb_entry_list { rpcb_entry rpcb_entry_map; struct rpcb_entry_list *rpcb_entry_next; }; typedef rpcb_entry_list *rpcb_entry_list_ptr; /* rpcbind 統計情報 */ const rpcb_highproc_2 = RPCBPROC_CALLIT; const rpcb_highproc_3 = RPCBPROC_TADDR2UADDR; const rpcb_highproc_4 = RPCBPROC_GETSTAT; const RPCBSTAT_HIGHPROC = 13; /* rpcbind V4 の手続き数 + 1 */ const RPCBVERS_STAT = 3; /* rpcbind V2,V3,V4 だけのために提供 */ const RPCBVERS_4_STAT = 2; const RPCBVERS_3_STAT = 1; const RPCBVERS_2_STAT = 0; /* getport と getaddr の全状態のリンクリスト */ struct rpcbs_addrlist { unsigned long prog; unsigned long vers; int success; int failure; string netid<>; struct rpcbs_addrlist *next; }; /* rmtcall の全状態のリンクリスト */ struct rpcbs_rmtcalllist { unsigned long prog; unsigned long vers; unsigned long proc; int success; int failure; int indirect; /* callit か indirect かを示す */ string netid<>; struct rpcbs_rmtcalllist *next; }; typedef int rpcbs_proc[RPCBSTAT_HIGHPROC]; typedef rpcbs_addrlist *rpcbs_addrlist_ptr; typedef rpcbs_rmtcalllist *rpcbs_rmtcalllist_ptr; struct rpcb_stat { rpcbs_proc info; int setinfo; int unsetinfo; rpcbs_addrlist_ptr addrinfo; rpcbs_rmtcalllist_ptr rmtinfo; }; /* * 監視される rpcbind の各バージョンに対して rpcb_stat 構造体が * 1 つずつ返されます。 */ typedef rpcb_stat rpcb_stat_byvers[RPCBVERS_STAT]; /* rpcbind 手続き */ program RPCBPROG { version RPCBVERS { void RPCBPROC_NULL(void) = 0; /* * この手続きは、[r_prog, r_vers, r_addr, r_owner, r_netid] の * 組み合わせを登録します。rpcbind サーバは、セキュリティ上の理由 * から、この手続きへの要求をループバックトランスポートだけで受け入れ * ます。 正常終了では TRUE、異常終了では FALSE が返されます。 */ bool RPCBPROC_SET(rpcb) = 1; /* *この手続きは、[r_prog, r_vers, r_owner, r_netid] の * 組み合わせの登録を解除します。 * vers がゼロの場合は、全バージョンを登録解除します。 * この手続きへの要求は、ループバックトランスポートだけで受け入れます。 * 正常終了では TRUE、異常終了では FALSE が返されます。 */ bool RPCBPROC_UNSET(rpcb) = 2; /* * この手続きは、[r_prog, r_vers, r_netid] の組み合わせが登録さ * れている汎用アドレスを返します。r_addr が指定されていれば、 * 汎用アドレスが r_addr にマージされて返されます。r_owner は無視 * します。 異常終了の場合は、FALSE が返されます。 */ string RPCBPROC_GETADDR(rpcb) = 3; /* この手続きは、全マップのリストを返します。 */ rpcblist RPCBPROC_DUMP(void) = 4; /* * この手続きは、遠隔マシン上の手続きを呼び出します。 * 手続きが登録されていない場合は何もしません。 * すなわち、エラー情報も返しません。 */ rpcb_rmtcallres RPCBPROC_CALLIT(rpcb_rmtcallargs) = 5; /* * この手続きは、rpcbind サーバシステムの時刻を返します。 */ unsigned int RPCBPROC_GETTIME(void) = 6; struct netbuf RPCBPROC_UADDR2TADDR(string) = 7; string RPCBPROC_TADDR2UADDR(struct netbuf) = 8; } = 3; version RPCBVERS4 { bool RPCBPROC_SET(rpcb) = 1; bool RPCBPROC_UNSET(rpcb) = 2; string RPCBPROC_GETADDR(rpcb) = 3; rpcblist_ptr RPCBPROC_DUMP(void) = 4; /* * 注: RPCBROC_BCAST と CALLIT の機能は同じです。 * 新たな名前を付けた目的は、ブロードキャスト RPC にはこの手続きを * 使用し、間接呼び出しには RPCBPROC_INDIRECT を * 使用することを示すためです。 */ rpcb_rmtcallres RPCBPROC_BCAST(rpcb_rmtcallargs) = RPCBPROC_CALLIT; unsigned int RPCBPROC_GETTIME(void) = 6; struct netbuf RPCBPROC_UADDR2TADDR(string) = 7; string RPCBPROC_TADDR2UADDR(struct netbuf) = 8; /* * この手続きは、RPCBPROC_GETADDR と同じですが、指定された * バージョン番号がない場合はアドレスを返さない点が異なります。 */ string RPCBPROC_GETVERSADDR(rpcb) = 9; /* * この手続きは、遠隔マシン上の手続きを呼び出します。 * 手続きが登録されていない場合は、エラー情報を * 返します。 */ rpcb_rmtcallres RPCBPROC_INDIRECT(rpcb_rmtcallargs) = 10; /* * この手続きは、RPCBPROC_GETADDR と同じですが、(prog, vers) の * 組み合わせで登録されているアドレスのリストを返す点が異なります。 */ rpcb_entry_list_ptr RPCBPROC_GETADDRLIST(rpcb) = 11; /* * この手続きは、rpcbind サーバのアクティビティに関する統計情報を返します。 */ rpcb_stat_byvers RPCBPROC_GETSTAT(void) = 12; } = 4; } = 100000;
rpcbind にアクセスするには、使用するトランスポートごとに割り当てられているアドレスを使用します。たとえばTCP/IP と UDP/IP の場合は、ポート番号 111 が割り当てられています。各トランスポートには、このようによく知られているアドレスがあります。以下には、rpcbind がサポートしている各手続きを説明します。
この手続きは何もしない手続きです。習慣的にどのプログラムでも、手続き 0 は引数も戻り値もない手続きとします。
マシン上でプログラムが初めて使用可能になるときは、そのマシンで実行されている rpcbind に自分自身を登録します。登録時にプログラムが渡すのは、プログラム番号 prog、バージョン番号 vers、ネットワーク ID netid、サービス要求を待つ汎用アドレス uaddr です。
この手続きは、プログラムのマッピングに成功すれば TRUE、失敗すれば FALSE のブール値を返します。指定された (prog、 vers、 netid) の組み合わせで既にマップされたものがあれば、新たなマップは行いません。
netid と uaddr はどちらも NULL にしてはいけません。また、netid には、呼び出しを行うマシン上のネットワーク ID が正しく指定されていなければなりません。
プログラムが使用できなくなった場合は、同一マシン上の rpcbind で自分自身を登録解除する必要があります。
この手続きの引数と戻り値は、RPCBPROC_SET と同じです。(prog、 vers、netid) の組み合わせと uaddr のマッピングが削除されます。
netid が NULL の場合は、(prog、vers、*) 組み合わせとそれに対応する汎用アドレスのマッピングがすべて削除されます。サービスの登録解除は、サービスの所有者かスーパーユーザだけが実行できます。
プログラム番号 prog、バージョン番号 vers、ネットワークID netid を指定してこの手続きを呼び出すと、そのプログラムが呼び出し要求を待っている汎用アドレスが返されます。
引数の netid フィールドは無視され、要求が到着するトランスポートの netid から取り出します。
この手続きは、rpcbind データベースの全エントリのリストを返します。
この手続きには引数がなく、戻り値は、プログラム、バージョン、ネットワーク ID、汎用アドレス、のリストです。この手続きを呼び出すときは、データグラムトランスポートではなくストリームトランスポートを使用します。これは、大量のデータが返されるのを回避するためです。
この手続きを使用すると、汎用アドレスがわからなくても同一マシン上にある遠隔手続きを呼び出すことができます。この手続きの目的は、rpcbind の汎用アドレスを通して任意の遠隔プログラムにブロードキャストできるようにすることです。
パラメタ prog、vers、proc、args_ptr にはそれぞれプログラム番号、バージョン番号、手続き番号、遠隔手続きへの引数を指定します
この手続きは正常終了の場合は応答しますが、異常終了の場合は一切応答しません。
この手続きからは、遠隔プログラムの汎用アドレスと、遠隔手続きからの戻り値が返されます。
この手続きは、自分のマシンのローカル時刻を、1970 年 1 月 1 日午前 0 時からの秒数で返します。
この手続きは、汎用アドレスをトランスポート (netbuf) アドレスに変換します。この手続きは、uaddr2taddr() (netdir(3N) マニュアルページを参照) と同じ機能を持ちます。名前・アドレス変換のライブラリモジュールとリンクできないプロセスだけが、この手続きを使用します。
この手続きは、トランスポート (netbuf) アドレスを汎用アドレスに変換します。この手続きは、taddr2uaddr() (netdir(3N) マニュアルページを参照) と同じ機能を持ちます。名前・アドレス変換のライブラリモジュールとリンクできないプロセスだけが、この手続きを使用します。
rpcbind のバージョン 4 では、これまでに説明した手続きのほかに、次に示す手続きが追加されています。
この手続きは、バージョン 3 の RPCBPROC_CALLIT 手続きと同じです。新たな名前を付けたのは、この手続きはブロードキャスト RPC だけに使用することを示すためです。これに対して、次のテキストで定義する RPCBPROC_INDIRECT は、間接 RPC 呼び出しだけに使用します。
この手続きは、RPCBPROC_GETADDR に似ています。異なる点は、rpcb 構造体の r_vers フィールドで目的のバージョンを指定できることです。そのバージョンが登録されていない場合、アドレスは返されません。
この手続きは、RPCBPROC_CALLIT に似ていますが、エラーが起こった場合 (たとえば、呼び出すプログラムがシステムに登録されていない場合) にエラー情報を返す点が異なります。この手続きはブロードキャスト RPC には使用できません。間接 RPC 呼び出しだけに使用します。
この手続きは、指定された rpcb エントリのアドレスリストを返します。クライアントはそのリストを使用して、サーバと通信するための代替トランスポートを調べることができます。
この手続きは、rpcbind サーバのアクティビティに関する統計情報を返します。統計情報には、サーバが受信した要求の種類と回数が示されます。
RPCBPROC_SET と RPCBPROC_UNSET 以外の手続きはすべて、rpcbind が実行されているマシンとは別のマシン上のクライアントから呼び出すことができます。rpcbind は、RPCPROC_SET と RPCBPROC_UNSET の要求だけはループバックトランスポートからでないと受け入れません。