1# serial 25
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-2021 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]GL_MDA_DEFINES[
59
60#ifndef S_IRWXU
61# define S_IRWXU 0700
62#endif
63
64/* The length of this name must be 8.  */
65#define DIR_NAME "confdir3"
66#define DIR_NAME_LEN 8
67#define DIR_NAME_SIZE (DIR_NAME_LEN + 1)
68
69/* The length of "../".  */
70#define DOTDOTSLASH_LEN 3
71
72/* Leftover bytes in the buffer, to work around library or OS bugs.  */
73#define BUF_SLOP 20
74
75int
76main ()
77{
78#ifndef PATH_MAX
79  /* The Hurd doesn't define this, so getcwd can't exhibit the bug --
80     at least not on a local file system.  And if we were to start worrying
81     about remote file systems, we'd have to enable the wrapper function
82     all of the time, just to be safe.  That's not worth the cost.  */
83  exit (0);
84#elif ((INT_MAX / (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1) \
85        - DIR_NAME_SIZE - BUF_SLOP) \
86       <= PATH_MAX)
87  /* FIXME: Assuming there's a system for which this is true,
88     this should be done in a compile test.  */
89  exit (0);
90#else
91  char buf[PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1)
92           + DIR_NAME_SIZE + BUF_SLOP];
93  char *cwd = getcwd (buf, PATH_MAX);
94  size_t initial_cwd_len;
95  size_t cwd_len;
96  int fail = 0;
97  size_t n_chdirs = 0;
98
99  if (cwd == NULL)
100    exit (10);
101
102  cwd_len = initial_cwd_len = strlen (cwd);
103
104  while (1)
105    {
106      size_t dotdot_max = PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN);
107      char *c = NULL;
108
109      cwd_len += DIR_NAME_SIZE;
110      /* If mkdir or chdir fails, it could be that this system cannot create
111         any file with an absolute name longer than PATH_MAX, such as cygwin.
112         If so, leave fail as 0, because the current working directory can't
113         be too long for getcwd if it can't even be created.  On Linux with
114         the 9p file system, mkdir fails with error EINVAL when cwd_len gets
115         too long; ignore this failure because the getcwd() system call
116         produces good results whereas the gnulib substitute calls getdents64
117         which fails with error EPROTO.
118         For other errors, be pessimistic and consider that as a failure,
119         too.  */
120      if (mkdir (DIR_NAME, S_IRWXU) < 0 || chdir (DIR_NAME) < 0)
121        {
122          if (! (errno == ERANGE || is_ENAMETOOLONG (errno)))
123            #ifdef __linux__
124            if (! (errno == EINVAL))
125            #endif
126              fail = 20;
127          break;
128        }
129
130      if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE)
131        {
132          struct stat sb;
133
134          c = getcwd (buf, PATH_MAX);
135          if (!c && errno == ENOENT)
136            {
137              fail = 11;
138              break;
139            }
140          if (c)
141            {
142              fail = 31;
143              break;
144            }
145          if (! (errno == ERANGE || is_ENAMETOOLONG (errno)))
146            {
147              fail = 21;
148              break;
149            }
150
151          /* Our replacement needs to be able to stat() long ../../paths,
152             so generate a path larger than PATH_MAX to check,
153             avoiding the replacement if we can't stat().  */
154          c = getcwd (buf, cwd_len + 1);
155          if (c && !AT_FDCWD && stat (c, &sb) != 0 && is_ENAMETOOLONG (errno))
156            {
157              fail = 32;
158              break;
159            }
160        }
161
162      if (dotdot_max <= cwd_len - initial_cwd_len)
163        {
164          if (dotdot_max + DIR_NAME_SIZE < cwd_len - initial_cwd_len)
165            break;
166          c = getcwd (buf, cwd_len + 1);
167          if (!c)
168            {
169              if (! (errno == ERANGE || errno == ENOENT
170                     || is_ENAMETOOLONG (errno)))
171                {
172                  fail = 22;
173                  break;
174                }
175              if (AT_FDCWD || errno == ERANGE || errno == ENOENT)
176                {
177                  fail = 12;
178                  break;
179                }
180            }
181        }
182
183      if (c && strlen (c) != cwd_len)
184        {
185          fail = 23;
186          break;
187        }
188      ++n_chdirs;
189    }
190
191  /* Leaving behind such a deep directory is not polite.
192     So clean up here, right away, even though the driving
193     shell script would also clean up.  */
194  {
195    size_t i;
196
197    /* Try rmdir first, in case the chdir failed.  */
198    rmdir (DIR_NAME);
199    for (i = 0; i <= n_chdirs; i++)
200      {
201        if (chdir ("..") < 0)
202          break;
203        if (rmdir (DIR_NAME) != 0)
204          break;
205      }
206  }
207
208  exit (fail);
209#endif
210}
211          ]])],
212       [gl_cv_func_getcwd_path_max=yes],
213       [case $? in
214        10|11|12) gl_cv_func_getcwd_path_max='no, but it is partly working';;
215        31) gl_cv_func_getcwd_path_max='no, it has the AIX bug';;
216        32) gl_cv_func_getcwd_path_max='yes, but with shorter paths';;
217        *) gl_cv_func_getcwd_path_max=no;;
218        esac],
219       [# Cross-compilation guesses:
220        case "$host_os" in
221          aix*) # On AIX, it has the AIX bug.
222            gl_cv_func_getcwd_path_max='guessing no, it has the AIX bug' ;;
223          gnu*) # On Hurd, it is 'yes'.
224            gl_cv_func_getcwd_path_max='guessing yes' ;;
225          linux* | kfreebsd*)
226            # On older Linux+glibc it's 'no, but it is partly working',
227            # on newer Linux+glibc it's 'yes'.
228            # On Linux+musl libc, it's 'no, but it is partly working'.
229            # On kFreeBSD+glibc, it's 'no, but it is partly working'.
230            gl_cv_func_getcwd_path_max='guessing no, but it is partly working' ;;
231          *) # If we don't know, obey --enable-cross-guesses.
232            gl_cv_func_getcwd_path_max="$gl_cross_guess_normal" ;;
233        esac
234       ])
235    ])
236])
237