システムプログラム(第8週): ネットワーク・プログラミング/UDP、アクセス制御


電子・情報工学系/システム情報工学研究科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/

連絡

Rubyの作者、まつもとゆきひろさんによる集中講義があります。

今日の重要な話

UDP/IP によるネットワーク・プログラミング ファイルに対するアクセス制御

復習

UDP/IPの考え方

UDP/IP は、IP と同じく、 データグラム転送サービス を提供する。データグラムでは、ストリーム(TCP/IP)とは異なり、通信路を 開設することなく、アドレスを指定してデータを送る。UDP/IP では、アドレ スとしては、次の2つを使う。
  1. IP アドレス(IPv4 で 32 ビット、IPv6 で 128 ビット)
  2. ポート番号(16ビット)

IP 層では、アドレスとしては、IP アドレスだけを使う。UDP/IP は、IP 層の 機能をほとんどそのままの形で利用可能にしたものである。違いは、ポート番 号を指定できることと、チェックサムでデータの内容を検査できることなどで ある。

UDP/IP でも、クライアント側のアドレスは、OSに任せ、自らは指 定しないことが多い(指定することもできる)。

UDP/IP でも、通信パタンによる クライアント・サーバ・モデル に基づくことが多い。

図? UDP/IPによるクライアント・サーバ型の通信

図? UDP/IPによるクライアント・サーバ型の通信

UDPの特徴

TCP/IP で提供されているストリームと比較して、UDP/IP で提供されているデー タグラムには次のような特徴がある。 UDP/IP では、connect() しなければ、1つのソケットで、複数の通信相手と データを送受信することができる。通信相手は、sendto() で指定し、 recvfrom() で任意の場所から受信することが普通である。connect() システ ムコールを使うと、特定の相手としか通信できなくなるが、connect() を使う 方法は、UDP/IP のプログラムの書き方としては一般的ではない。

telnet

telnet は、TCP/IP の汎用のクライアント(文字列の送受信用)として使える。 しかし、UDP/IP 用のクライアントとしては使えない。

echo-client-udp

UDP/IP のポート番号 7 (echo) では、受け取ったデータをそのまま返すサー ビスを提供している。ただし、セキュリティ上の理由から、echo サービスを 停止することが、一般的である。以下は、このサービスを利用するクライアン トである。
   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 . [←]
% 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
% []
このプログラムは、コマンドラインから2の引数をとる。第1引数は、ホスト 名、第2引数は、ポート番号である。このプログラムは、標準入力から得られ た行を、その指定されたホスト上のポート番号で動作しているサーバに対して 送る。サーバは、 同じものを送り返してくるので、それを受け取る。

echo-server-udp.c

UDP/IP のポート番号 7 (echo) では、受け取ったデータをそのまま返すサー ビスを提供している。以下は、これと同じような機能を提供するサーバである。
   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 [←]
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
% []
サーバ側では、bind() をつかっているので、最初からポート番号が固定され ていることがわかる。

ユーザとグループ

ユーザ

ユーザ(user, 利用者)とは、コンピュータの外の世界では、コンピュータを使 う人間のことである。コンピュータの内部(Unix)では、人間を ユーザ名(user name) という文字列、または、それとほぼ1対1に対応した16ビットの整数(Linuxでは 32ビット)で表す。この数を UID(user ID, user identifier) という。

Unixでは、全てのファイルやプロセスは、あるユーザの所有物である。これを、 Unixでは、ファイルとプロセスに属性として、UID を持たせることで実現して いる。

図? 実世界のユーザとUnix中のプロセス・ファイル

図? 実世界のユーザとUnix中のプロセス・ファイル

グループ

グループ(group)とは、コンピュータの世界では、 コンピュータ を使う人間の集合 のことである。コンピュータの内部では、ユーザ名/UIDと同様に、 グループ名(group name) と呼ばれる文字列、または、それとほぼ1対1に対応した16ビットの整数 (Linuxでは32ビット)で表す。この数を GID(group ID, group identifier) と言う。

1人のユーザが複数のグループに属することがある。

ファイルの属性とls -l

ファイルには、内容の他に属性(attribute)がある。ファイルの属性は、ls -l で表示される。
% ls -l proc-uid-print.c [←]
-rw-r--r--    1 yas      lab          1769 Jun 16 22:37 proc-uid-print.c
% []
行の左から、型とモード、リンク数、ユーザ名(所有者)、グループ名、大き さ、更新時刻、名前が表示されている。

