ユーザ、アクセス制御、ディレクトリの構造

システム・プログラム

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

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

■今日の重要な話

ユーザ(UID)、グループ(GID)の考え方

属性

アクセス制御 ディレクトリの構造

■ユーザとグループ

◆ユーザ

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

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

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

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

◆グループ

グループ(group)とは、コンピュータの世界では、 コンピュータ を使う人間の集合 のことである。コンピュータの内部では、ユーザ名/UIDと同様に、 グループ名(group name) と呼ばれる文字列、 または、それとほぼ1対1に対応した16ビッ トの整数で表す。この数を 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)の 権限が必要である。

■プロセスのユーザ属性

プロセスには、属性として UID が付いている。この属性は、ファイルを open() する時に内部的に使われる。

プロセスの UID 属性を得るには、getuid() システムコールを用いる。以下の プログラムは、現在のプロセスの UID とユーザ名、および、UID 0 の UID と ユーザ名を表示するものである。

----------------------------------------------------------------------
   1:	/*
   2:	        proc-uid-print.c -- 現在のプロセスのUIDを表示するプログラム。
   3:	        ~yas/syspro/proc/proc-uid-print.c
   4:	        Start: 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:	        uid = getuid();
  51:	        printf("%d: %s\n",uid,uid2uname(uid) );
  52:	        uid = 0 ;
  53:	        printf("%d: %s\n",uid,uid2uname(uid) );
  54:	}
  55:	
  56:	char *uid2uname(uid_t uid)
  57:	{
  58:	    struct passwd *pwd ;
  59:	        pwd = getpwuid( uid );
  60:	        if( pwd )
  61:	            return( pwd->pw_name );
  62:	        else
  63:	        {
  64:	             static char buf[100] ; /* must be static, bad for multithreading */
  65:	             sprintf(buf,"%d",uid );
  66:	             return( buf );
  67:	        }
  68:	}
  69:	
  70:	char *gid2gname(gid_t gid)
  71:	{
  72:	    struct group *grp ;
  73:	        grp = getgrgid( gid );
  74:	        if( grp )
  75:	            return( grp->gr_name );
  76:	        else
  77:	        {
  78:	             static char buf[100] ; /* must be static, bad for multithreading */
  79:	             sprintf(buf,"%d",gid );
  80:	             return( buf );
  81:	        }
  82:	}
----------------------------------------------------------------------
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  [←]
1013: yas
0: root
% []
----------------------------------------------------------------------

最初の行は、各自異なるはずである。UID 0 は、rootと いう特別なユーザに対応している。

■アクセス制御とは

Unixは、マルチユーザのシステムである。1つのシステムを複数のユーザが同時 に利用する。システムに含まれているファイルやプロセスは、それぞれのユー ザと結び付いている。

Unixは、マルチユーザのシステムなので、ユーザ1人ひとりを認識するような アクセス制御の仕組みを持っている。 アクセス制御(access control) とは、ユーザ(プロセス) が、ファイルなどの資源をアクセスする時、どんな アクセスの仕方なら正しいということを定義して、それがきちんと守られてい ることをということを保証することである。

ファイル
内容の読出し・書き込み・実行、属性の読出し・変更がで きるかどうかを調べる。
プロセス
シグナルを送れるかどうかを調べる。許されれば、その操作 は実行されて、許されなければ、エラーになる。

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

これに対して、Personal Computer用のOS(Windows 95/98/ME, MacOS 9以下) では、このような複数のユーザを識別したアクセス制御は、できない。 Windows NT, Windows 2000, 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)

Unix では、UID が 0 のユーザは、 全てのアクセス制御の仕組みを無視して何でも実行することができる。 これを スーパー・ユーザ(super user)特権ユーザ(privileged user)、 あるいは、 ルート(root) と呼ばれる。

次の例は、モードが 000 ファイルをスーパーユーザの権限で読むことが出き ることを示している。


