ファイルチェックに関する調査

SPECIAL


はじめに

ただ慣れていないせいなのか、どうもファイル操作というのは簡単には行えないようで…。なのでちょっと調べたついでに記録に残しておくことにしました。

内容は、実際に試しているとは限らないので、そのあたりを注意して利用してください。

ディレクトリ一覧を取得する

ディレクトリの中のファイルエントリを取得したい場合には、 opendir システムコールを利用するのがいいようです。

このシステムコールは戻り値として DIR 型のディレクトリエントリの先頭を示す値を返します。これが NULL として戻ってきた場合はエラーです。

エラーは errno の値を参照することによって区別でき、その内容は次のようなものだそうです。

EACCES 13 アクセス権がない。
EMFILE 24 プロセスで使われているファイルディスクリプタが多すぎる。
ENFILE 23 システムで開かれているファイルが多すぎる。
ENOENT 2 ディレクトリエントリが存在しない。
ENOMEM 12 十分なメモリがない。
ENOTDIR 20 ディレクトリではない。

なお、これらのエラー定数は <errno.h> にて定義されているそうです。

 

これを利用してディレクトリエントリを取得するには、戻ってきた DIR 型の値を、readdir() を用いて順次取り出して、最後に closedir() にて手続きを終了する、という形になります。

readdir() は dirent 型へのポインタを返します。エントリを読みきったり、またはエラーが発生した場合は NULL を返します。

 

dirent 構造体には次のようなメンバが存在します。

d_ino inode number of entry
d_off offset of disk directory entry
d_reclen length of this record
d_name name of file

なにやらいくつかあるようですけど、とりあえず、d_name を参照することによってエントリ名の文字列を取得することが出来ます。

 

これらを利用して、実際にエントリを取得するプログラムは次のような感じになります。

#include <sys/types.h>

#include <dirent.h>

 

 

DIR *dp = opendir(dir);

struct dirent *ent;

 

if (dp != NULL)

{

while ((ent = readdir(dp)) != NULL)

{

// ここで ent->d_name を利用して何かをする…

}

}

 

closedir(dp);

取得できるエントリには、一般的なものの他、"." から始まる隠しファイルや、カレントディレクトリを示す "." と、親ディレクトリを示す ".." も含まれるようです。

ファイルが存在するかを調べる [opendir]

ファイルの存在チェックなのですけど、どうもいまいちこれだっ、という情報が得られませんでした^^;;

なので上の 「ディレクトリ一覧を取得する」 で調べた内容をつかってやる方法を書いてみることにします。

 

まあ、ディレクトリエントリを取得して名前と比較すればとりあえずそのエントリがあるかはわかるのですけど、ループさせるのも、文字列比較するのもなんだし。

幸い、opendir がいろいろとエラーを返してくれるようなのでこちらが使えそうです。

 

ファイルが存在するかを調べるのですから、ファイル名はわかるでしょう…。

そのファイル名を直接、opendir に渡します。そうすると、ディレクトリならば NULL 以外が返ってきますし、エラーが発生すれば NULL とともに、errno 変数にエラー内容が保存されます。

このエラー内容には、権限とかそもそも存在しないとかいったシステム的なエラーのほかに、ENOENT としてエントリが存在しない、ENOTDIR としてエントリはディレクトリではない、といった判別が可能のようです。

なのでこれらをうまく判定すれば、単純にエントリが存在していることをしらべるとか、またはそのエントリがディレクトリであるのかいないのか、といった程度の判別が可能です。

ファイルが存在するかを調べる [stat]

もう少し調べてみたところ、stat という関数を利用してファイルの状態を取得することができるようでした。これを利用するには sys/types.h, sys/stat.h, unistd.h を組み込む必要があるようです。

stat 関数は次のような値を持つ stat 構造体を返すとのことなので、基本的にはこれらの情報を取得することができます。