ファイルの型

ls -l で、一番左の文字は、 ファイルの型 を表わしている。 -の場合は普通のファイル、 dの場合はディレクトリを意味する。

許可されたアクセス方法(モード)

ls -l で、2文字目から9文字目までは、アクセスの可否を決めるための情報で ある。この9文字を、モードという(型まで含めてモードと呼ぶこともある)。 9文字は、3文字の固まりが3組に分けられる。

1つのブロックの中の3文字はアクセス毎にその許可・拒否を表す。

r	読込み可
w	書込み可
x	実行可(ディレクトリの場合は探索可)
9文字のうち、該当する部分が「-」の場合は、その種類のアクセスが許可さ れてないことを意味する。

「読込み可」とは、その内容を参照することができるという意味する。たとえ ば、読み出し可能なファイルは、cp コマンドでコピーしたり、less で内容を 表示することができる。読出し可能なディレクトリなら、ls コマンドでその ディレクトリ中のファイル名の一覧を表示することができる。

「書込み可」とは、その内容を変更することができるという意味です。たとえ ば、テキスト・ファイルなら、エディタで修正したものを書き込むことができ る。書込み可能なディレクトリなら、mv コマンドでそのディレクトリのなか にあるファイル名前を変更することができる。

「実行可」というのは、ファイルの内容がプログラムの場合は、 そのプログラムを実行することができることを意味する。

「検索可」というのは、その下にあるファイルやディレクトリを たぐることができることを意味する。 ディレクトリが「読込み可」でも、「検索可」でないと、 ディレクトリに「読込み可」のファイルがあっても、 ディレクトリ以下のファイルを読むことができない。 逆に、「検索可」でも、ディレクトリが「読込み可」でないと、 ディレクトリにあるファイル名やディレクトリ名を ls で表示させることが できないが、そのディレクトリにあるファイル名を知っていて、 そのファイルが「読み込み可」なら less などで表示させることはできる。

あるファイルを、特定の人にだけ特定のアクセスの方法をさせたいことがある。 このために、そのために、rwxの指定は、ファイルの所有者、ファイルの属す グループ、それ以外の人用に3セット用意されている。

たとえば、モードが「rw-r--r--」のファイルは、次のようなことを意味する。

ファイルの所有者
読み書きはできるが、実行はできない
ファイルの属すグループに属する人
読めるが書いたり実行したりはできない
その他の人
読めるが書いたり実行したりはできない

リンク数

Unix では、木構造を用いてファイルに名前を付ける。 リンク数のリンクとは、木構造の枝、つまり、 ファイルの名前という意味である。 リンク数は、ファイルの名前のうち、実リンクの数を示す。

普通のファイルは、多くの場合、リンク数が1になっている。ディレクトリは、 「.」や「..」でもアクセスできるので、リンク数が2以上になっている。

所有者

ls -l の表示の中で、左から3番目の固まり表示されるのが、ファイルやディ レクトリの 所有者owner ) である。

ファイルの属性としては、UID で保存されているが、ls - l では、それをユー ザ名に変換して表示する。

グループ名

UNIXでは複数のユーザが属す グループgroup ) というものを設定できる。 あるファイルは、必ずどれか一つのグループに所属する。 ファイルの属性としては、GID で保存されているが、ls -l では、それをグルー プ名に変換して表示する。

大きさ

ls -l の5番目の桁は、 ファイルの大きさ である。これは、 ファイルの内容をバイト数で数えた値である。

時刻

UNIXのファイルの属性には次の3種類の時刻が記録されている。
最終アクセス時刻 (the last access time)
ファイルの「内容」が最後にアクセス(読み込み)された時刻。 ls -lu で表示される。
最終更新時刻 (the modification time)
ファイルの「内容」が最後に変更(書き込み)された時刻。 ls -l で表示される「時刻」。 単に「ファイルの時刻」といった場合にはこの時刻を指す。
最終変更時刻 (the status change time)
ファイルの「属性」が最後に変更された時刻。 ls -lcで表示される「時刻」。 時刻も属性の1つなので、「最終更新時刻」を変更すると、 「最終変更時刻」も変更される。

ファイルの属性の変更

ファイルとディレクトリの属性のうち以下のものは変更できる ただし、所有者を変更するには、以下で説明する スーパーユーザ(root)の 権限が必要である。

