findprog, relocatable-prog: Ignore directories during PATH search.
[gnulib.git] / m4 / getcwd-path-max.m4
blob1ff3f25176e46c2ced7405f2882f446a2d6ce156
1 # serial 23
2 # Check for several getcwd bugs with long file names.
3 # If so, arrange to compile the wrapper function.
5 # This is necessary for at least GNU libc on linux-2.4.19 and 2.4.20.
6 # I've heard that this is due to a Linux kernel bug, and that it has
7 # been fixed between 2.4.21-pre3 and 2.4.21-pre4.
9 # Copyright (C) 2003-2007, 2009-2020 Free Software Foundation, Inc.
10 # This file is free software; the Free Software Foundation
11 # gives unlimited permission to copy and/or distribute it,
12 # with or without modifications, as long as this notice is preserved.
14 # From Jim Meyering
16 AC_DEFUN([gl_FUNC_GETCWD_PATH_MAX],
18   AC_CHECK_DECLS_ONCE([getcwd])
19   AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
20   AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
21   AC_CHECK_HEADERS_ONCE([unistd.h])
22   AC_REQUIRE([gl_PATHMAX_SNIPPET_PREREQ])
23   AC_CACHE_CHECK([whether getcwd handles long file names properly],
24     [gl_cv_func_getcwd_path_max],
25     [# Arrange for deletion of the temporary directory this test creates.
26      ac_clean_files="$ac_clean_files confdir3"
27      dnl Please keep this in sync with tests/test-getcwd.c.
28      AC_RUN_IFELSE(
29        [AC_LANG_SOURCE(
30           [[
31 #include <errno.h>
32 #include <stdlib.h>
33 #if HAVE_UNISTD_H
34 # include <unistd.h>
35 #else
36 # include <direct.h>
37 #endif
38 #include <string.h>
39 #include <limits.h>
40 #include <sys/stat.h>
41 #include <sys/types.h>
42 #include <fcntl.h>
44 ]gl_PATHMAX_SNIPPET[
46 #ifndef AT_FDCWD
47 # define AT_FDCWD 0
48 #endif
49 #ifdef ENAMETOOLONG
50 # define is_ENAMETOOLONG(x) ((x) == ENAMETOOLONG)
51 #else
52 # define is_ENAMETOOLONG(x) 0
53 #endif
55 /* Use the getcwd function, not any macro.  */
56 #undef getcwd
58 /* Don't get link errors because mkdir is redefined to rpl_mkdir.  */
59 #undef mkdir
61 #ifndef S_IRWXU
62 # define S_IRWXU 0700
63 #endif
65 /* The length of this name must be 8.  */
66 #define DIR_NAME "confdir3"
67 #define DIR_NAME_LEN 8
68 #define DIR_NAME_SIZE (DIR_NAME_LEN + 1)
70 /* The length of "../".  */
71 #define DOTDOTSLASH_LEN 3
73 /* Leftover bytes in the buffer, to work around library or OS bugs.  */
74 #define BUF_SLOP 20
76 int
77 main ()
79 #ifndef PATH_MAX
80   /* The Hurd doesn't define this, so getcwd can't exhibit the bug --
81      at least not on a local file system.  And if we were to start worrying
82      about remote file systems, we'd have to enable the wrapper function
83      all of the time, just to be safe.  That's not worth the cost.  */
84   exit (0);
85 #elif ((INT_MAX / (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1) \
86         - DIR_NAME_SIZE - BUF_SLOP) \
87        <= PATH_MAX)
88   /* FIXME: Assuming there's a system for which this is true,
89      this should be done in a compile test.  */
90   exit (0);
91 #else
92   char buf[PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1)
93            + DIR_NAME_SIZE + BUF_SLOP];
94   char *cwd = getcwd (buf, PATH_MAX);
95   size_t initial_cwd_len;
96   size_t cwd_len;
97   int fail = 0;
98   size_t n_chdirs = 0;
100   if (cwd == NULL)
101     exit (10);
103   cwd_len = initial_cwd_len = strlen (cwd);
105   while (1)
106     {
107       size_t dotdot_max = PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN);
108       char *c = NULL;
110       cwd_len += DIR_NAME_SIZE;
111       /* If mkdir or chdir fails, it could be that this system cannot create
112          any file with an absolute name longer than PATH_MAX, such as cygwin.
113          If so, leave fail as 0, because the current working directory can't
114          be too long for getcwd if it can't even be created.  On Linux with
115          the 9p file system, mkdir fails with error EINVAL when cwd_len gets
116          too long; ignore this failure because the getcwd() system call
117          produces good results whereas the gnulib substitute calls getdents64
118          which fails with error EPROTO.
119          For other errors, be pessimistic and consider that as a failure,
120          too.  */
121       if (mkdir (DIR_NAME, S_IRWXU) < 0 || chdir (DIR_NAME) < 0)
122         {
123           if (! (errno == ERANGE || is_ENAMETOOLONG (errno)))
124             #ifdef __linux__
125             if (! (errno == EINVAL))
126             #endif
127               fail = 20;
128           break;
129         }
131       if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE)
132         {
133           struct stat sb;
135           c = getcwd (buf, PATH_MAX);
136           if (!c && errno == ENOENT)
137             {
138               fail = 11;
139               break;
140             }
141           if (c)
142             {
143               fail = 31;
144               break;
145             }
146           if (! (errno == ERANGE || is_ENAMETOOLONG (errno)))
147             {
148               fail = 21;
149               break;
150             }
152           /* Our replacement needs to be able to stat() long ../../paths,
153              so generate a path larger than PATH_MAX to check,
154              avoiding the replacement if we can't stat().  */
155           c = getcwd (buf, cwd_len + 1);
156           if (c && !AT_FDCWD && stat (c, &sb) != 0 && is_ENAMETOOLONG (errno))
157             {
158               fail = 32;
159               break;
160             }
161         }
163       if (dotdot_max <= cwd_len - initial_cwd_len)
164         {
165           if (dotdot_max + DIR_NAME_SIZE < cwd_len - initial_cwd_len)
166             break;
167           c = getcwd (buf, cwd_len + 1);
168           if (!c)
169             {
170               if (! (errno == ERANGE || errno == ENOENT
171                      || is_ENAMETOOLONG (errno)))
172                 {
173                   fail = 22;
174                   break;
175                 }
176               if (AT_FDCWD || errno == ERANGE || errno == ENOENT)
177                 {
178                   fail = 12;
179                   break;
180                 }
181             }
182         }
184       if (c && strlen (c) != cwd_len)
185         {
186           fail = 23;
187           break;
188         }
189       ++n_chdirs;
190     }
192   /* Leaving behind such a deep directory is not polite.
193      So clean up here, right away, even though the driving
194      shell script would also clean up.  */
195   {
196     size_t i;
198     /* Try rmdir first, in case the chdir failed.  */
199     rmdir (DIR_NAME);
200     for (i = 0; i <= n_chdirs; i++)
201       {
202         if (chdir ("..") < 0)
203           break;
204         if (rmdir (DIR_NAME) != 0)
205           break;
206       }
207   }
209   exit (fail);
210 #endif
212           ]])],
213        [gl_cv_func_getcwd_path_max=yes],
214        [case $? in
215         10|11|12) gl_cv_func_getcwd_path_max='no, but it is partly working';;
216         31) gl_cv_func_getcwd_path_max='no, it has the AIX bug';;
217         32) gl_cv_func_getcwd_path_max='yes, but with shorter paths';;
218         *) gl_cv_func_getcwd_path_max=no;;
219         esac],
220        [# Cross-compilation guesses:
221         case "$host_os" in
222           aix*) # On AIX, it has the AIX bug.
223             gl_cv_func_getcwd_path_max='guessing no, it has the AIX bug' ;;
224           gnu*) # On Hurd, it is 'yes'.
225             gl_cv_func_getcwd_path_max='guessing yes' ;;
226           linux* | kfreebsd*)
227             # On older Linux+glibc it's 'no, but it is partly working',
228             # on newer Linux+glibc it's 'yes'.
229             # On Linux+musl libc, it's 'no, but it is partly working'.
230             # On kFreeBSD+glibc, it's 'no, but it is partly working'.
231             gl_cv_func_getcwd_path_max='guessing no, but it is partly working' ;;
232           *) # If we don't know, obey --enable-cross-guesses.
233             gl_cv_func_getcwd_path_max="$gl_cross_guess_normal" ;;
234         esac
235        ])
236     ])