----------------------------------------------------------------------
% su [←]
Password: 
[root@adonis9 tmp]# cd /tmp
[root@adonis9 /tmp]# ls -l file1
ls: file1: そのようなファイルやディレクトリはありません
[root@adonis9 /tmp]# echo "This is file1" > file1
[root@adonis9 /tmp]# chmod 000 file1
[root@adonis9 /tmp]# ls -l file1
----------    1 root     root           14  6月 17 01:52 file1
[root@adonis9 /tmp]# cat file1
This is file1
[root@adonis9 /tmp]# 
----------------------------------------------------------------------

◆スーパー・ユーザの権限のまとめ

ファイル関係

プロセス関係

プロセス間通信

システム管理

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

UNIXでは、stat() (あるいは、lstat(), fstat())システム・コールを 用いてファイルの属性を調べることができる。 次のプログラム ystat.c は、ファイルの属性を調べ、画面に出力するプログ ラムである。
----------------------------------------------------------------------
   1:	/*
   2:	        ystat.c -- stat システム・コールのシェル・インタフェース
   3:	        ~yas/syspro/file/ystat.c
   4:	        Start: 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 stat_print( char *path )
  25:	{
  26:	    struct stat buf ;
  27:	        if( stat( path,&buf ) == -1 )
  28:	        {
  29:	            perror( path );
  30:	            exit( 1 );
  31:	        }
  32:	
  33:	        printf("path: %s\n",path );
  34:	        printf("dev: %d,%d\n",major(buf.st_dev),minor(buf.st_dev) );
  35:	        printf("ino: %d\n",buf.st_ino );
  36:	        printf("mode: 0%o\n",buf.st_mode );
  37:	        printf("nlink: %d\n",buf.st_nlink );
  38:	        printf("uid: %d\n",buf.st_uid );
  39:	        printf("gid: %d\n",buf.st_gid );
  40:	        printf("rdev: %d,%d\n",major(buf.st_rdev),minor(buf.st_rdev) );
  41:	        printf("size: %d\n",buf.st_size );
  42:	        printf("blksize: %d\n",buf.st_blksize );
  43:	        printf("blocks: %d\n",buf.st_blocks );
  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:	}
----------------------------------------------------------------------

stat() システムコールは、引数として、ファイル名(ファイルやディレクト リの名前、パス名)と、struct stat のポインタを取る。この構造体は、マニュ アルには、次のようなものであると説明されている。

% man 2 stat 
...

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,8
ino: 1694566923
mode: 0100644
nlink: 1
uid: 1013
gid: 40
rdev: 0,0
size: 1178
blksize: 8192
blocks: 8
atime: Sun Jun 16 23:48:53 2002
mtime: Sun Jun 16 23:48:49 2002
ctime: Sun Jun 16 23:48:49 2002
% /usr/bin/stat ystat.c [←]
  File: "ystat.c"
  Size: 1178       Blocks: 8         Regular File
Access: (0644/-rw-r--r--)         Uid: ( 1013/     yas)  Gid: (   40/     lab)
Device: 8          Inode: 1694566923 Links: 1    
Access: Sun Jun 16 23:48:53 2002
Modify: Sun Jun 16 23:48:49 2002
Change: Sun Jun 16 23:48:49 2002

% []
----------------------------------------------------------------------

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

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

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" の意味が異なる。

■umaskの影響

ファイルやディレクトリを新たに作る時にどのようなモードになるかは、次の 2つで決まる。
  1. ファイルやディレクトリを作るプログラムの中で指定したモード
  2. マスク
実際に作られるファイルのモードは、1. から 2. を引いた値になる。同じプ ログラムを使ったとしても、マスクの値によっては、作られるファイルのモー ドが違ってくる。

◆マスクの影響と操作

◆マスクの表示

マスクを見るには、umask コマンドコマンドを使う。
----------------------------------------------------------------------
% umask [←]
22
% []
----------------------------------------------------------------------
umask コマンドは、シェル(csh, tcsh)の内部コマンドである。マスク は、プロセスの属性の1つで、子プロセス、孫プロセスと代々受け継がれてい きく。現在のシェルのマスクを見れば、シェルから実行されるプロセスのマス クがわかる。

umask を操作するシステム・コールは、umask(コマンドと同じ名前)である。

マスクは、モードと同じく8進数で考える。たとえば、022 は、次のように 読む。

----------------------------------------------------------------------

   ---       000 新しいファイルは、自分自身は、読み書き自由。
      -w-    020 同じグループの人は、ファイルの書込みを禁止する。
+)       -w- 002 その他の人も、ファイルの書込みを禁止する。
----------------
             022

----------------------------------------------------------------------
このように、モードの逆になる。

◆マスクを変える

マスクを変えるには、umask コマンドに、引数を与えて実行する。
----------------------------------------------------------------------
% umask [←]
22
% umask 066 [←]
% umask [←]
66
% []
----------------------------------------------------------------------
この例では、シェルのマスクを 022 から 066 へ変更している。

umask コマンドによるマスクの設定は、 普通、~/.cshrc~/.login に入れる。 ssh でリモートログインした時にも実行さ れるように、~/.cshrc に入れて置くのが無難である。

◆普通のファイルを作る時のマスクの影響

新しくファイルを作る時、そのモードは、最大 666 (rw-rw-rw-)になることが多い。これは、ファイルを作るプロ グラム(cp, mule などで)で、次のようにファイルを作っていることに由来す る。
	open("file1",O_CREAT|O_TRUNC,0666);
すると、UNIXオペレーティング・システムのカーネルは、システム・コールの 引数とマスクを保持している変数 mask を使って
	mode = 0666 & ~mask ;
というモードのファイルを作る。「&」 は、ビットごとのAND、「~」は、ビット反 転である。この場合、繰り下がりがないので、
	mode = 0666 - mask ;
と考えてもよい。結果として、マスクの部分のビットが落ちたモード を持つファイルが作られる。

たとえば、マスクが 022 の時、作 成されるファイルのモードは、666からマスクの022を引くので、次のように 644 (rw-r--r--) になる。


----------------------------------------------------------------------
% ls -l file1 [←]
ls: file1: No such file or directory
% umask  [←]
22
% echo "This is file1" > file1 [←]
% ls -l file1 [←]
-rw-r--r--    1 yas      lab            14 Jun 17 00:06 file1
% []
----------------------------------------------------------------------
マスクを 0 にして、同じことをすると、モードが 666 になる。

----------------------------------------------------------------------
% rm file1 [←]
% umask 000 [←]
% umask [←]
0
% echo "This is file1" > file1 [←]
% ls -l file1 [←]
-rw-rw-rw-    1 yas      lab            14 Jun 17 00:07 file1
% []
----------------------------------------------------------------------

マスクは、新しくファイルを作る時にのみ有効である。すでに、ファイルが存 在する場合、ファイルに書込みをしても、モードは変わらない。たとえば、


----------------------------------------------------------------------
% ls -l file1 [←]
-rw-rw-rw-    1 yas      lab            14 Jun 17 00:07 file1
% chmod 777 file1 [←]
% ls -l file1 [←]
-rwxrwxrwx    1 yas      lab            14 Jun 17 00:07 file1
% umask 022 [←]
% echo a > file1 [←]
% ls -l file1 [←]
-rwxrwxrwx    1 yas      lab             2 Jun 17 00:08 file1
% []
----------------------------------------------------------------------

◆ディレクトリを作る時のマスクの影響

新しくディレクトリを作る時、そのモードは、最大 777 (rwxrwxrwx)になることが多い。これは、ディレクトリを作る プログラムが、
	mkdir("dir1",0777 );
となっているからである。この結果、ファイルと同様に
	mode = 0777 & ~mask
というモードを持つディレクトリが作られる。

たとえば、マスクが 022 の時、作成されるディレクトリのモードは、777から マスクの022を引くので、次のように 755 (rwxr-xr-x) に なる。


----------------------------------------------------------------------
% umask [←]
22
% mkdir dir1 [←]
% ls -ld dir1 [←]
drwxr-xr-x    2 yas      lab             6 Jun 17 00:08 dir1
% []
----------------------------------------------------------------------
マスクを 0 にして、同じことをすると、

----------------------------------------------------------------------
% ls -ld dir1 [←]
ls: dir1: No such file or directory
% umask  [←]
0
% mkdir dir1 [←]
% ls -ld dir1 [←]
drwxrwxrwx    2 yas      lab          1024 Jun 17 00:10 dir1
% []
----------------------------------------------------------------------
新しく mkdir コマンドで作ったディレクトリのモードが 777 になる。

上の結果は、/tmp で試したものである。 情報学類の計算機の場合、ホーム・ディレクトリ(実体は、ファイル・サーバ 上にある)で実験すると、モードが 777 にはならない。


----------------------------------------------------------------------
% rmdir dir1 [←]
% ls -ld dir1 [←]
ls: dir1: No such file or directory
% umask [←]
0
% mkdir dir1 [←]
% ls -ld dir1 [←]
drwxr-xr-x    2 yas      lab             6 Jun 17 00:14 dir1
% []
----------------------------------------------------------------------

■リンクと名前

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

次は、3つの実リンクを持っているファイルの例である。


----------------------------------------------------------------------
% ls -li /usr/bin/{yppasswd,ypchsh,ypchfn} [←]
1089359 -r-xr-xr-x    3 root     root        16988 Feb 27  2001 /usr/bin/ypchfn
1089359 -r-xr-xr-x    3 root     root        16988 Feb 27  2001 /usr/bin/ypchsh
1089359 -r-xr-xr-x    3 root     root        16988 Feb 27  2001 /usr/bin/yppasswd
% []
----------------------------------------------------------------------

1つのファイルの実体を、3つの名前でアクセスできる。どれか1つの名前で も残っている限り、ファイルの実体は残る。全ての名前が消された時に ファイルの実体も消される。

次は、1つの実リンクと2つのシンボリック・リンクを持っているファイルの 例である。


----------------------------------------------------------------------
% ls -li /usr/bin/{mdir,mcopy,mtools} [←]
1088204 lrwxrwxrwx    1 root     root            6 Feb 24 07:18 /usr/bin/mcopy -> mtools
1088209 lrwxrwxrwx    1 root     root            6 Feb 24 07:18 /usr/bin/mdir -> mtools
1088317 -rwxr-xr-x    1 root     root       139132 Jan 10  2001 /usr/bin/mtools
% []
----------------------------------------------------------------------

mtools が消されると、ファイルの実体が消される。mdirmcopy が消されても、ファイルの実体はそのままである。

シンボリック・リンクは、内部的には別のファイルの名前(シンボル)を含ん でいる特殊なファイルである。open() などのシステムコールでは、自動的に リンクの先の名前に置き換えられる。

シンボリック・リンクは、symlink() システム・コールで作成することができ る。

◆ディレクトリの実リンク

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

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

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

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

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


----------------------------------------------------------------------
   1:	/*
   2:	        dir-getdents.c -- ディレクトリの内容を表示するプログラム
   3:	        ~yas/syspro/dir/dir-getdents.c
   4:	        Start: 1995/03/07 21:44:51
   5:	*/
   6:	
   7:	#include <stdio.h>      /* fprintf(), stderr */
   8:	#include <sys/types.h>          /* open(2) */
   9:	#include <sys/stat.h>           /* open(2) */
  10:	#include <fcntl.h>              /* open(2) */
  11:	#include <unistd.h>             /* getdents(2) */
  12:	#include <linux/types.h>        /* getdents(2) */
  13:	#include <linux/dirent.h>       /* getdents(2) */
  14:	#include <linux/unistd.h>       /* getdents(2) */
  15:	
  16:	_syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count);
  17:	
  18:	extern  void dir_list( char *dirname );
  19:	extern  void xdump( unsigned char *buff, int n );
  20:	extern  void xdump16( unsigned char *buff, int n );
  21:	
  22:	main( int argc, char *argv[] )
  23:	{
  24:	        if( argc != 2 )
  25:	        {
  26:	            fprintf( stderr,"Usage:%% %s dirname \n",argv[0] );
  27:	            exit( 1 );
  28:	        }
  29:	        dir_list( argv[1] );
  30:	}
  31:	
  32:	#define BUFFERSIZE      8192
  33:	
  34:	void dir_list( char *dirname )
  35:	{
  36:	    int fd ;
  37:	    struct dirent *p ;
  38:	    char buff[BUFFERSIZE] ;
  39:	    int rcount ;
  40:	
  41:	        fd = open( dirname,O_RDONLY );
  42:	        if( fd == -1 )
  43:	        {
  44:	            perror( dirname );
  45:	            exit( 1 );
  46:	        }
  47:	
  48:	        while( (rcount=getdents(fd,(struct dirent *)buff,BUFFERSIZE)) >0 )
  49:	        {
  50:	            xdump( buff, rcount );
  51:	            for( p = (struct dirent *)buff ; (char *)p < &buff[rcount] ;
  52:	                 p=(struct dirent *) ((int)p+(p->d_reclen)) )
  53:	            {
  54:	                printf("p:%d, ", (char *)p - buff );
  55:	                printf("off:%u, ", p->d_off );  /* long */
  56:	                printf("ino:%u, ", p->d_ino );  /* unsigned long */
  57:	                printf("reclen:%d, ", p->d_reclen );
  58:	                printf("name:%s\n", p->d_name );
  59:	            }
  60:	        }
  61:	        close( fd );
  62:	}
  63:	
  64:	void xdump( unsigned char *buff, int n )
  65:	{
  66:	        if( n<0 || n>100000 )
  67:	            return;
  68:	        for( ; n>0 ; n-=16, buff+=16 )
  69:	            xdump16( buff,n>=16?16:n );
  70:	}
  71:	
  72:	void xdump16( unsigned char *buff, int n )
  73:	{
  74:	    register int i ;
  75:	        for( i=0 ; i<n ; i++ )
  76:	            printf("%02x ",buff[i] );
  77:	        for( i=n ; i<16 ; i++ )
  78:	            printf("   ");
  79:	        for( i=0 ; i<n ; i++ )
  80:	            printf("%c",isprint(buff[i])?buff[i]:'#' );
  81:	        for( i=n ; i<16 ; i++ )
  82:	            printf(" ");
  83:	        printf("\n");
  84:	}
