man readdir
literally says:
On success, readdir()
returns a pointer to a dirent structure.
(This structure may be statically allocated; do not attempt to
free(3)
it.)
(Code formatters added.)
That means the space for it is not allocated at runtime such as stack or free store memory but is static
: it is in the executable itsself, comparable to string literals with the difference that writing to string literals is undefined behavior.
Imagine the implementation to be something like this:
struct dirent *readdir(DIR *dirp) {
static struct dirent dir;
/* Fill dir with appropriate values. */
return &dir;
}
dir
is statically allocated here. Returning its address isn't wrong because it exists throughout the whole runtime of the program.
Here is the actual source code of readdir
on my glibc 2.22 implementation (the path is /sysdeps/posix/readdir.c
):
DIRENT_TYPE *
__READDIR (DIR *dirp)
{
DIRENT_TYPE *dp;
int saved_errno = errno;
#if IS_IN (libc)
__libc_lock_lock (dirp->lock);
#endif
do
{
size_t reclen;
if (dirp->offset >= dirp->size)
{
/* We've emptied out our buffer. Refill it. */
size_t maxread;
ssize_t bytes;
#ifndef _DIRENT_HAVE_D_RECLEN
/* Fixed-size struct; must read one at a time (see below). */
maxread = sizeof *dp;
#else
maxread = dirp->allocation;
#endif
bytes = __GETDENTS (dirp->fd, dirp->data, maxread);
if (bytes <= 0)
{
/* On some systems getdents fails with ENOENT when the
open directory has been rmdir'd already. POSIX.1
requires that we treat this condition like normal EOF. */
if (bytes < 0 && errno == ENOENT)
bytes = 0;
/* Don't modifiy errno when reaching EOF. */
if (bytes == 0)
__set_errno (saved_errno);
dp = NULL;
break;
}
dirp->size = (size_t) bytes;
/* Reset the offset into the buffer. */
dirp->offset = 0;
}
dp = (DIRENT_TYPE *) &dirp->data[dirp->offset];
#ifdef _DIRENT_HAVE_D_RECLEN
reclen = dp->d_reclen;
#else
/* The only version of `struct dirent*' that lacks `d_reclen'
is fixed-size. */
assert (sizeof dp->d_name > 1);
reclen = sizeof *dp;
/* The name is not terminated if it is the largest possible size.
Clobber the following byte to ensure proper null termination. We
read jst one entry at a time above so we know that byte will not
be used later. */
dp->d_name[sizeof dp->d_name] = '\0';
#endif
dirp->offset += reclen;
#ifdef _DIRENT_HAVE_D_OFF
dirp->filepos = dp->d_off;
#else
dirp->filepos += reclen;
#endif
/* Skip deleted files. */
} while (dp->d_ino == 0);
#if IS_IN (libc)
__libc_lock_unlock (dirp->lock);
#endif
return dp;
}
I don't know much about glibc but the line
dp = (DIRENT_TYPE *) &dirp->data[dirp->offset];
seems the most interesting to us. dirp->data
is the static
data here, as far as I can tell.
That is the reason as to why there is the reentrant alternative readdir_r
and readdir
is not reentrant.
Imagine two threads concurrently executing readdir
. Both will attempt to fill dir
, which is shared among all readdir
invocations, simultaneously, resulting in unsequenced memory reads/writes.