pathmax: Leave PATH_MAX undefined on the Hurd, and a constant otherwise.
[gnulib.git] / tests / test-getcwd.c
1 /* Test of getcwd() function.
2    Copyright (C) 2009-2011 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17 #include <config.h>
18
19 #include <unistd.h>
20
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/stat.h>
28
29 #include "pathmax.h"
30 #include "macros.h"
31
32 #if ! HAVE_GETPAGESIZE
33 # define getpagesize() 0
34 #endif
35
36 /* This size is chosen to be larger than PATH_MAX (4k), yet smaller than
37    the 16kB pagesize on ia64 linux.  Those conditions make the code below
38    trigger a bug in glibc's getcwd implementation before 2.4.90-10.  */
39 #define TARGET_LEN (5 * 1024)
40
41 /* Keep this test in sync with m4/getcwd-abort-bug.m4.  */
42 static int
43 test_abort_bug (void)
44 {
45   char const *dir_name = "confdir-14B---";
46   char *cwd;
47   size_t initial_cwd_len;
48   int fail = 0;
49   size_t desired_depth;
50   size_t d;
51
52 #ifdef PATH_MAX
53   /* The bug is triggered when PATH_MAX < getpagesize (), so skip
54      this relatively expensive and invasive test if that's not true.  */
55   if (getpagesize () <= PATH_MAX)
56     return 0;
57 #endif
58
59   cwd = getcwd (NULL, 0);
60   if (cwd == NULL)
61     return 2;
62
63   initial_cwd_len = strlen (cwd);
64   free (cwd);
65   desired_depth = ((TARGET_LEN - 1 - initial_cwd_len)
66                    / (1 + strlen (dir_name)));
67   for (d = 0; d < desired_depth; d++)
68     {
69       if (mkdir (dir_name, S_IRWXU) < 0 || chdir (dir_name) < 0)
70         {
71           fail = 3; /* Unable to construct deep hierarchy.  */
72           break;
73         }
74     }
75
76   /* If libc has the bug in question, this invocation of getcwd
77      results in a failed assertion.  */
78   cwd = getcwd (NULL, 0);
79   if (cwd == NULL)
80     fail = 4; /* getcwd failed.  This is ok, and expected.  */
81   free (cwd);
82
83   /* Call rmdir first, in case the above chdir failed.  */
84   rmdir (dir_name);
85   while (0 < d--)
86     {
87       if (chdir ("..") < 0)
88         {
89           fail = 5;
90           break;
91         }
92       rmdir (dir_name);
93     }
94
95   return fail;
96 }
97
98 /* The length of this name must be 8.  */
99 #define DIR_NAME "confdir3"
100 #define DIR_NAME_LEN 8
101 #define DIR_NAME_SIZE (DIR_NAME_LEN + 1)
102
103 /* The length of "../".  */
104 #define DOTDOTSLASH_LEN 3
105
106 /* Leftover bytes in the buffer, to work around library or OS bugs.  */
107 #define BUF_SLOP 20
108
109 /* Keep this test in sync with m4/getcwd-path-max.m4.  */
110 static int
111 test_long_name (void)
112 {
113 #ifndef PATH_MAX
114   /* The Hurd doesn't define this, so getcwd can't exhibit the bug --
115      at least not on a local file system.  And if we were to start worrying
116      about remote file systems, we'd have to enable the wrapper function
117      all of the time, just to be safe.  That's not worth the cost.  */
118   return 0;
119 #elif ((INT_MAX / (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1) \
120         - DIR_NAME_SIZE - BUF_SLOP) \
121        <= PATH_MAX)
122   /* FIXME: Assuming there's a system for which this is true,
123      this should be done in a compile test.  */
124   return 0;
125 #else
126   char buf[PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1)
127            + DIR_NAME_SIZE + BUF_SLOP];
128   char *cwd = getcwd (buf, PATH_MAX);
129   size_t initial_cwd_len;
130   size_t cwd_len;
131   int fail = 0;
132   size_t n_chdirs = 0;
133
134   if (cwd == NULL)
135     return 10;
136
137   cwd_len = initial_cwd_len = strlen (cwd);
138
139   while (1)
140     {
141       size_t dotdot_max = PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN);
142       char *c = NULL;
143
144       cwd_len += DIR_NAME_SIZE;
145       /* If mkdir or chdir fails, it could be that this system cannot create
146          any file with an absolute name longer than PATH_MAX, such as cygwin.
147          If so, leave fail as 0, because the current working directory can't
148          be too long for getcwd if it can't even be created.  For other
149          errors, be pessimistic and consider that as a failure, too.  */
150       if (mkdir (DIR_NAME, S_IRWXU) < 0 || chdir (DIR_NAME) < 0)
151         {
152           if (! (errno == ERANGE || errno == ENAMETOOLONG))
153             fail = 20;
154           break;
155         }
156
157       if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE)
158         {
159           c = getcwd (buf, PATH_MAX);
160           if (!c && errno == ENOENT)
161             {
162               fail = 11;
163               break;
164             }
165           if (c || ! (errno == ERANGE || errno == ENAMETOOLONG))
166             {
167               fail = 21;
168               break;
169             }
170         }
171
172       if (dotdot_max <= cwd_len - initial_cwd_len)
173         {
174           if (dotdot_max + DIR_NAME_SIZE < cwd_len - initial_cwd_len)
175             break;
176           c = getcwd (buf, cwd_len + 1);
177           if (!c)
178             {
179               if (! (errno == ERANGE || errno == ENOENT
180                      || errno == ENAMETOOLONG))
181                 {
182                   fail = 22;
183                   break;
184                 }
185               if (AT_FDCWD || errno == ERANGE || errno == ENOENT)
186                 {
187                   fail = 12;
188                   break;
189                 }
190             }
191         }
192
193       if (c && strlen (c) != cwd_len)
194         {
195           fail = 23;
196           break;
197         }
198       ++n_chdirs;
199     }
200
201   /* Leaving behind such a deep directory is not polite.
202      So clean up here, right away, even though the driving
203      shell script would also clean up.  */
204   {
205     size_t i;
206
207     /* Try rmdir first, in case the chdir failed.  */
208     rmdir (DIR_NAME);
209     for (i = 0; i <= n_chdirs; i++)
210       {
211         if (chdir ("..") < 0)
212           break;
213         if (rmdir (DIR_NAME) != 0)
214           break;
215       }
216   }
217
218   return fail;
219 #endif
220 }
221
222 int
223 main (int argc, char **argv)
224 {
225   return test_abort_bug () + test_long_name ();
226 }