分散システム 電子・情報工学系 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2005/2006-01-24
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~yas/
http://www.cs.tsukuba.ac.jp/~yas/
1: /* 2: array-pointer.c -- 文字列用バッファの配列とポインタ 3: ~yas/syspro/string/array-pointer.c 4: Start: 2002/04/21 23:02:39 5: */ 6: 7: #include <stdio.h> /* stderr, snprintf() */ 8: #include <stdlib.h> /* malloc() */ 9: #include <string.h> /* strlen() */ 10: 11: #define BUFFERSIZE 100 12: 13: main() 14: { 15: char a[BUFFERSIZE]; 16: char *p ; 17: p = malloc( BUFFERSIZE ); 18: snprintf( a, sizeof(a), "hello,%s","world" ); 19: snprintf( p, BUFFERSIZE, "hello,%s","world" ); 20: printf("sizeof(a)==%d, sizeof(p) == %d, sizeof(*p) == %d\n", 21: sizeof(a), sizeof(p), sizeof(*p) ); 22: printf("strlen(a)==%d, a:[%s]\n", strlen(a), a ); 23: printf("strlen(p)==%d, p:[%s]\n", strlen(p), p ); 24: }
% cp ~yas/syspro/string/array-pointer.c .% make array-pointer
cc array-pointer.c -o array-pointer % ./array-pointer
sizeof(a)==100, sizeof(p) == 4, sizeof(*p) == 1 strlen(a)==11, a:[hello,world] strlen(p)==11, p:[hello,world] %
![]()
1: /* 2: strcat-snprintf.c -- snprintf を使った文字列のコピーと結合 3: ~yas/syspro/string/strcat-snprintf.c 4: Created on: 2006/01/17 03:17:24 5: */ 6: 7: #include <stdio.h> /* stderr, snprintf() */ 8: #include <stdlib.h> /* malloc() */ 9: #include <string.h> /* strcat() */ 10: 11: 12: main( int argc, char *argv[], char *envp[] ) 13: { 14: if( argc != 2 ) 15: { 16: fprintf(stderr,"Usage:%% %s string\n",argv[0] ); 17: exit( 1 ); 18: } 19: good( argv[1] ); 20: bad( argv[1] ); 21: } 22: 23: #define DOCUMENT_ROOT "/usr/local/httpd/htdocs/" 24: #define BUFFSIZE 100 25: 26: bad( char *s ) 27: { 28: char buf[BUFFSIZE]; 29: int fd ; 30: strcpy( buf,DOCUMENT_ROOT ); 31: strcat( buf,s ); 32: printf(" bad: open(\"%s\", O_RDONLY )\n",buf ); 33: } 34: 35: good( char *s ) 36: { 37: char buf[BUFFSIZE]; 38: int req ; 39: req = snprintf( buf,sizeof(buf),"%s%s",DOCUMENT_ROOT,s ); 40: if( req >= sizeof(buf) ) 41: { 42: fprintf(stderr,"good: buffer overflow attack detected.\n"); 43: fprintf(stderr,"required: %d, actual: %d, strlen: %d, [%s]\n", 44: req, sizeof(buf), strlen(buf), buf ); 45: return( 0 ); 46: } 47: printf("good: open(\"%s\", O_RDONLY )\n",buf ); 48: return( 1 ); 49: }
snprintf() の引数
snprintf() は、%s 以外に %d や %c も使える。
注意:Linux (Glibc を使っている)では、snprintf() の仕様が変更された。 古い仕様(glibc 2.0.6以前)では、snprinf() は、エラーが起きると -1 を返 す。新しい仕様(glibc 2.1以降)では、snprintf() は、必要なバイト数、すな わち、バッファが無限大であるときに書き込まれる文字列の長さ(最後の0を除 く)を返す。これは、strlen() が返すものと同じである。従って、新しい snprintf() では、結果とバッファの大きさを比較して正確に書き込まれたか を検査する。
古い Unix や古い Linux では snprintf は sprintf を呼び出しているだけのことがあり、安全ではないことがある。 coins の環境は問題ない。
snprintf() は、決してバッファ・オーバーフローを起こすことはないが、バッ ファが足りない時には意図していない結果が保存されていることになる。後ろ が切れてもよい場合には、リターンされた結果を比較しない方法がある。 (void とも書かなくてもよい。)
(void)snprintf( buf,sizeof(buf),"%s%s",DOCUMENT_ROOT,s );
実行例:
% cp ~yas/syspro/string/strcat-snprintf.c .% make strcat-snprintf
cc strcat-snprintf.c -o strcat-snprintf % ./strcat-snprintf index.html
good: open("/usr/local/httpd/htdocs/index.html", O_RDONLY ) bad: open("/usr/local/httpd/htdocs/index.html", O_RDONLY ) % ./strcat-snprintf 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
good: buffer overflow attack detected. required: 124, actual: 100, strlen: 99, [/usr/local/httpd/htdocs/012345678901234567890123456789012345678901234567890123456789012345678901234] bad: open("/usr/local/httpd/htdocs/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", O_RDONLY ) Segmentation fault %
![]()
rpcgen
というコマンド。
rpcgen
コマンドを使うには、次のようなファイルを作成する。
図? rpcgenによるRPCプログラム開発で利用するファイル
name.x
name_client.c
name_server.c
% rpcgen name.x次の4つのファイルが生成される。![]()
name.h
name_clnt.c
name_xdr.c
name.x
で定義したデータ構造について、
XDR
のための手続き(整列化と非整列化を行なう手続き) 。
クライアント側とサーバ側の両方で使われる。
name_svc.c
/etc/rpc
にある。
SunRPCでは, 最終的にはTCP/IPまたはUDP/IPでデータが送られる。プログラム 番号などTCP/IPまたはUDP/IPのポート番号を対応させる必要がある。
各ホストには
portmap
というサーバがいて、3つ組
<プログラム番号,バージョン,プロトコル>を、TCP/IPまたはUDP/IPのポート番号へ変換する。
portmap の情報は、
rpcinfo
コマンドで表示できる。
% rpcinfo -p他のホストの情報も調べられる。program vers proto port 100000 2 tcp 111 portmapper 100000 2 udp 111 portmapper 100007 2 tcp 1024 ypbind 100007 2 udp 1027 ypbind 100007 1 tcp 1024 ypbind 100007 1 udp 1027 ypbind 100005 1 udp 831 mountd 100005 2 udp 831 mountd 100005 1 tcp 834 mountd 100005 2 tcp 834 mountd 100003 2 udp 2049 nfs 100001 2 udp 4193 rstatd 100001 3 udp 4193 rstatd 100001 4 udp 4193 rstatd %
![]()
% rpcinfo -p hostnameサーバは、起動時に、portmap に登録する。![]()
pmap_set(prognum, versnum, protocol, port) u_long prognum, versnum, protocol; u_short port;クライアントは、呼び出す前に、ポート番号を調べる。
u_short pmap_getport(addr, prognum, versnum, protocol)
スタブが自動的に呼び出すので、普段は気にすることはない。
portmap 自身も RPC で動いている。portmap の自身のポート番号は、111 と 決まっている。
1: struct delist { 2: string del_name<> ; 3: struct delist *del_next ; 4: }; 5: 6: union dirlist_res 7: switch(int dlr_errno) 8: { 9: case 0: 10: delist *dlr_head; 11: default: 12: void; 13: }; 14: 15: program DIRLIST_PROG { 16: version DIRLIST_VERSION { 17: dirlist_res DIRLIST(string) = 11 ; 18: } = 1; 19: } = 0x20001003 ;17行目が手続きの定義。
DIRLIST
が手続きの名前(手続き番号を示す定数の定義)。
この手続きの引数は、string
型、結果は、dirlist_res
型。SunRPC では、基本的には引数も結果も1つずつ。複数必要な時には、構造体を
定義する。
手続き DIRLIST
の定義を program
番号と
version
番号の括弧が取り囲んでいる。
dirlist_res は、共用体(可変長の構造体、Pascal 可変長レコード)。
struct delist は、自分自身へのポインタ(del_next)を含むリスト構造。
一般的に RPC では、ポインタを含むデータ構造を送ることはできない。 SunRPC では、ポインタの先の1要素を再帰的に送る機能がある。 これにより、木構造や線形リストを送ることができる。
string del_name<> は、文字列型。可変長(<>)で最 大文字数には定められていない(<>の中が空)。
ヘッダファイルの主要部分は、以下の通りである。
struct delist { char *del_name; struct delist *del_next; }; typedef struct delist delist; struct dirlist_res { int dlr_errno; union { delist *dlr_head; } dirlist_res_u; }; typedef struct dirlist_res dirlist_res; #define DIRLIST_PROG 0x20001003 #define DIRLIST_VERSION 1 #if defined(__STDC__) || defined(__cplusplus) #define DIRLIST 11 extern dirlist_res * dirlist_1(char **, CLIENT *); extern dirlist_res * dirlist_1_svc(char **, struct svc_req *); extern int dirlist_prog_1_freeresult (SVCXPRT *, xdrproc_t, caddr_t); #else /* K&R C */ #define DIRLIST 11 extern dirlist_res * dirlist_1(); extern dirlist_res * dirlist_1_svc(); extern int dirlist_prog_1_freeresult (); #endif /* K&R C */
: /* 2: dirlist_server.c -- ディレクトリの内容を表示するRPCのプログラム(サーバ側) 3: Created on: 2006/01/18 20:33:31 4: */ 5: 6: #include <sys/types.h> /* opendir(2) */ 7: #include <dirent.h> /* opendir(2) */ 8: #include <errno.h> /* errno */ 9: #include <stdlib.h> /* malloc() */ 10: #include <string.h> /* strlen() */ 11: #include <stdio.h> /* snprintf() */ 12: 13: #include "dirlist.h" 14: static struct delist *make_delist( DIR *dirp ); 15: 16: dirlist_res * 17: dirlist_1_svc(char **argp, struct svc_req *rqstp) 18: { 19: static dirlist_res result; 20: DIR *dirp ; 21: char *dirname ; 22: 23: xdr_free((xdrproc_t)xdr_dirlist_res, (char *)&result); 24: 25: dirname = *argp ; 26: dirp = opendir( dirname ); 27: if( dirp == 0 ) 28: { 29: result.dlr_errno = errno ; 30: return( &result ); 31: } 32: result.dlr_errno = 0; 33: result.dirlist_res_u.dlr_head = make_delist( dirp ); 34: closedir( dirp ); 35: return( &result ); 36: } 37:サーバ側では、手続き dirlist_1_svc() を記述する。 引数と結果は、
rpcgen
のソースで定義した構造体へのポインタ。
結果を返す時の構造体へのポインタを返す方法が問題。自動変数(auto 変数) にすると、呼出し側に戻った瞬間に無効になる。よく使われるのは、静的変数 (static 変数)に結果を代入して、返すことだが、マルチスレッドプログラミ ングでは大いに問題になる。
opendir(), readdir(), closedir() は、ディレクトリの内容を得るためのラ イブラリ関数。次のようなコードで、"/" の内容が表示される(ls -f /)。
dirp = opendir( "/" ); while( (dp = readdir(dirp)) != NULL ) { printf("%s\n", dp->d_name ); } closedir( dirp );readdir() は、make_delist() の中で行われる。
38: static struct delist * 39: make_delist( DIR *dirp ) 40: { 41: struct dirent *dp ; 42: struct delist *del ; 43: int namelen; 44: if( (dp = readdir(dirp)) == NULL ) 45: { 46: return( 0 ); 47: } 48: else 49: { 50: del = malloc(sizeof(struct delist)); 51: namelen = strlen( dp->d_name ); 52: del->del_name = malloc( namelen+1 ); 53: snprintf(del->del_name,namelen+1,"%s",dp->d_name ); 54: del->del_next = make_delist( dirp ); 55: return( del ); 56: } 57: }make_delist() は、再帰呼び出しで、struct delist のリストを作る。
自動生成されるサーバのmainプログラムでは、 ポートマッパ(port mapper) へのプログラム番号とバージョン番号の登録される。
1: /* 2: dirlist_client.c -- ディレクトリの内容を表示するRPCのプログラム(クライアント側) 3: Created on: 2006/01/18 20:57:49 4: */ 5: 6: #include <stdlib.h> /* exit() */ 7: #include <stdio.h> /* printf() */ 8: 9: #include "dirlist.h" 10: 11: main( int argc, char *argv[] ) 12: { 13: if( argc != 3 ) 14: { 15: fprintf(stderr,"usage: %% %s server_host dir\n", argv[0]); 16: exit( 1 ); 17: } 18: dirlist( argv[1], argv[2] ); 19: } 20: 21: dirlist(char *host, char *dir) 22: { 23: CLIENT *clnt; 24: dirlist_res *result; 25: char *arg; 26: 27: clnt = clnt_create (host, DIRLIST_PROG, DIRLIST_VERSION, "tcp"); 28: if( clnt == NULL ) 29: { 30: clnt_pcreateerror( host ); 31: exit( 1 ); 32: } 33: 34: arg = dir; 35: result = dirlist_1( &arg, clnt ); 36: if( result == NULL ) 37: { 38: clnt_perror( clnt, "call failed"); 39: exit( 1 ); 40: } 41: print_dirlist_res( result ); 42: xdr_free( (xdrproc_t)xdr_dirlist_res, (char *)result ); 43: clnt_destroy( clnt ); 44: } 45:
main() の引数は、サーバが動作しているホスト名とディレクトリ。
clnt_create()
は、CLIENT
構造体を確保する。
引数は、サーバのホスト名、
プログラム番号、
バージョン番号、
通信に使うプロトコルである。
33行目で呼び出している dirlist_1()
が、rpcgen
によ
り生成されたスタブ。引数は、インタフェースで定義された構造体
dirlistarg
へのポインタと、CLIENT
構造体へ
のポインタ。結果は、インタフェースで定義された構造体
dirlistres
へのポインタである。この関数は、rpcgen
が生成する *_clnt.c
というファイルに含まれてる。
スタブ dirlist_1()
は、成功すると内部で malloc()
を呼
び出しメモリを確保して、そこにサーバから受け取った応答メッセージを
非整列化(unmarshalling)
して保存する。このメモリは、利用し終わると xdr_free()
で
解放する。
free( result );だと、トップレベルの構造体のメモリしか解放されない。内部に含まれている 構造体へのポインタや文字列のメモリを解放するためには、
xdr_free()
を呼び出す。
46: print_dirlist_res( dirlist_res *result ) 47: { 48: printf("errno: %d (%s)\n", 49: result->dlr_errno, strerror(result->dlr_errno)); 50: switch( result->dlr_errno ) 51: { 52: case 0: 53: print_delist( result->dirlist_res_u.dlr_head ); 54: break; 55: default: 56: break; 57: } 58: } 59: 60: print_delist( struct delist *del ) 61: { 62: if( del == NULL ) 63: { 64: } 65: else 66: { 67: printf("%s\n",del->del_name ); 68: print_delist( del->del_next ); 69: } 70: }print_dirlist_res() は、struct dirlist_res を表示する。 print_delist() は、struct delist を表示する。 構造体が再帰している所で、関数も再帰している。
% wget http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2005/2006-01-24/ex/dirlist.xSolaris, Linux では、-lnsl が必要。Solaris, MacOSX では、-DDEBUG をつ けてコンパイルとfork() しないでフォアグランドで動くサーバができる。% wget http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2005/2006-01-24/ex/dirlist_server.c
% wget http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2005/2006-01-24/ex/dirlist_client.c
% wget http://www.coins.tsukuba.ac.jp/~yas/coins/dsys-2005/2006-01-24/ex/Makefile
% emacs Makefile
% make
rpcgen dirlist.x gcc -g -DDEBUG -c -o dirlist_clnt.o dirlist_clnt.c gcc -g -DDEBUG -c -o dirlist_client.o dirlist_client.c gcc -g -DDEBUG -c -o dirlist_xdr.o dirlist_xdr.c gcc -g -DDEBUG -o dirlist_client dirlist_clnt.o dirlist_client.o dirlist_xdr.o -lnsl gcc -g -DDEBUG -c -o dirlist_svc.o dirlist_svc.c gcc -g -DDEBUG -c -o dirlist_server.o dirlist_server.c gcc -g -DDEBUG -o dirlist_server dirlist_svc.o dirlist_server.o dirlist_xdr.o -lnsl %
![]()
% ./dirlist_serverサーバ側は自動的には止まらないので、止めたい時には、![]()
^C
を押す。
サーバを動かしているホストの別の端末で rpcinfo -p を実行すると、プログ ラム番号536875011 (0x20001003) のプログラムが表示される。
% rpcinfo -pprogram vers proto port 100000 2 tcp 111 portmapper 100000 2 udp 111 portmapper 536875011 1 udp 59692 536875011 1 tcp 50138 %
![]()
MacOSX 10.4 では、portmapp が動作していないことがある。rpcinfo -p で何 も表示されない時には、root で 次のようにして実行する。
launchctl start com.apple.portmap
% ./dirlist_client localhost /usrerrno: 0 (Success) . .. lost+found X11R6 bin dict <中略> tmp %
![]()
整数: char, short, int (32ビット), long, long long (hyper, 64ビット)
各 unsigned。
浮動小数点: float, double, (quadruple 4倍精度)
ほぼC言語と同等に書ける。自動的に typedef される。 ほぼC言語と同等に書れる。自動的に typedef される。RPCのインタフェースの定義では、定数を記述することができる。
const LSIZE = 80 ;これは、
rpcgen
により、C言語のプリプロセッサのマクロ
#define
に置き換えられる。
RPCのインタフェースの定義では、配列を定義することができる。配列には、
固定長と可変長の2種類あり、固定長は、C言語と同じ。可変長は、次のよう
に[]
の代わりに<>
とする。
int varray<>;
<>
の中には、最大長を書くこともできる。これは、
rpcgen
により、次のような構造体に置き換えられる。
struct { u_int varray_len; int *varray_val; } varray; typedef struct varray varray ;C言語でプログラムを作成する時には、この
varray_len
に要素数を、
varray_val
に、配列の先頭のポインタをセットする。
int a1[10] ; varray v1 ; v1.varray_len = 10 ; v1.varray_val = &a1[0] ;文字列を送るには、特殊な
string
型を使う。
string s<> ;これは、C言語では、
char *
になるが、文字列のつもりで次のよう
に書いても、文字列は送られない。
char *s ;これも、
rpcgen
により、やはり char *
にコンパイルされる
が、この場合、ポインタの先の1文字しか送られない。C言語の文字列を送り
たい時には、必ず string
を使うこと。
SunRPC ではポインタ型も送ること ができるが、ポインタの先の1要素しか送られない。 複数要素を送りいた時には、配列を使う。
C言語の main の引数と同様の構造を送りたい時には、次のようにする。
typedef string argstr_t<>; typedef argstr_t argvt<>;大量のデータをそのまま送るには、
opaque
型
(
不定形型
)
を使う。これには固定長と可変長がある。
opaque fileblock[512] ; opaque filedata<> ;
opaque
の代わりにchar
の配列にすると、文字と見なして1
文字1文字変換が行なわれることになり、非常に遅くなる。場合によっては文
字コードの変換が行なわれる。opaque
では、そのような変換は一切
行なわれず、そのままの形で送られる。
RPCのインタフェースの定義では、共用体(可変長の構造体)が書ける。
union int_result_t switch( int status ) { case OK: int data ; default: void; };これは、次のようにコンパイルされる。
struct int_result_t { int status; union { int data; } int_result_t_u; };
status
という値がOK
の時だけdata
が有効になる。
すなわち、その時だけ実際にネットワークにたいしてdata
の部分が
送られる。
RPCのインタフェースの定義では、
bool_t
という型が使える。値は、
TRUE
か FALSE
。
Sunの技術で、構造体をファイルに保存することができる。
構造体を整列化し、他のプロセスに通信メッセージとして送る代わりにファイ ルに保存する。
SunRPCでは、xdrstdio_create() という関数が用意されている。 ストリーム入出力(FILE *) に対して構造体を読み書きすることができるようになる。
RPCと同様に、構造体にポインタが含まれていた場合、再帰的にファイルに保 存される。異なる機種で読み出すことができる。
注意:putenv() の引数は、strdup() すること。(同じ名前の環境変数がある と、ゴミが出てきてしまうが、この課題では問題ないとする。)
次のようなインタフェースを持つハッシュ表を RPC で実現しなさい。
typedef string key_t<256>; struct keyvalue_t { key_t key; int value ; }; typedef key_t keyarray_t<>; program HASHTABLE_PROG { version HASHTABLE_VERSION { int PUT(keyvalue_t) = 11 ; int GETVALUE(key_t) = 12 ; keyarray_t GETKEYS(void) = 13 ; } = 1 ; } = 0x20051001 ;次のような手続きを持つカウンタを RPC サーバとして実現しなさい。
このクライアントは、4つの引数を取るものとする。
% myypmatch host domain map key![]()
const YPMAXRECORD = 1024; const YPMAXDOMAIN = 64; const YPMAXMAP = 64; ... typedef string domainname<YPMAXDOMAIN>; typedef string mapname<YPMAXMAP>; typedef opaque keydat<YPMAXRECORD>; ... struct ypreq_key { domainname domain; mapname map; keydat key; }; ... program YPPROG { version YPVERS { ... ypresp_val YPPROC_MATCH(ypreq_key) = 3; ... } = 2; } = 100004;keydat は、不定形(任意のバイナリ)である。 keydat_val には、文字列の先頭番地、keydat_len には、文字列の長さ(0を 含まない)を指定する。 ypmatchクライアント と同様に他のNIS の手続きを実 行しなさい。次のものが簡単と思われる。
ヒント:stat() システムコールや lstat() システムコールでファイルの属性 を得る。RPC では、単に string を返すのではなく、属性も返す。 返す属性は、自分で .x ファイルに定義して、struct stat からコピーする。
struct stat にある属性のうち、数個を送ればよい。全てを送らなくてもよい。