構造体の入出力、ランダムアクセス、ディレクトリの構造

システム・プログラムI

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

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

■復習

例題のファイルは、簡単にコピーできる。

ディレクトリを作ろう。

■構造体の入出力

fread(), fwrite() を使う。

ポータビリティ(別のOSやCPUのコンピュータでも使う)を考える時には、 文字コード、バイトオーダー、浮動小数点の形式、構造体の穴に注意する。 XDR ライブラリを使えば、ポータビリティが高くなる。(XDR については、3 学期の「分散システム」で。)

 
----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        utmp-print.c -- /var/adm/utmp の内容を表示するプログラム
   4:	        ~/syspro1/file/utmp-print.c
   5:	        $Header: /home/lab2/OS/yas/syspro1-1998/file/RCS/utmp-print.c,v 1.4 1998/05/11 14:54:49 yas Exp $
   6:	        Start: 1995/03/06 09:01:09
   7:	*/
   8:	
   9:	#include <stdio.h>      /* FILE, NULL, stderr */
  10:	#include <utmp.h>       /* struct utmp */
  11:	#include <time.h>       /* ctime() */
  12:	
  13:	#if     0
  14:	struct utmp
  15:	{
  16:	    char ut_user[8] ;           /* User login name */
  17:	    char ut_id[4] ;             /* /etc/inittab id(usually line #) */
  18:	    char ut_line[12] ;          /* device name (console, lnxx) */
  19:	    short ut_pid ;              /* leave short for compatiblity - process id */
  20:	    short ut_type ;             /* type of entry */
  21:	    struct exit_status ut_exit ;/* The exit status of a process
  22:	                                 * marked as DEAD_PROCESS.
  23:	                                 */
  24:	    time_t ut_time ;            /* time entry was made */
  25:	} ;
  26:	#endif
  27:	
  28:	void utmp_print_file();
  29:	void utmp_print( struct utmp *u );
  30:	void print_char_array( char a[], int len );
  31:	
  32:	main()
  33:	{
  34:	        utmp_print_file();
  35:	}
  36:	
  37:	void utmp_print_file()
  38:	{
  39:	    struct utmp entry;
  40:	    FILE        *fp ;
  41:	        fp = fopen(  UTMP_FILE, "r" );
  42:	        if( fp == NULL )
  43:	        {
  44:	            perror( UTMP_FILE );
  45:	            exit( -1 );
  46:	        }
  47:	        while( fread(&entry, sizeof(entry),1,fp ) == 1 )
  48:	        {
  49:	            utmp_print( &entry );
  50:	        }
  51:	        fclose( fp );
  52:	}
  53:	
  54:	void utmp_print( struct utmp *u )
  55:	{
  56:	        print_char_array( u->ut_user, sizeof(u->ut_user) );
  57:	        print_char_array( u->ut_id, sizeof(u->ut_id) );
  58:	        print_char_array( u->ut_line, sizeof(u->ut_line) );
  59:	        printf("%6d ", u->ut_pid );
  60:	        printf("%6d ", u->ut_type );
  61:	        printf("%s",ctime(&u->ut_time) );
  62:	}
  63:	
  64:	void print_char_array( char a[], 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:	}
----------------------------------------------------------------------
 
実行結果

----------------------------------------------------------------------
% ./utmp-print  [←]
              system boot       0      2 Sun Apr 26 05:32:18 1998
              run-level 2       0      1 Sun Apr 26 05:32:18 1998
lnsyscon link                  32      8 Sun Apr 26 05:32:18 1998
rc2      s2                    35      8 Sun Apr 26 05:33:27 1998
LOGIN    t1   ttyd1           703      6 Sun Apr 26 05:33:28 1998
rlogin   q0   ttyq0         11481      8 Mon May 11 23:24:53 1998
yas      q1   ttyq1         11506      7 Mon May 11 23:24:45 1998
yas      q2   ttyq2         11521      7 Mon May 11 23:24:59 1998
i97????  q3   ttyq3           265      8 Sat May  9 18:01:24 1998
t???     q4   ttyq4         28706      8 Wed May  6 13:55:54 1998
s???     q5   ttyq5          2818      8 Sun Apr 26 16:40:12 1998
s???     q6   ttyq6          2889      8 Sun Apr 26 17:05:52 1998
s???     q7   ttyq7          2890      8 Sun Apr 26 17:05:52 1998
% who [←]
yas        ttyq1         5月 11日 23時24分
yas        ttyq2         5月 11日 23時24分
% []
----------------------------------------------------------------------

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

