diff options
Diffstat (limited to 'sysdeps/unix/opendir.c')
-rw-r--r-- | sysdeps/unix/opendir.c | 65 |
1 files changed, 53 insertions, 12 deletions
diff --git a/sysdeps/unix/opendir.c b/sysdeps/unix/opendir.c index 067dc2cb59..3cbd6628a3 100644 --- a/sysdeps/unix/opendir.c +++ b/sysdeps/unix/opendir.c @@ -29,9 +29,40 @@ #include <dirstream.h> - -#ifndef O_DIRECTORY -# define O_DIRECTORY 0 +/* opendir() must not accidentally open something other than a directory. + Some OS's have kernel support for that, some don't. In the worst + case we have to stat() before the open() AND fstat() after. + + We have to test at runtime for kernel support since libc may have + been compiled with different headers to the kernel it's running on. + This test can't be done reliably in the general case. We'll use + /dev/null, which if it's not a device lots of stuff will break, as + a guinea pig. It may be missing in chroot environments, so we + make sure to fail safe. */ +#ifdef O_DIRECTORY +static int o_directory_works = -1; + +static void +tryopen_o_directory (void) +{ + int serrno = errno; + int x = __open ("/dev/null", O_RDONLY|O_NDELAY|O_DIRECTORY); + + if (x >= 0) + { + __close (x); + o_directory_works = 0; + } + else if (errno != ENOTDIR) + o_directory_works = 0; + else + o_directory_works = 1; + + __set_errno (serrno); +} +# define EXTRA_FLAGS O_DIRECTORY +#else +# define EXTRA_FLAGS 0 #endif @@ -53,18 +84,28 @@ __opendir (const char *name) return NULL; } - /* We first have to check whether the name is for a directory. We - cannot do this after the open() call since the open/close operation - performed on, say, a tape device might have undesirable effects. */ - if (__stat (name, &statbuf) < 0) - return NULL; - if (! S_ISDIR (statbuf.st_mode)) +#ifdef O_DIRECTORY + /* Test whether O_DIRECTORY works. */ + if (o_directory_works == -1) + tryopen_o_directory (); + + /* We can skip the expensive `stat' call if O_DIRECTORY works. */ + if (o_directory_works == 0) +#endif { - __set_errno (ENOTDIR); - return NULL; + /* We first have to check whether the name is for a directory. We + cannot do this after the open() call since the open/close operation + performed on, say, a tape device might have undesirable effects. */ + if (__stat (name, &statbuf) < 0) + return NULL; + if (! S_ISDIR (statbuf.st_mode)) + { + __set_errno (ENOTDIR); + return NULL; + } } - fd = __open (name, O_RDONLY|O_NDELAY|O_DIRECTORY); + fd = __open (name, O_RDONLY|O_NDELAY|EXTRA_FLAGS); if (fd < 0) return NULL; |