構造体の入力、ディレクトリの構造、リンク

システム・プログラムI

                                       電子・情報工学系
                                       新城 靖
                                       <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/syspro1-1997/1997-05-13
あるいは、次のページから手繰っていくこともできます。
http://www.hlla.is.tsukuba.ac.jp/~yas/coins/
http://www.hlla.is.tsukuba.ac.jp/~yas/index-j.html

■復習

■プログラムの断片

プログラムを書く時に、実際に動く小さいプログラムの断片は、便利である。 日頃から自分で収集するといい。

■構造体の入出力

■構造体の入出力

★練習問題(1)(再掲) whoプログラム

UNIXの who コマンドと似た動きをするプログラムを作りなさい。

■getdirentries(2)によるディレクトリの内容の取得

UNIXのディレクトリは、ディスク中では、可変長の構造体になっている。C言 語では、直接的には可変長の構造体を扱うことはできない。

HP-UX では、getdirentries(2) システム・コールを使うと、ディスク中に保 存されたディレクトリに近いデータを得ることができる。(システム・コール は、システムにより異なる。システムによっては、ディレクトリについても、 read() が使えるものがある。SunOS, Solaris では、getdents()。)


----------------------------------------------------------------------
   1:	/*
   2:	        dir-getdirentries.c -- ディレクトリの内容を表示するプログラム
   3:	        /usr/local/LECTURES/syspro-1997-shinjo/dir/dir-getdirentries.c
   4:	        $Header: dir-getdirentries.c,v 1.4 97/05/12 20:17:29 yas Exp $
   5:	        Start: 1995/03/07 21:44:51
   6:	*/
   7:	
   8:	#include <stdio.h>      /* fprintf(), stderr */
   9:	#include <fcntl.h>      /* open(2) */
  10:	#include <ndir.h>       /* getdirentries(2) */
  11:	
  12:	#if     0
  13:	struct  direct {
  14:	        long    d_fileno;               /* file number of entry */
  15:	        short   d_reclen;               /* length of this record */
  16:	        short   d_namlen;               /* length of string in d_name */
  17:	        char    d_name[MAXNAMLEN + 1];  /* name (up to MAXNAMLEN + 1) */
  18:	};
  19:	#endif  0
  20:	
  21:	main( argc,argv )
  22:	    int argc ;
  23:	    char *argv[] ;
  24:	{
  25:	        if( argc != 2 )
  26:	        {
  27:	            fprintf( stderr,"Usage:%% %s dirname \n",argv[0] );
  28:	            exit( 1 );
  29:	        }
  30:	        dir_list( argv[1] );
  31:	}
  32:	
  33:	#define BUFFSIZE        1024
  34:	
  35:	dir_list( dirname )
  36:	    char *dirname ;
  37:	{
  38:	    int fd ;
  39:	    struct direct *p ;
  40:	    char buff[BUFFSIZE] ;
  41:	    int rcount ;
  42:	    off_t offset ;
  43:	
  44:	        fd = open( dirname,O_RDONLY );
  45:	        if( fd == -1 )
  46:	        {
  47:	            perror( dirname );
  48:	            exit( 1 );
  49:	        }
  50:	
  51:	        offset = 0 ;
  52:	        while( (rcount=getdirentries(fd,buff,BUFFSIZE,&offset)) >0 )
  53:	        {
  54:	            xdump( buff, rcount );
  55:	            for( p = (struct dirent *)buff ; (char *)p < &buff[rcount] ;
  56:	                 p=(struct dirent *) ((int)p+(p->d_reclen)) )
  57:	            {
  58:	                printf("p:%d, ", (char *)p - buff );
  59:	                printf("fileno:%d, ", p->d_fileno );
  60:	                printf("reclen:%d, ", p->d_reclen );
  61:	                printf("namlen:%d, ", p->d_namlen );
  62:	                printf("name:%s\n", p->d_name );
  63:	            }
  64:	        }
  65:	        close( fd );
  66:	        free( buff );
  67:	}
  68:	
  69:	xdump( buff,n )
  70:	    unsigned char *buff ;
  71:	    int n ;
  72:	{
  73:	        if( n<0 || n>100000 )
  74:	            return;
  75:	        for( ; n>0 ; n-=16, buff+=16 )
  76:	            xdump16( buff,n>=16?16:n );
  77:	}
  78:	
  79:	xdump16( buff,n )
  80:	    register unsigned char *buff ;
  81:	{
  82:	    register int i ;
  83:	        for( i=0 ; i<n ; i++ )
  84:	            printf("%02x ",buff[i] );
  85:	        for( i=n ; i<16 ; i++ )
  86:	            printf("   ");
  87:	        for( i=0 ; i<n ; i++ )
  88:	            printf("%c",isprint(buff[i])?buff[i]:'#' );
  89:	        for( i=n ; i<16 ; i++ )
  90:	            printf(" ");
  91:	        printf("\n");
  92:	}
  93:	
