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