----------------------------------------------------------------------

_syscall3() は、Linux で3引数のシステムコールを使うためのマクロである。 getdents() システムコールは、普通のプログラムで使うものではないので、 単純にこのシステムコールを使うことはできない。(普通のプログラムは、移 植性がある opendir(3),readdir(3),closedir(3)を使う。)

dir_list() は、引数で会い得られた名前のディレクトリをopen() システムコー ルで開き、getdents() システムコールでその内容を読込み、画面に表示する。

getdents() システムコールは、read() システムコールと使い方が似ているが、 ディレクトリに対してしか有効ではない。引数は、マニュアルには、struct dirent * を取るように書かれているが、このように適当な大きさのバッファ を確保して、その番地を与える。

getdents() の結果、引数で指定された番地に、struct dirent の構造体で値 が保存される。ただし、この構造体は、固定長ではない。固定長ならば、次の ような形でアクセスできるであろう。


	p = (struct dirent *)buff ;
	n = rcount/sizeof(struct dirent);
	for( i= 0 ; i< n ; i++, p++ )
	{
	    ...
	}

C言語で 「p++」と書くと、内部的には、sizeof(struct dirent) バ イト分だけ増える。

しかし、getdents() の結果のバッファは、固定長の構造体(struct dirent)が 並んでいるのではなく、可変長である。つまり、構造体の大きさは、それぞれ 異なり、実際の大きさは、sizeof(struct dirent) バイトではなく、 p->d_reclen バイトである。したがって、ループの最後(forの3 番目)で、このバイト数だけ増やしている。