----------------------------------------------------------------------

実行例。

----------------------------------------------------------------------
% ls -ia dir1 [←]
227366 .      184351 ..     227367 file1  227369 file2
% ./dir-getdirentries dir1 [←]
00 03 78 26 00 0c 00 01 2e 00 73 6c 00 02 d0 1f ##x&####.#sl####
00 0c 00 02 2e 2e 00 72 00 03 78 27 00 10 00 05 ####..#r##x’####
66 69 6c 65 31 00 78 64 00 03 78 29 00 10 00 05 file1#xd##x)####
66 69 6c 65 32 00 00 2f                         file2##/        
p:0, fileno:227366, reclen:12, namlen:1, name:.
p:12, fileno:184351, reclen:12, namlen:2, name:..
p:24, fileno:227367, reclen:16, namlen:5, name:file1
p:40, fileno:227369, reclen:16, namlen:5, name:file2
% []
----------------------------------------------------------------------

ディレクトリの構造

ディレクトリを読むためのライブラリ関数として、次のようなものがある。


----------------------------------------------------------------------
      #include <dirent.h>
      DIR *opendir(const char *dirname);
      struct dirent *readdir(DIR *dirp);
      long int telldir(DIR *dirp);
      void seekdir(DIR *dirp, long int loc);
      void rewinddir(DIR *dirp);
      int closedir(DIR *dirp);
----------------------------------------------------------------------

このライブラリ関数は、システム・コールと比較して移植性が高い。

★練習問題(2) lsプログラム

opendir(3) を用いて、次のような動きをするプログラムを作りなさい。

★練習問題(3) inode番号を表示するプログラム

次のような長いファイル名が与えられたとする。

/usr/local/bin/tcsh
これに対して、次のようにディレクトリとファイルのinode番号(fileno)を表 示するプログラムを作りなさい。

     2 /
     2 usr
 27650 local
 29966 bin
 29970 tcsh

この時、chdir(2) を使って、探すディレクトリを "." で参照できるようにし なさい。ディレクトリの内容の読み込みには、getdirentries(2) か、 opendir(3) を使いなさい。

■ディレクトリの作成

ディレクトリを作成するには、mkdir(2) システム・コールを用いる。 mkdir(1) コマンドは、mkdir(2) システム・コールを使って作られている。

----------------------------------------------------------------------
   1:	/*
   2:	        dir-mkdir.c -- ディレクトリを作成するプログラム
   3:	        /usr/local/LECTURES/syspro-1997-shinjo/dir/dir-mkdir.c
   4:	        $Header: dir-mkdir.c,v 1.1 97/05/12 21:29:36 yas Exp $
   5:	        Start: 1997/05/12 21:26:10
   6:	*/
   7:	
   8:	#include <stdio.h>
   9:	#include <sys/stat.h>
  10:	
  11:	main( argc,argv )
  12:	    int argc ;
  13:	    char *argv[] ;
  14:	{
  15:	        if( argc != 2 )
  16:	        {
  17:	            fprintf( stderr,"Usage:%% %s dirname \n",argv[0] );
  18:	            exit( 1 );
  19:	        }
  20:	        if( mkdir( argv[1],0777 ) == -1 )
  21:	        {
  22:	            perror( argv[1] );
  23:	        }
  24:	}
----------------------------------------------------------------------

単純に man mkdir と打つと、mkdir(1) コマンドのマニュアルが表示される。 mkdir(2) システム・コールを見るには、次のように打つ。
% man 2 mkdir [←]

★練習問題(4) ディレクトリの削除

rmdir(2) を使ってディレクトリを削除するプログラムを作りなさい。 ou

■リンク

UNIXでは、ファイル名は、枝(リンク)に付いている。UNIXでは、ファイルの 名前には、次の2種類がある。 本名は、ファイルの寿命と関係している。普通のファイルは、1である。複数 の本名を持っているファイルもある。

----------------------------------------------------------------------
% ls -li /usr/bin/{compress,uncompress,zcat} [←]
  4146 -r-xr-xr-x   3 bin      bin        24576 Nov  8  1993 /usr/bin/compress
  4146 -r-xr-xr-x   3 bin      bin        24576 Nov  8  1993 /usr/bin/uncompress
  4146 -r-xr-xr-x   3 bin      bin        24576 Nov  8  1993 /usr/bin/zcat
