9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【C言語】readdir() vs readdir_r()

Last updated at Posted at 2019-05-19

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定数定義の曖昧さから、
バッファオーバーフローにつながるとの指摘がされている。

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

9
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?