chdir-long.c 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /* provide a chdir function that tries not to fail due to ENAMETOOLONG
  2. Copyright (C) 2004-2021 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 <https://www.gnu.org/licenses/>. */
  13. /* written by Jim Meyering */
  14. #include <config.h>
  15. #include "chdir-long.h"
  16. #include <errno.h>
  17. #include <fcntl.h>
  18. #include <stdlib.h>
  19. #include <stdbool.h>
  20. #include <string.h>
  21. #include <stdio.h>
  22. #include "assure.h"
  23. #ifndef PATH_MAX
  24. # error "compile this file only if your system defines PATH_MAX"
  25. #endif
  26. /* The results of openat() in this file are not leaked to any
  27. single-threaded code that could use stdio.
  28. FIXME - if the kernel ever adds support for multi-thread safety for
  29. avoiding standard fds, then we should use openat_safer. */
  30. struct cd_buf
  31. {
  32. int fd;
  33. };
  34. static void
  35. cdb_init (struct cd_buf *cdb)
  36. {
  37. cdb->fd = AT_FDCWD;
  38. }
  39. static int
  40. cdb_fchdir (struct cd_buf const *cdb)
  41. {
  42. return fchdir (cdb->fd);
  43. }
  44. static void
  45. cdb_free (struct cd_buf const *cdb)
  46. {
  47. if (0 <= cdb->fd)
  48. {
  49. bool close_fail = close (cdb->fd);
  50. assure (! close_fail);
  51. }
  52. }
  53. /* Given a file descriptor of an open directory (or AT_FDCWD), CDB->fd,
  54. try to open the CDB->fd-relative directory, DIR. If the open succeeds,
  55. update CDB->fd with the resulting descriptor, close the incoming file
  56. descriptor, and return zero. Upon failure, return -1 and set errno. */
  57. static int
  58. cdb_advance_fd (struct cd_buf *cdb, char const *dir)
  59. {
  60. int new_fd = openat (cdb->fd, dir,
  61. O_SEARCH | O_DIRECTORY | O_NOCTTY | O_NONBLOCK);
  62. if (new_fd < 0)
  63. return -1;
  64. cdb_free (cdb);
  65. cdb->fd = new_fd;
  66. return 0;
  67. }
  68. /* Return a pointer to the first non-slash in S. */
  69. static char * _GL_ATTRIBUTE_PURE
  70. find_non_slash (char const *s)
  71. {
  72. size_t n_slash = strspn (s, "/");
  73. return (char *) s + n_slash;
  74. }
  75. /* This is a function much like chdir, but without the PATH_MAX limitation
  76. on the length of the directory name. A significant difference is that
  77. it must be able to modify (albeit only temporarily) the directory
  78. name. It handles an arbitrarily long directory name by operating
  79. on manageable portions of the name. On systems without the openat
  80. syscall, this means changing the working directory to more and more
  81. "distant" points along the long directory name and then restoring
  82. the working directory. If any of those attempts to save or restore
  83. the working directory fails, this function exits nonzero.
  84. Note that this function may still fail with errno == ENAMETOOLONG, but
  85. only if the specified directory name contains a component that is long
  86. enough to provoke such a failure all by itself (e.g. if the component
  87. has length PATH_MAX or greater on systems that define PATH_MAX). */
  88. int
  89. chdir_long (char *dir)
  90. {
  91. int e = chdir (dir);
  92. if (e == 0 || errno != ENAMETOOLONG)
  93. return e;
  94. {
  95. size_t len = strlen (dir);
  96. char *dir_end = dir + len;
  97. struct cd_buf cdb;
  98. size_t n_leading_slash;
  99. cdb_init (&cdb);
  100. /* If DIR is the empty string, then the chdir above
  101. must have failed and set errno to ENOENT. */
  102. assure (0 < len);
  103. assure (PATH_MAX <= len);
  104. /* Count leading slashes. */
  105. n_leading_slash = strspn (dir, "/");
  106. /* Handle any leading slashes as well as any name that matches
  107. the regular expression, m!^//hostname[/]*! . Handling this
  108. prefix separately usually results in a single additional
  109. cdb_advance_fd call, but it's worthwhile, since it makes the
  110. code in the following loop cleaner. */
  111. if (n_leading_slash == 2)
  112. {
  113. int err;
  114. /* Find next slash.
  115. We already know that dir[2] is neither a slash nor '\0'. */
  116. char *slash = memchr (dir + 3, '/', dir_end - (dir + 3));
  117. if (slash == NULL)
  118. {
  119. errno = ENAMETOOLONG;
  120. return -1;
  121. }
  122. *slash = '\0';
  123. err = cdb_advance_fd (&cdb, dir);
  124. *slash = '/';
  125. if (err != 0)
  126. goto Fail;
  127. dir = find_non_slash (slash + 1);
  128. }
  129. else if (n_leading_slash)
  130. {
  131. if (cdb_advance_fd (&cdb, "/") != 0)
  132. goto Fail;
  133. dir += n_leading_slash;
  134. }
  135. assure (*dir != '/');
  136. assure (dir <= dir_end);
  137. while (PATH_MAX <= dir_end - dir)
  138. {
  139. int err;
  140. /* Find a slash that is PATH_MAX or fewer bytes away from dir.
  141. I.e. see if there is a slash that will give us a name of
  142. length PATH_MAX-1 or less. */
  143. char *slash = memrchr (dir, '/', PATH_MAX);
  144. if (slash == NULL)
  145. {
  146. errno = ENAMETOOLONG;
  147. return -1;
  148. }
  149. *slash = '\0';
  150. assure (slash - dir < PATH_MAX);
  151. err = cdb_advance_fd (&cdb, dir);
  152. *slash = '/';
  153. if (err != 0)
  154. goto Fail;
  155. dir = find_non_slash (slash + 1);
  156. }
  157. if (dir < dir_end)
  158. {
  159. if (cdb_advance_fd (&cdb, dir) != 0)
  160. goto Fail;
  161. }
  162. if (cdb_fchdir (&cdb) != 0)
  163. goto Fail;
  164. cdb_free (&cdb);
  165. return 0;
  166. Fail:
  167. {
  168. int saved_errno = errno;
  169. cdb_free (&cdb);
  170. errno = saved_errno;
  171. return -1;
  172. }
  173. }
  174. }
  175. #if TEST_CHDIR
  176. # include "closeout.h"
  177. # include "error.h"
  178. int
  179. main (int argc, char *argv[])
  180. {
  181. char *line = NULL;
  182. size_t n = 0;
  183. int len;
  184. atexit (close_stdout);
  185. len = getline (&line, &n, stdin);
  186. if (len < 0)
  187. {
  188. int saved_errno = errno;
  189. if (feof (stdin))
  190. exit (0);
  191. error (EXIT_FAILURE, saved_errno,
  192. "reading standard input");
  193. }
  194. else if (len == 0)
  195. exit (0);
  196. if (line[len-1] == '\n')
  197. line[len-1] = '\0';
  198. if (chdir_long (line) != 0)
  199. error (EXIT_FAILURE, errno,
  200. "chdir_long failed: %s", line);
  201. if (argc <= 1)
  202. {
  203. /* Using 'pwd' here makes sense only if it is a robust implementation,
  204. like the one in coreutils after the 2004-04-19 changes. */
  205. char const *cmd = "pwd";
  206. execlp (cmd, (char *) NULL);
  207. error (EXIT_FAILURE, errno, "%s", cmd);
  208. }
  209. fclose (stdin);
  210. fclose (stderr);
  211. exit (EXIT_SUCCESS);
  212. }
  213. #endif
  214. /*
  215. Local Variables:
  216. compile-command: "gcc -DTEST_CHDIR=1 -g -O -W -Wall chdir-long.c libcoreutils.a"
  217. End:
  218. */