% []
----------------------------------------------------------------------

ディレクトリを stat(2) で調べると、リンク数が2以上になっている。 "." と 子ディレクトリの ".." の分だけ増えている。

本名は、open(), creat(), mkdir() の時に作られる。それ以外に、link() シ ステム・コールで増やすことができる。リンクを減らすには、unlink() シス テム・コールを使う。リンクが1つしかないファイルに unlink() を行うと、 ファイルが削除される。

シンボリック・リンクは、別のファイルの名前(シンボル)を含んでいる特殊 なファイルである。シンボリック・リンクは、symlink() システム・コールで 作成することができる。

★練習問題(5) リンクの作成

link(2) を使ってリンクを作成するプログラム(ln コマンドに似た動きをす るプログラム)を作りなさい。

★練習問題(6) リンクの削除

unlink(2) を使って、リンクを削除するプログラム(rm コマンドと似た動きを するプログラム)を作りなさい。

★練習問題(7) リンクの削除

symlink(2) を使って、シンボリック・リンクを作成するプログラム(ln -s と 似た動きをするプログラム)を作りなさい。

★練習問題(8) 名前の変更

rename(2) を使って、ファイルの名前を変えるするプログラム(mv コマンドと 似た動きをするプログラム)を作りなさい。

■lseek

UNIXでは、ファイルを read() したり write() したりするたびに、ファイル を読み書きする位置(先頭からのバイト数)が移動する。この位置は、シーク・ ポインタやファイル・ポインタと呼ばれる。(FILE *とは違うので注意。)

ファイルを先頭から順にアクセスするのではなく、ランダムにアクセスしたい 時には、まず読み書きしたい場所にシーク・ポインタを移動させる。これを行 うのが、lseek() システム・コールである。

off_t lseek(int fildes, off_t offset, int whence);
引数は、ファイル記述子、オフセット(バイト数)、移動方法である。移動方法 (whence)には、次の3種類のいずれかを指定する。
SEEK_SET
ファイルの先頭から offset バイト目に移動。
SEEK_CUR
現在の位置から offset バイト目移動。
SEEK_END
ファイルの末尾から offset バイト目移動。
ファイルの末尾から読むプログラムの例。

----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        wtmp-last10.c -- /etc/wtmpの最後の10個を表示するプログラム
   4:	        /usr/local/LECTURES/syspro-1997-shinjo/file/wtmp-last10.c
   5:	        $Header: wtmp-last10.c,v 1.2 97/05/12 22:20:46 yas Exp $
   6:	        Start: 1995/03/06 09:01:09
   7:	*/
   8:	
   9:	#include <stdio.h>      /* FILE, NULL, stderr */
  10:	#include <sys/types.h>  /* struct utmp */
  11:	#include <utmp.h>       /* struct utmp */
  12:	#include <fcntl.h>      /* open() */
  13:	#include <unistd.h>     /* lseek() */
  14:	
  15:	main()
  16:	{
  17:	        wtmp_last10_print_file();
  18:	}
  19:	
  20:	wtmp_last10_print_file()
  21:	{
  22:	    struct utmp  entry;
  23:	    int fd, i ;
  24:	    off_t offset ;
  25:	
  26:	        if( (fd = open(  WTMP_FILE, O_RDONLY )) == -1 )
  27:	        {
  28:	            perror( WTMP_FILE );
  29:	            exit( -1 );
  30:	        }
  31:	        for( i=-1 ; i>-10; i-- )
  32:	        {
  33:	            offset = sizeof(struct utmp)*i ;
  34:	
  35:	            if( lseek( fd, offset, SEEK_END ) == -1 )
  36:	            {
  37:	                perror("lseek");
  38:	                exit( -1 );
  39:	            }
  40:	            if( read(fd, &entry, sizeof(entry)) != sizeof(entry) )
  41:	            {
  42:	                perror("read");
  43:	                exit( -1 );
  44:	            }
  45:	            utmp_print( &entry );
  46:	        }
  47:	        close( fd );
  48:	}
  49:	
  50:	utmp_print( u )
  51:	    register struct utmp *u ;
  52:	{
  53:	        print_char_array( u->ut_user, sizeof(u->ut_user) );
  54:	        print_char_array( u->ut_id, sizeof(u->ut_id) );
  55:	        print_char_array( u->ut_line, sizeof(u->ut_line) );
  56:	        printf("%d ", u->ut_type );
  57:	        printf("%6d ", u->ut_pid );
  58:	        print_char_array( u->ut_host, sizeof(u->ut_host) );
  59:	        printf("%s",ctime(&u->ut_time) );
  60:	}
  61:	
  62:	print_char_array( a,len )
  63:	    char a[];
  64:	    int len ;
  65:	{
  66:	    int i ;
  67:	        for( i=0 ; i<len && a[i] ; i++ )
  68:	            putchar( a[i] );
  69:	        for( ; i<len ; i++ )
  70:	            putchar(' ');
  71:	        putchar(' ');
  72:	}
