ファイルI/O、標準入出力 と属性

システム・プログラムI

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

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

■復習

■mainの引数


----------------------------------------------------------------------
   1:	/*
   2:	        arg-print.c -- mainの引数を表示するプログラム
   3:	        /usr/local/LECTURES/syspro-1997-shinjo/file/arg-print.c
   4:	        $Header: arg-print.c,v 1.2 97/04/21 18:29:06 yas Exp $
   5:	        Start: 1997/04/21 18:23:13
   6:	*/
   7:	
   8:	main( argc,argv )
   9:	    int argc ;
  10:	    char *argv[] ;
  11:	{
  12:	    int i ;
  13:	        printf("argc: %d\n", argc );
  14:	        for( i=0 ; i<argc ; i++ )
  15:	            printf("argv[%d]: %s\n",i,argv[i] );
  16:	}
----------------------------------------------------------------------

実行例。
----------------------------------------------------------------------
% cp /usr/local/LECTURES/syspro-1997-shinjo/file/arg-print.c . [←]
% make arg-print [←]
        cc -O arg-print.c  -o arg-print
% ./arg-print [←]
argc: 1
argv[0]: ./arg-print
% ./arg-print who am i [←]
argc: 4
argv[0]: ./arg-print
argv[1]: who
argv[2]: am
argv[3]: i
%
----------------------------------------------------------------------

■ファイルのコピー

file-copy.c は、引数で指定されたファイルを開き、その内容をコピーするプ ログラムである。stdio-thru.cは、標準入力で指定されたファイルの内容を標 準出力で指定されたファイルへコピーするプログラムである。
----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        file-copy.c -- ファイルをコピーする簡単なプログラム
   4:	        /usr/local/LECTURES/syspro-1997-shinjo/file/file-copy.c
   5:	        $Header: file-copy.c,v 1.2 97/04/21 21:44:27 yas Exp $
   6:	        Start: 1995/03/04 16:40:24
   7:	*/
   8:	
   9:	#include <stdio.h>      /* stderr */
  10:	#include <fcntl.h>      /* open(2) */
  11:	
  12:	main( argc,argv )
  13:	    int argc ;
  14:	    char *argv[] ;
  15:	{
  16:	        if( argc != 3 )
  17:	        {
  18:	            fprintf( stderr,"Usage: %s from to\n", argv[0] );
  19:	            exit( 1 );
  20:	        }
  21:	        file_copy( argv[1],argv[2] );
  22:	}
  23:	
  24:	#define BUFFSIZE        1024
  25:	
  26:	file_copy( from_name,to_name )
  27:	    char *from_name, *to_name ;
  28:	{
  29:	    int from_fd,to_fd ;
  30:	    char buff[BUFFSIZE] ;
  31:	    int rcount ;
  32:	    int wcount ;
  33:	
  34:	        from_fd = open( from_name,O_RDONLY );
  35:	        if( from_fd == -1 )
  36:	        {
  37:	            perror( from_name );
  38:	            exit( 1 );
  39:	        }
  40:	        to_fd = open( to_name,O_WRONLY|O_CREAT|O_TRUNC,0666 );
  41:	        if( to_fd == -1 )
  42:	        {
  43:	            perror( to_name );
  44:	            exit( 1 );
  45:	        }
  46:	        while( (rcount=read(from_fd,buff,BUFFSIZE)) > 0 )
  47:	        {
  48:	            if( (wcount=write(to_fd,buff,rcount))!= rcount )
  49:	            {
  50:	                 perror( to_name );
  51:	                 exit( 1 );
  52:	            }
  53:	        }
  54:	        close( from_fd );
  55:	        close( to_fd ); 
  56:	}
----------------------------------------------------------------------

open()は、ファイルを開くシステム・コールである。O_RDONLYとは、 読み込み専用でファイルを開くことを意味している。open() システム・コー ルは、結果としてファイル記述子を返す。ファイル記述子は、負でない小さな 整数(だいたい0〜128の範囲)である。ファイル記述子は、read()システ ム・コールやwrite()システム・コールで実際にファイルを読み書きする時に 使われる。

エラーが起きた時には、open() システム・コールは、-1 を返す。この時、エ ラーの番号が errno という変数に格納される。perror() は、errno変数を解 析して、より詳しいエラー・メッセージを表示する関数である。perror() の 引数は、エラー・メッセージとともに表示される文字列である。