実行例。
----------------------------------------------------------------------
% ./wtmp-last10 [←]
yas      q4   ttyq4         16360      7 Tue May 12 00:07:23 1998
rlogin   q4   ttyq4         16360      6 Tue May 12 00:07:23 1998
telnet   q3   ttyq3         16201      8 Mon May 11 23:47:57 1998
e96????  q3   ttyq3         16202      7 Mon May 11 23:43:51 1998
telnet   q3   ttyq3         16202      6 Mon May 11 23:43:47 1998
a???     q1   ttyq1         16110      7 Mon May 11 23:33:52 1998
telnet   q1   ttyq1         16110      6 Mon May 11 23:33:49 1998
telnet   q1   ttyq1         16063      8 Mon May 11 23:33:43 1998
a???     q1   ttyq1         16064      7 Mon May 11 23:08:18 1998
% last | head -10 [←]
yas      ttyq4        hlla-gw.hlla.is.tsuku Tue May 12 00:07   still logged in
e96????  ttyq3        130.158.125.185       Mon May 11 23:43 - 23:47  (00:04)
a???     ttyq1        AMIcc-01p08.ppp.xxxxx Mon May 11 23:33   still logged in
a???     ttyq1        AMIcc-01p08.ppp.xxxxx Mon May 11 23:08 - 23:33  (00:25)
k?????   ttyq0        balsam-serv           Mon May 11 22:31   still logged in
t????    ttyq1        adonis7               Mon May 11 21:49 - 21:49  (00:00)
to????   ttyq0        icho.ipe.tsukuba.ac.j Mon May 11 21:38 - 22:01  (00:23)
m????    ttyq0        azalea11              Mon May 11 21:06 - 21:17  (00:11)
to????   ttyq4        icho.ipe.tsukuba.ac.j Mon May 11 19:06 - 19:09  (00:03)
m????    ttyq4        azalea11              Mon May 11 18:01 - 18:07  (00:05)
% []
----------------------------------------------------------------------

■mmap

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


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

実行例。
----------------------------------------------------------------------
% ./mmap-head /etc/passwd  [←]
root:aBcDefGhijklmN:0:0:Super-User:/:/bin/csh
sysadm:*:0:0:System V Administration:/usr/admin:/bin/sh
diag:*:0:996:Hardware Diagnostics:/usr/diags:/bin/csh
daemon:*:1:1:daemons:/:/dev/null
bin:*:2:2:System Tools Owner:/bin:/dev/null
uucp:*:3:5:UUCP Owner:/usr/lib/uucp:/bin/csh
sys:*:4:0:System Activity Owner:/var/adm:/bin/sh
adm:*:5:3:Accounting Files Owner:/var/adm:/bin/sh
lp:*:9:9:Print Spooler Owner:/var/spool/lp:/bin/sh
nuucp:*:10:10:Remote UUCP User:/var/spool/uucppublic:/usr/lib/uucp/uucico
% []
----------------------------------------------------------------------

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

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

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


