git: 69b6dc0b920b - stable/14 - libc/gen: split user-visible opendir()-like functions into separate source files

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Tue, 04 Mar 2025 04:20:45 UTC
The branch stable/14 has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=69b6dc0b920baaf7bd12b83df08ff78155052261

commit 69b6dc0b920baaf7bd12b83df08ff78155052261
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2025-02-20 23:32:22 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2025-03-04 04:19:08 +0000

    libc/gen: split user-visible opendir()-like functions into separate source files
    
    (cherry picked from commit d40daefca64750c1076822bdbd3c409a9519f513)
---
 lib/libc/gen/Makefile.inc               |   2 +
 lib/libc/gen/fdopendir.c                |  56 ++++++
 lib/libc/gen/opendir.c                  | 312 +----------------------------
 lib/libc/gen/opendir2.c                 | 340 ++++++++++++++++++++++++++++++++
 lib/libc/gen/telldir.h                  |   1 +
 libexec/rtld-elf/rtld-libc/Makefile.inc |   2 +-
 6 files changed, 401 insertions(+), 312 deletions(-)

diff --git a/lib/libc/gen/Makefile.inc b/lib/libc/gen/Makefile.inc
index 332edfcd6695..f9f6f3139379 100644
--- a/lib/libc/gen/Makefile.inc
+++ b/lib/libc/gen/Makefile.inc
@@ -54,6 +54,7 @@ SRCS+=	__getosreldate.c \
 	exec.c \
 	exect.c \
 	fdevname.c \
+	fdopendir.c \
 	feature_present.c \
 	fmtcheck.c \
 	fmtmsg.c \
@@ -112,6 +113,7 @@ SRCS+=	__getosreldate.c \
 	nlist.c \
 	nrand48.c \
 	opendir.c \
+	opendir2.c \
 	pause.c \
 	pmadvise.c \
 	popen.c \
diff --git a/lib/libc/gen/fdopendir.c b/lib/libc/gen/fdopendir.c
new file mode 100644
index 000000000000..67c0766b6d83
--- /dev/null
+++ b/lib/libc/gen/fdopendir.c
@@ -0,0 +1,56 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "namespace.h"
+#include <sys/param.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "un-namespace.h"
+
+#include "gen-private.h"
+#include "telldir.h"
+
+/*
+ * Open a directory with existing file descriptor.
+ */
+DIR *
+fdopendir(int fd)
+{
+
+	if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
+		return (NULL);
+	return (__opendir_common(fd, DTF_HIDEW | DTF_NODUP, true));
+}
diff --git a/lib/libc/gen/opendir.c b/lib/libc/gen/opendir.c
index a3864b0939fc..df288d416a2f 100644
--- a/lib/libc/gen/opendir.c
+++ b/lib/libc/gen/opendir.c
@@ -45,8 +45,6 @@ __SCCSID("@(#)opendir.c	8.8 (Berkeley) 5/1/95");
 #include "gen-private.h"
 #include "telldir.h"
 
-static DIR * __opendir_common(int, int, bool);
-
 /*
  * Open a directory.
  */
