648b1853b9d0e2b26b1d4af34cfef8c15a6907e5
[gnulib.git] / tests / test-getcwd.c
1 /* Test of getcwd() function.
2    Copyright (C) 2009-2012 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           if (! (errno == ERANGE || errno == ENAMETOOLONG || errno == ENOENT))
72             fail = 3; /* Unable to construct deep hierarchy.  */
73           break;
74         }
75     }
76
77   /* If libc has the bug in question, this invocation of getcwd
78      results in a failed assertion.  */
79   cwd = getcwd (NULL, 0);
80   if (cwd == NULL)
81     fail = 4; /* getcwd didn't assert, but it failed for a long name
82                  where the answer could have been learned.  */
83   free (cwd);
84
85   /* Call rmdir first, in case the above chdir failed.  */
86   rmdir (dir_name);
87   while (0 < d--)
88     {
89       if (chdir ("..") < 0)
90         {
91           fail = 5;
92           break;
93         }
94       rmdir (dir_name);
95     }
96
97   return fail;
98 }
99
100 /* The length of this name must be 8.  */
101 #define DIR_NAME "confdir3"
102 #define DIR_NAME_LEN 8
103 #define DIR_NAME_SIZE (DIR_NAME_LEN + 1)
104
105 /* The length of "../".  */
106 #define DOTDOTSLASH_LEN 3
107
108 /* Leftover bytes in the buffer, to work around library or OS bugs.  */
109 #define BUF_SLOP 20
110
111 /* Keep this test in sync with m4/getcwd-path-max.m4.  */
112 static int
113 test_long_name (void)
114 {
115 #ifndef PATH_MAX
116   /* The Hurd doesn't define this, so getcwd can't exhibit the bug --
117      at least not on a local file system.  And if we were to start worrying
118      about remote file systems, we'd have to enable the wrapper function
119      all of the time, just to be safe.  That's not worth the cost.  */
120   return 0;
121 #elif ((INT_MAX / (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1) \
122         - DIR_NAME_SIZE - BUF_SLOP) \
123        <= PATH_MAX)
124   /* FIXME: Assuming there's a system for which this is true,
125      this should be done in a compile test.  */
126   return 0;
127 #else
128   char buf[PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN + 1)
129            + DIR_NAME_SIZE + BUF_SLOP];
130   char *cwd = getcwd (buf, PATH_MAX);
131   size_t initial_cwd_len;
132   size_t cwd_len;
133   int fail = 0;
134   size_t n_chdirs = 0;
135
136   if (cwd == NULL)
137     return 1;
138
139   cwd_len = initial_cwd_len = strlen (cwd);
140
141   while (1)
142     {
143       size_t dotdot_max = PATH_MAX * (DIR_NAME_SIZE / DOTDOTSLASH_LEN);
144       char *c = NULL;
145
146       cwd_len += DIR_NAME_SIZE;
147       /* If mkdir or chdir fails, it could be that this system cannot create
148          any file with an absolute name longer than PATH_MAX, such as cygwin.
149          If so, leave fail as 0, because the current working directory can't
150          be too long for getcwd if it can't even be created.  For other
151          errors, be pessimistic and consider that as a failure, too.  */
152       if (mkdir (DIR_NAME, S_IRWXU) < 0 || chdir (DIR_NAME) < 0)
153         {
154           if (! (errno == ERANGE || errno == ENAMETOOLONG || errno == ENOENT))
155             fail = 2;
156           break;
157         }
158
159       if (PATH_MAX <= cwd_len && cwd_len < PATH_MAX + DIR_NAME_SIZE)
160         {
161           c = getcwd (buf, PATH_MAX);
162           if (!c && errno == ENOENT)
163             {
164               fail = 3;
165               break;
166             }
167           if (c)
168             {
169               fail = 4;
170               break;
171             }
172           if (! (errno == ERANGE || errno == ENAMETOOLONG))
173             {
174               fail = 5;
175               break;
176             }
177         }
178
179       if (dotdot_max <= cwd_len - initial_cwd_len)
180         {
181           if (dotdot_max + DIR_NAME_SIZE < cwd_len - initial_cwd_len)
182             break;
183           c = getcwd (buf, cwd_len + 1);
184           if (!c)
185             {
186               if (! (errno == ERANGE || errno == ENOENT
187                      || errno == ENAMETOOLONG))
188                 {
189                   fail = 6;
190                   break;
191                 }
192               if (AT_FDCWD || errno == ERANGE || errno == ENOENT)
193                 {
194                   fail = 7;
195                   break;
196                 }
197             }
198         }
199
200       if (c && strlen (c) != cwd_len)
201         {
202           fail = 8;
203           break;
204         }
205       ++n_chdirs;
206     }
207
208   /* Leaving behind such a deep directory is not polite.
209      So clean up here, right away, even though the driving
210      shell script would also clean up.  */
211   {
212     size_t i;
213
214     /* Try rmdir first, in case the chdir failed.  */
215     rmdir (DIR_NAME);
216     for (i = 0; i <= n_chdirs; i++)
217       {
218         if (chdir ("..") < 0)
219           break;
220         if (rmdir (DIR_NAME) != 0)
221           break;
222       }
223   }
224
225   return fail;
226 #endif
227 }
228
229 int
230 main (int argc, char **argv)
231 {
232   return test_abort_bug () * 10 + test_long_name ();
233 }