ここでは、出力用のファイルを開いている。O_WRONLYは、書き込み専用でファ イルを開くことを意味している。O_CREATは、ファイルが存在しなければ作る ように指示するものである。ファイルが存在する場合、上書きされる。 O_TRUNCは、ファイルが存在している時には、その大きさを0にすることを指 示するものである。0666 (C言語で0から始まる数は、8進数)は、ファイルを 作る時のモードである。この数値に従って、ファイルのモード(ls -l で  rwxrwxrwx と表示される部分)が決定される。作成されるファイルのモードは、 ここで指定されたモードから現在の umask が落とされた(引かれた)値とな る。

read() システム・コールは、第1引数で指定されたファイル記述子のファイ ルを読み込み、それを第2引数の番地へ保存する。読み込むバイト数は、第3 引数で与えられる。結果として読み込んだバイト数を返す。通常は、BUFFSIZE が返される。read() システム・コールの結果、ファイル上の読み書きする位 置が、実際に読み込んだバイト数だけずれる。ファイルの末尾近くや、ファイ ルが端末の時、BUFFSIZE 以下の値が返される。ファイルの末尾に行き着くと  0 が返される。エラーが起きると、-1 が返される。

write() システム・コールは、第1引数で指定されたファイル記述子のファイ ルへデータを書き込む。書き込まれるデータは、第2引数で与えられた番地か ら、第3引数で与えらた大きさである。write() システム・コールは、結果と して書き込んだバイト数を返す。ファイル上の読み書きする位置が、実際に読 み込んだバイト数だけずれる。通常は、BUFFSIZE が返される。空き容量不足 などで書き込みが失敗した時には、-1 を返す。エラーが起きると、-1 が返さ れる。

close() は、ファイルを閉じるシステム・コールである。このシステム・コールを実行してしまうと、そのファイル記述子は無効になる。すなわち、read() や write() を行うと、エラーになる。

----------------------------------------------------------------------
% ls aaa [←]
aaa not found
% ./file-copy file-copy.c aaa [←]
% ls aaa [←]
aaa
% diff file-copy.c aaa [←]
% ls -l file-copy.c aaa [←]
-rw-r--r--   1 yas      lab         1052 Apr 21 21:42 aaa
-rw-r--r--   1 yas      lab         1052 Apr 21 21:41 file-copy.c
% []
----------------------------------------------------------------------

■openシステム・コールのマニュアル

----------------------------------------------------------------------
% man open
 open(2)                                                             open(2)

 NAME
      open - open file for reading or writing

 SYNOPSIS
      #include <fcntl.h>

      int open(
           const char *path,
           int oflag, ...
           /* mode_t mode */
      );

 DESCRIPTION
      open() opens a file descriptor for the named file and sets the file
      status flags according to the value of oflag.  path points to a path
      name naming a file, and must not exceed PATH_MAX bytes in length.
      oflag values are constructed by OR-ing flags from the list below.

      Exactly one of the flags O_RDONLY, O_WRONLY, or O_RDWR must be used in
      composing the value of oflag.  If none or more than one is used, the
      behavior is undefined.  Several other flags listed below can be
      changed by using fcntl() while the file is open.  See fcntl(2) and
      fcntl(5) for details.

           O_RDONLY       Open for reading only.

           O_WRONLY       Open for writing only.

           O_RDWR         Open for reading and writing.
...
           O_CREAT        If the file exists, this flag has no effect,
                          except as noted under O_EXCL below.  Otherwise,
                          the owner ID of the file is set to the effective
...
           O_TRUNC        If the file exists, its length is truncated to 0
                          and the mode and owner are unchanged.
...
 EXAMPLES
      The following call to open() opens file inputfile for reading only and
      returns a file descriptor for inputfile.  For an example of reading
      from file inputfile, see the read(2) manual entry.

           int myfd;

           myfd = open ("inputfile", O_RDONLY);

...

 RETURN VALUE
      Upon successful completion, the file descriptor is returned.
      Otherwise, a value of -1 is returned and errno is set to indicate the
      error.

 ERRORS
      open() fails and the file is not opened if any of the following
      conditions are encountered:

           [EACCES]       oflag permission is denied for the named file.
...

 AUTHOR
      open() was developed by HP, AT&T, and the University of California,
      Berkeley.

 SEE ALSO
      chmod(2), close(2), creat(2), dup(2), fcntl(2), lockf(2), lseek(2),
      read(2), select(2), setacl(2), umask(2), write(2), acl(5), fcntl(5),
      signal(5).

 STANDARDS CONFORMANCE
      open(): AES, SVID2, XPG2, XPG3, XPG4, FIPS 151-2, POSIX.1