@@ -54,313 +52,5 @@ DIR *
 opendir(const char *name)
 {
 
-	return (__opendir2(name, DTF_HIDEW|DTF_NODUP));
-}
-
-/*
- * Open a directory with existing file descriptor.
- */
-DIR *
-fdopendir(int fd)
-{
-
-	if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
-		return (NULL);
-	return (__opendir_common(fd, DTF_HIDEW|DTF_NODUP, true));
-}
-
-DIR *
-__opendir2(const char *name, int flags)
-{
-	int fd;
-	DIR *dir;
-	int saved_errno;
-
-	if ((flags & (__DTF_READALL | __DTF_SKIPREAD)) != 0)
-		return (NULL);
-	if ((fd = _open(name,
-	    O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1)
-		return (NULL);
-
-	dir = __opendir_common(fd, flags, false);
-	if (dir == NULL) {
-		saved_errno = errno;
-		_close(fd);
-		errno = saved_errno;
-	}
-	return (dir);
-}
-
-static int
-opendir_compar(const void *p1, const void *p2)
-{
-
-	return (strcmp((*(const struct dirent * const *)p1)->d_name,
-	    (*(const struct dirent * const *)p2)->d_name));
-}
-
-/*
- * For a directory at the top of a unionfs stack, the entire directory's
- * contents are read and cached locally until the next call to rewinddir().
- * For the fdopendir() case, the initial seek position must be preserved.
- * For rewinddir(), the full directory should always be re-read from the
- * beginning.
- *
- * If an error occurs, the existing buffer and state of 'dirp' is left
- * unchanged.
- */
-bool
-_filldir(DIR *dirp, bool use_current_pos)
-{
-	struct dirent **dpv;
-	char *buf, *ddptr, *ddeptr;
-	off_t pos;
-	int fd2, incr, len, n, saved_errno, space;
-	
-	len = 0;
-	space = 0;
-	buf = NULL;
-	ddptr = NULL;
-
-	/*
-	 * Use the system page size if that is a multiple of DIRBLKSIZ.
-	 * Hopefully this can be a big win someday by allowing page
-	 * trades to user space to be done by _getdirentries().
-	 */
-	incr = getpagesize();
-	if ((incr % DIRBLKSIZ) != 0) 
-		incr = DIRBLKSIZ;
-
-	/*
-	 * The strategy here is to read all the directory
-	 * entries into a buffer, sort the buffer, and
-	 * remove duplicate entries by setting the inode
-	 * number to zero.
-	 *
-	 * We reopen the directory because _getdirentries()
-	 * on a MNT_UNION mount modifies the open directory,
-	 * making it refer to the lower directory after the
-	 * upper directory's entries are exhausted.
-	 * This would otherwise break software that uses
-	 * the directory descriptor for fchdir or *at
-	 * functions, such as fts.c.
-	 */
-	if ((fd2 = _openat(dirp->dd_fd, ".", O_RDONLY | O_CLOEXEC)) == -1)
-		return (false);
-
-	if (use_current_pos) {
-		pos = lseek(dirp->dd_fd, 0, SEEK_CUR);
-		if (pos == -1 || lseek(fd2, pos, SEEK_SET) == -1) {
-			saved_errno = errno;
-			_close(fd2);
-			errno = saved_errno;
-			return (false);
-		}
-	}
-
-	do {
-		/*
-		 * Always make at least DIRBLKSIZ bytes
-		 * available to _getdirentries
-		 */
-		if (space < DIRBLKSIZ) {
-			space += incr;
-			len += incr;
-			buf = reallocf(buf, len);
-			if (buf == NULL) {
-				saved_errno = errno;
-				_close(fd2);
-				errno = saved_errno;
-				return (false);
-			}
-			ddptr = buf + (len - space);
-		}
-
-		n = _getdirentries(fd2, ddptr, space, &dirp->dd_seek);
-		if (n > 0) {
-			ddptr += n;
-			space -= n;
-		}
-		if (n < 0) {
-			saved_errno = errno;
-			_close(fd2);
-			errno = saved_errno;
-			return (false);
-		}
-	} while (n > 0);
-	_close(fd2);
-
-	ddeptr = ddptr;
-
-	/*
-	 * There is now a buffer full of (possibly) duplicate
-	 * names.
-	 */
-	dirp->dd_buf = buf;
-
-	/*
-	 * Go round this loop twice...
-	 *
-	 * Scan through the buffer, counting entries.
-	 * On the second pass, save pointers to each one.
-	 * Then sort the pointers and remove duplicate names.
-	 */
-	for (dpv = NULL;;) {
-		n = 0;
-		ddptr = buf;
-		while (ddptr < ddeptr) {
-			struct dirent *dp;
-
-			dp = (struct dirent *) ddptr;
-			if ((long)dp & 03L)
-				break;
-			if ((dp->d_reclen <= 0) ||
-			    (dp->d_reclen > (ddeptr + 1 - ddptr)))
-				break;
-			ddptr += dp->d_reclen;
-			if (dp->d_fileno) {
-				if (dpv)
-					dpv[n] = dp;
-				n++;
-			}
-		}
-
-		if (dpv) {
-			struct dirent *xp;
-
-			/*
-			 * This sort must be stable.
-			 */
-			mergesort(dpv, n, sizeof(*dpv), opendir_compar);
-
-			dpv[n] = NULL;
-			xp = NULL;
-
-			/*
-			 * Scan through the buffer in sort order,
-			 * zapping the inode number of any
-			 * duplicate names.
-			 */
-			for (n = 0; dpv[n]; n++) {
-				struct dirent *dp = dpv[n];
-
-				if ((xp == NULL) ||
-				    strcmp(dp->d_name, xp->d_name)) {
-					xp = dp;
-				} else {
-					dp->d_fileno = 0;
-				}
-				if (dp->d_type == DT_WHT &&
-				    (dirp->dd_flags & DTF_HIDEW))
-					dp->d_fileno = 0;
-			}
-
-			free(dpv);
-			break;
-		} else {
-			dpv = malloc((n+1) * sizeof(struct dirent *));
-			if (dpv == NULL)
-				break;
-		}
-	}
-
-	dirp->dd_len = len;
-	dirp->dd_size = ddptr - dirp->dd_buf;
-	return (true);
-}
-
-static bool
-is_unionstack(int fd)
-{
-	int unionstack;
-
-	unionstack = _fcntl(fd, F_ISUNIONSTACK, 0);
-	if (unionstack != -1)
-		return (unionstack);
-
-	/*
-	 * Should not happen unless running on a kernel without the op,
-	 * but no use rendering the system useless in such a case.
-	 */
-	return (0);
-}
-
-/*
- * Common routine for opendir(3), __opendir2(3) and fdopendir(3).
- */
-static DIR *
-__opendir_common(int fd, int flags, bool use_current_pos)
-{
-	DIR *dirp;
-	int incr;
-	int saved_errno;
-	bool unionstack;
-
-	if ((dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL)
-		return (NULL);
-
-	dirp->dd_buf = NULL;
-	dirp->dd_fd = fd;
-	dirp->dd_flags = flags;
-	dirp->dd_loc = 0;
-	dirp->dd_lock = NULL;
-	dirp->dd_td = (struct _telldir *)((char *)dirp + sizeof(DIR));
-	LIST_INIT(&dirp->dd_td->td_locq);
-	dirp->dd_td->td_loccnt = 0;
-	dirp->dd_compat_de = NULL;
-
-	/*
-	 * Use the system page size if that is a multiple of DIRBLKSIZ.
-	 * Hopefully this can be a big win someday by allowing page
-	 * trades to user space to be done by _getdirentries().
-	 */
-	incr = getpagesize();
-	if ((incr % DIRBLKSIZ) != 0) 
-		incr = DIRBLKSIZ;
-
-	/*
-	 * Determine whether this directory is the top of a union stack.
-	 */
-	unionstack = false;
-	if (flags & DTF_NODUP) {
-		unionstack = is_unionstack(fd);
-	}
-
-	if (unionstack) {
-		if (!_filldir(dirp, use_current_pos))
-			goto fail;
-		dirp->dd_flags |= __DTF_READALL;
-	} else {
-		dirp->dd_len = incr;
-		dirp->dd_buf = malloc(dirp->dd_len);
-		if (dirp->dd_buf == NULL)
-			goto fail;
-		if (use_current_pos) {
-			/*
-			 * Read the first batch of directory entries
-			 * to prime dd_seek.  This also checks if the
-			 * fd passed to fdopendir() is a directory.
-			 */
-			dirp->dd_size = _getdirentries(dirp->dd_fd,
-			    dirp->dd_buf, dirp->dd_len, &dirp->dd_seek);
-			if (dirp->dd_size < 0) {
-				if (errno == EINVAL)
-					errno = ENOTDIR;
-				goto fail;
-			}
-			dirp->dd_flags |= __DTF_SKIPREAD;
-		} else {
-			dirp->dd_size = 0;
-			dirp->dd_seek = 0;
-		}
-	}
-
-	return (dirp);
-
-fail:
-	saved_errno = errno;
-	free(dirp->dd_buf);
-	free(dirp);
-	errno = saved_errno;
-	return (NULL);
+	return (__opendir2(name, DTF_HIDEW | DTF_NODUP));
 }
diff --git a/lib/libc/gen/opendir2.c b/lib/libc/gen/opendir2.c
new file mode 100644
index 000000000000..30a9030693e4
--- /dev/null
+++ b/lib/libc/gen/opendir2.c
@@ -0,0 +1,340 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1983, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "namespace.h"
+#include <sys/param.h>
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "un-namespace.h"
+
+#include "gen-private.h"
+#include "telldir.h"
+
+DIR *
+__opendir2(const char *name, int flags)
+{
+	int fd;
+	DIR *dir;
+	int saved_errno;
+
+	if ((flags & (__DTF_READALL | __DTF_SKIPREAD)) != 0)
+		return (NULL);
+	if ((fd = _open(name,
+	    O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1)
+		return (NULL);
+
+	dir = __opendir_common(fd, flags, false);
+	if (dir == NULL) {
+		saved_errno = errno;
+		_close(fd);
+		errno = saved_errno;
+	}
+	return (dir);
+}
+
+static int
+opendir_compar(const void *p1, const void *p2)
+{
+
+	return (strcmp((*(const struct dirent * const *)p1)->d_name,
+	    (*(const struct dirent * const *)p2)->d_name));
+}
+
+/*
+ * For a directory at the top of a unionfs stack, the entire directory's
+ * contents are read and cached locally until the next call to rewinddir().
+ * For the fdopendir() case, the initial seek position must be preserved.
+ * For rewinddir(), the full directory should always be re-read from the
+ * beginning.
+ *
+ * If an error occurs, the existing buffer and state of 'dirp' is left
+ * unchanged.
+ */
+bool
+_filldir(DIR *dirp, bool use_current_pos)
+{
+	struct dirent **dpv;
+	char *buf, *ddptr, *ddeptr;
+	off_t pos;
+	int fd2, incr, len, n, saved_errno, space;
+
+	len = 0;
+	space = 0;
+	buf = NULL;
+	ddptr = NULL;
+
+	/*
+	 * Use the system page size if that is a multiple of DIRBLKSIZ.
+	 * Hopefully this can be a big win someday by allowing page
+	 * trades to user space to be done by _getdirentries().
+	 */
+	incr = getpagesize();
+	if ((incr % DIRBLKSIZ) != 0)
+		incr = DIRBLKSIZ;
+
+	/*
+	 * The strategy here is to read all the directory
+	 * entries into a buffer, sort the buffer, and
+	 * remove duplicate entries by setting the inode
+	 * number to zero.
+	 *
+	 * We reopen the directory because _getdirentries()
+	 * on a MNT_UNION mount modifies the open directory,
+	 * making it refer to the lower directory after the
+	 * upper directory's entries are exhausted.
+	 * This would otherwise break software that uses
+	 * the directory descriptor for fchdir or *at
+	 * functions, such as fts.c.
+	 */
+	if ((fd2 = _openat(dirp->dd_fd, ".", O_RDONLY | O_CLOEXEC)) == -1)
+		return (false);
+
+	if (use_current_pos) {
+		pos = lseek(dirp->dd_fd, 0, SEEK_CUR);
+		if (pos == -1 || lseek(fd2, pos, SEEK_SET) == -1) {
+			saved_errno = errno;
+			_close(fd2);
+			errno = saved_errno;
+			return (false);
+		}
+	}
+
+	do {
+		/*
+		 * Always make at least DIRBLKSIZ bytes
+		 * available to _getdirentries
+		 */
+		if (space < DIRBLKSIZ) {
+			space += incr;
+			len += incr;
+			buf = reallocf(buf, len);
+			if (buf == NULL) {
+				saved_errno = errno;
+				_close(fd2);
+				errno = saved_errno;
+				return (false);
+			}
+			ddptr = buf + (len - space);
+		}
+
+		n = _getdirentries(fd2, ddptr, space, &dirp->dd_seek);
+		if (n > 0) {
+			ddptr += n;
+			space -= n;
+		}
+		if (n < 0) {
+			saved_errno = errno;
+			_close(fd2);
+			errno = saved_errno;
+			return (false);
+		}
+	} while (n > 0);
+	_close(fd2);
+
+	ddeptr = ddptr;
+
+	/*
+	 * There is now a buffer full of (possibly) duplicate
+	 * names.
+	 */
+	dirp->dd_buf = buf;
+
+	/*
+	 * Go round this loop twice...
+	 *
+	 * Scan through the buffer, counting entries.
+	 * On the second pass, save pointers to each one.
+	 * Then sort the pointers and remove duplicate names.
+	 */
+	for (dpv = NULL;;) {
+		n = 0;
+		ddptr = buf;
+		while (ddptr < ddeptr) {
+			struct dirent *dp;
+
+			dp = (struct dirent *) ddptr;
+			if ((long)dp & 03L)
+				break;
+			if ((dp->d_reclen <= 0) ||
+			    (dp->d_reclen > (ddeptr + 1 - ddptr)))
+				break;
+			ddptr += dp->d_reclen;
+			if (dp->d_fileno) {
+				if (dpv)
+					dpv[n] = dp;
+				n++;
+			}
+		}
+
+		if (dpv) {
+			struct dirent *xp;
+
+			/*
+			 * This sort must be stable.
+			 */
+			mergesort(dpv, n, sizeof(*dpv), opendir_compar);
+
+			dpv[n] = NULL;
+			xp = NULL;
+
+			/*
+			 * Scan through the buffer in sort order,
+			 * zapping the inode number of any
+			 * duplicate names.
+			 */
+			for (n = 0; dpv[n]; n++) {
+				struct dirent *dp = dpv[n];
+
+				if ((xp == NULL) ||
+				    strcmp(dp->d_name, xp->d_name)) {
+					xp = dp;
+				} else {
+					dp->d_fileno = 0;
+				}
+				if (dp->d_type == DT_WHT &&
+				    (dirp->dd_flags & DTF_HIDEW))
+					dp->d_fileno = 0;
+			}
+
+			free(dpv);
+			break;
+		} else {
+			dpv = malloc((n+1) * sizeof(struct dirent *));
+			if (dpv == NULL)
+				break;
+		}
+	}
+
+	dirp->dd_len = len;
+	dirp->dd_size = ddptr - dirp->dd_buf;
+	return (true);
+}
+
+static bool
+is_unionstack(int fd)
+{
+	int unionstack;
+
+	unionstack = _fcntl(fd, F_ISUNIONSTACK, 0);
+	if (unionstack != -1)
+		return (unionstack);
+
+	/*
+	 * Should not happen unless running on a kernel without the op,
+	 * but no use rendering the system useless in such a case.
+	 */
+	return (0);
+}
+
+/*
+ * Common routine for opendir(3), __opendir2(3) and fdopendir(3).
+ */
+DIR *
+__opendir_common(int fd, int flags, bool use_current_pos)
+{
+	DIR *dirp;
+	int incr;
+	int saved_errno;
+	bool unionstack;
+
+	if ((dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL)
+		return (NULL);
+
+	dirp->dd_buf = NULL;
+	dirp->dd_fd = fd;
+	dirp->dd_flags = flags;
+	dirp->dd_loc = 0;
+	dirp->dd_lock = NULL;
+	dirp->dd_td = (struct _telldir *)((char *)dirp + sizeof(DIR));
+	LIST_INIT(&dirp->dd_td->td_locq);
+	dirp->dd_td->td_loccnt = 0;
+	dirp->dd_compat_de = NULL;
+
+	/*
+	 * Use the system page size if that is a multiple of DIRBLKSIZ.
+	 * Hopefully this can be a big win someday by allowing page
+	 * trades to user space to be done by _getdirentries().
+	 */
+	incr = getpagesize();
+	if ((incr % DIRBLKSIZ) != 0)
+		incr = DIRBLKSIZ;
+
+	/*
+	 * Determine whether this directory is the top of a union stack.
+	 */
+	unionstack = false;
+	if (flags & DTF_NODUP) {
+		unionstack = is_unionstack(fd);
+	}
+
+	if (unionstack) {
+		if (!_filldir(dirp, use_current_pos))
+			goto fail;
+		dirp->dd_flags |= __DTF_READALL;
+	} else {
+		dirp->dd_len = incr;
+		dirp->dd_buf = malloc(dirp->dd_len);
+		if (dirp->dd_buf == NULL)
+			goto fail;
+		if (use_current_pos) {
+			/*
+			 * Read the first batch of directory entries
+			 * to prime dd_seek.  This also checks if the
+			 * fd passed to fdopendir() is a directory.
+			 */
+			dirp->dd_size = _getdirentries(dirp->dd_fd,
+			    dirp->dd_buf, dirp->dd_len, &dirp->dd_seek);
+			if (dirp->dd_size < 0) {
+				if (errno == EINVAL)
+					errno = ENOTDIR;
+				goto fail;
+			}
+			dirp->dd_flags |= __DTF_SKIPREAD;
+		} else {
+			dirp->dd_size = 0;
+			dirp->dd_seek = 0;
+		}
+	}
+
+	return (dirp);
+
+fail:
+	saved_errno = errno;
+	free(dirp->dd_buf);
+	free(dirp);
+	errno = saved_errno;
+	return (NULL);
+}
diff --git a/lib/libc/gen/telldir.h b/lib/libc/gen/telldir.h
index aafa6ac71b1e..6d113491e819 100644
--- a/lib/libc/gen/telldir.h
+++ b/lib/libc/gen/telldir.h
@@ -103,6 +103,7 @@ struct dirent	*_readdir_unlocked(DIR *, int);
 void 		_reclaim_telldir(DIR *);
 void 		_seekdir(DIR *, long);
 void		_fixtelldir(DIR *dirp, long oldseek, long oldloc);
+DIR		*__opendir_common(int, int, bool);
 
 #define	RDU_SKIP	0x0001
 #define	RDU_SHORT	0x0002
diff --git a/libexec/rtld-elf/rtld-libc/Makefile.inc b/libexec/rtld-elf/rtld-libc/Makefile.inc
index 91eb6f5cabec..8a9480bc746e 100644
--- a/libexec/rtld-elf/rtld-libc/Makefile.inc
+++ b/libexec/rtld-elf/rtld-libc/Makefile.inc
@@ -17,7 +17,7 @@ CFLAGS+=	-I${SRCTOP}/libexec/rtld-elf/rtld-libc
 # redirects all calls to interposed functions to use the non-interposed version
 # instead.
 .PATH: ${LIBC_SRCTOP}/gen
-SRCS+=	opendir.c closedir.c readdir.c telldir.c
+SRCS+=	fdopendir.c opendir.c opendir2.c closedir.c readdir.c telldir.c
 
 # Avoid further dependencies by providing simple implementations of libc
 # functions such as __error(), etc.