----------------------------------------------------------------------
   1:	/*
   2:	        dir-getdents.c -- ディレクトリの内容を表示するプログラム
   3:	        ~yas/syspro1/dir/dir-getdents.c
   4:	        $Header: /home/lab2/OS/yas/syspro1/dir/RCS/dir-getdents.c,v 1.2 1998/05/11 17:18:01 yas Exp $
   5:	        Start: 1995/03/07 21:44:51
   6:	*/
   7:	
   8:	#include <stdio.h>      /* fprintf(), stderr */
   9:	#include <sys/types.h>  /* open(2) */
  10:	#include <sys/stat.h>   /* open(2) */
  11:	#include <fcntl.h>      /* open(2) */
  12:	#include <sys/dirent.h> /* getdents(2) */
  13:	
  14:	#if     0
  15:	typedef struct dirent {                 /* data from readdir() */
  16:	        ino_t           d_ino;          /* inode number of entry */
  17:	        off_t           d_off;          /* offset of disk directory entry */
  18:	        unsigned short  d_reclen;       /* length of this record */
  19:	        char            d_name[1];      /* name of file */
  20:	} dirent_t;
  21:	#endif
  22:	
  23:	
  24:	extern  void dir_list( char *dirname );
  25:	extern  void xdump( unsigned char *buff, int n );
  26:	extern  void xdump16( unsigned char *buff, int n );
  27:	
  28:	main( int argc, char *argv[] )
  29:	{
  30:	        if( argc != 2 )
  31:	        {
  32:	            fprintf( stderr,"Usage:%% %s dirname \n",argv[0] );
  33:	            exit( 1 );
  34:	        }
  35:	        dir_list( argv[1] );
  36:	}
  37:	
  38:	void dir_list( char *dirname )
  39:	{
  40:	    int fd ;
  41:	    struct dirent *p ;
  42:	    char buff[BUFSIZ] ;
  43:	    int rcount ;
  44:	
  45:	        fd = open( dirname,O_RDONLY );
  46:	        if( fd == -1 )
  47:	        {
  48:	            perror( dirname );
  49:	            exit( 1 );
  50:	        }
  51:	
  52:	        while( (rcount=getdents(fd,(dirent_t *)buff,BUFSIZ)) >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("off:%u, ", p->d_off );  /* long */
  60:	                printf("ino:%u, ", p->d_ino );  /* unsigned long */
  61:	                printf("reclen:%d, ", p->d_reclen );
  62:	                printf("name:%s\n", p->d_name );
  63:	            }
  64:	        }
  65:	        close( fd );
  66:	}
  67:	
  68:	void xdump( unsigned char *buff, int n )
  69:	{
  70:	        if( n<0 || n>100000 )
  71:	            return;
  72:	        for( ; n>0 ; n-=16, buff+=16 )
  73:	            xdump16( buff,n>=16?16:n );
  74:	}
  75:	
  76:	void xdump16( unsigned char *buff, int n )
  77:	{
  78:	    register int i ;
  79:	        for( i=0 ; i<n ; i++ )
  80:	            printf("%02x ",buff[i] );
  81:	        for( i=n ; i<16 ; i++ )
  82:	            printf("   ");
  83:	        for( i=0 ; i<n ; i++ )
  84:	            printf("%c",isprint(buff[i])?buff[i]:'#' );
  85:	        for( i=n ; i<16 ; i++ )
  86:	            printf(" ");
  87:	        printf("\n");
  88:	}
  89:	
----------------------------------------------------------------------

実行例。
----------------------------------------------------------------------
% ./dir-getdents dir1 [←]
00 07 a5 60 00 00 17 2e 00 0c 2e 00 01 8f 70 21 ###`###.##.###p!
6d 3b 32 b4 00 10 2e 2e 00 00 00 00 00 07 a5 63 m;2###..#######c
6d 3b 32 b7 00 10 66 69 6c 65 32 00 00 07 a5 61 m;2###file2####a
ff ff ff ff 00 10 66 69 6c 65 31 00             ######file1#    
p:0, off:5934, ino:501088, reclen:12, name:.
p:12, off:1832596148, ino:26177569, reclen:16, name:..
p:28, off:1832596151, ino:501091, reclen:16, name:file2
p:44, off:4294967295, ino:501089, reclen:16, name:file1
% []
----------------------------------------------------------------------

ディレクトリの構造

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


----------------------------------------------------------------------
     #include <sys/types.h>
     #include <sys/dir.h>
     DIR *opendir(char *filename);
     struct direct *readdir(DIR *dirp);
     long telldir(DIR *dirp);
     void seekdir(DIR *dirp, long loc);
     void rewinddir(DIR *dirp);
     void closedir(DIR *dirp);
     int dirfd(DIR *dirp)
----------------------------------------------------------------------

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

■ディレクトリの作成

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

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

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

■練習問題と課題

★練習問題17,長いwhoプログラム

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


----------------------------------------------------------------------
% who [←]
yas      ttyp0   Apr 28 20:32   (top:0.0)
yas      ttyp1   May 11 22:19   (revolution)
yas      ttyp2   May 11 20:03   (hlla0:0.0)
%
----------------------------------------------------------------------

括弧の中は、ログインしてきたホスト名やXウィンドウのディスプレイの名前。

ヒント:utmp ではなく、utmpx を使うとよい。

★練習問題18 lastコマンド

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

wtmp ではなく wtmpx を使うと、ホスト名まで調べられる。

★練習問題19 lsプログラム

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

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

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

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

     2 /
     2 usr
 27650 local
 29966 bin
 29970 tcsh

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

★練習問題21 ディレクトリの削除

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

■課題提出方法


↑[もどる] [課題提出方法] ←[4月21日] ・[5月12日] →[5月19日]
Last updated: 1998/05/19 00:19:01
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>