なお、キャストを付けずに単純に次のようにするのは、意味が違う。


          for( p = (struct dirent *)buff ; (char *)p < &buff[rcount] ;
               p=p+(p->d_reclen) )

この場合、p は、p->d_reclen バイトずれるのではなくて、 p->d_reclen*sizeof(struct dirent)バイトずれてしまう。

実行例。


----------------------------------------------------------------------
% cp ~yas/syspro/dir/dir-getdents.c . [←]
% make dir-getdents [←]
cc     dir-getdents.c   -o dir-getdents
% ls -ld dir1 [←]
ls: dir1: No such file or directory
% mkdir dir1 [←]
% ls -ld dir1 [←]
drwxr-xr-x    2 yas      lab             6 Jun 17 00:30 dir1
% echo file1 > dir1/file1 [←]
% echo file2 > dir1/file2 [←]
% ./dir-getdents dir1 [←]
c6 1d 17 53 04 00 00 00 0c 00 2e 00 b5 0c 06 49 ###S######.####I
06 00 00 00 10 00 2e 2e 00 00 00 00 c7 1d 17 53 ######..#######S
08 00 00 00 10 00 66 69 6c 65 31 00 c8 1d 17 53 ######file1####S
00 02 00 00 10 00 66 69 6c 65 32 00             ######file2#    
p:0, off:4, ino:1394023878, reclen:12, name:.
p:12, off:6, ino:1225133237, reclen:16, name:..
p:28, off:8, ino:1394023879, reclen:16, name:file1
p:44, off:512, ino:1394023880, reclen:16, name:file2
% []
----------------------------------------------------------------------

