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