fdopendir.c 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /* provide a replacement fdopendir function
  2. Copyright (C) 2004-2015 Free Software Foundation, Inc.
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation; either version 3 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>. */
  13. /* written by Jim Meyering */
  14. #include <config.h>
  15. #include <dirent.h>
  16. #include <stdlib.h>
  17. #include <unistd.h>
  18. #if !HAVE_FDOPENDIR
  19. # include "openat.h"
  20. # include "openat-priv.h"
  21. # include "save-cwd.h"
  22. # if GNULIB_DIRENT_SAFER
  23. # include "dirent--.h"
  24. # endif
  25. # ifndef REPLACE_FCHDIR
  26. # define REPLACE_FCHDIR 0
  27. # endif
  28. static DIR *fdopendir_with_dup (int, int, struct saved_cwd const *);
  29. static DIR *fd_clone_opendir (int, struct saved_cwd const *);
  30. /* Replacement for POSIX fdopendir.
  31. First, try to simulate it via opendir ("/proc/self/fd/..."). Failing
  32. that, simulate it by using fchdir metadata, or by doing
  33. save_cwd/fchdir/opendir(".")/restore_cwd.
  34. If either the save_cwd or the restore_cwd fails (relatively unlikely),
  35. then give a diagnostic and exit nonzero.
  36. If successful, the resulting stream is based on FD in
  37. implementations where streams are based on file descriptors and in
  38. applications where no other thread or signal handler allocates or
  39. frees file descriptors. In other cases, consult dirfd on the result
  40. to find out whether FD is still being used.
  41. Otherwise, this function works just like POSIX fdopendir.
  42. W A R N I N G:
  43. Unlike other fd-related functions, this one places constraints on FD.
  44. If this function returns successfully, FD is under control of the
  45. dirent.h system, and the caller should not close or modify the state of
  46. FD other than by the dirent.h functions. */
  47. DIR *
  48. fdopendir (int fd)
  49. {
  50. DIR *dir = fdopendir_with_dup (fd, -1, NULL);
  51. if (! REPLACE_FCHDIR && ! dir)
  52. {
  53. int saved_errno = errno;
  54. if (EXPECTED_ERRNO (saved_errno))
  55. {
  56. struct saved_cwd cwd;
  57. if (save_cwd (&cwd) != 0)
  58. openat_save_fail (errno);
  59. dir = fdopendir_with_dup (fd, -1, &cwd);
  60. saved_errno = errno;
  61. free_cwd (&cwd);
  62. errno = saved_errno;
  63. }
  64. }
  65. return dir;
  66. }
  67. /* Like fdopendir, except that if OLDER_DUPFD is not -1, it is known
  68. to be a dup of FD which is less than FD - 1 and which will be
  69. closed by the caller and not otherwise used by the caller. This
  70. function makes sure that FD is closed and all file descriptors less
  71. than FD are open, and then calls fd_clone_opendir on a dup of FD.
  72. That way, barring race conditions, fd_clone_opendir returns a
  73. stream whose file descriptor is FD.
  74. If REPLACE_FCHDIR or CWD is null, use opendir ("/proc/self/fd/...",
  75. falling back on fchdir metadata. Otherwise, CWD is a saved version
  76. of the working directory; use fchdir/opendir(".")/restore_cwd(CWD). */
  77. static DIR *
  78. fdopendir_with_dup (int fd, int older_dupfd, struct saved_cwd const *cwd)
  79. {
  80. int dupfd = dup (fd);
  81. if (dupfd < 0 && errno == EMFILE)
  82. dupfd = older_dupfd;
  83. if (dupfd < 0)
  84. return NULL;
  85. else
  86. {
  87. DIR *dir;
  88. int saved_errno;
  89. if (dupfd < fd - 1 && dupfd != older_dupfd)
  90. {
  91. dir = fdopendir_with_dup (fd, dupfd, cwd);
  92. saved_errno = errno;
  93. }
  94. else
  95. {
  96. close (fd);
  97. dir = fd_clone_opendir (dupfd, cwd);
  98. saved_errno = errno;
  99. if (! dir)
  100. {
  101. int fd1 = dup (dupfd);
  102. if (fd1 != fd)
  103. openat_save_fail (fd1 < 0 ? errno : EBADF);
  104. }
  105. }
  106. if (dupfd != older_dupfd)
  107. close (dupfd);
  108. errno = saved_errno;
  109. return dir;
  110. }
  111. }
  112. /* Like fdopendir, except the result controls a clone of FD. It is
  113. the caller's responsibility both to close FD and (if the result is
  114. not null) to closedir the result. */
  115. static DIR *
  116. fd_clone_opendir (int fd, struct saved_cwd const *cwd)
  117. {
  118. if (REPLACE_FCHDIR || ! cwd)
  119. {
  120. DIR *dir = NULL;
  121. int saved_errno = EOPNOTSUPP;
  122. char buf[OPENAT_BUFFER_SIZE];
  123. char *proc_file = openat_proc_name (buf, fd, ".");
  124. if (proc_file)
  125. {
  126. dir = opendir (proc_file);
  127. saved_errno = errno;
  128. if (proc_file != buf)
  129. free (proc_file);
  130. }
  131. # if REPLACE_FCHDIR
  132. if (! dir && EXPECTED_ERRNO (saved_errno))
  133. {
  134. char const *name = _gl_directory_name (fd);
  135. DIR *dp = name ? opendir (name) : NULL;
  136. /* The caller has done an elaborate dance to arrange for opendir to
  137. consume just the right file descriptor. If dirfd returns -1,
  138. though, we're on a system like mingw where opendir does not
  139. consume a file descriptor. Consume it via 'dup' instead. */
  140. if (dp && dirfd (dp) < 0)
  141. dup (fd);
  142. return dp;
  143. }
  144. # endif
  145. errno = saved_errno;
  146. return dir;
  147. }
  148. else
  149. {
  150. if (fchdir (fd) != 0)
  151. return NULL;
  152. else
  153. {
  154. DIR *dir = opendir (".");
  155. int saved_errno = errno;
  156. if (restore_cwd (cwd) != 0)
  157. openat_restore_fail (errno);
  158. errno = saved_errno;
  159. return dir;
  160. }
  161. }
  162. }
  163. #else /* HAVE_FDOPENDIR */
  164. # include <errno.h>
  165. # include <sys/stat.h>
  166. # undef fdopendir
  167. /* Like fdopendir, but work around GNU/Hurd bug by validating FD. */
  168. DIR *
  169. rpl_fdopendir (int fd)
  170. {
  171. struct stat st;
  172. if (fstat (fd, &st))
  173. return NULL;
  174. if (!S_ISDIR (st.st_mode))
  175. {
  176. errno = ENOTDIR;
  177. return NULL;
  178. }
  179. return fdopendir (fd);
  180. }
  181. #endif /* HAVE_FDOPENDIR */