----------------------------------------------------------------------

■ファイル記述子

ファイル記述子は、小さな整数。
----------------------------------------------------------------------
   1:	/*
   2:	        fd-print.c -- ファイル記述子を表示するプログラム
   3:	        /usr/local/LECTURES/syspro-1997-shinjo/file/fd-print.c
   4:	        $Header: fd-print.c,v 1.1 97/04/21 21:29:00 yas Exp $
   5:	        Start: 1997/04/21 18:23:13
   6:	*/
   7:	
   8:	#include <fcntl.h>
   9:	
  10:	main( argc,argv )
  11:	    int argc ;
  12:	    char *argv[] ;
  13:	{
  14:	    int i ;
  15:	    int fd ;
  16:	        for( i=1 ; i<argc ; i++ )
  17:	        {
  18:	            fd = open( argv[i],O_RDONLY );
  19:	            printf("%s: %d\n",argv[i],fd );
  20:	        }
  21:	}
----------------------------------------------------------------------
----------------------------------------------------------------------
% ./fd-print fd-print.c [←]
fd-print.c: 3
% ./fd-print fd-print.c /etc/passwd fd-print.c [←]
fd-print.c: 3
/etc/passwd: 4
fd-print.c: 5
% []
----------------------------------------------------------------------

■標準入出力

プログラムが実行される時、open() システム・コールを使わなくても、次の 3つのファイル記述子は使える状態になっている。

  1. 標準入力(stdin)
  2. 標準出力(stdout)
  3. 標準エラー出力(stderr)

stdio-thru.cは、標準入力で指定されたファイルの内容を標準出力で指定され たファイルへコピーするプログラムである。

----------------------------------------------------------------------
   1:	
   2:	/*
   3:	        stdio-thru.c -- 標準入力から標準出力へのコピー
   4:	        /usr/local/LECTURES/syspro-1997-shinjo/file/stdio-thru.c
   5:	        $Header: stdio-thru.c,v 1.1 97/04/21 21:48:30 yas Exp $
   6:	        Start: 1995/03/04 16:40:24
   7:	*/
   8:	
   9:	#include <stdio.h>      /* stderr */
  10:	
  11:	main( argc,argv )
  12:	    int argc ;
  13:	    char *argv[] ;
  14:	{
  15:	        if( argc != 1 )
  16:	        {
  17:	            fprintf( stderr,"Usage: %s\n", argv[0] );
  18:	            exit( 1 );
  19:	        }
  20:	        stdio_thru();
  21:	}
  22:	
  23:	#define BUFFSIZE        1024
  24:	
  25:	stdio_thru()
  26:	{
  27:	    char buff[BUFFSIZE] ;
  28:	    int rcount ;
  29:	    int wcount ;
  30:	
  31:	        while( (rcount=read(0,buff,BUFFSIZE)) > 0 )
  32:	        {
  33:	            if( (wcount=write(1,buff,rcount))!= rcount )
  34:	            {
  35:	                 perror("stdout");
  36:	                 exit( 1 );
  37:	            }
  38:	        }
  39:	        close( 0 );
  40:	        close( 1 );
  41:	}
----------------------------------------------------------------------
このプログラムは、引数をとらない。"<" や ">" は、シェルにより解釈され、 このプログラムには渡されない。

file_copy() とは異なり、stdio_thru() では、ファイルを開く操作(open()) を行うことなく、入出力(read(),write)を行っている。このような事が可能 な理由は、UNIXでは、ファイル記述子 0, 1, 2 は、シェルにより既に開 かれているからである。

----------------------------------------------------------------------
% ./stdio-thru [←]
ljd[←]
ljd
sk[←]
sk
^D
% ls bbb [←]
bbb not found
% ./stdio-thru < file-copy.c > bbb [←]
% ls bbb [←]
bbb
% diff file-copy.c bbb [←]
% ls -l file-copy.c bbb [←]
-rw-r--r--   1 yas      lab         1052 Apr 21 21:46 bbb
-rw-r--r--   1 yas      lab         1052 Apr 21 21:44 file-copy.c
% []
----------------------------------------------------------------------

■ファイルの属性