----------------------------------------------------------------------

実行例。
----------------------------------------------------------------------
% ./wtmp-last10 [←]
yas      s0   pty/ttys0    8  18905                  Mon May 12 22:40:28 1997
yas      s0   pty/ttys0    7  18905 orchid           Mon May 12 22:39:13 1997
LOGIN    s0   pty/ttys0    6  18905 orchid           Mon May 12 22:39:12 1997
xxxx     s0   pty/ttys0    8  18875                  Mon May 12 20:33:21 1997
xxxx     s0   pty/ttys0    7  18875                  Mon May 12 20:31:24 1997
LOGIN    s0   pty/ttys0    6  18875                  Mon May 12 20:31:23 1997
xxxx     s0   pty/ttys0    8  18848                  Mon May 12 20:05:42 1997
xxxx     s0   pty/ttys0    7  18848                  Mon May 12 20:02:02 1997
LOGIN    s0   pty/ttys0    6  18848                  Mon May 12 20:02:01 1997
% last | head -10 [←]
yas      pty/ttys0    Mon May 12 22:39 - 22:40  (00:01)
xxxx     pty/ttys0    Mon May 12 20:31 - 20:33  (00:01)
xxxx     pty/ttys0    Mon May 12 20:02 - 20:05  (00:03)
xxxx     pty/ttys0    Mon May 12 18:56 - 18:58  (00:01)
e999999  console      Mon May 12 18:41 - 19:11  (00:29)
e999999  console      Mon May 12 18:32 - 18:41  (00:08)
i999999  pty/ttys0    Mon May 12 17:40 - 17:41  (00:01)
e999999  console      Mon May 12 17:26 - 18:32  (01:05)
i999999  pty/ttys2    Mon May 12 16:42 - 17:37  (00:54)
xxxx     pty/ttys1    Mon May 12 16:26 - 18:02  (01:35)
% who [←]
yas        pty/ttysb    May 12 22:39
% []
----------------------------------------------------------------------

★練習問題(9) lastコマンド

last コマンドと似た動きをするプログラムを作りなさい。

■mmap

ファイルの内容を操作する方法として、read(), write() とは全く違った方法 がある。それは、ファイルの内容をメモリにマップ(map, 張り付ける)し、 ファイルの内容を配列のようにして扱う方法である。


----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        mmap-cat.c -- mmap(2) を使った cat プログラム
   4:	        $Header: mmap-cat.c,v 1.1 97/05/12 22:56:02 yas Exp $
   5:	        /usr/local/LECTURES/syspro-1997-shinjo/file/wtmp-last10.c
   6:	        Start: 1997/05/12 17:15:57
   7:	*/
   8:	
   9:	#include <stdio.h>      /* stderr */
  10:	#include <sys/types.h>  /* open(),mmap() */
  11:	#include <sys/stat.h>   /* open() */
  12:	#include <fcntl.h>      /* open() */
  13:	#include <sys/mman.h>   /* mmap() */
  14:	#include <unistd.h>     /* sysconf() */
  15:	
  16:	extern char *mmap_file();
  17:	
  18:	main( argc,argv )
  19:	    int argc ;
  20:	    char *argv[] ;
  21:	{
  22:	        if( argc != 2 )
  23:	        {
  24:	            fprintf( stderr,"Usage: %% %s filename\n",argv[0] );
  25:	            exit( 1 );
  26:	        }
  27:	        mmap_cat( argv[1] );
  28:	}
  29:	
  30:	#define min( x, y )     ((x)<=(y)?(x):(y))
  31:	
  32:	mmap_cat( filename )
  33:	    char *filename ;
  34:	{
  35:	    int pagesize,rem,wcount ;
  36:	    size_t size,pos ;   /* unsigned int */
  37:	    char *file_address ;
  38:	
  39:	        file_address = mmap_file( filename,&size );
  40:	        if( ((int)file_address) == -1 )
  41:	        {
  42:	            perror( filename );
  43:	            exit( 1 );
  44:	        }
  45:	        pagesize = sysconf( _SC_PAGE_SIZE );
  46:	        for( pos = 0 ; pos<size ; pos += pagesize )
  47:	        {
  48:	            rem = min( pagesize, size );
  49:	            if( (wcount = write( 1, &file_address[pos], rem )) != rem )
  50:	            {
  51:	                perror("write");
  52:	                exit( 1 );
  53:	            }
  54:	        }
  55:	}
  56:	
  57:	char *mmap_file( filename,filesizep )
  58:	    char *filename ;    /* in */
  59:	    size_t *filesizep ; /* out */
  60:	{
  61:	    int fd ;
  62:	    struct stat buf ;
  63:	    size_t size,p ;     /* unsigned int */
  64:	    off_t off ;         /* long */
  65:	    caddr_t addr ;      /* char * */
  66:	        if( (fd = open( filename,O_RDONLY )) == -1 )
  67:	            return( (char *)-1 );
  68:	        fstat( fd, &buf );
  69:	        size = buf.st_size ;
  70:	        addr = 0 ;      off = 0 ;
  71:	        addr = mmap( addr, size, PROT_READ, MAP_PRIVATE, fd, off );
  72:	        if( (int)addr == -1 )
  73:	        {
  74:	            close( fd );
  75:	            return( (char *)-1 );
  76:	        }
  77:	        close( fd );
  78:	        if( filesizep )
  79:	            *filesizep = size ;
  80:	        return( addr );
  81:	}
