readdir() vs readdir_r()
ディレクトリを読み込む関数として
readdir()
/ readdir_r()
の2つの関数が標準ライブラリから提供されている。
使用箇所がシングルスレッドであれば readdir()
を使用しても問題ないが、
マルチスレッドであるとスレッドセーフである readdir_r()
を使用すれば
良いと考えるのが順当である。
結論からいうと readdir_r()
は使用すべきではない。
マルチスレッド場合は readdir() + 排他制御
をした方が良い。
readdir()readdir_r() の仕様
それでは何故そうなるかを見ていこう。
まずはreaddir() と readdir_r() の仕様を確認していく。
readdir()の仕様(POSIX.1-2008)
POSIX.1-2008では readdir()
のスレッドセーフ性は保障されないとされている。
DESCRIPTION
The readdir() function need not be thread-safe.
参照:readdir, readdir_r - read a directory
2.9.1 Thread-Safety
All functions defined by this volume of POSIX.1-2017 shall be thread-safe, except that the following functions1 need not be thread-safe.
readdir()
参照:The Open Group Base Specifications Issue 7, 2018 edition - 2.9.1 Thread-Safety
readdir_r()の仕様
int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result);
readdir_r()
はディレクトリストリーム dirp から次のディレクトリエントリーを読み込み、
entry が指す呼び出し元が割り当てたバッファーにそのエントリーを格納して返す。
返されるエントリーへのポインターが *result に格納される。
ディレクトリストリームの末尾に達した場合は、 NULL が *result に格納される。
Linux では dirent 構造体は以下のように定義されている。
struct dirent {
ino_t d_ino; /* inode number */
off_t d_off; /* not an offset; see NOTES */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file; not supported by all file system types */
char d_name[256]; /* filename */
};
POSIX.1 における曖昧さが起こす問題点
POSIX.1 では d_name フィールドのサイズは規定されておらず、(*1)
dirent 構造体の d_name の後ろに他の非標準のフィールドがあるかもしれないので、
移植性が必要なアプリケーションで readdir_r() を使う場合は entry に渡すバッファーを
次のようにして割り当てるべきである。
name_max = pathconf(dirpath, _PC_NAME_MAX);
if (name_max == -1) /* Limit not defined, or error */
name_max = 255; /* Take a guess */
len = offsetof(struct dirent, d_name) + name_max + 1;
entryp = malloc(len);
(*1)Solarisではd_nameが d_name[1] として定義されているそう。
しかし、この上記の対策が_PC_NAME_MAX定数定義の曖昧さから、
バッファオーバーフローにつながるとの指摘がされている。
- 参照:readdir_r considered harmful
- 参照:0000696: either NAME_MAX shouldn't be optional, or readdir_r() needs clarification
Linux/glibc側の見解
In the current POSIX.1 specification (POSIX.1-2008), readdir() is not
required to be thread-safe. However, in modern implementations
(including the glibc implementation), concurrent calls to readdir()
that specify different directory streams are thread-safe. In cases
where multiple threads must read from the same directory stream,
using readdir() with external synchronization is still preferable to
the use of the deprecated readdir_r(3) function. It is expected that
a future version of POSIX.1 will require that readdir() be thread-
safe when concurrently employed on different directory streams.
- 異なるディレクトリストリームを指定するreaddir()の同時呼び出しはスレッドセーフである。
- 複数のスレッドが同じディレクトリストリームから読み取らなければならない場合、非推奨のreaddir_r() を使用するより、readdir() をmutex等の排他制御と併用することが望ましい。
参照:readdir(3) - Linux manual page
glibc 2.24ではreaddir_r() / readdir64_r()は非推奨
また、glibc 2.24ではreaddir_r()およびreaddir64_r()は非推奨となった。
- The readdir_r and readdir64_r functions have been deprecated. It is
recommended to use readdir and readdir64 instead.
参照:Adhemerval Zanella - The GNU C Library version 2.24 is now available
結論
以上の事からマルチスレッドにおいてもreaddir_r()
を使うのではなく、
readdir() + 排他制御
の使用を検討すべきである。
参考
http://mkosaki.blog46.fc2.com/blog-entry-1237.html
http://blog.gachapin-sensei.com/archives/618834.html