UNIXでは、stat() (あるいは、lstat(), fstat())システム・コールを 用いてファイルの属性を調べることができる。ファイルの属性を調べ、画面に 出力するプログラムを stat.c に示す。
----------------------------------------------------------------------
   1:	/*
   2:	        stat.c -- stat システム・コールのシェル・インタフェース
   3:	        /usr/local/LECTURES/syspro-1997-shinjo/file/stat.c
   4:	        $Header: stat.c,v 1.2 97/04/21 22:03:35 yas Exp $
   5:	        Start: 1995/03/07 20:59:12
   6:	*/
   7:	
   8:	#include <sys/types.h>
   9:	#include <sys/stat.h>
  10:	#include <sys/sysmacros.h>      /* major(), minor() */
  11:	#include <stdio.h>
  12:	
  13:	main( argc,argv )
  14:	    int argc ;
  15:	    char *argv[] ;
  16:	{
  17:	        if( argc != 2 )
  18:	        {
  19:	            fprintf( stderr,"Usage:%% %s filename \n",argv[0] );
  20:	            exit( 1 );
  21:	        }
  22:	        stat_print( argv[1] );  /* 引数はファイル */
  23:	}
  24:	
  25:	stat_print( path )
  26:	    char *path ;
  27:	{
  28:	    struct stat buf ;
  29:	        if( stat( path,&buf ) == -1 )
  30:	        {
  31:	            perror( path );
  32:	            exit( 1 );
  33:	        }
  34:	
  35:	        printf("path: %s\n",path );
  36:	        printf("dev: %d,%d\n",major(buf.st_dev),minor(buf.st_dev) );
  37:	        printf("ino: %d\n",buf.st_ino );
  38:	        printf("mode: 0%o\n",buf.st_mode );
  39:	        printf("nlink: %d\n",buf.st_nlink );
  40:	        printf("uid: %d\n",buf.st_uid );
  41:	        printf("gid: %d\n",buf.st_gid );
  42:	        printf("rdev: %d,%d\n",major(buf.st_rdev),minor(buf.st_rdev) );
  43:	        printf("size: %d\n",buf.st_size );
  44:	        printf("atime: %s",ctime(&buf.st_atime) );
  45:	        printf("mtime: %s",ctime(&buf.st_mtime) );
  46:	        printf("ctime: %s",ctime(&buf.st_ctime) );
  47:	        printf("blksize: %d\n",buf.st_blksize );
  48:	        printf("blocks: %d\n",buf.st_blocks );
  49:	}
----------------------------------------------------------------------

----------------------------------------------------------------------
% ./stat stat.c [←]
path: stat.c
dev: 64,8
ino: 129621
mode: 0100644
nlink: 1
uid: 1231
gid: 40
rdev: 0,926860
size: 1233
atime: Mon Apr 21 22:04:19 1997
mtime: Mon Apr 21 22:03:44 1997
ctime: Mon Apr 21 22:03:44 1997
blksize: 8192
blocks: 2
%
----------------------------------------------------------------------

この実行結果から次のようなことがわかる。
  1. ファイル名は、stat.c である。
  2. 存在するデバイスは、メジャー番号64, マイナー番号8で識別される。
  3. アイノード番号は、129621である。
  4. モードは、0100644である。
  5. リンク数は、1である。
  6. ファイルの所有者のIDは、1231である。
  7. ファイルのグループは、40である。
  8. デバイスの識別子は、メジャー番号0, マイナー番号926860である。 (この場合無効。デバイス・ファイルのみ有効。)
  9. ファイルの大きさは、1233バイトである。
  10. 最終アクセス時刻は、Mon Apr 21 22:04:19 1997である。
  11. 最終更新時刻は、Mon Apr 21 22:03:44 1997である。
  12. 最終変更時刻は、Mon Apr 21 22:03:44 1997である。
  13. 入出力に適したブロックサイズは、8192バイトである。
  14. ディスク中で実際に消費しているのは、2ブロックである。
ここで、モードが8進数で 0100644 (C言語の文法で、0から始まる数は、8進 数)であることから、ファイルの型(普通のファイルかディレクトリかという 情報)を調べることができる。0100644の上位4ビット、つまり、0170000と AND (C言語のでは、&演算子)をとった結果は 0100000 となる。この値は、普 通のファイル(regular file)を意味する。ディレクトリの場合、0040000 と なる。これらの数は、 (/usr/include/sys/stat.h)で定義されて いる。
----------------------------------------------------------------------
#  define _S_IFMT   0170000     /* type of file */
#  define _S_IFREG  0100000     /* regular */
#  define _S_IFBLK  0060000     /* block special */
#  define _S_IFCHR  0020000     /* character special */
#  define _S_IFDIR  0040000     /* directory */
#  define _S_IFIFO  0010000     /* pipe or FIFO */

