1# serial 22
2# Check for several getcwd bugs with long file names.
3# If so, arrange to compile the wrapper function.
4
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.
8
9# Copyright (C) 2003-2007, 2009-2019 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.
13
14# From Jim Meyering
15
16AC_DEFUN([gl_FUNC_GETCWD_PATH_MAX],
17[
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>
43
44]gl_PATHMAX_SNIPPET[
45
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
54
55/* Use the getcwd function, not any macro.  */
56#undef getcwd
57
58/* Don't get link errors because mkdir is redefined to rpl_mkdir.  */
59#undef mkdir
60
61#ifndef S_IRWXU
62# define S_IRWXU 0700
63#endif
64
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)
69
70/* The length of "../".  */
71#define DOTDOTSLASH_LEN 3
72
73/* Leftover bytes in the buffer, to work around library or OS bugs.  */
74#define BUF_SLOP 20
75
76int
77main ()
78{
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;
99
100  if (cwd == NULL)
101    exit (10);
102
103  cwd_len = initial_cwd_len = strlen (cwd);
104
105  while (1)
106    {
107      size_t dotdot_max = PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN);
108      char *c = NULL;
109
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        }
130
131      if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE)
132        {
133          struct stat sb;
134
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            }
151
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        }
162
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        }
183
184      if (c && strlen (c) != cwd_len)
185        {
186          fail = 23;
187          break;
188        }
189      ++n_chdirs;
190    }
191
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;
197
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  }
208
209  exit (fail);
210#endif
211}
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='no, it has the AIX bug' ;;
224          gnu*) # On Hurd, it is 'yes'.
225            gl_cv_func_getcwd_path_max=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='no, but it is partly working' ;;
232          *) # If we don't know, assume the worst.
233            gl_cv_func_getcwd_path_max=no ;;
234        esac
235       ])
236    ])
237])
238