ディレクトリの構造

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


----------------------------------------------------------------------
     #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);
----------------------------------------------------------------------

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

■ディレクトリの作成

ディレクトリを作成するには、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 [←]
実行例。

----------------------------------------------------------------------
% cp ~yas/syspro/dir/dir-mkdir.c . [←]
% make dir-mkdir [←]
cc     dir-mkdir.c   -o dir-mkdir
% ./dir-mkdir  [←]
Usage:% ./dir-mkdir dirname 
% ./dir-mkdir dir100 [←]
% ls -ld dir100 [←]
drwxr-xr-x    2 yas      lab             6 Jun 17 01:05 dir100
% []
----------------------------------------------------------------------

■練習問題と課題

★練習問題 62 gidの表示

プロセスの GID属性を表示するプログラムを作りなさい。

注意:getgid() システムコールや getgroups() システムコールを使う。

★練習問題 63 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 が返される。

★練習問題 64 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 と同じようにシンボリック・リンクの内容を表示しなさい。

さらに、opendir(3) や scandir(3) を使って、ディレクトリの内容を表示 するようにしてもよい。

★練習問題 65 chmod プログラム

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

★練習問題 66 touch プログラム

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

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

★練習問題 67 条件コピー

ファイルのコピー(file-copy.c) を修正して、ファイルが更新されていた時だけコピーするようなプログラムを 作りなさい。

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

★練習問題 68 属性まで含んだコピー

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

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

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

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

★練習問題 69 リンクの作成

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

★練習問題 70 リンクの削除

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

★練習問題 71 シンボリック・リンクの作成

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

★練習問題 72 名前の変更

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

★練習問題 73 単純なlsプログラム

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

ls dirname
ファイル名の表示。ただし、「'.'」から始まる名前は表示しない。
ls -ia dirname
アイノード番号とファイル名の表示。
dirname が省略された時には、"." が指定されたものとし て扱うようにしてもよい。

余裕があれば、ls-lプログラムの課題のように、 属性を扱いなさい。

★練習問題 74 ディレクトリの木構造の探索(ls-R)

ls -R のように、ディレクトリの木構造を再帰的に探索して表示するプログラ ムを作りなさい。

この時、次の2つの名前を特別扱いして、探索しないようにする。

さらに、シンボリック・リンク を手繰らないようにしなさい。 シンボリック・リンクを手繰ると、無限ループに陥る可能性がある。

シンボリック・リンクかどうかの判定には、lstat() システムコールを使いな さい。

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

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