#  define S_ISDIR(_M)  ((_M & _S_IFMT)==_S_IFDIR) /* test for directory */
#  define S_ISCHR(_M)  ((_M & _S_IFMT)==_S_IFCHR) /* test for char special */
#  define S_ISBLK(_M)  ((_M & _S_IFMT)==_S_IFBLK) /* test for block special */
#  define S_ISREG(_M)  ((_M & _S_IFMT)==_S_IFREG) /* test for regular file */
#  define S_ISFIFO(_M) ((_M & _S_IFMT)==_S_IFIFO) /* test for pipe or FIFO */
----------------------------------------------------------------------
プログラム中では、次のようにしてファイルの型を調べることができる。
----------------------------------------------------------------------
	switch( buf.st_mode & _S_IFMT )
	{
	case _S_IFREG: 
	    ファイルの時の処理;
	    break;
	case _S_IFDIR: ...
	    ディレクトリの時の処理;
	    break;
	....
	}
----------------------------------------------------------------------
あるいは、 に含まれている S_ISREG(), S_ISDIR() というマク ロを用いて、次のように記述する方法もある。
----------------------------------------------------------------------
	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" の意味が異なる。

★[report] report-1,teeプログラム

file-copy.c, stdio-thru.c の2つのプログラムを参考にして、UNIX の tee プログラムと同じ機能をもつプログラムを作りなさい。プログラムの名前は、 tee ではなく、別の名前(mytee) としなさい。tee の場合、標準の tee が動 いているのか、自分で作った tee が動いているのか区別がつかない。x2

mytee は、次のようにして、標準入力を標準出力にコピーしながら、同時にファ イルにも保存するものである。

% grep pattern file1 | ./mytee result [←]
この結果として、画面には、次のように grep コマンドを実行した時と同じ結果が表示される。

% grep pattern file1 [←]

同時に、ファイル reult には、画面に出力された結果とまったく同じものが 保存される。

tee コマンドの名前は、アルファベットの「T」に由来する。図形的に考えれ ば、動きを理解することができる。tee コマンドは、左(パイプラインで標準 入力)から入ったデータを下(引数で与えられたファイル)に保存しながら、 右(標準出力)にも出力する。tee コマンドは、時間のかかる処理の結果や、 後で参照したい中間結果をファイルに保存するために利用される。次の例は、 makeコマンドの実行結果を make.out へ保存すると同時に、それを user にメー ルで送るものである。

% make |& tee make.out | mail -s make result user [←]
''

これにより、user は、メールが届いたことで、make コマンドの実行終了を知 ることができる。同時に、作業していたディレクトリでその結果を参照するこ とができる。

★[report] report-2,catプログラム

UNIXの cat コマンドと同様の動きをするプログラムを作りなさい。このプロ グラムの名前を mycat とする。引数として、なにも与えられなかった時には、 標準入力を標準出力にコピーしなさい。また、引数として "-" が指定された 時には、その位置で標準入力を標準出力にコピーしなさい。たとえば、次のよ うな場合、file1 とfile2 の内容の間に、標準入力の内容をコピーしなさい。

% mycat file1 - file2 [←]

★[report] report-3,ls-lプログラム

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

ls -l では、3つの時刻のうち、どの時刻が表示されているのかを調べなさい。 また、他の2つの時刻を表示させる方法を調べなさい。

myls-l の結果の表示形式は、ls -l と完全に一致しなくてもよい。たとえば、 時刻の表示は、上の stat.c の結果と同じでもよい。ocaltime(), strftime() ライブラリ関数を利用すると、時刻の表示をより簡単に ls -l の表示に近づ けることができるであろう。

uid (st_uid) については、ls -l では、ユーザ名(ログイン名)で表示され る。myls-l では、数字のままでよい。ライブラリ関数 getpwuid() を使えば、 UID からユーザ名を調べることができる。

プログラムの引数となるファイルの数は、1個とする。複数のファイルについ て、ls -l と同様の表示をするように拡張してもよい。

引数としてディレクトリの名前が与えられた場合にも、ディレクトリの内容で はなくディレクトリ自身の属性を表示する。シンボリック・リンクには対応し なくてもよい。(よって正確には、ls -l filename ではなく、ls -ldL filename である。)

余裕があれば、lstat() と readlink() の、2つのシステム・コールを用いて、 ls -l と同じようにシンボリック・リンクの内容を表示しなさい。


↑[もどる] [4月15日]← [4月22日]・ [5月06日]→
Last updated: 1997/04/21 22:56:41
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>