ファイルの属性と stat システムコール

UNIXでは、stat() (あるいは、lstat(), fstat())システム・コールを 用いてファイルの属性を調べることができる。 次のプログラム ystat.c は、ファイルの属性を調べ、画面に出力するプログ ラムである。
   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  [←]
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 */
	 };
...
この構造体を使う限り、移植性がある(portable)プログラムが書ける。実際の システムでは、もう少し複雑な構造をしていることがある。たとえば、Linux では、/usr/include/bits/stat.h に定義がある。

% 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
% []

この実行結果から次のようなことがわかる。
  1. ファイル名は、ystat.c である。
  2. 存在するデバイスは、メジャー番号0, マイナー番号9で識別される。
  3. アイノード番号は、81494554 である。
  4. モードは、0100644 である。
  5. リンク数(本名の数)は、1である。
  6. ファイルの所有者のIDは、1013である。
  7. ファイルのグループは、40である。
  8. デバイスの識別子は、メジャー番号0, マイナー番号0である。 (この場合無効。デバイス・ファイルのみ有効。)
  9. ファイルの大きさは、1183バイトである。
  10. 入出力に適したブロックサイズは、4096バイトである。
  11. ディスク中で実際に消費しているのは、8ブロックである。
  12. 最終アクセス時刻は、atime: Sat Jun 11 19:19:38 2005 である。
  13. 最終更新時刻は、mtime: Sat Jun 11 19:17:58 2005 である。
  14. 最終変更時刻は、Sat Jun 11 19:17:58 2005 である。
アイノード番号(i-node number)とは、オペレーティング・システムの内部で 使われているファイルを区別するための番号である。この番号さえわかれば、 オペレーティング・システムはファイルの属性や内容をアクセスできる。オペ レーティング・システムにとっては、可変長の名前よりも固定長のアイノード 番号の方が扱いやすい。

ここで、モードが8進数で 0100644 (C言語の文法で、0から始まる数は、8進 数)であることから、ファイルの型(普通のファイルかディレクトリかという 情報)を調べることができる。

図? Unixのファイルの属性

図? Unixのファイルの属性

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 が付いている。この属性は、ファイルを open() する時に内部的に使われる。

プロセスの 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)
% []

最初の行は、各自異なるはずである。

アクセス制御とは

アクセス制御(access control) とは、「主体」が、「オブジェクト」を「操作」する時、どんな操作なら正し いということを定義して、それがきちんと守られていることをということを保 証することである。

図? 図? アクセス制御における主体、オブジェクト、および、操作

図? アクセス制御における主体、オブジェクト、および、操作

Unixにおけるアクセス制御とは

Unixでは、アクセス制御の主体は、プロセスである。 アクセス制御のオブジェクトは、ファイル、または、プロセスである。 操作には、次のようなものがある。

Unixは、マルチユーザのシステムなので、ユーザ1人ひとりを認識するような アクセス制御の仕組みを持っている。たとえば、Unix では、共同プロジェク トに関連したファイルは、他のユーザにも見せてもよいが、個人の電子メール は、他の人には見せないといった制御ができる。

これに対して、古いパソコン用のOS(Windows 95/98/ME, MacOS 9以下) では、このような複数のユーザを識別したアクセス制御は、できない。 Windows NT, Windows 2000, Windows XP, MacOS X は、可能である。

ファイルに対するアクセス制御

UNIXでは、ファイルの「内容」のアクセス制御を次の3段階で行う。
ユーザ
ファイルのUID(所有者)が、プロセスのUIDと同じ
グループ
ファイルのGIDが、プロセスのGIDのリストのどれかと同じ
その他
上の2つに当てはまらない時
これをつかって、モード属性の下位9ビットのうち、どの3ビットを使うかを 決める。そして、そのビットが1になっていれば、その操作が許される。

ファイルの「内容」のアクセス3段階であるが、ファイルの「属性」次の2段 階である。

ユーザ
ファイルのUID(所有者)が、プロセスのUIDと同じ
それ以外
ファイルのUID(所有者)が、プロセスのUIDと異なる
ユーザの権限では、ファイルの属性(モード、グループ、時刻)を変更する ことができる。それ以外の権 限では、属性を読み出すことはできるが、変更は一切できない。 つまり、ファイルの内容がアクセスできなくても、ls -l, stat(2) で 属性を調べることはでる。

