電子・情報工学系/システム情報工学研究科CS専攻 新城 靖 <yas@is.tsukuba.ac.jp>
このページは、次の URL にあります。
http://www.coins.tsukuba.ac.jp/~syspro/2005/No8.html
あるいは、次のページから手繰っていくこともできます。
http://www.coins.tsukuba.ac.jp/~syspro/2005/
http://www.coins.tsukuba.ac.jp/~yas/
IP 層では、アドレスとしては、IP アドレスだけを使う。UDP/IP は、IP 層の 機能をほとんどそのままの形で利用可能にしたものである。違いは、ポート番 号を指定できることと、チェックサムでデータの内容を検査できることなどで ある。
UDP/IP でも、クライアント側のアドレスは、OSに任せ、自らは指 定しないことが多い(指定することもできる)。
UDP/IP でも、通信パタンによる
クライアント・サーバ・モデル
に基づくことが多い。
図? UDP/IPによるクライアント・サーバ型の通信
telnet は、TCP/IP の汎用のクライアント(文字列の送受信用)として使える。 しかし、UDP/IP 用のクライアントとしては使えない。
1: 2: /* 3: echo-client-udp.c -- 文字列を送受信するクライアント(UDP/IP版) 4: ~yas/syspro/ipc/echo-client-udp.c 5: Created on: 1997/06/16 21:22:26 6: */ 7: #include <stdio.h> 8: #include <sys/types.h> /* socket() */ 9: #include <sys/socket.h> /* socket() */ 10: #include <netinet/in.h> /* struct sockaddr_in */ 11: #include <netdb.h> /* getaddrinfo() */ 12: 13: extern void echo_client_udp( char *server, int portno ); 14: extern int udp_port_nobind(); 15: extern int sockaddr_in_init( struct sockaddr_in *addr, int addrlen, 16: char *hostname, int portno ); 17: extern void sockaddr_print( struct sockaddr *addrp, int addr_len ); 18: extern void sockname_print( int s ); 19: 20: main( int argc, char *argv[] ) 21: { 22: char *server ; 23: int portno ; 24: if( argc != 3 ) 25: { 26: fprintf( stdout,"Usage: %% %s host port\n",argv[0] ); 27: exit( -1 ); 28: } 29: server = argv[1] ; 30: portno = strtol( argv[2],0,10 ); 31: echo_client_udp( server, portno ); 32: }
main の部分は、TCP/IP 版の echo サービスのクライアント とほとんど同じである。
main() 関数は、コマンドラインの引数を調べて、echo_client() を読んでい る。strtol() で、文字列で与えられたポート番号を、int に変換している。
33: 34: #define BUFFERSIZE 1024 35: 36: void 37: echo_client_udp( char *server, int portno ) 38: { 39: int sock ; 40: int slen,scount,rcount ; 41: char sbuf[BUFFERSIZE]; 42: char rbuf[BUFFERSIZE]; 43: int i ; 44: int fromlen ; 45: struct sockaddr_in to; struct sockaddr_storage from ; 46: 47: sock = udp_port_nobind(); 48: if( sock<0 ) 49: exit( -1 ); 50: printf("my port is "); sockname_print( sock ); printf("\n"); 51: 52: if( sockaddr_in_init( &to, sizeof(to), server, portno )<0 ) 53: { 54: perror("sockaddr_in_init"); 55: exit( -1 ); 56: } 57: printf("server is "); 58: sockaddr_print( (struct sockaddr *)&to, sizeof(to) ); printf("\n"); 59: 60: printf("==> "); fflush(stdout); 61: while( fgets(sbuf,BUFFERSIZE,stdin) ) 62: { 63: slen = strlen( sbuf ); 64: printf("sending [%s] (%d bytes) to ", sbuf, slen ); 65: sockaddr_print( (struct sockaddr *)&to, sizeof(to) ); printf("\n"); 66: 67: scount = sendto( sock, sbuf, slen, 0, 68: (struct sockaddr *)&to, sizeof(to) ); 69: if( scount != slen ) 70: { 71: perror("sendto()"); 72: exit( 1 ); 73: } 74: printf("after sendo(), my port is "); sockname_print( sock ); printf("\n"); 75: 76: fromlen = sizeof( from ); 77: rcount = recvfrom( sock, rbuf, BUFFERSIZE, 0, 78: (struct sockaddr *)&from, &fromlen); 79: if( rcount < 0 ) 80: { 81: perror("recvfrom()"); 82: exit( 1 ); 83: } 84: rbuf[rcount] = 0 ; 85: printf("received %d bytes [%s] from ",rcount, rbuf ); 86: sockaddr_print( (struct sockaddr *)&from, fromlen ); printf("\n"); 87: 88: printf("==> "); fflush(stdout); 89: } 90: printf("\n"); 91: 92: close( sock ); 93: } 94:echo_client_udp() では、udp_port_nobind() という関数を呼び出している。 この結果、UDP/IP の通信用ポートが作られ、通信可能なファイル記述子が返される。 ただし、サーバとの間には、通信路は確保されない。このファイル記述子は、 標準入出力(0,1,2)や open() システム・コールの結果と同じもので、不要に なったら close() で解放する。しかし、この状態では、write() や read() システムコールは使えない。その代りに、sendto() や recvfrom() システム コールを使う。
クライアント側では、bind() でアドレス(IPアドレスとポート番号)を設定し ないことが多い。通信に使う前には、アドレスは固定されていない。通信に使 うと、その瞬間に固定され、close() するまで有効である。(クライアント側 でも、bind() でアドレスを固定することもできるが、一般的ではない。)
sockaddr_in_init() は、構造体 to に送り先のアドレス(IPアドレスとポート 番号)をセットしている。
送るデータは、TCP/IP版の同じく文字列である。UDP/IP の通信では、データ の区切りは保存されるので、行末の区切りをつけずに構造体をそのまま送るも のも多い。
sendto() システムコールで、データを送信している。送り先は、第5引数に指 定されている。TCP/IP とは異なり、データの区切りは保存される。また、一 度に全データが送られるので、TCP/IP とは異なり、全デー タを送り出すためのループは不要である。ただし、一度に送ることができるデー タの大きさには上限がある。
途中、階層の通信媒体の上限を越えた場合、「フラグメント化」が行われ、実 際のデータとしては分割されることがある。たとえば、イーサネットでは、 1500バイトを越えた場合、フラグメント化される。フラグメント化されたデー タは、受信側のプロセスに送られる前に組み立てられる。
データを受け取るには、recvfrom() システムコールを使っている。この時、 データの送り手のアドレス(IPアドレスとポート番号)が&from番 地に保存される。普通は、sendto() と同じアドレスから返ってくるが、 違うアドレスから返ってくることもある。
recvfrom() では、最後に終端の 0 を付けてくれないので、自分で 0 を付け ている。
95: int 96: udp_port_nobind() 97: { 98: int s ; 99: if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0 ) 100: { 101: perror("socket"); 102: return( -1 ); 103: } 104: return( s ); 105: } 106:
udp_port_nobind() は、クライアント側の UDP/IP のポートを作る関数である。 内部では、ソケットを socket() システムコールで作成しているだけである。 PF_INET と SOCK_DGRAMの組み合わせなので、 UDP を使うことを意味する。 socket() の引数で、PF_INET の変りに、AF_INET と書いてもよい。ここでは、 Protocol を選んでいるので、PF_ が正しいが、実際には、PF_INET と AF_INET は同じであり、また、多くのテキストで混在されて使われいる。
UDP/IP でも、connect() システムコールで接続先を固定する方法もあるが、 一般的ではない。接続先を固定した場合は、sendto() や recvfrom() ではな く、write() やread() を使って通信することもできる。その場合も、UDP の 性質は保たれるので、write() したデータのパケットが失われた時には、再転 送は行われない。
107: int 108: sockaddr_in_init( struct sockaddr_in *addr, int addrlen, 109: char *hostname, int portno ) 110: { 111: struct addrinfo hints, *ai; 112: int err ; 113: 114: if( addrlen < sizeof(struct sockaddr_in) ) 115: { 116: fprintf(stderr,"sockaddr_in, not enough space (%d) > (%d)\n", 117: addrlen, sizeof(struct sockaddr_in) ); 118: return( -1 ); 119: } 120: memset( &hints, 0, sizeof(hints) ); 121: hints.ai_family = AF_INET ; 122: hints.ai_socktype = SOCK_DGRAM ; 123: if( (err = getaddrinfo( hostname, NULL, &hints, &ai )) ) 124: { 125: fprintf(stderr,"unknown host %s (%s)\n",hostname, 126: gai_strerror(err) ); 127: return( -1 ); 128: } 129: if( ai->ai_addrlen > addrlen ) 130: { 131: fprintf(stderr,"sockaddr too large (%d) > (%d)\n", 132: ai->ai_addrlen,sizeof(addr) ); 133: freeaddrinfo( ai ); 134: return( -1 ); 135: } 136: memcpy( addr, ai->ai_addr, ai->ai_addrlen ); 137: addr->sin_port = htons( portno ); 138: freeaddrinfo( ai ); 139: 140: return( 0 ); 141: } 142:UDP/IP の場合も、TCP/IP と同様に、アドレスの指定には、sockaddr_in 構造 体を使う。sendto() システムコールのマニュアルには、sockaddr 構造体を使 うようにと書かれているが、UDP/IP (IPv4) では、そのサブクラス(オブジェ クト指向用語)であるsockaddr_in を使う。この場合、IP アドレスとポート番 号が埋められている必要がある。その他に先頭に、sockaddr_in であることを 示す定数 AF_INET を置く。 getaddrinfo() は、ホスト名を IP アドレスし、さらに、先頭に定数 AF_INET を設定している。ポート番号は、第2引数が NULL なので、0 を指定している。 この例では、引数で与えられたポート番号を htons() を介してバイトオーダ を調整してから設定している。
143: void 144: sockaddr_print( struct sockaddr *addrp, int addr_len ) 145: { 146: char host[BUFFERSIZE] ; 147: char port[BUFFERSIZE] ; 148: if( getnameinfo(addrp, addr_len, host, sizeof(host), 149: port, sizeof(port), NI_NUMERICHOST|NI_NUMERICSERV)<0 ) 150: return; 151: printf("%s:%s", host, port ); 152: } 153: 154: void 155: sockname_print( int s ) 156: { 157: struct sockaddr_storage addr ; 158: int len ; 159: len = sizeof( addr ); 160: if( getsockname( s, (struct sockaddr *)&addr, &len )< 0 ) 161: { 162: perror("getsockname"); 163: exit( -1 ); 164: } 165: sockaddr_print( (struct sockaddr *)&addr,len ); 166: }sockaddr_print() は、 echo-server-nofork-fdopen.c のものと同じである。 getaddrinfo() ライブラリ関数により、ホスト名とポート番号の文字列表現を 得ている。文字列といっても、NI_NUMERICHOST|NI_NUMERICSERV というフラグ を指定しているので、結果は "123.4.5.6" や "80" のような数字の並びによ る表記になる。
sockname_print() は、自分自身のポートのアドレスを表示する関数である。 アドレスは、getsockname() で得られる。なお、UDP/IP では、TCP/IP とは異 なり、getpeername() で通信相手を調べることは、この場合できない。UDP/IP では、通信相手は、recvfrom() の引数で知ることができる。 sockname_print() は、 echo-server-nofork-fdopen.c の tcp_peeraddr_print() の getpeername() を、getsockname() に変えたものである。getsockname() は、自分自身の名前(TCP/IP では、IP アドレスとポート番号)を得るためのシステムコールである。
実行例:
% cp ~yas/syspro/ipc/echo-client-udp.c .このプログラムは、コマンドラインから2の引数をとる。第1引数は、ホスト 名、第2引数は、ポート番号である。このプログラムは、標準入力から得られ た行を、その指定されたホスト上のポート番号で動作しているサーバに対して 送る。サーバは、 同じものを送り返してくるので、それを受け取る。% make echo-client-udp
cc echo-client-udp.c -o echo-client-udp % ./echo-client-udp localhost 7
my port is 0.0.0.0:0 server is 127.0.0.1:7 ==> hello
sending [hello ] (6 bytes) to 127.0.0.1:7 after sendo(), my port is 0.0.0.0:33041 received 6 bytes [hello ] from 127.0.0.1:7 ==> world!
sending [world! ] (7 bytes) to 127.0.0.1:7 after sendo(), my port is 0.0.0.0:33041 received 7 bytes [world! ] from 127.0.0.1:7 ==> ^D % ./echo-client-udp adonis8 7
my port is 0.0.0.0:0 server is 130.158.86.28:7 ==> aaa
sending [aaa ] (4 bytes) to 130.158.86.28:7 after sendo(), my port is 0.0.0.0:33041 received 4 bytes [aaa ] from 130.158.86.28:7 ==> bbb
sending [bbb ] (4 bytes) to 130.158.86.28:7 after sendo(), my port is 0.0.0.0:33041 received 4 bytes [bbb ] from 130.158.86.28:7 ==> ^D %
![]()
1: 2: /* 3: echo-server-udp.c -- 文字列を送受信するサーバ(UDP/IP版) 4: ~yas/syspro/ipc/echo-server-udp.c 5: Created on: 1997/06/16 21:22:26 6: */ 7: 8: #include <stdio.h> 9: #include <sys/types.h> /* socket() */ 10: #include <sys/socket.h> /* socket() */ 11: #include <netinet/in.h> /* struct sockaddr_in */ 12: #include <netdb.h> /* getaddrinfo() */ 13: 14: extern void echo_server_udp( int portno ); 15: extern int udp_port_bind( int portno ); 16: extern void sockaddr_print( struct sockaddr *addrp, int addr_len ); 17: extern void sockname_print( int s ); 18: 19: main( int argc, char *argv[] ) 20: { 21: int portno ; 22: if( argc >= 3 ) 23: { 24: fprintf( stdout,"Usage: %s [portno]\n",argv[0] ); 25: exit( -1 ); 26: } 27: if( argc == 2 ) 28: portno = strtol( argv[1],0,10 ); 29: else 30: portno = getuid(); 31: echo_server_udp( portno ); 32: } 33:TCP版のecho サービスのサーバ と同様に、 引数として、ポート番号を取る。ポート番号が与えられなければ、そのプロセ スの UID (User ID) から生成する。UID は、個人個人を識別するための番号 (16ビット程度、システムによっては 32 ビット)である。この課題では、(1台 のコンピュータでは)個人ごとに別々の UID を使う必要がある。上のように UID から生成する方法は、一般的ではない。(筑波大学情報学類のシステムで はうまく働く。)
34: #define BUFFERSIZE 1024 35: 36: void 37: echo_server_udp( int portno ) 38: { 39: int s, rcount, scount, addrlen ; 40: struct sockaddr_storage addr ; 41: char buffer[BUFFERSIZE]; 42: 43: s = udp_port_bind( portno ); 44: if( s<0 ) 45: exit( -1 ); 46: printf("my port is "); sockname_print( s ); printf("\n"); 47: 48: while( 1 ) 49: { 50: addrlen = sizeof( addr ); 51: rcount = recvfrom( s, buffer, BUFFERSIZE, 0, 52: (struct sockaddr *)&addr, &addrlen ); 53: if( rcount < 0 ) 54: { 55: perror("recvfrom()"); 56: exit( 1 ); 57: } 58: buffer[rcount] = 0 ; 59: printf("received %d bytes [%s] from ",rcount, buffer ); 60: sockaddr_print( (struct sockaddr *)&addr,addrlen ); printf("\n"); 61: 62: printf("sending back [%s] (%d bytes) to ", buffer, rcount ); 63: sockaddr_print( (struct sockaddr *)&addr,addrlen ); printf("\n"); 64: scount=sendto( s, buffer, rcount, 0, (struct sockaddr *)&addr, addrlen ); 65: if( scount!= rcount ) 66: { 67: perror("sendto()"); 68: exit( 1 ); 69: } 70: } 71: } 72:udp_port_bind() は、引数で与えられたポート番号を使ってUDP/IP のポート を作成し、そのファイル記述子(ソケット)を返す。sockname_print() は、そ のアドレス(IPアドレスとポート番号)を表示する。 TCP/IP とは異なり、このポートは、そのまま通信に使うことができる。 逆に、accept() は使うことはできない。
サーバのプログラムの特徴は、内部に無限ループを持っていることである。 サーバは、普通の状態では、終了しない。
このサーバは、fork() しない。サービスの内容が簡単であり、sendto() シス テムコールでブロックすることもないからである。サービスが重たい時には、 他のクライアントからの処理を並列に進めるために、子プロセスを作ること可 能である。
73: int 74: udp_port_bind( int portno ) 75: { 76: struct sockaddr_in addr ; 77: int addr_len ; 78: int s ; 79: 80: if( (s = socket(PF_INET, SOCK_DGRAM, 0)) < 0 ) 81: { 82: perror("socket"); 83: return( -1 ); 84: } 85: 86: memset( &addr, 0, sizeof(addr) ); 87: addr.sin_family = AF_INET ; 88: addr.sin_addr.s_addr = INADDR_ANY ; 89: addr.sin_port = htons( portno ); 90: 91: if( bind(s,(struct sockaddr *)&addr,sizeof(addr)) < 0 ) 92: { 93: perror("bind"); 94: fprintf(stderr,"port number %d is already used. wait a moment or kill another program.\n", portno ); 95: return( -1 ); 96: } 97: return( s ); 98: } 99: ... 102: sockaddr_print( struct sockaddr *addrp, int addr_len ) ... 113: sockname_print( int s )udp_port_bind() は、サーバ用に、ポート番号を固定した UDP/IP のポートを作成する。 クライアント側との違いは、 bind() システムコールを使っている点にある。
bind() システムコールの引数は、 TCP版のecho サービスのサーバ と同じである。 ソケットが作成できたら、bind() システムコールで、サーバ側で利用するア ドレス(IPアドレスとポート番号)を設定する。IP アドレスは、普通は、 INADDR_ANY を指定する。複数の IP アドレスがある時には、どれに要求が来 ても受け付ける。特定の IP アドレスを指定すると、そのアドレスに来た要求 だけを受け付けるようになる。
ポート番号は、引数で与えられたものを、htons() でネットワーク・バイトオー ダに変換して与える。
listen() システムコールは、UDP/IP では使われない。 要求、すなわち、データが大量に送られ、サーバが処理できない時には、(普 通は古いものから順に)捨てられる。
実行例。
サーバは、終了しないので、最後に、^C を押して、割り込みを掛け て終了させる。
注意:全員が同じコンピュータでポート番号 1231 を使うとプログラムが動かないことがある。
% ./echo-server-udp 1231サーバ側では、bind() をつかっているので、最初からポート番号が固定され ていることがわかる。my port is 0.0.0.0:1231 received 6 bytes [hello ] from 127.0.0.1:33042 sending back [hello ] (6 bytes) to 127.0.0.1:33042 received 7 bytes [world! ] from 127.0.0.1:33042 sending back [world! ] (7 bytes) to 127.0.0.1:33042 received 4 bytes [aaa ] from 130.158.86.28:33015 sending back [aaa ] (4 bytes) to 130.158.86.28:33015 received 4 bytes [133 ] from 130.158.86.28:33015 sending back [133 ] (4 bytes) to 130.158.86.28:33015 ^C %
![]()
Unixでは、全てのファイルやプロセスは、あるユーザの所有物である。これを、
Unixでは、ファイルとプロセスに属性として、UID を持たせることで実現して
いる。
図? 実世界のユーザとUnix中のプロセス・ファイル
1人のユーザが複数のグループに属することがある。
% ls -l proc-uid-print.c行の左から、型とモード、リンク数、ユーザ名(所有者)、グループ名、大き さ、更新時刻、名前が表示されている。-rw-r--r-- 1 yas lab 1769 Jun 16 22:37 proc-uid-print.c %
![]()
-
の場合は普通のファイル、
d
の場合はディレクトリを意味する。
1つのブロックの中の3文字はアクセス毎にその許可・拒否を表す。
r 読込み可 w 書込み可 x 実行可(ディレクトリの場合は探索可)9文字のうち、該当する部分が「-」の場合は、その種類のアクセスが許可さ れてないことを意味する。
「読込み可」とは、その内容を参照することができるという意味する。たとえ ば、読み出し可能なファイルは、cp コマンドでコピーしたり、less で内容を 表示することができる。読出し可能なディレクトリなら、ls コマンドでその ディレクトリ中のファイル名の一覧を表示することができる。
「書込み可」とは、その内容を変更することができるという意味です。たとえ ば、テキスト・ファイルなら、エディタで修正したものを書き込むことができ る。書込み可能なディレクトリなら、mv コマンドでそのディレクトリのなか にあるファイル名前を変更することができる。
「実行可」というのは、ファイルの内容がプログラムの場合は、 そのプログラムを実行することができることを意味する。
「検索可」というのは、その下にあるファイルやディレクトリを たぐることができることを意味する。 ディレクトリが「読込み可」でも、「検索可」でないと、 ディレクトリに「読込み可」のファイルがあっても、 ディレクトリ以下のファイルを読むことができない。 逆に、「検索可」でも、ディレクトリが「読込み可」でないと、 ディレクトリにあるファイル名やディレクトリ名を ls で表示させることが できないが、そのディレクトリにあるファイル名を知っていて、 そのファイルが「読み込み可」なら less などで表示させることはできる。
あるファイルを、特定の人にだけ特定のアクセスの方法をさせたいことがある。 このために、そのために、rwxの指定は、ファイルの所有者、ファイルの属す グループ、それ以外の人用に3セット用意されている。
たとえば、モードが「rw-r--r--」のファイルは、次のようなことを意味する。
普通のファイルは、多くの場合、リンク数が1になっている。ディレクトリは、 「.」や「..」でもアクセスできるので、リンク数が2以上になっている。
ファイルの属性としては、UID で保存されているが、ls - l では、それをユー ザ名に変換して表示する。
ls -lu
で表示される。
ls -l
で表示される「時刻」。
単に「ファイルの時刻」といった場合にはこの時刻を指す。
ls -lc
で表示される「時刻」。
時刻も属性の1つなので、「最終更新時刻」を変更すると、
「最終変更時刻」も変更される。
1: /* 2: ystat.c -- stat システム・コールのシェル・インタフェース 3: ~yas/syspro/file/ystat.c 4: Created on: 1995/03/07 20:59:12 5: */ 6: 7: #include <sys/types.h> /* stat(2) */ 8: #include <sys/stat.h> /* stat(2) */ 9: #include <sys/sysmacros.h> /* major(), minor() */ 10: #include <stdio.h> 11: 12: extern void stat_print( char *path ); 13: 14: main( int argc, char *argv[] ) 15: { 16: if( argc != 2 ) 17: { 18: fprintf( stderr,"Usage:%% %s filename \n",argv[0] ); 19: exit( 1 ); 20: } 21: stat_print( argv[1] ); 22: } 23: 24: void 25: stat_print( char *path ) 26: { 27: struct stat buf ; 28: if( stat( path,&buf ) == -1 ) 29: { 30: perror( path ); 31: exit( 1 ); 32: } 33: 34: printf("path: %s\n",path ); 35: printf("dev: %d,%d\n",major(buf.st_dev),minor(buf.st_dev) ); 36: printf("ino: %d\n",buf.st_ino ); 37: printf("mode: 0%o\n",buf.st_mode ); 38: printf("nlink: %d\n",buf.st_nlink ); 39: printf("uid: %d\n",buf.st_uid ); 40: printf("gid: %d\n",buf.st_gid ); 41: printf("rdev: %d,%d\n",major(buf.st_rdev),minor(buf.st_rdev) ); 42: printf("size: %d\n",buf.st_size ); 43: printf("blksize: %d\n",buf.st_blksize ); 44: printf("blocks: %d\n",buf.st_blocks ); 45: printf("atime: %s",ctime(&buf.st_atime) ); 46: printf("mtime: %s",ctime(&buf.st_mtime) ); 47: printf("ctime: %s",ctime(&buf.st_ctime) ); 48: }
stat() システムコールは、引数として、ファイル名(ファイルやディレクト リの名前、パス名)と、struct stat のポインタを取る。この構造体は、マニュ アルには、次のようなものであると説明されている。
% man 2 statこの構造体を使う限り、移植性がある(portable)プログラムが書ける。実際の システムでは、もう少し複雑な構造をしていることがある。たとえば、Linux では、/usr/include/bits/stat.h に定義がある。STAT(2) System calls STAT(2) NAME stat, fstat, lstat - get file status SYNOPSIS #include
#include #include int stat(const char *file_name, struct stat *buf); int fstat(int filedes, struct stat *buf); int lstat(const char *file_name, struct stat *buf); DESCRIPTION ... They all return a stat structure, which contains the fol- lowing fields: struct stat { dev_t st_dev; /* device */ ino_t st_ino; /* inode */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device type (if inode device) */ off_t st_size; /* total size, in bytes */ unsigned long st_blksize; /* blocksize for filesystem I/O */ unsigned long st_blocks; /* number of blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last change */ }; ...
% cp ~yas/syspro/file/ystat.c .この実行結果から次のようなことがわかる。% make ystat
cc ystat.c -o ystat % ./ystat ystat.c
path: ystat.c dev: 0,9 ino: 81494554 mode: 0100644 nlink: 1 uid: 1013 gid: 40 rdev: 0,0 size: 1183 blksize: 4096 blocks: 8 atime: Sat Jun 11 19:19:38 2005 mtime: Sat Jun 11 19:17:58 2005 ctime: Sat Jun 11 19:17:58 2005 %
![]()
ここで、モードが8進数で 0100644 (C言語の文法で、0から始まる数は、8進 数)であることから、ファイルの型(普通のファイルかディレクトリかという 情報)を調べることができる。
0100644の上位4ビット、つまり、0170000と AND (C言語のでは、 &演算子)をとった結果は 0100000 となる。この値は、普通のファ イル(regular file)を意味する。ディレクトリの場合、0040000 となる。こ れらの数は、<sys/stat.h> (/usr/include/sys/stat.h)で定義さ れている。(Linux では、/usr/include/linux/stat.h にある)
#define S_IFMT 0xF000 /* type of file */ #define S_IFIFO 0x1000 /* fifo */ #define S_IFCHR 0x2000 /* character special */ #define S_IFDIR 0x4000 /* directory */ #define S_IFBLK 0x6000 /* block special */ #define S_IFREG 0x8000 /* regular */ #define S_IFLNK 0xA000 /* symbolic link */ #define S_IFSOCK 0xC000 /* socket */ #define S_ISFIFO(mode) ((mode&S_IFMT) == S_IFIFO) #define S_ISCHR(mode) ((mode&S_IFMT) == S_IFCHR) #define S_ISDIR(mode) ((mode&S_IFMT) == S_IFDIR) #define S_ISBLK(mode) ((mode&S_IFMT) == S_IFBLK) #define S_ISREG(mode) ((mode&S_IFMT) == S_IFREG) #define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) #define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)プログラム中では、次のようにしてファイルの型を調べることができる。
struct stat buf ; stat( path, &buf ); switch( buf.st_mode & S_IFMT ) { case S_IFREG: ファイルの時の処理; break; case S_IFDIR: ... ディレクトリの時の処理; break; .... }あるいは、<sys/stat.h> に含まれている S_ISREG(), S_ISDIR() というマクロを用いて、次のように記述する方法もある。
struct stat buf ; stat( path, &buf ); if( S_ISREG(buf.st_mode) ) { ファイルの時の処理; } else if( S_ISDIR(buf.st_mode) ) { ディレクトリの時の処理; }
モードの下位9ビット(上の例では、8進数で 644 )は、許可されたアクセス 方法を表している。その9ビットは、3ビットづつに区切られおり、上位から 所有者(owner)、グループ(group)、その他(others)に許可(permission) されているアクセス方式を表している。所有者(owner)の代りに、利用者 (user)という言葉が使われることもある。
各3ビットは次の様なアクセス方法が許可されていることを意味する。
ls -l | 3ビット値 | アクセス権 |
---|---|---|
r | 4 | 読込み可能 |
w | 2 | 書込み可能 |
x | 1 | 実行可能(ディレクトリの場合は、検索可能) |
このように、普通のファイルとディレクトリで "x" の意味が異なる。
プロセスの UID 属性を得るには、getuid() システムコールを用いる。 プロセスの GID 属性を得るには、getgid() システムコールを用いる。 以下のプログラムは、現在のプロセスの UID とユーザ名、 UID とユーザ名を 表示するものである。
1: /* 2: proc-uid-print.c -- 現在のプロセスのUIDを表示するプログラム。 3: ~yas/syspro/proc/proc-uid-print.c 4: Created on: 1998/05/18 23:20:16 5: */ 6: 7: #include <sys/types.h> /* getuid(2) */ 8: #include <unistd.h> /* getuid(2) */ 9: #include <pwd.h> /* getpwuid(3) */ 10: #include <grp.h> /* getgrgid(3) */ 11: 12: #if 0 13: 14: -------------------- /usr/include/bits/types.h: -------------------- 15: ... 16: typedef unsigned char __u_char; 17: typedef unsigned short __u_short; 18: typedef unsigned int __u_int; 19: typedef unsigned long __u_long; 20: ... 21: typedef __u_int __uid_t; /* Type of user identifications. */ 22: typedef __u_int __gid_t; /* Type of group identifications. */ 23: ... 24: 25: -------------------- /usr/include/sys/types.h: -------------------- 26: #include <bits/types.h> 27: ... 28: #ifndef __uid_t_defined 29: typedef __uid_t uid_t; 30: # define __uid_t_defined 31: #endif 32: ... 33: #ifndef __gid_t_defined 34: typedef __gid_t gid_t; 35: # define __gid_t_defined 36: #endif 37: 38: -------------------- /usr/include/unistd.h: -------------------- 39: /* Get the real user ID of the calling process. */ 40: extern __uid_t getuid (void) __THROW; 41: 42: #endif 43: 44: extern char *uid2uname(uid_t uid); 45: extern char *gid2gname(gid_t gid); 46: 47: main() 48: { 49: uid_t uid ; 50: gid_t gid ; 51: uid = getuid(); 52: printf("uid==%u (%s)\n",uid,uid2uname(uid) ); 53: gid = getgid(); 54: printf("gid==%u (%s)\n",gid,gid2gname(gid) ); 55: } 56: 57: char * 58: uid2uname(uid_t uid) 59: { 60: struct passwd *pwd ; 61: pwd = getpwuid( uid ); 62: if( pwd ) 63: return( pwd->pw_name ); 64: else 65: { 66: static char buf[100] ; /* must be static, bad for multithreading */ 67: snprintf(buf,sizeof(buf),"%d",uid ); 68: return( buf ); 69: } 70: } 71: 72: char * 73: gid2gname(gid_t gid) 74: { 75: struct group *grp ; 76: grp = getgrgid( gid ); 77: if( grp ) 78: return( grp->gr_name ); 79: else 80: { 81: static char buf[100] ; /* must be static, bad for multithreading */ 82: snprintf(buf,sizeof(buf),"%d",gid ); 83: return( buf ); 84: } 85: }uid_t は、最終的には、unsigned int として定義されている。unistd.h の __ THROW は、C++言語で使う時に有効なものであり、C言語のプログラムの場 合は、空の定義で置き換えられ、ソース・プログラムからは消えてる。
getuid() は、そのプロセス(現在実行中のプロセス)の UID を返すシステム コールである。
uid2uname() は、引数で与えられた UID を、標準ライブラリ関数getpwuid() を使って文字列に変換する。このライブラリ関数は、struct passwd へのポイ ンタを返す。この構造体は、次のようになっている。
struct passwd { char *pw_name; /* user name */ char *pw_passwd; /* user password */ uid_t pw_uid; /* user id */ gid_t pw_gid; /* group id */ char *pw_gecos; /* real name */ char *pw_dir; /* home directory */ char *pw_shell; /* shell program */ };pw_name に、文字列のユーザ名が入っている他に、パスワード(ハッシュ値) やユーザのホーム・ディレクトリやログイン・シェルも含まれている。
ライブラリ関数 getpwuid() は、/etc/passwd ファイルや NIS (Network Information Service) のパスワード・データベースを引いて、この構造体を 作り上げる。getpwuid() の他に、getpwnam() も、この構造体へのポインタを 返す。
struct passwd *getpwnam(const char * name); struct passwd *getpwuid(uid_t uid);uid2uname() は、UID を、対応したユーザ名(文字列)に変換する関数である。 uid2uname() は、結果を static で宣言したバッファに保存して返している。 この方法は、後で free() しなくてもよいという意味では便利な方法だが、マ ルチスレッドのプログラムではよくない。(マルチスレッドについては、3学 期のオペレーティング・システムIIに出てくる。)
gid2gname() は、uid2uname() と同様に、GID をグループ名に対応した文字列 に変換する関数である。内部では、標準のライブラリ関数 getgrgid() を使っ ている。これは、struct group を返す。詳しくは、man getgrgid を見なさい。
実行例:
% cp ~yas/syspro/proc/proc-uid-print.c .最初の行は、各自異なるはずである。% make proc-uid-print
cc proc-uid-print.c -o proc-uid-print % ./proc-uid-print
uid==1013 (yas) gid==40 (lab) %
![]()
図? アクセス制御における主体、オブジェクト、および、操作
Unixは、マルチユーザのシステムなので、ユーザ1人ひとりを認識するような アクセス制御の仕組みを持っている。たとえば、Unix では、共同プロジェク トに関連したファイルは、他のユーザにも見せてもよいが、個人の電子メール は、他の人には見せないといった制御ができる。
これに対して、古いパソコン用のOS(Windows 95/98/ME, MacOS 9以下) では、このような複数のユーザを識別したアクセス制御は、できない。 Windows NT, Windows 2000, Windows XP, MacOS X は、可能である。
ファイルの「内容」のアクセス3段階であるが、ファイルの「属性」次の2段 階である。
ヒント:DatagramSocket() を使う。
UDP/IP では、一度に送ることができるデータの大きさにには、実装上の制限 が付いている。これがいくつかを調べるプログラムを作りなさい。
UDP/IP のデータを中継するようなプログラムを作りなさい。 単方向だけでなく、双方向で中継するようにしなさい。
このようなプログラムの例として、udprelay と呼ばれるプログラムがある。
UDP/IP のサーバ、または、中継プログラムで、クライアントの IP アドレス によってアクセスを許したりエラーを発生させたりしなさい。
% idここで、uid は、getuid()、 gid は、getgid()、groups は、getgroups() シ ステムコールの結果である。getgroups() では、複数の GID が返される。uid=1013(yas) gid=40(lab) groups=40(lab),510(softadm),500(jikken3) %
![]()
ls -l では、3つの時刻のうち、どの時刻が表示されているのかを調べなさい。 また、他の2つの時刻を表示させる方法を調べなさい。
myls-l の結果の表示形式は、ls -l と完全に一致しなくてもよい。たとえば、 時刻の表示は、上の ystat.c の結果と同じでもよい。ファイル名を先に、 時刻を後に表示してもよい。
時刻の扱いの回 で紹介したlocaltime() や strftime() ライブラリ関数を利用すると、時刻の 表示をより簡単に ls -l の表示に近づけることができる。
uid (st_uid) については、ls -l では、ユーザ名(ログイン名)で表示され る。この課題では、ユーザやグループは、数字のまま表示してもよい。 proc-uid-print.c にある uid2uname(), gid2gname() を利用すれば、 数字ではなく文字列で表示することができる。
プログラムの引数となるファイルの数は、1個とする。複数のファイルについ て、ls -l と同様の表示をするように拡張してもよい。
普通のファイル(「-」)とディレクトリ(「d」)を必ず扱えるようにする。それ 以外の型のファイルについては、扱えなくてもよい。
引数としてディレクトリの名前が与えられた場合にも、ディレクトリの内容で はなくディレクトリ自身の属性を表示する。シンボリック・リンクには対応し なくてもよい。(よって正確には、ls -l filename ではなく、ls -ldL filename である。)
余裕があれば、lstat() と readlink() の、2つのシステム・コールを用いて、 ls -l と同じようにシンボリック・リンクの内容を表示しなさい。
ヒント:struct stat の st_mode から rwxrwxrwx の表示を得るには、いくつ もの方法がある。たとえば、3ビットずつ表示する関数を3回繰り返す方法が ある。
print_rwx( st_modeの8ビット目から6ビット目を取り出す ); print_rwx( st_modeの5ビット目から3ビット目を取り出す ); print_rwx( st_modeの2ビット目から0ビット目を取り出す ); ... print_rwx( int rwx ) { if( 2ビット目が1なら ) printf("r") else printf("-") ... }特定のビットを取り出すには、ビットごとの AND (& 演算子) を 用いる。ビットの位置をずらすには、左シフト<< や右シフト >> を用いる。
その外に、S_IRUSR, S_IWUSR などのマクロを使う方法がある。
if( mode & S_IRUSR ) printf("r"); else printf("-"); if( mode & S_IWUSR ) printf("w"); ...利用可能なマクロについては、2 章の stat (man 2 stat) を見なさい。
% mychmod 755 filenameモードは、8進数で与えるものとする。引数として取ることができるファイル 名は、1つだけでよい。![]()
% mytouch filenameこの結果、ファイルの最終更新時刻が現在の時刻になる。![]()
ヒント:time(2) と utime(2) を使う。
ヒント:stat(2)システムコールで、コピー元ファイルとコピー先ファイル最 終更新時刻を調べる。もし、前者が新しければ、コピーする。後者が新しけれ ば、なにもしない。
この課題では、次のような属性を保存しなさい。
コピーする時に、内容をコピーした後に、コピー元のファイルに stat() を実 行する。こうして得られた属性を、chmod() や utime() でコピー先のファイ ルに設定する。