st_dev デバイス
st_ino i node
st_mode プロテクション
st_nlink ハードリンクの数
st_uid 所有者の UID
st_gid 所有者の GID
st_rdev デバイスタイプ (i node デバイスのみ)
st_size 合計サイズ (バイト単位) / シンボリックリンク自身の大きさは、含まれるパス名の長さらしい。
st_blksize ブロックサイズ (ファイルシステム I/O 用?)
st_blocks 割り当てられているブロック数 / 1 ブロックは 512 バイトらしい。
st_atime 最終アクセス日
st_mtime 最終更新日(内容の修正で更新されるらしい) [modification]
st_ctime 最終更新日(内容の修正や所有権の変更等で更新されるらしい) [change]

このような感じのようです。

なお、stat 系の関数には3タイプがあり、どれも2番目の引数に stat 型の構造体へのポインタを渡すのですけど、1番目の引数は、stat と lstat はファイル名を文字列で、fstat は open が返すファイルハンドルで指定します。

stat と lstat の違いはシンボリックリンクに対する振る舞いの違いで、stat の方はリンク先の情報を取得するのに対して、lstat の方はシンボリックリンク自身の情報を取得するそうです。

戻り値は int 型で、0 ならば成功、-1 ならば失敗となります。またエラーの場合、errno に次の情報が設定されます。

EBADF fstat の第一引数 (ファイルハンドル) が無効
ENOTENT stat または lstat の第一引数 (パス) が存在しない
ENOTDIR パスの成分がディレクトリではない(?)
ELOOP 指定されたパスに含まれるシンボリックリンクが多すぎる
EFAULT アドレスが間違っている(?)
EACCES アクセス許可がない
ENOMEM カーネルのメモリが足りない
ENAMETOOLONG ファイル名が長すぎる

 

さて今回使いたいと思われる肝心の st_rdev に関する情報が検索不足なのか得られませんでしたが、なにやら st_mode の方がファイルであるかどうかなどを判定するのに使う情報のような感じがしたので、とりあえず、そのあたりをまとめてみます。

S_IFMT 0x0170000 ビットマスク (ファイル型の判定用)
S_IFSOCK 0x0140000 ソケット
S_IFLNK 0x0120000 シンボリックリンク
S_IFREG 0x0100000 通常のファイル
S_IFBLK 0x0060000 ブロックデバイス
S_IFDIR 0x0040000 ディレクトリ
S_IFCHR 0x0020000 キャラクターデバイス
S_IFIFO 0x0010000 パイプ (FIFO)
S_ISUID 0x0004000 set UID ビット
S_ISGID 0x0002000 set GID ビット
S_ISVTX 0x0001000 スティッキービット
S_IRWXU 0x0000700 ビットマスク (所有者の権限判定用)
S_IRUSR 0x0000400 所有者の読み込み許可
S_IWUSR 0x0000200 所有者の書き込み許可
S_IXUSR 0x0000100 所有者の実行許可
S_IRWXG 0x0000070 ビットマスク (グループの権限判定用)
S_IRGRP 0x0000040 グループの読み込み許可
S_IWGRP 0x0000020 グループの書き込み許可
S_IXGRP 0x0000010 グループの実行許可
S_IRWXO 0x0000007 ビットマスク (他人の権限判定用)
S_IROTH 0x0000004 他人の読み込み許可
S_IWOTH 0x0000002 他人の書き込み許可
S_IXOTH 0x0000001 他人の実行許可

これらを使って論理演算すれば、普通のファイルなのかディレクトリなのかとかが判別できそうな感じですね。

そして、この判定を行ってくれるマクロがある、とのことなのでそれを挙げてみます。

S_ISREG(m) 通常のファイルかどうかを調べる。
S_ISDIR(m) ディレクトリかどうかを調べる。
S_ISCHR(m) キャラクターデバイスであるかを調べる。
S_ISBLK(m) ブロックデバイスであるかを調べる。
S_ISFIFO(m) FIFO (名前付きパイプ) であるかを調べる。
S_ISLINK(m) シンボリックリンクであるかを調べる。
S_ISSOCK(m) ソケットであるかを調べる。

よって、取得した st_mode の値をこれらのマクロで処理すれば、けっこう手軽にファイルの種類を特定できそうですね。