プロセスに対するアクセス制御

プロセスのアクセス制御は、次の2段階で行なう。
同一ユーザ
操作対象のプロセスが、操作するプロセスのUIDと一致している。
それ以外
操作対象のプロセスが、操作するプロセスのUIDと一致していない。
プロセスの操作としては、シグナルを送ることができるかどうか (kill() システムコール) と、デバッガで デバッグすることができるとか(ptrace() システムコール)、トレースを調べることができるかなどが ある。それらの操作は、同一ユーザの場合 許され、そうではない場合は、許されない。

スーパーユーザ(root)

umaskの影響

練習問題

練習問題(62) Javaによるecho-client-udpの実現

echo-client-udp.c を Java で書き直しなさい。

ヒント:DatagramSocket() を使う。

練習問題(63) Javaによるecho-server-udpの実現

echo-server-udp.c を Java で書き直しなさい。

練習問題(64) UDP/IPで送れるデータの大きさの上限

UDP/IP では、一度に送ることができるデータの大きさにには、実装上の制限 が付いている。これがいくつかを調べるプログラムを作りなさい。

練習問題(65) udprelay

UDP/IP のデータを中継するようなプログラムを作りなさい。 単方向だけでなく、双方向で中継するようにしなさい。

このようなプログラムの例として、udprelay と呼ばれるプログラムがある。

練習問題(66) UDP/IPでのアクセス制御

UDP/IP のサーバ、または、中継プログラムで、クライアントの IP アドレス によってアクセスを許したりエラーを発生させたりしなさい。

練習問題(67) UDP/IPによるサービスの利用

次の UDP/IP で提供されているサービスを利用するクライアントを作成しなさい。
daytime (13, Daytime Protocol)
rfc867.txt
time (37, Time Protocol)
rfc868.txt
NTP (123, Network Time Protocol)
rfc1305.tx
DNS (53, Domain Name System)
rfc1034.txt, rfc1035.txt
それぞれのプロトコルでは、次のホストで動いているサーバを使いなさい。
daytime, time
adonis1-adonis45, azalea1-azalea45, localhost
NTP
orchid-a
DNS
orchid-a, orchid-b

練習問題(68) idコマンド

id コマンドと似たような動きをするコマンドを作りなさい。id コマンドは、 次のように、プロセスの UID, GID を表示するコマンドである。
% id [←]
uid=1013(yas) gid=40(lab) groups=40(lab),510(softadm),500(jikken3)
% []
ここで、uid は、getuid()、 gid は、getgid()、groups は、getgroups() シ ステムコールの結果である。getgroups() では、複数の GID が返される。

練習問題(69) ls-lプログラム

stat() システム・コールを用いて ls -l filename と似たような結果を出力 するプログラムを作りなさい。このプログラムの名前を myls-l とする。

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) を見なさい。

練習問題(70) chmod プログラム

ファイルのモードを変更するコマンド chmod に似たコマンドを作りなさい。
% mychmod 755 filename [←]
モードは、8進数で与えるものとする。引数として取ることができるファイル 名は、1つだけでよい。

練習問題(71) touch プログラム

ファイルの時刻を変更するコマンド touch に似たコマンドを作りなさい。
% mytouch filename [←]
この結果、ファイルの最終更新時刻が現在の時刻になる。

ヒント:time(2) と utime(2) を使う。

練習問題(72) 条件コピー

ファイルが更新されていた時だけコピーするようなプログラムを作りなさい。

ヒント:stat(2)システムコールで、コピー元ファイルとコピー先ファイル最 終更新時刻を調べる。もし、前者が新しければ、コピーする。後者が新しけれ ば、なにもしない。

練習問題(73) 属性まで含んだコピー

cp -p (Preserve) と同じように、ファイルの内容に加えていくつかの属性を 保存しながらファイルをコピーするプログラムをつくりなさい。

この課題では、次のような属性を保存しなさい。

UID (owner) や GID については、保存しなくてもよい。UID の保存は、 スーパーユーザしかできない。 スーパーユーザが実行した時には、保存できるようなプログラムを作成しても よい。一般ユーザで実行した時には、エラーを無視してよい。

コピーする時に、内容をコピーした後に、コピー元のファイルに stat() を実 行する。こうして得られた属性を、chmod() や utime() でコピー先のファイ ルに設定する。


Last updated: 2005/06/25 00:49:04
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>