----------------------------------------------------------------------

実行例。
----------------------------------------------------------------------
% ls -l /hp-ux  [←]
-rwxr-xr-x   1 root     other    2297856 Jun 16  1995 /hp-ux
% []
% time ./mmap-cat /hp-ux > /dev/null [←]
0.03u 0.06s 0:00.09 100.0%
% time ./mmap-cat /hp-ux > /dev/null [←]
0.01u 0.07s 0:00.00 0.0%
% time ./mmap-cat /hp-ux > /dev/null [←]
0.04u 0.04s 0:00.08 100.0%
% time ./mmap-cat /hp-ux > /dev/null [←]
0.01u 0.07s 0:00.08 100.0%
% time cat /hp-ux > /dev/null [←]
0.05u 0.33s 0:02.33 16.3%
% time cat /hp-ux > /dev/null [←]
0.02u 0.19s 0:00.33 63.6%
% time cat /hp-ux > /dev/null [←]
0.04u 0.18s 0:00.22 100.0%
% []
----------------------------------------------------------------------
mmap() の方が速いのは、なぜか。

★[report] report-5,ディレクトリまたはwho/last

練習問題(1),(2),(3),(9) の中から1つ以上選んでやりなさい。 問題を難しい方に変更してもよい。

■付録mnewsの使い方

ニュース・グループ coins.lectures.system-program-1 を見るには、mnews 便利である。mnews を実行するには、端末(ktermなど)で次のように打つ。
----------------------------------------------------------------------
% mnews [←]
----------------------------------------------------------------------

◆ 常に使えるキー

----------------------------------------------------------------------
キー            動き  
----------------------------------------------------------------------
q               抜ける 
?               ヘルプの表示(表示の終了はq) 
----------------------------------------------------------------------

◆ なにかを選ぶ時に使えるキー (ニュー スカテゴリ/グループ選択モード、ニュース記事選択モード、メールボックス)

----------------------------------------------------------------------
キー            動き            |       キー            動き
----------------------------------------------------------------------
Q               終了            |       SPACE,i,→      入る(参照)
?               ヘルプ          |       o,q,←          抜ける(復帰)
j,^N, ↓        次の行へ移動    |       k,^P, ↑        前の行へ移動
m               電子メールを出す|       a               記事を出す
<               最初の記事へ    |       >               最後の記事へ
----------------------------------------------------------------------

◆電子メールや 記事を読んでいる時に使え るキー(ページャ・モード)

----------------------------------------------------------------------
キー            動き            |       キー            動き
----------------------------------------------------------------------
SPACE           1ページ進める  |       b               1ページ戻す
j               1行進める      |       k               1行戻す
q               ページャの終了  |       ?               ヘルプの表示
n               次の未読記事へ  |       p               前の記事へ
r,R             メールで返事    |       f,F             フォローアップ
----------------------------------------------------------------------


↑[もどる] ←[5月06日] ・[5月13日] →[5月20日]
Last updated: 1997/05/12 23:35:01
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>